/** * Delete the indicated client from the system. * * @param clientId * @param m * @param auth * @return */ @PreAuthorize( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") @RequestMapping( value = "/{id}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) public String deleteResource( @PathVariable("id") String clientId, Model m, OAuth2Authentication auth) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { clientService.deleteClient(client); m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); // http 204 return HttpCodeView.VIEWNAME; } else { // client mismatch logger.error( "readClientConfiguration failed, client ID mismatch: " + clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403 return HttpCodeView.VIEWNAME; } }
/** * Delete the indicated client from the system. * * @param clientId * @param m * @param auth * @return */ @PreAuthorize( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE + "')") @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json") public String deleteClient( @PathVariable("id") String clientId, Model m, OAuth2Authentication auth) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { clientService.deleteClient(client); m.addAttribute("code", HttpStatus.NO_CONTENT); // http 204 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 * * @param json * @param m * @param principal * @return */ @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping( method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public String apiAddClient(@RequestBody String jsonString, Model m, Authentication auth) { JsonObject json = null; ClientDetailsEntity client = null; try { json = parser.parse(jsonString).getAsJsonObject(); client = gson.fromJson(json, ClientDetailsEntity.class); } catch (JsonSyntaxException e) { logger.error("apiAddClient failed due to JsonSyntaxException", e); m.addAttribute("code", HttpStatus.BAD_REQUEST); m.addAttribute( "errorMessage", "Could not save new client. The server encountered a JSON syntax exception. Contact a system administrator for assistance."); return "jsonErrorView"; } catch (IllegalStateException e) { logger.error("apiAddClient failed due to IllegalStateException", e); m.addAttribute("code", HttpStatus.BAD_REQUEST); m.addAttribute( "errorMessage", "Could not save new client. The server encountered an IllegalStateException. Refresh and try again - if the problem persists, contact a system administrator for assistance."); return "jsonErrorView"; } // if they leave the client identifier empty, force it to be generated if (Strings.isNullOrEmpty(client.getClientId())) { client = clientService.generateClientId(client); } // if they've asked for us to generate a client secret, do so here if (json.has("generateClientSecret") && json.get("generateClientSecret").getAsBoolean()) { client = clientService.generateClientSecret(client); } // set owners as current logged in user // try to look up a user based on the principal's name if (client.getContacts() == null || client.getContacts().isEmpty()) { UserInfo user = userInfoService.getByUsername(auth.getName()); if (user != null && user.getEmail() != null) { client.setContacts(Sets.newHashSet(user.getEmail())); } } client.setDynamicallyRegistered(false); ClientDetailsEntity newClient = clientService.saveNewClient(client); m.addAttribute("entity", newClient); if (isAdmin(auth)) { return "clientEntityViewAdmins"; } else { return "clientEntityViewUsers"; } }
@PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping( value = "/registration/{clientId}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) public String rotateRegistrationTokenByClientId( @PathVariable("clientId") String clientId, ModelMap m, Principal p) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null) { OAuth2AccessTokenEntity token = oidcTokenService.rotateRegistrationAccessTokenForClient(client); token = tokenService.saveAccessToken(token); if (token != null) { m.put(JsonEntityView.ENTITY, token); return TokenApiView.VIEWNAME; } else { m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.put(JsonErrorView.ERROR_MESSAGE, "No registration token could be found."); return JsonErrorView.VIEWNAME; } } else { // client not found m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.put( JsonErrorView.ERROR_MESSAGE, "The requested client with id " + clientId + " could not be found."); return JsonErrorView.VIEWNAME; } }
private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient) throws ValidationException { 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) { if (Strings.isNullOrEmpty(newClient.getClientSecret())) { // no secret yet, we need to generate a secret newClient = clientService.generateClientSecret(newClient); } } else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.PRIVATE_KEY) { if (Strings.isNullOrEmpty(newClient.getJwksUri()) && newClient.getJwks() == null) { throw new ValidationException( "invalid_client_metadata", "JWK Set URI required when using private key authentication", HttpStatus.BAD_REQUEST); } newClient.setClientSecret(null); } else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.NONE) { newClient.setClientSecret(null); } else { throw new ValidationException( "invalid_client_metadata", "Unknown authentication method", HttpStatus.BAD_REQUEST); } return newClient; }
/** * Get a list of all clients * * @param modelAndView * @return */ @RequestMapping(method = RequestMethod.GET, produces = "application/json") public String apiGetAllClients(Model model, Authentication auth) { Collection<ClientDetailsEntity> clients = clientService.getAllClients(); model.addAttribute("entity", clients); if (isAdmin(auth)) { return "clientEntityViewAdmins"; } else { return "clientEntityViewUsers"; } }
/** * 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; } }
/** * Prepares a collection of ApprovedSite mocks to be returned from the approvedSiteService and a * collection of ClientDetailEntity mocks to be returned from the clientService. */ @Before public void prepare() { Mockito.reset(approvedSiteService, clientService); Mockito.when(ap1.getUserId()).thenReturn(userId1); Mockito.when(ap1.getClientId()).thenReturn(clientId1); Mockito.when(ap2.getUserId()).thenReturn(userId1); Mockito.when(ap2.getClientId()).thenReturn(clientId1); Mockito.when(ap3.getUserId()).thenReturn(userId2); Mockito.when(ap3.getClientId()).thenReturn(clientId2); Mockito.when(ap4.getUserId()).thenReturn(userId2); Mockito.when(ap4.getClientId()).thenReturn(clientId3); Mockito.when(ap5.getUserId()).thenReturn(userId2); Mockito.when(ap5.getClientId()).thenReturn(clientId1); Mockito.when(ap6.getUserId()).thenReturn(userId1); Mockito.when(ap6.getClientId()).thenReturn(clientId4); Mockito.when(approvedSiteService.getAll()).thenReturn(Sets.newHashSet(ap1, ap2, ap3, ap4)); Mockito.when(client1.getId()).thenReturn(1L); Mockito.when(client2.getId()).thenReturn(2L); Mockito.when(client3.getId()).thenReturn(3L); Mockito.when(client4.getId()).thenReturn(4L); Mockito.when(clientService.getAllClients()) .thenReturn(Sets.newHashSet(client1, client2, client3, client4)); Mockito.when(clientService.loadClientByClientId(clientId1)).thenReturn(client1); Mockito.when(clientService.loadClientByClientId(clientId2)).thenReturn(client2); Mockito.when(clientService.loadClientByClientId(clientId3)).thenReturn(client3); Mockito.when(clientService.loadClientByClientId(clientId4)).thenReturn(client4); }
/** * Delete a client * * @param id * @param modelAndView * @return */ @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public String apiDeleteClient(@PathVariable("id") Long id, ModelAndView modelAndView) { ClientDetailsEntity client = clientService.getClientById(id); if (client == null) { logger.error("apiDeleteClient failed; client with id " + id + " could not be found."); modelAndView.getModelMap().put("code", HttpStatus.NOT_FOUND); modelAndView .getModelMap() .put( "errorMessage", "Could not delete client. The requested client with id " + id + "could not be found."); return "jsonErrorView"; } else { modelAndView.getModelMap().put("code", HttpStatus.OK); clientService.deleteClient(client); } return "httpCodeView"; }
/** * Get an individual client * * @param id * @param modelAndView * @return */ @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json") public String apiShowClient(@PathVariable("id") Long id, Model model, Authentication auth) { ClientDetailsEntity client = clientService.getClientById(id); if (client == null) { logger.error("apiShowClient failed; client with id " + id + " could not be found."); model.addAttribute("code", HttpStatus.NOT_FOUND); model.addAttribute( "errorMessage", "The requested client with id " + id + "could not be found."); return "jsonErrorView"; } model.addAttribute("entity", client); if (isAdmin(auth)) { return "clientEntityViewAdmins"; } else { return "clientEntityViewUsers"; } }
/** * 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"; } }
@PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping( value = "/client/{clientId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public String getAccessTokensByClientId( @PathVariable("clientId") String clientId, ModelMap m, Principal p) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null) { List<OAuth2AccessTokenEntity> tokens = tokenService.getAccessTokensForClient(client); m.put(JsonEntityView.ENTITY, tokens); return TokenApiView.VIEWNAME; } else { // client not found m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.put( JsonErrorView.ERROR_MESSAGE, "The requested client with id " + clientId + " could not be found."); return JsonErrorView.VIEWNAME; } }
/** * 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; } }