@Test public void acceptInvitationWithInvalidRedirectUri() throws Exception { ScimUser user = new ScimUser("user-id-001", "*****@*****.**", "first", "last"); user.setOrigin(UAA); BaseClientDetails clientDetails = new BaseClientDetails("client-id", null, null, null, null, "http://example.com/redirect"); when(scimUserProvisioning.verifyUser(anyString(), anyInt())).thenReturn(user); when(scimUserProvisioning.update(anyString(), anyObject())).thenReturn(user); when(scimUserProvisioning.retrieve(eq("user-id-001"))).thenReturn(user); when(clientDetailsService.loadClientByClientId("acmeClientId")).thenReturn(clientDetails); Map<String, String> userData = new HashMap<>(); userData.put(USER_ID, "user-id-001"); userData.put(EMAIL, "*****@*****.**"); userData.put(REDIRECT_URI, "http://someother/redirect"); userData.put(CLIENT_ID, "acmeClientId"); when(expiringCodeStore.retrieveCode(anyString())) .thenReturn( new ExpiringCode( "code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); String redirectLocation = emailInvitationsService.acceptInvitation("code", "password").getRedirectUri(); verify(scimUserProvisioning).verifyUser(user.getId(), user.getVersion()); verify(scimUserProvisioning).changePassword(user.getId(), null, "password"); assertEquals("/home", redirectLocation); }
/** * Is a refresh token supported for this client (or the global setting if {@link * #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set. * * @param authorizationRequest the current authorization request * @return boolean to indicate if refresh token is supported */ protected boolean isSupportRefreshToken(OAuth2Request authorizationRequest) { if (clientDetailsService != null) { ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); return client.getAuthorizedGrantTypes().contains("refresh_token"); } return this.supportRefreshToken; }
@Override public AuthorizationRequest createAuthorizationRequest(Map<String, String> parameters) { String clientId = parameters.get("client_id"); if (clientId == null) { throw new InvalidClientException("A client id must be provided"); } ClientDetails client = clientDetailsService.loadClientByClientId(clientId); String requestNonce = parameters.get("nonce"); // Only process if the user is authenticated. If the user is not authenticated yet, this // code will be called a second time once the user is redirected from the login page back // to the auth endpoint. Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (requestNonce != null && principal != null && principal instanceof User) { // Check request nonce for reuse Collection<Nonce> clientNonces = nonceService.getByClientId(client.getClientId()); for (Nonce nonce : clientNonces) { String nonceVal = nonce.getValue(); if (nonceVal.equals(requestNonce)) { throw new NonceReuseException(client.getClientId(), nonce); } } // Store nonce Nonce nonce = new Nonce(); nonce.setClientId(client.getClientId()); nonce.setValue(requestNonce); DateTime now = new DateTime(new Date()); nonce.setUseDate(now.toDate()); DateTime expDate = now.plus(nonceStorageDuration); Date expirationJdkDate = expDate.toDate(); nonce.setExpireDate(expirationJdkDate); nonceService.save(nonce); } Set<String> scopes = OAuth2Utils.parseParameterList(parameters.get("scope")); if ((scopes == null || scopes.isEmpty())) { // TODO: do we want to allow default scoping at all? // If no scopes are specified in the incoming data, it is possible to default to the client's // registered scopes, but minus the "openid" scope. OpenID Connect requests MUST have the // "openid" scope. Set<String> clientScopes = client.getScope(); if (clientScopes.contains("openid")) { clientScopes.remove("openid"); } scopes = clientScopes; } DefaultAuthorizationRequest request = new DefaultAuthorizationRequest( parameters, Collections.<String, String>emptyMap(), clientId, scopes); request.addClientDetails(client); return request; }
/** * Create an authorization request applying various UAA rules to the authorizationParameters and * the registered client details. * * <ul> * <li>For client_credentials grants, the default scopes are the client's granted authorities * <li>For other grant types the default scopes are the registered scopes in the client details * <li>Only scopes in those lists are valid, otherwise there is an exception * <li>If the scopes contain separators then resource ids are extracted as the scope value up to * the last index of the separator * <li>Some scopes can be hard-wired to resource ids (like the open id connect values), in which * case the separator is ignored * </ul> * * @see * org.springframework.security.oauth2.provider.AuthorizationRequestFactory#createAuthorizationRequest(java.util.Map, * java.lang.String, java.lang.String, java.util.Set) */ @Override public AuthorizationRequest createAuthorizationRequest( Map<String, String> authorizationParameters) { String clientId = authorizationParameters.get("client_id"); BaseClientDetails clientDetails = new BaseClientDetails(clientDetailsService.loadClientByClientId(clientId)); Set<String> scopes = OAuth2Utils.parseParameterList(authorizationParameters.get("scope")); String grantType = authorizationParameters.get("grant_type"); if ((scopes == null || scopes.isEmpty())) { if ("client_credentials".equals(grantType)) { // The client authorities should be a list of scopes scopes = AuthorityUtils.authorityListToSet(clientDetails.getAuthorities()); } else { // The default for a user token is the scopes registered with // the client scopes = clientDetails.getScope(); } } Set<String> scopesFromExternalAuthorities = null; if (!"client_credentials".equals(grantType) && securityContextAccessor.isUser()) { scopes = checkUserScopes(scopes, securityContextAccessor.getAuthorities(), clientDetails); // TODO: will the grantType ever contain client_credentials or // authorization_code // External Authorities are things like LDAP groups that will be // mapped to Oauth scopes // Add those scopes to the request. These scopes will not be // validated against the scopes // registered to a client. // These scopes also do not need approval. The fact that they are // already in an external // group communicates user approval. Denying approval does not mean // much scopesFromExternalAuthorities = findScopesFromAuthorities(authorizationParameters.get("authorities")); } Set<String> resourceIds = getResourceIds(clientDetails, scopes); clientDetails.setResourceIds(resourceIds); DefaultAuthorizationRequest request = new DefaultAuthorizationRequest(authorizationParameters); if (!scopes.isEmpty()) { request.setScope(scopes); } if (scopesFromExternalAuthorities != null) { Map<String, String> existingAuthorizationParameters = new LinkedHashMap<String, String>(); existingAuthorizationParameters.putAll(request.getAuthorizationParameters()); existingAuthorizationParameters.put( "external_scopes", OAuth2Utils.formatParameterList(scopesFromExternalAuthorities)); request.setAuthorizationParameters(existingAuthorizationParameters); } request.addClientDetails(clientDetails); return request; }
@RequestMapping(value = "/oauth/clients/{client}", method = RequestMethod.GET) @ResponseBody public ClientDetails getClientDetails(@PathVariable String client) throws Exception { try { return removeSecret(clientDetailsService.loadClientByClientId(client)); } catch (InvalidClientException e) { throw new NoSuchClientException("No such client: " + client); } }
/** * The refresh token validity period in seconds * * @param authorizationRequest the current authorization request * @return the refresh token validity period in seconds */ protected int getRefreshTokenValiditySeconds(OAuth2Request authorizationRequest) { if (clientDetailsService != null) { ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); Integer validity = client.getRefreshTokenValiditySeconds(); if (validity != null) { return validity; } } return refreshTokenValiditySeconds; }
@RequestMapping("/oauth/confirm_access") public ModelAndView getAccessConfirmation(@ModelAttribute AuthorizationRequest clientAuth) throws Exception { ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); TreeMap<String, Object> model = new TreeMap<String, Object>(); model.put("auth_request", clientAuth); model.put("client", client); return new ModelAndView("access_confirmation", model); }
@RequestMapping(value = "/oauth/clients/{client}/secret", method = RequestMethod.PUT) @ResponseStatus(HttpStatus.NO_CONTENT) public void changeSecret(@PathVariable String client, @RequestBody SecretChangeRequest change) { ClientDetails clientDetails; try { clientDetails = clientDetailsService.loadClientByClientId(client); } catch (InvalidClientException e) { throw new NoSuchClientException("No such client: " + client); } checkPasswordChangeIsAllowed(clientDetails, change.getOldSecret()); clientRegistrationService.updateClientSecret(client, change.getSecret()); }
@RequestMapping("/oauth/confirm_access") public String confirm( @ModelAttribute AuthorizationRequest clientAuth, Map<String, Object> model, final HttpServletRequest request) throws Exception { if (clientAuth == null) { model.put( "error", "No authorizatioun request is present, so we cannot confirm access (we don't know what you are asking for)."); // response.sendError(HttpServletResponse.SC_BAD_REQUEST); } else { ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); model.put("auth_request", clientAuth); model.put("client", client); model.put( "message", "To confirm or deny access POST to the following locations with the parameters requested."); Map<String, Object> options = new HashMap<String, Object>() { { put( "confirm", new HashMap<String, String>() { { put("location", getLocation(request, "oauth/authorize")); put("path", getPath(request, "oauth/authorize")); put("key", "user_oauth_approval"); put("value", "true"); } }); put( "deny", new HashMap<String, String>() { { put("location", getLocation(request, "oauth/authorize")); put("path", getPath(request, "oauth/authorize")); put("key", "user_oauth_approval"); put("value", "false"); } }); } }; model.put("options", options); } return "access_confirmation"; }
@Test public void testValidCredentialsScopesForClientOnly() throws Exception { ClientDetailsEntity clientDetailsEntity = new ClientDetailsEntity(); Set<ClientScopeEntity> scopes = new HashSet<ClientScopeEntity>(2); scopes.add(new ClientScopeEntity(ScopePathType.ORCID_PROFILE_CREATE.value())); clientDetailsEntity.setClientScopes(scopes); String orcid = "2875-8158-1475-6194"; when(clientDetailsService.loadClientByClientId(orcid)).thenReturn(clientDetailsEntity); OrcidClientCredentialsChecker checker = new OrcidClientCredentialsChecker(clientDetailsService, oAuth2RequestFactory); Set<String> requestedScopes = new HashSet<String>(Arrays.asList(ScopePathType.READ_PUBLIC.value())); checker.validateCredentials( "client_credentials", new TokenRequest( Collections.<String, String>emptyMap(), orcid, requestedScopes, "client_credentials")); }
// TODO: add cases for username no existing external user with username not email @Test public void accept_invitation_with_external_user_that_does_not_have_email_as_their_username() { String userId = "user-id-001"; String email = "*****@*****.**"; String actualUsername = "******"; ScimUser userBeforeAccept = new ScimUser(userId, email, "first", "last"); userBeforeAccept.setPrimaryEmail(email); userBeforeAccept.setOrigin(Origin.SAML); when(scimUserProvisioning.verifyUser(eq(userId), anyInt())).thenReturn(userBeforeAccept); when(scimUserProvisioning.retrieve(eq(userId))).thenReturn(userBeforeAccept); BaseClientDetails clientDetails = new BaseClientDetails("client-id", null, null, null, null, "http://example.com/redirect"); when(clientDetailsService.loadClientByClientId("acmeClientId")).thenReturn(clientDetails); Map<String, String> userData = new HashMap<>(); userData.put(USER_ID, userBeforeAccept.getId()); userData.put(EMAIL, userBeforeAccept.getPrimaryEmail()); userData.put(REDIRECT_URI, "http://someother/redirect"); userData.put(CLIENT_ID, "acmeClientId"); when(expiringCodeStore.retrieveCode(anyString())) .thenReturn( new ExpiringCode( "code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); ScimUser userAfterAccept = new ScimUser( userId, actualUsername, userBeforeAccept.getGivenName(), userBeforeAccept.getFamilyName()); userAfterAccept.setPrimaryEmail(email); when(scimUserProvisioning.verifyUser(eq(userId), anyInt())).thenReturn(userAfterAccept); ScimUser acceptedUser = emailInvitationsService.acceptInvitation("code", "password").getUser(); assertEquals(userAfterAccept.getUserName(), acceptedUser.getUserName()); assertEquals(userAfterAccept.getName(), acceptedUser.getName()); assertEquals(userAfterAccept.getPrimaryEmail(), acceptedUser.getPrimaryEmail()); verify(scimUserProvisioning).verifyUser(eq(userId), anyInt()); }
@Test public void testDefaultConfiguration() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); this.context.register( AuthorizationAndResourceServerConfiguration.class, MinimalSecureWebApplication.class); this.context.refresh(); this.context.getBean(AUTHORIZATION_SERVER_CONFIG); this.context.getBean(RESOURCE_SERVER_CONFIG); this.context.getBean(OAuth2MethodSecurityConfiguration.class); ClientDetails config = this.context.getBean(BaseClientDetails.class); AuthorizationEndpoint endpoint = this.context.getBean(AuthorizationEndpoint.class); UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils.getField(endpoint, "userApprovalHandler"); ClientDetailsService clientDetailsService = this.context.getBean(ClientDetailsService.class); ClientDetails clientDetails = clientDetailsService.loadClientByClientId(config.getClientId()); assertThat(AopUtils.isJdkDynamicProxy(clientDetailsService), equalTo(true)); assertThat( AopUtils.getTargetClass(clientDetailsService).getName(), equalTo(ClientDetailsService.class.getName())); assertThat(handler instanceof ApprovalStoreUserApprovalHandler, equalTo(true)); assertThat(clientDetails, equalTo(config)); verifyAuthentication(config); }
@Override public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException, DataAccessException { ClientDetails client = clientDetailsService.loadClientByClientId(clientId); String password = client.getClientSecret(); boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); GrantedAuthority roleClient = new SimpleGrantedAuthority("ROLE_CLIENT"); authorities.add(roleClient); return new User( clientId, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); }
@RequestMapping( value = "/invite_users", method = RequestMethod.POST, consumes = "application/json") public ResponseEntity<InvitationsResponse> inviteUsers( @RequestBody InvitationsRequest invitations, @RequestParam(value = "client_id", required = false) String clientId, @RequestParam(value = "redirect_uri") String redirectUri) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication instanceof OAuth2Authentication) { OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication; if (clientId == null) { clientId = oAuth2Authentication.getOAuth2Request().getClientId(); } } InvitationsResponse invitationsResponse = new InvitationsResponse(); DomainFilter filter = new DomainFilter(); List<IdentityProvider> activeProviders = providers.retrieveActive(IdentityZoneHolder.get().getId()); ClientDetails client = clients.loadClientByClientId(clientId); for (String email : invitations.getEmails()) { try { List<IdentityProvider> providers = filter.filter(activeProviders, client, email); if (providers.size() == 1) { ScimUser user = findOrCreateUser(email, providers.get(0).getOriginKey()); String accountsUrl = UaaUrlUtils.getUaaUrl("/invitations/accept"); Map<String, String> data = new HashMap<>(); data.put(InvitationConstants.USER_ID, user.getId()); data.put(InvitationConstants.EMAIL, user.getPrimaryEmail()); data.put(CLIENT_ID, clientId); data.put(REDIRECT_URI, redirectUri); data.put(ORIGIN, user.getOrigin()); Timestamp expiry = new Timestamp( System.currentTimeMillis() + (INVITATION_EXPIRY_DAYS * 24 * 60 * 60 * 1000)); ExpiringCode code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(data), expiry, null); String invitationLink = accountsUrl + "?code=" + code.getCode(); try { URL inviteLink = new URL(invitationLink); invitationsResponse .getNewInvites() .add( InvitationsResponse.success( user.getPrimaryEmail(), user.getId(), user.getOrigin(), inviteLink)); } catch (MalformedURLException mue) { invitationsResponse .getFailedInvites() .add( InvitationsResponse.failure( email, "invitation.exception.url", String.format("Malformed url", invitationLink))); } } else if (providers.size() == 0) { invitationsResponse .getFailedInvites() .add( InvitationsResponse.failure( email, "provider.non-existent", "No authentication provider found.")); } else { invitationsResponse .getFailedInvites() .add( InvitationsResponse.failure( email, "provider.ambiguous", "Multiple authentication providers found.")); } } catch (ScimResourceConflictException x) { invitationsResponse .getFailedInvites() .add( InvitationsResponse.failure( email, "user.ambiguous", "Multiple users with the same origin matched to the email address.")); } catch (UaaException uaae) { invitationsResponse .getFailedInvites() .add(InvitationsResponse.failure(email, "invitation.exception", uaae.getMessage())); } } return new ResponseEntity<>(invitationsResponse, HttpStatus.OK); }
@Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { return clients_.loadClientByClientId(clientId); }
@Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final boolean debug = logger.isDebugEnabled(); final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; try { Authentication credentials = extractCredentials(request); if (credentials != null) { if (debug) { logger.debug("Authentication credentials found for '" + credentials.getName() + "'"); } Authentication authResult = authenticationManager.authenticate(credentials); if (debug) { logger.debug("Authentication success: " + authResult.getName()); } Authentication requestingPrincipal = SecurityContextHolder.getContext().getAuthentication(); if (requestingPrincipal == null) { throw new BadCredentialsException( "No client authentication found. Remember to put a filter upstream of the LoginAuthenticationFilter."); } String clientId = request.getParameter("client_id"); if (null == clientId) { logger.error("No client_id in the request"); throw new BadCredentialsException("No client_id in the request"); } // Check that the client exists ClientDetails authenticatingClient = clientDetailsService.loadClientByClientId(clientId); if (authenticatingClient == null) { throw new BadCredentialsException("No client " + clientId + " found"); } DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest( getSingleValueMap(request), null, authenticatingClient.getClientId(), getScope(request)); if (requestingPrincipal.isAuthenticated()) { // Ensure the OAuth2Authentication is authenticated authorizationRequest.setApproved(true); } SecurityContextHolder.getContext() .setAuthentication(new OAuth2Authentication(authorizationRequest, authResult)); onSuccessfulAuthentication(request, response, authResult); } } catch (AuthenticationException failed) { SecurityContextHolder.clearContext(); if (debug) { logger.debug("Authentication request for failed: " + failed); } onUnsuccessfulAuthentication(request, response, failed); authenticationEntryPoint.commence(request, response, failed); return; } chain.doFilter(request, response); }