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 daddbbfdd6c..861b6a095e6 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 @@ -151,41 +151,26 @@ public Response requestAccessToken(String grantType, String code, return umaTokenService.requestRpt(grantType, ticket, claimToken, claimTokenFormat, pctCode, rptCode, scope, request, response); } - OAuth2AuditLog oAuth2AuditLog = new OAuth2AuditLog(ServerUtil.getIpAddress(request), Action.TOKEN_REQUEST); - oAuth2AuditLog.setClientId(clientId); - oAuth2AuditLog.setUsername(username); - oAuth2AuditLog.setScope(scope); + OAuth2AuditLog auditLog = new OAuth2AuditLog(ServerUtil.getIpAddress(request), Action.TOKEN_REQUEST); + auditLog.setClientId(clientId); + auditLog.setUsername(username); + auditLog.setScope(scope); String tokenBindingHeader = request.getHeader("Sec-Token-Binding"); scope = ServerUtil.urlDecode(scope); // it may be encoded in uma case ResponseBuilder builder = Response.ok(); - String dpopStr = runDPoP(request, oAuth2AuditLog); + String dpopStr = runDPoP(request, auditLog); try { - tokenRestWebServiceValidator.validateParams(grantType, code, redirectUri, refreshToken, oAuth2AuditLog); + tokenRestWebServiceValidator.validateParams(grantType, code, redirectUri, refreshToken, auditLog); GrantType gt = GrantType.fromString(grantType); log.debug("Grant type: '{}'", gt); - SessionClient sessionClient = identity.getSessionClient(); - Client client = null; - if (sessionClient != null) { - client = sessionClient.getClient(); - log.debug("Get sessionClient: '{}'", sessionClient); - } - - if (client == null) { - return response(error(401, TokenErrorResponseType.INVALID_GRANT, "Unable to find client."), oAuth2AuditLog); - } - - log.debug("Get client from session: '{}'", client.getClientId()); - if (client.isDisabled()) { - return response(error(Response.Status.FORBIDDEN.getStatusCode(), TokenErrorResponseType.DISABLED_CLIENT, "Client is disabled."), oAuth2AuditLog); - } - - tokenRestWebServiceValidator.validateGrantType(gt, client.getGrantTypes(), oAuth2AuditLog); + Client client = tokenRestWebServiceValidator.validateClient(getClient(), auditLog); + tokenRestWebServiceValidator.validateGrantType(gt, client, auditLog); final Function idTokenTokingBindingPreprocessing = TokenBindingMessage.createIdTokenTokingBindingPreprocessing( tokenBindingHeader, client.getIdTokenTokenBindingCnf()); // for all except authorization code grant @@ -209,17 +194,17 @@ public Response requestAccessToken(String grantType, String code, log.debug("AuthorizationCodeGrant is empty by clientId: '{}', code: '{}'", client.getClientId(), code); // if authorization code is not found then code was already used or wrong client provided = remove all grants with this auth code grantService.removeAllByAuthorizationCode(code); - return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object for given code."), oAuth2AuditLog); + return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object for given code."), auditLog); } if (!client.getClientId().equals(authorizationCodeGrant.getClientId())) { log.debug("AuthorizationCodeGrant is found but belongs to another client. Grant's clientId: '{}', code: '{}'", authorizationCodeGrant.getClientId(), code); // if authorization code is not found then code was already used or wrong client provided = remove all grants with this auth code grantService.removeAllByAuthorizationCode(code); - return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Client mismatch."), oAuth2AuditLog); + return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Client mismatch."), auditLog); } - validatePKCE(authorizationCodeGrant, codeVerifier, oAuth2AuditLog); + validatePKCE(authorizationCodeGrant, codeVerifier, auditLog); authorizationCodeGrant.setIsCachedWithNoPersistence(false); authorizationCodeGrant.save(); @@ -254,12 +239,12 @@ public Response requestAccessToken(String grantType, String code, nonce, authorizationCodeGrant.getAuthorizationCode(), accToken, null, null, executionContext); } - oAuth2AuditLog.updateOAuth2AuditLog(authorizationCodeGrant, true); + auditLog.updateOAuth2AuditLog(authorizationCodeGrant, true); grantService.removeAuthorizationCode(authorizationCodeGrant.getAuthorizationCode().getCode()); final String entity = getJSonResponse(accToken, accToken.getTokenType(), accToken.getExpiresIn(), reToken, scope, idToken); - return response(Response.ok().entity(entity), oAuth2AuditLog); + return response(Response.ok().entity(entity), auditLog); } if (gt == GrantType.REFRESH_TOKEN) { @@ -267,13 +252,13 @@ public Response requestAccessToken(String grantType, String code, if (authorizationGrant == null) { log.trace("Grant object is not found by refresh token."); - return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object by refresh token or otherwise token type or client does not match."), oAuth2AuditLog); + return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object by refresh token or otherwise token type or client does not match."), auditLog); } final RefreshToken refreshTokenObject = authorizationGrant.getRefreshToken(refreshToken); if (refreshTokenObject == null || !refreshTokenObject.isValid()) { log.trace("Invalid refresh token."); - return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find refresh token or otherwise token type or client does not match."), oAuth2AuditLog); + return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find refresh token or otherwise token type or client does not match."), auditLog); } executionContext.setGrant(authorizationGrant); @@ -321,7 +306,7 @@ public Response requestAccessToken(String grantType, String code, reToken, scope, idToken)); - oAuth2AuditLog.updateOAuth2AuditLog(authorizationGrant, true); + auditLog.updateOAuth2AuditLog(authorizationGrant, true); } else if (gt == GrantType.CLIENT_CREDENTIALS) { ClientCredentialsGrant clientCredentialsGrant = authorizationGrantList.createClientCredentialsGrant(new User(), client); @@ -345,7 +330,7 @@ public Response requestAccessToken(String grantType, String code, null, null, null, null, null, executionContext); } - oAuth2AuditLog.updateOAuth2AuditLog(clientCredentialsGrant, true); + auditLog.updateOAuth2AuditLog(clientCredentialsGrant, true); builder.entity(getJSonResponse(accessToken, accessToken.getTokenType(), accessToken.getExpiresIn(), @@ -407,7 +392,7 @@ public Response requestAccessToken(String grantType, String code, null, null, null, null, null, executionContext); } - oAuth2AuditLog.updateOAuth2AuditLog(resourceOwnerPasswordCredentialsGrant, true); + auditLog.updateOAuth2AuditLog(resourceOwnerPasswordCredentialsGrant, true); builder.entity(getJSonResponse(accessToken, accessToken.getTokenType(), accessToken.getExpiresIn(), @@ -430,7 +415,7 @@ public Response requestAccessToken(String grantType, String code, if (cibaGrant != null) { if (!cibaGrant.getClientId().equals(client.getClientId())) { builder = error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED); - return response(builder, oAuth2AuditLog); + return response(builder, auditLog); } if (cibaGrant.getClient().getBackchannelTokenDeliveryMode() == BackchannelTokenDeliveryMode.PING || cibaGrant.getClient().getBackchannelTokenDeliveryMode() == BackchannelTokenDeliveryMode.POLL) { @@ -465,7 +450,7 @@ public Response requestAccessToken(String grantType, String code, scope, idToken)); - oAuth2AuditLog.updateOAuth2AuditLog(cibaGrant, true); + auditLog.updateOAuth2AuditLog(cibaGrant, true); } else { builder = error(400, TokenErrorResponseType.INVALID_GRANT, "AuthReqId is no longer available."); } @@ -479,7 +464,7 @@ public Response requestAccessToken(String grantType, String code, if (cibaRequest != null) { if (!cibaRequest.getClient().getClientId().equals(client.getClientId())) { builder = error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED); - return response(builder, oAuth2AuditLog); + return response(builder, auditLog); } long currentTime = new Date().getTime(); Long lastAccess = cibaRequest.getLastAccessControl(); @@ -513,7 +498,7 @@ public Response requestAccessToken(String grantType, String code, } } } else if (gt == GrantType.DEVICE_CODE) { - return processDeviceCodeGrantType(gt, client, deviceCode, scope, request, response, oAuth2AuditLog); + return processDeviceCodeGrantType(client, deviceCode, scope, request, response, auditLog); } } catch (WebApplicationException e) { throw e; @@ -522,7 +507,18 @@ public Response requestAccessToken(String grantType, String code, log.error(e.getMessage(), e); } - return response(builder, oAuth2AuditLog); + return response(builder, auditLog); + } + + @Nullable + private Client getClient() { + SessionClient sessionClient = identity.getSessionClient(); + Client client = null; + if (sessionClient != null) { + client = sessionClient.getClient(); + log.debug("Get sessionClient: '{}'", sessionClient); + } + return client; } private User authenticateUser(String username, String password, ExecutionContext executionContext, User user) { @@ -585,17 +581,16 @@ private RefreshToken createRefreshToken(@NotNull HttpServletRequest request, @No /** * Processes token request for device code grant type. * - * @param grantType Grant type used, should be device code. * @param client Client in process. * @param deviceCode Device code generated in device authn request. * @param scope Scope registered in device authn request. * @param request HttpServletRequest * @param response HttpServletResponse - * @param oAuth2AuditLog OAuth2AuditLog + * @param auditLog OAuth2AuditLog */ - private Response processDeviceCodeGrantType(final GrantType grantType, final Client client, final String deviceCode, + private Response processDeviceCodeGrantType(final Client client, final String deviceCode, String scope, final HttpServletRequest request, - final HttpServletResponse response, final OAuth2AuditLog oAuth2AuditLog) { + final HttpServletResponse response, final OAuth2AuditLog auditLog) { log.debug("Attempting to find authorizationGrant by deviceCode: '{}'", deviceCode); final DeviceCodeGrant deviceCodeGrant = authorizationGrantList.getDeviceCodeGrant(deviceCode); @@ -603,7 +598,7 @@ private Response processDeviceCodeGrantType(final GrantType grantType, final Cli if (deviceCodeGrant != null) { if (!deviceCodeGrant.getClientId().equals(client.getClientId())) { - throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), oAuth2AuditLog)); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), auditLog)); } RefreshToken refToken = createRefreshToken(request, client, scope, deviceCodeGrant, null); @@ -631,7 +626,7 @@ private Response processDeviceCodeGrantType(final GrantType grantType, final Cli log.info("Device authorization in token endpoint processed and return to the client, device_code: {}", deviceCodeGrant.getDeviceCode()); - oAuth2AuditLog.updateOAuth2AuditLog(deviceCodeGrant, true); + auditLog.updateOAuth2AuditLog(deviceCodeGrant, true); grantService.removeByCode(deviceCodeGrant.getDeviceCode()); @@ -640,13 +635,8 @@ private Response processDeviceCodeGrantType(final GrantType grantType, final Cli } else { final DeviceAuthorizationCacheControl cacheData = deviceAuthorizationService.getDeviceAuthzByDeviceCode(deviceCode); log.trace("DeviceAuthorizationCacheControl data : '{}'", cacheData); - if (cacheData == null) { - log.debug("The authentication request has expired for deviceCode: '{}'", deviceCode); - throw new WebApplicationException(response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "The authentication request has expired."), oAuth2AuditLog)); - } - if (!cacheData.getClient().getClientId().equals(client.getClientId())) { - throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), oAuth2AuditLog)); - } + tokenRestWebServiceValidator.validateDeviceAuthorization(client, deviceCode, cacheData, auditLog); + long currentTime = new Date().getTime(); Long lastAccess = cacheData.getLastAccessControl(); if (lastAccess == null) { @@ -661,18 +651,18 @@ private Response processDeviceCodeGrantType(final GrantType grantType, final Cli if (timeFromLastAccess > intervalSeconds * 1000) { log.debug("Access hasn't been granted yet for deviceCode: '{}'", deviceCode); - throw new WebApplicationException(response(error(400, TokenErrorResponseType.AUTHORIZATION_PENDING, "User hasn't answered yet"), oAuth2AuditLog)); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.AUTHORIZATION_PENDING, "User hasn't answered yet"), auditLog)); } else { log.debug("Slow down protection deviceCode: '{}'", deviceCode); - throw new WebApplicationException(response(error(400, TokenErrorResponseType.SLOW_DOWN, "Client is asking too fast the token."), oAuth2AuditLog)); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.SLOW_DOWN, "Client is asking too fast the token."), auditLog)); } } if (cacheData.getStatus() == DeviceAuthorizationStatus.DENIED) { log.debug("The end-user denied the authorization request for deviceCode: '{}'", deviceCode); - throw new WebApplicationException(response(error(400, TokenErrorResponseType.ACCESS_DENIED, "The end-user denied the authorization request."), oAuth2AuditLog)); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.ACCESS_DENIED, "The end-user denied the authorization request."), auditLog)); } log.debug("The authentication request has expired for deviceCode: '{}'", deviceCode); - throw new WebApplicationException(response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "The authentication request has expired"), oAuth2AuditLog)); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "The authentication request has expired"), auditLog)); } } 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 1a7adab8d8c..4d147344d1f 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,11 +1,13 @@ package io.jans.as.server.token.ws.rs; +import io.jans.as.common.model.registration.Client; import io.jans.as.model.common.GrantType; import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.model.token.TokenErrorResponseType; import io.jans.as.server.audit.ApplicationAuditLogger; import io.jans.as.server.model.audit.OAuth2AuditLog; +import io.jans.as.server.model.common.DeviceAuthorizationCacheControl; import io.jans.as.server.util.ServerUtil; import jakarta.ejb.Stateless; import jakarta.inject.Inject; @@ -13,12 +15,15 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.apache.tika.utils.StringUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import java.util.Arrays; import java.util.List; +import static io.jans.as.model.config.Constants.REASON_CLIENT_NOT_AUTHORIZED; + /** * @author Yuriy Zabrovarnyy */ @@ -71,12 +76,11 @@ public void validateParams(String grantType, String code, } public static boolean validateParams(String clientId, String clientSecret) { - return clientId != null && !clientId.isEmpty() - && clientSecret != null && !clientSecret.isEmpty(); + return StringUtils.isNotBlank(clientId) && StringUtils.isNotBlank(clientSecret); } - public void validateGrantType(GrantType requestedGrantType, GrantType[] clientGrantTypesArray, OAuth2AuditLog auditLog) { - List clientGrantTypes = Arrays.asList(clientGrantTypesArray); + public void validateGrantType(GrantType requestedGrantType, Client client, OAuth2AuditLog auditLog) { + List clientGrantTypes = Arrays.asList(client.getGrantTypes()); if (!clientGrantTypes.contains(requestedGrantType)) { final String msg = "GrantType is not allowed by client's grantTypes."; log.trace(msg); @@ -103,4 +107,27 @@ private Response.ResponseBuilder error(int status, TokenErrorResponseType type, return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(errorResponseFactory.errorAsJson(type, reason)); } + @NotNull + public Client validateClient(Client client, OAuth2AuditLog auditLog) { + if (client == null) { + throw new WebApplicationException(response(error(Response.Status.UNAUTHORIZED.getStatusCode(), TokenErrorResponseType.INVALID_GRANT, "Unable to find client."), auditLog)); + } + + log.debug("Get client from session: '{}'", client.getClientId()); + if (client.isDisabled()) { + throw new WebApplicationException(response(error(Response.Status.FORBIDDEN.getStatusCode(), TokenErrorResponseType.DISABLED_CLIENT, "Client is disabled."), auditLog)); + } + return client; + } + + + public void validateDeviceAuthorization(Client client, String deviceCode, DeviceAuthorizationCacheControl cacheData, OAuth2AuditLog oAuth2AuditLog) { + if (cacheData == null) { + log.debug("The authentication request has expired for deviceCode: '{}'", deviceCode); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.EXPIRED_TOKEN, "The authentication request has expired."), oAuth2AuditLog)); + } + if (!cacheData.getClient().getClientId().equals(client.getClientId())) { + throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), oAuth2AuditLog)); + } + } } 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 0a25a113644..e691d557a44 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 @@ -1,11 +1,14 @@ package io.jans.as.server.token.ws.rs; +import io.jans.as.common.model.registration.Client; import io.jans.as.model.common.GrantType; import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.server.audit.ApplicationAuditLogger; import io.jans.as.server.model.audit.OAuth2AuditLog; +import io.jans.as.server.model.common.DeviceAuthorizationCacheControl; import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; @@ -15,6 +18,7 @@ import static io.jans.as.server.util.TestUtil.assertBadRequest; import static org.junit.Assert.fail; +import static org.testng.AssertJUnit.assertEquals; /** * @author Yuriy Zabrovarnyy @@ -105,11 +109,66 @@ public void validateParams_whenGrantTypeIsRefreshTokenAndRefreshTokenIsNotBlank_ @Test public void validateGrantType_whenClientDotNotHaveGrantType_shouldRaiseError() { try { - tokenRestWebServiceValidator.validateGrantType(GrantType.AUTHORIZATION_CODE, new GrantType[0], AUDIT_LOG); + tokenRestWebServiceValidator.validateGrantType(GrantType.AUTHORIZATION_CODE, new Client(), AUDIT_LOG); } catch (WebApplicationException e) { assertBadRequest(e.getResponse()); return; } fail("No error for grant_type which is not allowed by client's grant_types."); } + + @Test + public void validateClient_whenClientIsNull_shouldRaiseError() { + try { + tokenRestWebServiceValidator.validateClient(null, AUDIT_LOG); + } catch (WebApplicationException e) { + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), e.getResponse().getStatus()); + return; + } + fail("No error when client is null."); + } + + @Test + public void validateClient_whenClientIsDisabled_shouldRaiseError() { + try { + Client client = new Client(); + client.setDisabled(true); + tokenRestWebServiceValidator.validateClient(client, AUDIT_LOG); + } catch (WebApplicationException e) { + assertEquals(Response.Status.FORBIDDEN.getStatusCode(), e.getResponse().getStatus()); + return; + } + fail("No error when client is null."); + } + + @Test + public void validateDeviceAuthorizationCacheControl_whenDeviceAuthzIsNull_shouldRaiseError() { + try { + Client client = new Client(); + client.setClientId("testId"); + + tokenRestWebServiceValidator.validateDeviceAuthorization(client, "code", null, AUDIT_LOG); + } catch (WebApplicationException e) { + assertBadRequest(e.getResponse()); + return; + } + fail("No error when client is null."); + } + + @Test + public void validateDeviceAuthorizationCacheControl_whenDeviceAuthzDoesNotBelongToClient_shouldRaiseError() { + try { + Client client = new Client(); + client.setClientId("testId"); + + DeviceAuthorizationCacheControl deviceAuthorization = new DeviceAuthorizationCacheControl(); + deviceAuthorization.setClient(client); + + tokenRestWebServiceValidator.validateDeviceAuthorization(new Client(), "code", deviceAuthorization, AUDIT_LOG); + } catch (WebApplicationException e) { + assertBadRequest(e.getResponse()); + return; + } + fail("No error when client is null."); + } }