From 9b543572bcb893683b4d425ebe2b36f5ccfc0ee9 Mon Sep 17 00:00:00 2001 From: YuriyZ <91314855+yuriyzz@users.noreply.github.com> Date: Tue, 2 Aug 2022 14:42:23 +0300 Subject: [PATCH] refactor(jans-auth-server): extracted into separate method each grant handling #1591 (#1998) * refactor(jans-auth-server): removed builder variable from Token Endpoint #1591 * refactor(jans-auth-server): extracted into separate methods refresh_token and client creds grants #1591 * refactor(jans-auth-server): extracted into separate methods each grant handling #1591 docs: no docs --- .../token/ws/rs/TokenRestWebServiceImpl.java | 317 +++++++++--------- .../ws/rs/TokenRestWebServiceValidator.java | 10 +- .../rs/TokenRestWebServiceValidatorTest.java | 26 ++ 3 files changed, 200 insertions(+), 153 deletions(-) diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java index a2a030be36a..105b5c49023 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java @@ -39,6 +39,7 @@ import io.jans.as.server.uma.service.UmaTokenService; import io.jans.as.server.util.ServerUtil; import io.jans.orm.exception.AuthenticationException; +import io.jans.orm.exception.operation.SearchException; import io.jans.util.OxConstants; import io.jans.util.StringHelper; import jakarta.inject.Inject; @@ -193,175 +194,86 @@ public Response requestAccessToken(String grantType, String code, } else if (gt == GrantType.CLIENT_CREDENTIALS) { return processClientGredentials(scope, request, auditLog, client, idTokenPreProcessing, executionContext); } else if (gt == GrantType.RESOURCE_OWNER_PASSWORD_CREDENTIALS) { - boolean authenticated = false; - User user = null; - if (authenticationFilterService.isEnabled()) { - String userDn = authenticationFilterService.processAuthenticationFilters(request.getParameterMap()); - if (StringHelper.isNotEmpty(userDn)) { - user = userService.getUserByDn(userDn); - authenticated = true; - } - } + return processROPC(username, password, scope, gt, idTokenPreProcessing, executionContext); + } else if (gt == GrantType.CIBA) { + return processCIBA(scope, authReqId, idTokenPreProcessing, executionContext); + } else if (gt == GrantType.DEVICE_CODE) { + return processDeviceCodeGrantType(executionContext, deviceCode, scope); + } + } catch (WebApplicationException e) { + throw e; + } catch (Exception e) { + log.error(e.getMessage(), e); + return response(Response.status(500), auditLog); + } + throw new WebApplicationException(tokenRestWebServiceValidator.error(400, TokenErrorResponseType.UNSUPPORTED_GRANT_TYPE, "Unsupported Grant Type.").build()); + } - if (!authenticated) { - user = authenticateUser(username, password, executionContext, user); - } + private Response processROPC(String username, String password, String scope, GrantType gt, Function idTokenPreProcessing, ExecutionContext executionContext) throws SearchException { + boolean authenticated = false; + User user = null; + if (authenticationFilterService.isEnabled()) { + String userDn = authenticationFilterService.processAuthenticationFilters(executionContext.getHttpRequest().getParameterMap()); + if (StringHelper.isNotEmpty(userDn)) { + user = userService.getUserByDn(userDn); + authenticated = true; + } + } - if (user != null) { - ResourceOwnerPasswordCredentialsGrant resourceOwnerPasswordCredentialsGrant = authorizationGrantList.createResourceOwnerPasswordCredentialsGrant(user, client); - executionContext.setGrant(resourceOwnerPasswordCredentialsGrant); - - SessionId sessionId = identity.getSessionId(); - if (sessionId != null) { - resourceOwnerPasswordCredentialsGrant.setAcrValues(OxConstants.SCRIPT_TYPE_INTERNAL_RESERVED_NAME); - resourceOwnerPasswordCredentialsGrant.setSessionDn(sessionId.getDn()); - resourceOwnerPasswordCredentialsGrant.save(); // call save after object modification!!! - - sessionId.getSessionAttributes().put(Constants.AUTHORIZED_GRANT, gt.getValue()); - boolean updateResult = sessionIdService.updateSessionId(sessionId, false, true, true); - if (!updateResult) { - log.debug("Failed to update session entry: '{}'", sessionId.getId()); - } - } - RefreshToken reToken = createRefreshToken(executionContext, scope); + if (!authenticated) { + user = authenticateUser(username, password, executionContext, user); + } - scope = resourceOwnerPasswordCredentialsGrant.checkScopesPolicy(scope); + tokenRestWebServiceValidator.validateUser(user, executionContext.getAuditLog()); - AccessToken accessToken = resourceOwnerPasswordCredentialsGrant.createAccessToken(executionContext); // create token after scopes are checked + ResourceOwnerPasswordCredentialsGrant resourceOwnerPasswordCredentialsGrant = authorizationGrantList.createResourceOwnerPasswordCredentialsGrant(user, executionContext.getClient()); + executionContext.setGrant(resourceOwnerPasswordCredentialsGrant); - IdToken idToken = null; - if (isTrue(appConfiguration.getOpenidScopeBackwardCompatibility()) && resourceOwnerPasswordCredentialsGrant.getScopes().contains("openid")) { - boolean includeIdTokenClaims = Boolean.TRUE.equals( - appConfiguration.getLegacyIdTokenClaims()); + SessionId sessionId = identity.getSessionId(); + if (sessionId != null) { + resourceOwnerPasswordCredentialsGrant.setAcrValues(OxConstants.SCRIPT_TYPE_INTERNAL_RESERVED_NAME); + resourceOwnerPasswordCredentialsGrant.setSessionDn(sessionId.getDn()); + resourceOwnerPasswordCredentialsGrant.save(); // call save after object modification!!! - ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(request, resourceOwnerPasswordCredentialsGrant, client, appConfiguration, attributeService); - context.setExecutionContext(executionContext); + sessionId.getSessionAttributes().put(Constants.AUTHORIZED_GRANT, gt.getValue()); + boolean updateResult = sessionIdService.updateSessionId(sessionId, false, true, true); + if (!updateResult) { + log.debug("Failed to update session entry: '{}'", sessionId.getId()); + } + } - executionContext.setIncludeIdTokenClaims(includeIdTokenClaims); - executionContext.setPreProcessing(idTokenPreProcessing); - executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); + RefreshToken reToken = createRefreshToken(executionContext, scope); - idToken = resourceOwnerPasswordCredentialsGrant.createIdToken( - null, null, null, null, null, executionContext); - } + scope = resourceOwnerPasswordCredentialsGrant.checkScopesPolicy(scope); - auditLog.updateOAuth2AuditLog(resourceOwnerPasswordCredentialsGrant, true); + AccessToken accessToken = resourceOwnerPasswordCredentialsGrant.createAccessToken(executionContext); // create token after scopes are checked - return response(Response.ok().entity(getJSonResponse(accessToken, - accessToken.getTokenType(), - accessToken.getExpiresIn(), - reToken, - scope, - idToken)), auditLog); - } else { - log.debug("Invalid user", new RuntimeException("User is empty")); - return response(error(401, TokenErrorResponseType.INVALID_CLIENT, "Invalid user."), auditLog); - } - } else if (gt == GrantType.CIBA) { - errorResponseFactory.validateComponentEnabled(ComponentType.CIBA); + IdToken idToken = null; + if (isTrue(appConfiguration.getOpenidScopeBackwardCompatibility()) && resourceOwnerPasswordCredentialsGrant.getScopes().contains("openid")) { + boolean includeIdTokenClaims = Boolean.TRUE.equals( + appConfiguration.getLegacyIdTokenClaims()); - log.debug("Attempting to find authorizationGrant by authReqId: '{}'", authReqId); - final CIBAGrant cibaGrant = authorizationGrantList.getCIBAGrant(authReqId); - executionContext.setGrant(cibaGrant); + ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(executionContext.getHttpRequest(), resourceOwnerPasswordCredentialsGrant, executionContext.getClient(), appConfiguration, attributeService); + context.setExecutionContext(executionContext); - log.trace("AuthorizationGrant : '{}'", cibaGrant); + executionContext.setIncludeIdTokenClaims(includeIdTokenClaims); + executionContext.setPreProcessing(idTokenPreProcessing); + executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); - if (cibaGrant != null) { - if (!cibaGrant.getClientId().equals(client.getClientId())) { - return response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), auditLog); - } - if (cibaGrant.getClient().getBackchannelTokenDeliveryMode() == BackchannelTokenDeliveryMode.PING || - cibaGrant.getClient().getBackchannelTokenDeliveryMode() == BackchannelTokenDeliveryMode.POLL) { - if (!cibaGrant.isTokensDelivered()) { - RefreshToken refToken = createRefreshToken(executionContext, scope); - AccessToken accessToken = cibaGrant.createAccessToken(executionContext); - - ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(request, cibaGrant, client, appConfiguration, attributeService); - context.setExecutionContext(executionContext); - - executionContext.setIncludeIdTokenClaims(false); - executionContext.setPreProcessing(idTokenPreProcessing); - executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); - - IdToken idToken = cibaGrant.createIdToken( - null, null, accessToken, refToken, null, executionContext); - - cibaGrant.setTokensDelivered(true); - cibaGrant.save(); - - RefreshToken reToken = null; - if (isRefreshTokenAllowed(client, scope, cibaGrant)) { - reToken = refToken; - } - - scope = cibaGrant.checkScopesPolicy(scope); - - auditLog.updateOAuth2AuditLog(cibaGrant, true); - - return response(Response.ok().entity(getJSonResponse(accessToken, - accessToken.getTokenType(), - accessToken.getExpiresIn(), - reToken, - scope, - idToken)), auditLog); - } else { - return response(error(400, TokenErrorResponseType.INVALID_GRANT, "AuthReqId is no longer available."), auditLog); - } - } else { - log.debug("Client is not using Poll flow authReqId: '{}'", authReqId); - return response(error(400, TokenErrorResponseType.UNAUTHORIZED_CLIENT, "The client is not authorized as it is configured in Push Mode"), auditLog); - } - } else { - final CibaRequestCacheControl cibaRequest = cibaRequestService.getCibaRequest(authReqId); - log.trace("Ciba request : '{}'", cibaRequest); - if (cibaRequest != null) { - if (!cibaRequest.getClient().getClientId().equals(client.getClientId())) { - return response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), auditLog); - } - long currentTime = new Date().getTime(); - Long lastAccess = cibaRequest.getLastAccessControl(); - if (lastAccess == null) { - lastAccess = currentTime; - } - cibaRequest.setLastAccessControl(currentTime); - cibaRequestService.update(cibaRequest); - - if (cibaRequest.getStatus() == CibaRequestStatus.PENDING) { - int intervalSeconds = appConfiguration.getBackchannelAuthenticationResponseInterval(); - long timeFromLastAccess = currentTime - lastAccess; - - if (timeFromLastAccess > intervalSeconds * 1000) { - log.debug("Access hasn't been granted yet for authReqId: '{}'", authReqId); - return response(error(400, TokenErrorResponseType.AUTHORIZATION_PENDING, "User hasn't answered yet"), auditLog); - } else { - log.debug("Slow down protection authReqId: '{}'", authReqId); - return response(error(400, TokenErrorResponseType.SLOW_DOWN, "Client is asking too fast the token."), auditLog); - } - } else if (cibaRequest.getStatus() == CibaRequestStatus.DENIED) { - log.debug("The end-user denied the authorization request for authReqId: '{}'", authReqId); - return response(error(400, TokenErrorResponseType.ACCESS_DENIED, "The end-user denied the authorization request."), auditLog); - } else if (cibaRequest.getStatus() == CibaRequestStatus.EXPIRED) { - log.debug("The authentication request has expired for authReqId: '{}'", authReqId); - return response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "The authentication request has expired"), auditLog); - } - } else { - log.debug("AuthorizationGrant is empty by authReqId: '{}'", authReqId); - return response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "Unable to find grant object for given auth_req_id."), auditLog); - } - } - } else if (gt == GrantType.DEVICE_CODE) { - return processDeviceCodeGrantType(executionContext, deviceCode, scope); - } - } catch (WebApplicationException e) { - throw e; - } catch (Exception e) { - log.error(e.getMessage(), e); - return response(Response.status(500), auditLog); + idToken = resourceOwnerPasswordCredentialsGrant.createIdToken( + null, null, null, null, null, executionContext); } - throw new WebApplicationException(tokenRestWebServiceValidator.error(400, TokenErrorResponseType.UNSUPPORTED_GRANT_TYPE, "Unsupported Grant Type.").build()); + executionContext.getAuditLog().updateOAuth2AuditLog(resourceOwnerPasswordCredentialsGrant, true); + + return response(Response.ok().entity(getJSonResponse(accessToken, + accessToken.getTokenType(), + accessToken.getExpiresIn(), + reToken, + scope, + idToken)), executionContext.getAuditLog()); } private Response processClientGredentials(String scope, HttpServletRequest request, OAuth2AuditLog auditLog, Client client, Function idTokenPreProcessing, ExecutionContext executionContext) { @@ -739,4 +651,105 @@ public String getJSonResponse(AccessToken accessToken, TokenType tokenType, return jsonObj.toString(); } + + private Response processCIBA(String scope, String authReqId, Function idTokenPreProcessing, ExecutionContext executionContext) { + errorResponseFactory.validateComponentEnabled(ComponentType.CIBA); + + log.debug("Attempting to find authorizationGrant by authReqId: '{}'", authReqId); + final CIBAGrant cibaGrant = authorizationGrantList.getCIBAGrant(authReqId); + executionContext.setGrant(cibaGrant); + + log.trace("AuthorizationGrant : '{}'", cibaGrant); + + Client client = executionContext.getClient(); + + if (cibaGrant != null) { + tokenRestWebServiceValidator.validateGrant(cibaGrant, client, authReqId, executionContext.getAuditLog()); + + if (cibaGrant.getClient().getBackchannelTokenDeliveryMode() == BackchannelTokenDeliveryMode.PING || + cibaGrant.getClient().getBackchannelTokenDeliveryMode() == BackchannelTokenDeliveryMode.POLL) { + if (!cibaGrant.isTokensDelivered()) { + RefreshToken refToken = createRefreshToken(executionContext, scope); + AccessToken accessToken = cibaGrant.createAccessToken(executionContext); + + ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(executionContext.getHttpRequest(), cibaGrant, client, appConfiguration, attributeService); + context.setExecutionContext(executionContext); + + executionContext.setIncludeIdTokenClaims(false); + executionContext.setPreProcessing(idTokenPreProcessing); + executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); + + IdToken idToken = cibaGrant.createIdToken( + null, null, accessToken, refToken, null, executionContext); + + cibaGrant.setTokensDelivered(true); + cibaGrant.save(); + + RefreshToken reToken = null; + if (isRefreshTokenAllowed(client, scope, cibaGrant)) { + reToken = refToken; + } + + scope = cibaGrant.checkScopesPolicy(scope); + + executionContext.getAuditLog().updateOAuth2AuditLog(cibaGrant, true); + + return response(Response.ok().entity(getJSonResponse(accessToken, + accessToken.getTokenType(), + accessToken.getExpiresIn(), + reToken, + scope, + idToken)), executionContext.getAuditLog()); + } else { + return response(error(400, TokenErrorResponseType.INVALID_GRANT, "AuthReqId is no longer available."), executionContext.getAuditLog()); + } + } else { + log.debug("Client is not using Poll flow authReqId: '{}'", authReqId); + return response(error(400, TokenErrorResponseType.UNAUTHORIZED_CLIENT, "The client is not authorized as it is configured in Push Mode"), executionContext.getAuditLog()); + } + } else { + return processCIBAIfGrantIsNull(authReqId, executionContext); + } + } + + private Response processCIBAIfGrantIsNull(String authReqId, ExecutionContext executionContext) { + final CibaRequestCacheControl cibaRequest = cibaRequestService.getCibaRequest(authReqId); + log.trace("Ciba request : '{}'", cibaRequest); + if (cibaRequest != null) { + if (!cibaRequest.getClient().getClientId().equals(executionContext.getClient().getClientId())) { + return response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), executionContext.getAuditLog()); + } + long currentTime = new Date().getTime(); + Long lastAccess = cibaRequest.getLastAccessControl(); + if (lastAccess == null) { + lastAccess = currentTime; + } + cibaRequest.setLastAccessControl(currentTime); + cibaRequestService.update(cibaRequest); + + if (cibaRequest.getStatus() == CibaRequestStatus.PENDING) { + int intervalSeconds = appConfiguration.getBackchannelAuthenticationResponseInterval(); + long timeFromLastAccess = currentTime - lastAccess; + + if (timeFromLastAccess > intervalSeconds * 1000) { + log.debug("Access hasn't been granted yet for authReqId: '{}'", authReqId); + return response(error(400, TokenErrorResponseType.AUTHORIZATION_PENDING, "User hasn't answered yet"), executionContext.getAuditLog()); + } else { + log.debug("Slow down protection authReqId: '{}'", authReqId); + return response(error(400, TokenErrorResponseType.SLOW_DOWN, "Client is asking too fast the token."), executionContext.getAuditLog()); + } + } else if (cibaRequest.getStatus() == CibaRequestStatus.DENIED) { + log.debug("The end-user denied the authorization request for authReqId: '{}'", authReqId); + return response(error(400, TokenErrorResponseType.ACCESS_DENIED, "The end-user denied the authorization request."), executionContext.getAuditLog()); + } else if (cibaRequest.getStatus() == CibaRequestStatus.EXPIRED) { + log.debug("The authentication request has expired for authReqId: '{}'", authReqId); + return response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "The authentication request has expired"), executionContext.getAuditLog()); + } + } else { + log.debug("AuthorizationGrant is empty by authReqId: '{}'", authReqId); + return response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "Unable to find grant object for given auth_req_id."), executionContext.getAuditLog()); + } + + return response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "Unable to find grant object for given auth_req_id."), executionContext.getAuditLog()); + } } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java index 30bc34995d4..b1a9def8d40 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java @@ -1,5 +1,6 @@ package io.jans.as.server.token.ws.rs; +import io.jans.as.common.model.common.User; import io.jans.as.common.model.registration.Client; import io.jans.as.model.common.GrantType; import io.jans.as.model.configuration.AppConfiguration; @@ -149,7 +150,7 @@ public void validateGrant(AuthorizationGrant grant, Client client, Object identi } if (!client.getClientId().equals(grant.getClientId())) { - log.debug("AuthorizationCodeGrant is found but belongs to another client. Grant's clientId: '{}', identifier: '{}'", grant.getClientId(), identifier); + log.debug("AuthorizationGrant is found but belongs to another client. Grant's clientId: '{}', identifier: '{}'", grant.getClientId(), identifier); if (onFailure != null) { onFailure.accept(grant); } @@ -163,4 +164,11 @@ public void validateRefreshToken(RefreshToken refreshTokenObject, OAuth2AuditLog throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find refresh token or otherwise token type or client does not match."), auditLog)); } } + + public void validateUser(User user, OAuth2AuditLog auditLog) { + if (user == null) { + log.debug("Invalid user", new RuntimeException("User is empty")); + throw new WebApplicationException(response(error(401, TokenErrorResponseType.INVALID_CLIENT, "Invalid user."), auditLog)); + } + } } diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java index dccae29b02d..893af49855f 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java @@ -251,4 +251,30 @@ public void validateRefreshToken_whenRefreshTokenIsExpired_shouldRaiseError() { } fail("No error when refreshToken is expired."); } + + @Test + public void validateUser_whenUserIsNull_shouldRaiseError() { + try { + validator.validateUser(null, AUDIT_LOG); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 401); + return; + } + fail("No error when user is null."); + } + + @Test + public void validateUser_whenUserIsValid_shouldNotRaiseError() { + try { + final User user = new User(); + user.setUserId("test_user"); + user.setCreatedAt(new Date()); + + validator.validateUser(user, AUDIT_LOG); + return; + } catch (WebApplicationException e) { + // ignore + } + fail("Error for valid user is raised."); + } }