private OAuth2AccessTokenEntity fetchValidRegistrationToken( OAuth2Authentication auth, ClientDetailsEntity client) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails(); OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue()); if (config.getRegTokenLifeTime() != null) { try { // Re-issue the token if it has been issued before [currentTime - validity] Date validToDate = new Date(System.currentTimeMillis() - config.getRegTokenLifeTime() * 1000); if (token.getJwt().getJWTClaimsSet().getIssueTime().before(validToDate)) { logger.info("Rotating the registration access token for " + client.getClientId()); tokenService.revokeAccessToken(token); OAuth2AccessTokenEntity newToken = connectTokenService.createResourceAccessToken(client); tokenService.saveAccessToken(newToken); return newToken; } else { // it's not expired, keep going return token; } } catch (ParseException e) { logger.error("Couldn't parse a known-valid token?", e); return token; } } else { // tokens don't expire, just return it return token; } }
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public void exportData(HttpServletResponse resp, Principal prin) throws IOException { resp.setContentType(MediaType.APPLICATION_JSON_VALUE); // this writer puts things out onto the wire JsonWriter writer = new JsonWriter(resp.getWriter()); writer.setIndent(" "); try { writer.beginObject(); writer.name("exported-at"); writer.value(dateFormat.format(new Date())); writer.name("exported-from"); writer.value(config.getIssuer()); writer.name("exported-by"); writer.value(prin.getName()); // delegate to the service to do the actual export dataService_1_3.exportData(writer); writer.endObject(); // end root writer.close(); } catch (IOException e) { logger.error("Unable to export data", e); } }
@RequestMapping( value = "/{id}", method = RequestMethod.PUT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String updateResourceSet( @PathVariable("id") Long id, @RequestBody String jsonString, Model m, Authentication auth) { ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); ResourceSet newRs = parseResourceSet(jsonString); if (newRs == null // there was no resource set in the body || Strings.isNullOrEmpty(newRs.getName()) // there was no name (required) || newRs.getScopes() == null // there were no scopes (required) || newRs.getId() == null || !newRs.getId().equals(id) // the IDs didn't match ) { logger.warn("Resource set registration missing one or more required fields."); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute( JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields."); return JsonErrorView.VIEWNAME; } ResourceSet rs = resourceSetService.getById(id); if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.addAttribute(JsonErrorView.ERROR, "not_found"); return JsonErrorView.VIEWNAME; } else { if (!auth.getName().equals(rs.getOwner())) { logger.warn( "Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); // it wasn't issued to this user m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; } else { ResourceSet saved = resourceSetService.update(rs, newRs); m.addAttribute(JsonEntityView.ENTITY, saved); m.addAttribute( ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + rs.getId()); return ResourceSetEntityAbbreviatedView.VIEWNAME; } } }
@RequestMapping( method = RequestMethod.POST, produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) public String createResourceSet(@RequestBody String jsonString, Model m, Authentication auth) { ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); ResourceSet rs = parseResourceSet(jsonString); if (rs == null) { // there was no resource set in the body logger.warn("Resource set registration missing body."); m.addAttribute("code", HttpStatus.BAD_REQUEST); m.addAttribute("error_description", "Resource request was missing body."); return JsonErrorView.VIEWNAME; } if (auth instanceof OAuth2Authentication) { // if it's an OAuth mediated call, it's on behalf of a client, so store that OAuth2Authentication o2a = (OAuth2Authentication) auth; rs.setClientId(o2a.getOAuth2Request().getClientId()); rs.setOwner(auth.getName()); // the username is going to be in the auth object } else { // this one shouldn't be called if it's not OAuth m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This call must be made with an OAuth token"); return JsonErrorView.VIEWNAME; } rs = validateScopes(rs); if (Strings.isNullOrEmpty(rs.getName()) // there was no name (required) || rs.getScopes() == null // there were no scopes (required) ) { logger.warn("Resource set registration missing one or more required fields."); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute( JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields."); return JsonErrorView.VIEWNAME; } ResourceSet saved = resourceSetService.saveNew(rs); m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); m.addAttribute(JsonEntityView.ENTITY, saved); m.addAttribute( ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + saved.getId()); return ResourceSetEntityAbbreviatedView.VIEWNAME; }
/** * Get the meta information for a client. * * @param clientId * @param m * @param auth * @return */ @PreAuthorize( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") @RequestMapping( value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public String readResourceConfiguration( @PathVariable("id") String clientId, Model m, OAuth2Authentication auth) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { try { // possibly update the token OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client); RegisteredClient registered = new RegisteredClient( client, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8")); // send it all out to the view m.addAttribute("client", registered); m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 return ClientInformationResponseView.VIEWNAME; } catch (UnsupportedEncodingException e) { logger.error("Unsupported encoding", e); m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); return HttpCodeView.VIEWNAME; } } else { // client mismatch logger.error( "readResourceConfiguration failed, client ID mismatch: " + clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403 return HttpCodeView.VIEWNAME; } }
/** * Get the meta information for a client. * * @param clientId * @param m * @param auth * @return */ @PreAuthorize( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE + "')") @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json") public String readClientConfiguration( @PathVariable("id") String clientId, Model m, OAuth2Authentication auth) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { // we return the token that we got in OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails(); OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue()); // TODO: urlencode the client id for safety? RegisteredClient registered = new RegisteredClient( client, token.getValue(), config.getIssuer() + "register/" + client.getClientId()); // send it all out to the view m.addAttribute("client", registered); m.addAttribute("code", HttpStatus.OK); // http 200 return "clientInformationResponseView"; } else { // client mismatch logger.error( "readClientConfiguration failed, client ID mismatch: " + clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403 return "httpCodeView"; } }
/** * Create a new Client, issue a client ID, and create a registration access token. * * @param jsonString * @param m * @param p * @return */ @RequestMapping( method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public String registerNewClient(@RequestBody String jsonString, Model m) { ClientDetailsEntity newClient = ClientDetailsEntityJsonProcessor.parse(jsonString); if (newClient != null) { // it parsed! // // Now do some post-processing consistency checks on it // // clear out any spurious id/secret (clients don't get to pick) newClient.setClientId(null); newClient.setClientSecret(null); // set of scopes that are OK for clients to dynamically register for Set<SystemScope> dynScopes = scopeService.getDynReg(); // scopes that the client is asking for Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope()); // the scopes that the client can have must be a subset of the dynamically allowed scopes Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes); // if the client didn't ask for any, give them the defaults if (allowedScopes == null || allowedScopes.isEmpty()) { allowedScopes = scopeService.getDefaults(); } newClient.setScope(scopeService.toStrings(allowedScopes)); // set default grant types if needed if (newClient.getGrantTypes() == null || newClient.getGrantTypes().isEmpty()) { if (newClient.getScope().contains("offline_access")) { // client asked for offline access newClient.setGrantTypes( Sets.newHashSet( "authorization_code", "refresh_token")); // allow authorization code and refresh token grant types by // default } else { newClient.setGrantTypes( Sets.newHashSet( "authorization_code")); // allow authorization code grant type by default } } // set default response types if needed // TODO: these aren't checked by SECOAUTH // TODO: the consistency between the response_type and grant_type needs to be checked by the // client service, most likely if (newClient.getResponseTypes() == null || newClient.getResponseTypes().isEmpty()) { newClient.setResponseTypes( Sets.newHashSet("code")); // default to allowing only the auth code flow } if (newClient.getTokenEndpointAuthMethod() == null) { newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC); } if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC || newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT || newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) { // we need to generate a secret newClient = clientService.generateClientSecret(newClient); } // set some defaults for token timeouts newClient.setAccessTokenValiditySeconds( (int) TimeUnit.HOURS.toSeconds(1)); // access tokens good for 1hr newClient.setIdTokenValiditySeconds( (int) TimeUnit.MINUTES.toSeconds(10)); // id tokens good for 10min newClient.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked // this client has been dynamically registered (obviously) newClient.setDynamicallyRegistered(true); // TODO: check and enforce the sector URI if it's not null (#504) // now save it try { ClientDetailsEntity savedClient = clientService.saveNewClient(newClient); // generate the registration access token OAuth2AccessTokenEntity token = connectTokenService.createRegistrationAccessToken(savedClient); tokenService.saveAccessToken(token); // send it all out to the view // TODO: urlencode the client id for safety? RegisteredClient registered = new RegisteredClient( savedClient, token.getValue(), config.getIssuer() + "register/" + savedClient.getClientId()); m.addAttribute("client", registered); m.addAttribute("code", HttpStatus.CREATED); // http 201 return "clientInformationResponseView"; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); m.addAttribute("code", HttpStatus.BAD_REQUEST); return "httpCodeView"; } } else { // didn't parse, this is a bad request logger.error("registerNewClient failed; submitted JSON is malformed"); m.addAttribute("code", HttpStatus.BAD_REQUEST); // http 400 return "httpCodeView"; } }
/** * Update the metainformation for a given client. * * @param clientId * @param jsonString * @param m * @param auth * @return */ @PreAuthorize( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE + "')") @RequestMapping( value = "/{id}", method = RequestMethod.PUT, produces = "application/json", consumes = "application/json") public String updateClient( @PathVariable("id") String clientId, @RequestBody String jsonString, Model m, OAuth2Authentication auth) { ClientDetailsEntity newClient = ClientDetailsEntityJsonProcessor.parse(jsonString); ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId); if (newClient != null && oldClient != null // we have an existing client and the new one parsed && oldClient .getClientId() .equals( auth.getOAuth2Request() .getClientId()) // the client passed in the URI matches the one in the auth && oldClient .getClientId() .equals( newClient.getClientId()) // the client passed in the body matches the one in the URI ) { // a client can't ask to update its own client secret to any particular value newClient.setClientSecret(oldClient.getClientSecret()); // we need to copy over all of the local and SECOAUTH fields newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds()); newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds()); newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds()); newClient.setDynamicallyRegistered(true); // it's still dynamically registered newClient.setAllowIntrospection(oldClient.isAllowIntrospection()); newClient.setAuthorities(oldClient.getAuthorities()); newClient.setClientDescription(oldClient.getClientDescription()); newClient.setCreatedAt(oldClient.getCreatedAt()); newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken()); // set of scopes that are OK for clients to dynamically register for Set<SystemScope> dynScopes = scopeService.getDynReg(); // scopes that the client is asking for Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope()); // the scopes that the client can have must be a subset of the dynamically allowed scopes Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes); // make sure that the client doesn't ask for scopes it can't have newClient.setScope(scopeService.toStrings(allowedScopes)); try { // save the client ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient); // we return the token that we got in // TODO: rotate this after some set amount of time OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails(); OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue()); // TODO: urlencode the client id for safety? RegisteredClient registered = new RegisteredClient( savedClient, token.getValue(), config.getIssuer() + "register/" + savedClient.getClientId()); // send it all out to the view m.addAttribute("client", registered); m.addAttribute("code", HttpStatus.OK); // http 200 return "clientInformationResponseView"; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); m.addAttribute("code", HttpStatus.BAD_REQUEST); return "httpCodeView"; } } else { // client mismatch logger.error( "readClientConfiguration failed, client ID mismatch: " + clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403 return "httpCodeView"; } }
/** * Create a new Client, issue a client ID, and create a registration access token. * * @param jsonString * @param m * @param p * @return */ @RequestMapping( method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public String registerNewProtectedResource(@RequestBody String jsonString, Model m) { ClientDetailsEntity newClient = null; try { newClient = ClientDetailsEntityJsonProcessor.parse(jsonString); } catch (JsonSyntaxException e) { // bad parse // didn't parse, this is a bad request logger.error("registerNewProtectedResource failed; submitted JSON is malformed"); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 return HttpCodeView.VIEWNAME; } if (newClient != null) { // it parsed! // // Now do some post-processing consistency checks on it // // clear out any spurious id/secret (clients don't get to pick) newClient.setClientId(null); newClient.setClientSecret(null); // do validation on the fields try { newClient = validateScopes(newClient); newClient = validateAuth(newClient); } catch (ValidationException ve) { // validation failed, return an error m.addAttribute(JsonErrorView.ERROR, ve.getError()); m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription()); m.addAttribute(HttpCodeView.CODE, ve.getStatus()); return JsonErrorView.VIEWNAME; } // no grant types are allowed newClient.setGrantTypes(new HashSet<String>()); newClient.setResponseTypes(new HashSet<String>()); newClient.setRedirectUris(new HashSet<String>()); // don't issue tokens to this client newClient.setAccessTokenValiditySeconds(0); newClient.setIdTokenValiditySeconds(0); newClient.setRefreshTokenValiditySeconds(0); // clear out unused fields newClient.setDefaultACRvalues(new HashSet<String>()); newClient.setDefaultMaxAge(null); newClient.setIdTokenEncryptedResponseAlg(null); newClient.setIdTokenEncryptedResponseEnc(null); newClient.setIdTokenSignedResponseAlg(null); newClient.setInitiateLoginUri(null); newClient.setPostLogoutRedirectUris(null); newClient.setRequestObjectSigningAlg(null); newClient.setRequireAuthTime(null); newClient.setReuseRefreshToken(false); newClient.setSectorIdentifierUri(null); newClient.setSubjectType(null); newClient.setUserInfoEncryptedResponseAlg(null); newClient.setUserInfoEncryptedResponseEnc(null); newClient.setUserInfoSignedResponseAlg(null); // this client has been dynamically registered (obviously) newClient.setDynamicallyRegistered(true); // this client has access to the introspection endpoint newClient.setAllowIntrospection(true); // now save it try { ClientDetailsEntity savedClient = clientService.saveNewClient(newClient); // generate the registration access token OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient); tokenService.saveAccessToken(token); // send it all out to the view RegisteredClient registered = new RegisteredClient( savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8")); m.addAttribute("client", registered); m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201 return ClientInformationResponseView.VIEWNAME; } catch (UnsupportedEncodingException e) { logger.error("Unsupported encoding", e); m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); return HttpCodeView.VIEWNAME; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata"); m.addAttribute( JsonErrorView.ERROR_MESSAGE, "Unable to save client due to invalid or inconsistent metadata."); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 return JsonErrorView.VIEWNAME; } } else { // didn't parse, this is a bad request logger.error("registerNewClient failed; submitted JSON is malformed"); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 return HttpCodeView.VIEWNAME; } }
/** * Update the metainformation for a given client. * * @param clientId * @param jsonString * @param m * @param auth * @return */ @PreAuthorize( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") @RequestMapping( value = "/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public String updateProtectedResource( @PathVariable("id") String clientId, @RequestBody String jsonString, Model m, OAuth2Authentication auth) { ClientDetailsEntity newClient = null; try { newClient = ClientDetailsEntityJsonProcessor.parse(jsonString); } catch (JsonSyntaxException e) { // bad parse // didn't parse, this is a bad request logger.error("updateProtectedResource failed; submitted JSON is malformed"); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 return HttpCodeView.VIEWNAME; } ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId); if (newClient != null && oldClient != null // we have an existing client and the new one parsed && oldClient .getClientId() .equals( auth.getOAuth2Request() .getClientId()) // the client passed in the URI matches the one in the auth && oldClient .getClientId() .equals( newClient.getClientId()) // the client passed in the body matches the one in the URI ) { // a client can't ask to update its own client secret to any particular value newClient.setClientSecret(oldClient.getClientSecret()); newClient.setCreatedAt(oldClient.getCreatedAt()); // no grant types are allowed newClient.setGrantTypes(new HashSet<String>()); newClient.setResponseTypes(new HashSet<String>()); newClient.setRedirectUris(new HashSet<String>()); // don't issue tokens to this client newClient.setAccessTokenValiditySeconds(0); newClient.setIdTokenValiditySeconds(0); newClient.setRefreshTokenValiditySeconds(0); // clear out unused fields newClient.setDefaultACRvalues(new HashSet<String>()); newClient.setDefaultMaxAge(null); newClient.setIdTokenEncryptedResponseAlg(null); newClient.setIdTokenEncryptedResponseEnc(null); newClient.setIdTokenSignedResponseAlg(null); newClient.setInitiateLoginUri(null); newClient.setPostLogoutRedirectUris(null); newClient.setRequestObjectSigningAlg(null); newClient.setRequireAuthTime(null); newClient.setReuseRefreshToken(false); newClient.setSectorIdentifierUri(null); newClient.setSubjectType(null); newClient.setUserInfoEncryptedResponseAlg(null); newClient.setUserInfoEncryptedResponseEnc(null); newClient.setUserInfoSignedResponseAlg(null); // this client has been dynamically registered (obviously) newClient.setDynamicallyRegistered(true); // this client has access to the introspection endpoint newClient.setAllowIntrospection(true); // do validation on the fields try { newClient = validateScopes(newClient); newClient = validateAuth(newClient); } catch (ValidationException ve) { // validation failed, return an error m.addAttribute(JsonErrorView.ERROR, ve.getError()); m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription()); m.addAttribute(HttpCodeView.CODE, ve.getStatus()); return JsonErrorView.VIEWNAME; } try { // save the client ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient); // possibly update the token OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient); RegisteredClient registered = new RegisteredClient( savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8")); // send it all out to the view m.addAttribute("client", registered); m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 return ClientInformationResponseView.VIEWNAME; } catch (UnsupportedEncodingException e) { logger.error("Unsupported encoding", e); m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); return HttpCodeView.VIEWNAME; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata"); m.addAttribute( JsonErrorView.ERROR_MESSAGE, "Unable to save client due to invalid or inconsistent metadata."); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 return JsonErrorView.VIEWNAME; } } else { // client mismatch logger.error( "updateProtectedResource" + " failed, client ID mismatch: " + clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403 return HttpCodeView.VIEWNAME; } }
@Override public OAuth2AccessTokenEntity createIdToken( ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken) { JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); if (client.getIdTokenSignedResponseAlg() != null) { signingAlg = client.getIdTokenSignedResponseAlg(); } OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity(); JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder(); // if the auth time claim was explicitly requested OR if the client always wants the auth time, // put it in if (request.getExtensions().containsKey("max_age") || (request .getExtensions() .containsKey( "idtoken")) // TODO: parse the ID Token claims (#473) -- for now assume it could be // in there || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) { if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) { Long authTimestamp = Long.parseLong( (String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP)); if (authTimestamp != null) { idClaims.claim("auth_time", authTimestamp / 1000L); } } else { // we couldn't find the timestamp! logger.warn( "Unable to find authentication timestamp! There is likely something wrong with the configuration."); } } idClaims.issueTime(issueTime); if (client.getIdTokenValiditySeconds() != null) { Date expiration = new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); idClaims.expirationTime(expiration); idTokenEntity.setExpiration(expiration); } idClaims.issuer(configBean.getIssuer()); idClaims.subject(sub); idClaims.audience(Lists.newArrayList(client.getClientId())); idClaims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it String nonce = (String) request.getExtensions().get("nonce"); if (!Strings.isNullOrEmpty(nonce)) { idClaims.claim("nonce", nonce); } Set<String> responseTypes = request.getResponseTypes(); if (responseTypes.contains("token")) { // calculate the token hash Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken); idClaims.claim("at_hash", at_hash); } if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) && client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) { JWTEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client); if (encrypter != null) { EncryptedJWT idToken = new EncryptedJWT( new JWEHeader( client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), idClaims.build()); encrypter.encryptJwt(idToken); idTokenEntity.setJwt(idToken); } else { logger.error("Couldn't find encrypter for client: " + client.getClientId()); } } else { JWT idToken; if (signingAlg.equals(Algorithm.NONE)) { // unsigned ID token idToken = new PlainJWT(idClaims.build()); } else { // signed ID token if (signingAlg.equals(JWSAlgorithm.HS256) || signingAlg.equals(JWSAlgorithm.HS384) || signingAlg.equals(JWSAlgorithm.HS512)) { JWSHeader header = new JWSHeader( signingAlg, null, null, null, null, null, null, null, null, null, jwtService.getDefaultSignerKeyId(), null, null); idToken = new SignedJWT(header, idClaims.build()); JWTSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client); // sign it with the client's secret signer.signJwt((SignedJWT) idToken); } else { idClaims.claim("kid", jwtService.getDefaultSignerKeyId()); JWSHeader header = new JWSHeader( signingAlg, null, null, null, null, null, null, null, null, null, jwtService.getDefaultSignerKeyId(), null, null); idToken = new SignedJWT(header, idClaims.build()); // sign it with the server's key jwtService.signJwt((SignedJWT) idToken); } } idTokenEntity.setJwt(idToken); } idTokenEntity.setAuthenticationHolder(accessToken.getAuthenticationHolder()); // create a scope set with just the special "id-token" scope // Set<String> idScopes = new HashSet<String>(token.getScope()); // this would copy the original // token's scopes in, we don't really want that Set<String> idScopes = Sets.newHashSet(SystemScopeService.ID_TOKEN_SCOPE); idTokenEntity.setScope(idScopes); idTokenEntity.setClient(accessToken.getClient()); return idTokenEntity; }
private OAuth2AccessTokenEntity createAssociatedToken( ClientDetailsEntity client, Set<String> scope) { // revoke any previous tokens that might exist, just to be sure OAuth2AccessTokenEntity oldToken = tokenService.getRegistrationAccessTokenForClient(client); if (oldToken != null) { tokenService.revokeAccessToken(oldToken); } // create a new token Map<String, String> authorizationParameters = Maps.newHashMap(); OAuth2Request clientAuth = new OAuth2Request( authorizationParameters, client.getClientId(), Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")), true, scope, null, null, null, null); OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null); OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); token.setClient(client); token.setScope(scope); AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); authHolder.setAuthentication(authentication); authHolder = authenticationHolderRepository.save(authHolder); token.setAuthenticationHolder(authHolder); JWTClaimsSet claims = new JWTClaimsSet.Builder() .audience(Lists.newArrayList(client.getClientId())) .issuer(configBean.getIssuer()) .issueTime(new Date()) .expirationTime(token.getExpiration()) .jwtID(UUID.randomUUID().toString()) // set a random NONCE in the middle of it .build(); JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); JWSHeader header = new JWSHeader( signingAlg, null, null, null, null, null, null, null, null, null, jwtService.getDefaultSignerKeyId(), null, null); SignedJWT signed = new SignedJWT(header, claims); jwtService.signJwt(signed); token.setJwt(signed); return token; }