From f7544ce0bcef1945b2015994e49b7e8cc8fb9c1a Mon Sep 17 00:00:00 2001 From: vimukthiRajapaksha Date: Fri, 26 Jul 2024 17:10:34 +0530 Subject: [PATCH] Accept and persist authorization_details of token request - Accept 'authorization_details' field in the token request - Persist access token authorization details in the database - Moving common logic from oauth.rar.common module to a new rar package within oauth module - Adds RAR support for hybrid authorization flows --- .../identity/oauth/common/OAuthConstants.java | 4 + .../endpoint/authz/OAuth2AuthzEndpoint.java | 90 ++- .../AuthorizationDetailsServiceFactory.java | 47 ++ .../endpoint/token/OAuth2TokenEndpoint.java | 16 + .../oauth/endpoint/util/EndpointUtil.java | 30 +- .../src/main/webapp/WEB-INF/cxf-servlet.xml | 2 + .../pom.xml | 60 -- .../common/dao/AuthorizationDetailsDAO.java | 69 --- .../dto/AuthorizationDetailsCodeDTO.java | 22 - .../dto/AuthorizationDetailsConsentDTO.java | 38 -- .../common/dto/AuthorizationDetailsDTO.java | 53 -- .../pom.xml | 44 +- .../rar/AuthorizationDetailsService.java | 278 --------- .../rar/AuthorizationDetailsValidator.java | 171 ------ .../rar/dao/AuthorizationDetailsDAO.java | 125 ++++ .../rar}/dao/AuthorizationDetailsDAOImpl.java | 156 +++-- .../identity/oauth2/rar}/dao/SQLQueries.java | 35 +- .../dto/AuthorizationDetailsConsentDTO.java | 67 +++ .../rar/dto/AuthorizationDetailsDTO.java | 98 +++ .../rar/dto/AuthorizationDetailsTokenDTO.java | 51 ++ ...thorizationDetailsProcessingException.java | 30 - .../RARAccessTokenResponseHandler.java | 61 -- .../AuthorizationDetailsDataHolder.java | 94 --- .../AuthorizationDetailsServiceComponent.java | 69 --- .../rar}/model/AuthorizationDetail.java | 61 +- .../rar}/model/AuthorizationDetails.java | 39 +- .../model/AuthorizationDetailsContext.java | 98 --- .../util/AuthorizationDetailsCommonUtils.java | 76 ++- .../util/AuthorizationDetailsConstants.java | 2 +- .../rar/util/AuthorizationDetailsUtils.java | 56 -- .../org.wso2.carbon.identity.oauth/pom.xml | 2 +- ...tityOAuth2AuthorizationDetailsService.java | 134 ----- .../authz/AuthorizationHandlerManager.java | 22 + .../authz/OAuthAuthzReqMessageContext.java | 2 +- .../handlers/AbstractResponseTypeHandler.java | 4 +- .../handlers/CodeResponseTypeHandler.java | 2 - .../util/ResponseTypeHandlerUtil.java | 3 + .../dao/OAuthTokenPersistenceFactory.java | 4 +- .../oauth2/dto/OAuth2AccessTokenReqDTO.java | 25 + .../oauth2/dto/OAuth2AuthorizeReqDTO.java | 24 + .../oauth2/dto/OAuth2AuthorizeRespDTO.java | 25 + .../internal/OAuth2ServiceComponent.java | 9 + .../OAuth2ServiceComponentHolder.java | 32 +- .../oauth2/model/OAuth2Parameters.java | 2 +- .../rar/AuthorizationDetailsService.java | 558 ++++++++++++++++++ .../core/AuthorizationDetailsProcessor.java} | 30 +- .../AuthorizationDetailsProviderFactory.java | 39 +- ...thorizationDetailsProcessingException.java | 37 ++ .../model/AuthorizationDetailsContext.java | 154 +++++ .../token/AccessTokenResponseRARHandler.java | 49 ++ .../token/IntrospectionRARDataProvider.java | 118 ++++ .../token/JWTAccessTokenRARClaimProvider.java | 70 +++ .../rar/util/AuthorizationDetailsUtils.java | 341 +++++++++++ .../AuthorizationDetailsValidator.java | 62 ++ .../DefaultAuthorizationDetailsValidator.java | 289 +++++++++ .../provider/SuccessResponseDTO.java | 23 + .../impl/FragmentResponseModeProvider.java | 10 + .../oauth2/token/AccessTokenIssuer.java | 37 ++ .../token/OAuthTokenReqMessageContext.java | 25 + .../AbstractAuthorizationGrantHandler.java | 27 + .../grant/AuthorizationCodeGrantHandler.java | 5 +- .../handlers/grant/RefreshGrantHandler.java | 3 + pom.xml | 6 - 63 files changed, 2761 insertions(+), 1454 deletions(-) create mode 100644 components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/factory/AuthorizationDetailsServiceFactory.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar.common/pom.xml delete mode 100644 components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAO.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsCodeDTO.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsConsentDTO.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsDTO.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsValidator.java create mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAO.java rename components/{org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common => org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar}/dao/AuthorizationDetailsDAOImpl.java (64%) rename components/{org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common => org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar}/dao/SQLQueries.java (58%) create mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsConsentDTO.java create mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsDTO.java create mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsTokenDTO.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/handler/RARAccessTokenResponseHandler.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsDataHolder.java delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsServiceComponent.java rename components/{org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common => org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar}/model/AuthorizationDetail.java (91%) rename components/{org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common => org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar}/model/AuthorizationDetails.java (80%) delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java rename components/{org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common => org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar}/util/AuthorizationDetailsCommonUtils.java (68%) rename components/{org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common => org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar}/util/AuthorizationDetailsConstants.java (96%) delete mode 100644 components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java delete mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/IdentityOAuth2AuthorizationDetailsService.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java rename components/{org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProvider.java => org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProcessor.java} (75%) rename components/{org.wso2.carbon.identity.oauth.rar => org.wso2.carbon.identity.oauth}/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java (75%) create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/AccessTokenResponseRARHandler.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/IntrospectionRARDataProvider.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/JWTAccessTokenRARClaimProvider.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/AuthorizationDetailsValidator.java create mode 100644 components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/DefaultAuthorizationDetailsValidator.java diff --git a/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java b/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java index 57e7feac1eb..78437a236b2 100644 --- a/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java +++ b/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java @@ -749,6 +749,9 @@ public static class ActionIDs { public static final String VALIDATE_EXISTING_CONSENT = "validate-existing-consent"; public static final String GENERATE_INTROSPECTION_RESPONSE = "generate-introspect-response"; public static final String RECEIVE_REVOKE_REQUEST = "receive-revoke-request"; + public static final String VALIDATE_AUTHORIZATION_DETAILS = "validate-authorization-details"; + public static final String VALIDATE_AUTHORIZATION_DETAILS_BEFORE_CONSENT + = "validate-authorization-details-before-consent"; } /** @@ -768,6 +771,7 @@ public static class InputKeys { public static final String CALLBACK_URI = "callback URI"; public static final String PROMPT = "prompt"; public static final String APP_STATE = "app state"; + public static final String REQUESTED_AUTHORIZATION_DETAILS = "requested authorization details"; } /** diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/authz/OAuth2AuthzEndpoint.java b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/authz/OAuth2AuthzEndpoint.java index 47f2f21f5b2..6c3a897d52c 100644 --- a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/authz/OAuth2AuthzEndpoint.java +++ b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/authz/OAuth2AuthzEndpoint.java @@ -116,10 +116,10 @@ import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.IdentityOAuth2ScopeException; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ServerException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2UnauthorizedScopeException; import org.wso2.carbon.identity.oauth2.OAuth2Service; import org.wso2.carbon.identity.oauth2.RequestObjectException; -import org.wso2.carbon.identity.oauth2.authz.AuthorizationHandlerManager; import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.bean.OAuthClientAuthnContext; import org.wso2.carbon.identity.oauth2.device.api.DeviceAuthService; @@ -136,10 +136,9 @@ import org.wso2.carbon.identity.oauth2.model.HttpRequestHeaderHandler; import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsService; -import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsValidator; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsConstants; import org.wso2.carbon.identity.oauth2.rar.exception.AuthorizationDetailsProcessingException; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; import org.wso2.carbon.identity.oauth2.responsemode.provider.AuthorizationResponseDTO; import org.wso2.carbon.identity.oauth2.responsemode.provider.ResponseModeProvider; @@ -1717,13 +1716,12 @@ private void storeUserConsent(OAuthMessage oAuthMessage, String consent) throws if (approvedAlways) { OpenIDConnectUserRPStore.getInstance().putUserRPToStore(loggedInUser, applicationName, true, clientId); - final AuthorizationDetails userConsentedAuthorizationDetails = - authorizationDetailsService.getUserConsentedAuthorizationDetails( - oAuthMessage.getRequest().getParameterMap(), oauth2Params); + final AuthorizationDetails userConsentedAuthorizationDetails = AuthorizationDetailsUtils + .extractAuthorizationDetailsFromRequest(oAuthMessage.getRequest(), oauth2Params); if (hasPromptContainsConsent(oauth2Params)) { EndpointUtil.storeOAuthScopeConsent(loggedInUser, oauth2Params, true); - authorizationDetailsService.storeOrReplaceUserConsentedAuthorizationDetails(loggedInUser, + authorizationDetailsService.replaceUserConsentedAuthorizationDetails(loggedInUser, clientId, oauth2Params, userConsentedAuthorizationDetails); } else { EndpointUtil.storeOAuthScopeConsent(loggedInUser, oauth2Params, false); @@ -1868,6 +1866,7 @@ private OAuthResponse handleSuccessAuthorization(OAuthMessage oAuthMessage, OIDC if (isResponseTypeNotIdTokenOrNone(responseType, authzRespDTO)) { setAccessToken(authzRespDTO, builder, authorizationResponseDTO); setScopes(authzRespDTO, builder, authorizationResponseDTO); + setAuthorizationDetails(authzRespDTO, builder, authorizationResponseDTO); } if (isSubjectTokenFlow(responseType, authzRespDTO)) { setSubjectToken(authzRespDTO, builder, authorizationResponseDTO); @@ -2606,9 +2605,11 @@ private String populateOauthParameters(OAuth2Parameters params, OAuthMessage oAu } if (AuthorizationDetailsUtils.isRichAuthorizationRequest(oauthRequest)) { + final String authorizationDetailsJson = oauthRequest .getParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS); - params.setAuthorizationDetails(new AuthorizationDetails(authorizationDetailsJson)); + params.setAuthorizationDetails(AuthorizationDetailsUtils + .generateAndAssignUniqueIDs(authorizationDetailsJson)); } handleMaxAgeParameter(oauthRequest, params); @@ -3015,7 +3016,7 @@ private String doUserAuthorization(OAuthMessage oAuthMessage, String sessionData } try { - validateAuthorizationDetailsBeforeConsent(oAuthMessage, oauth2Params, authzReqDTO); + validateAuthorizationDetailsBeforeConsent(oAuthMessage, oauth2Params); } catch (AuthorizationDetailsProcessingException e) { log.debug("Error occurred while validating authorization details. Caused by, ", e); @@ -3728,6 +3729,9 @@ private boolean isUserAlreadyApproved(OAuth2Parameters oauth2Params, Authenticat } catch (IdentityOAuth2ScopeException | IdentityOAuthAdminException e) { throw new OAuthSystemException("Error occurred while checking user has already approved the consent " + "required OAuth scopes.", e); + } catch (IdentityOAuth2Exception e) { + throw new OAuthSystemException("Error occurred while checking user has already approved the consent " + + "required authorization details.", e); } } @@ -3820,6 +3824,7 @@ private OAuth2AuthorizeReqDTO buildAuthRequest(OAuth2Parameters oauth2Params, Se authzReqDTO.setState(oauth2Params.getState()); authzReqDTO.setHttpServletRequestWrapper(new HttpServletRequestWrapper(request)); authzReqDTO.setRequestedSubjectId(oauth2Params.getRequestedSubjectId()); + authzReqDTO.setAuthorizationDetails(oauth2Params.getAuthorizationDetails()); if (sessionDataCacheEntry.getParamMap() != null && sessionDataCacheEntry.getParamMap().get(OAuthConstants .AMR) != null) { @@ -4808,14 +4813,12 @@ private Response handleUnsupportedGrantForApiBasedAuth() { * * @param oAuthMessage The {@link OAuthMessage} containing the authorization request details. * @param oAuth2Parameters The {@link OAuth2Parameters} object holding the parameters of the OAuth request. - * @param oAuth2AuthorizeReqDTO The {@link OAuth2AuthorizeReqDTO} object containing the authorization request. - * @throws OAuthSystemException If there is an error during the validation process. * @throws AuthorizationDetailsProcessingException If there is an error processing the authorization details. + * @throws OAuthSystemException If there is an error during the validation process. */ private void validateAuthorizationDetailsBeforeConsent(final OAuthMessage oAuthMessage, - final OAuth2Parameters oAuth2Parameters, - final OAuth2AuthorizeReqDTO oAuth2AuthorizeReqDTO) - throws OAuthSystemException, AuthorizationDetailsProcessingException { + final OAuth2Parameters oAuth2Parameters) + throws AuthorizationDetailsProcessingException, OAuthSystemException { if (!AuthorizationDetailsUtils.isRichAuthorizationRequest(oAuth2Parameters)) { log.debug("Authorization request is not a rich authorization request. Skipping validation."); @@ -4823,26 +4826,61 @@ private void validateAuthorizationDetailsBeforeConsent(final OAuthMessage oAuthM } try { - final OAuthAppDO oAuthAppDO = AuthorizationHandlerManager.getInstance() - .getAppInformation(oAuth2AuthorizeReqDTO); + if (LoggerUtils.isDiagnosticLogsEnabled()) { + DiagnosticLog.DiagnosticLogBuilder diagnosticLogBuilder = new DiagnosticLog.DiagnosticLogBuilder( + OAuthConstants.LogConstants.OAUTH_INBOUND_SERVICE, + OAuthConstants.LogConstants.ActionIDs.VALIDATE_AUTHORIZATION_DETAILS_BEFORE_CONSENT); + diagnosticLogBuilder.inputParam(LogConstants.InputKeys.CLIENT_ID, oAuth2Parameters.getClientId()) + .inputParam(LogConstants.InputKeys.APPLICATION_NAME, oAuth2Parameters.getApplicationName()) + .inputParam("authorization details to be validated", + oAuth2Parameters.getAuthorizationDetails().toSet()) + .resultStatus(DiagnosticLog.ResultStatus.SUCCESS) + .resultMessage("authorization details validation started") + .logDetailLevel(DiagnosticLog.LogDetailLevel.APPLICATION); + LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder); + } + + final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext = + oAuthMessage.getSessionDataCacheEntry().getAuthzReqMsgCtx(); + // Validate the authorization details - final AuthorizationDetails validatedAuthorizationDetails = new AuthorizationDetailsValidator() - .getValidatedAuthorizationDetails(oAuth2Parameters, oAuthAppDO, oAuth2AuthorizeReqDTO.getUser()); + final AuthorizationDetails validatedAuthorizationDetails = OAuth2ServiceComponentHolder.getInstance() + .getAuthorizationDetailsValidator() + .getValidatedAuthorizationDetails(oAuthAuthzReqMessageContext); - if (log.isDebugEnabled()) { - log.debug("Authorization details validated successfully for user: " - + oAuth2AuthorizeReqDTO.getUser().getLoggableMaskedUserId()); - } // update oAuth2Parameters with validated authorization details oAuth2Parameters.setAuthorizationDetails(validatedAuthorizationDetails); // Update the authorization message context with validated authorization details - OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext = - oAuthMessage.getSessionDataCacheEntry().getAuthzReqMsgCtx(); oAuthAuthzReqMessageContext.setAuthorizationDetails(validatedAuthorizationDetails); - } catch (IdentityOAuth2Exception | InvalidOAuthClientException e) { + oAuthMessage.getSessionDataCacheEntry().setAuthzReqMsgCtx(oAuthAuthzReqMessageContext); + + if (LoggerUtils.isDiagnosticLogsEnabled()) { + DiagnosticLog.DiagnosticLogBuilder diagnosticLogBuilder = new DiagnosticLog.DiagnosticLogBuilder( + OAuthConstants.LogConstants.OAUTH_INBOUND_SERVICE, + OAuthConstants.LogConstants.ActionIDs.VALIDATE_AUTHORIZATION_DETAILS_BEFORE_CONSENT); + diagnosticLogBuilder.inputParam(LogConstants.InputKeys.CLIENT_ID, oAuth2Parameters.getClientId()) + .inputParam(LogConstants.InputKeys.APPLICATION_NAME, oAuth2Parameters.getApplicationName()) + .inputParam("authorization details after validation", + oAuth2Parameters.getAuthorizationDetails().toSet()) + .resultStatus(DiagnosticLog.ResultStatus.SUCCESS) + .resultMessage("authorization details validation completed") + .logDetailLevel(DiagnosticLog.LogDetailLevel.APPLICATION); + LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder); + } + } catch (IdentityOAuth2ServerException e) { log.error("Error occurred while validating authorization details. Caused by, ", e); throw new OAuthSystemException("Error occurred while validating requested authorization details", e); } } + + private void setAuthorizationDetails(final OAuth2AuthorizeRespDTO oAuth2AuthorizeRespDTO, + final OAuthASResponse.OAuthAuthorizationResponseBuilder builder, + final AuthorizationResponseDTO authorizationResponseDTO) { + + final AuthorizationDetails authorizationDetails = oAuth2AuthorizeRespDTO.getAuthorizationDetails(); + builder.setParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS, + AuthorizationDetailsUtils.getUrlEncodedAuthorizationDetails(authorizationDetails)); + authorizationResponseDTO.getSuccessResponseDTO().setAuthorizationDetails(authorizationDetails); + } } diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/factory/AuthorizationDetailsServiceFactory.java b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/factory/AuthorizationDetailsServiceFactory.java new file mode 100644 index 00000000000..f33588a7227 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/factory/AuthorizationDetailsServiceFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.endpoint.factory; + +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsService; + +/** + * This class is used to register AuthorizationDetailsService as a factory bean. + */ +public class AuthorizationDetailsServiceFactory extends AbstractFactoryBean { + + private AuthorizationDetailsService authorizationDetailsService; + + @Override + public Class getObjectType() { + + return AuthorizationDetailsService.class; + } + + @Override + protected AuthorizationDetailsService createInstance() throws Exception { + + if (this.authorizationDetailsService == null) { + this.authorizationDetailsService = + OAuth2ServiceComponentHolder.getInstance().getAuthorizationDetailsService(); + } + return this.authorizationDetailsService; + } +} diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java index 54403f9d35e..b521a4c7990 100644 --- a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java +++ b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java @@ -46,6 +46,9 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO; import org.wso2.carbon.identity.oauth2.model.CarbonOAuthTokenRequest; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; import org.wso2.carbon.identity.oauth2.token.handlers.response.OAuth2TokenResponse; import org.wso2.carbon.user.core.util.UserCoreUtil; import org.wso2.carbon.utils.DiagnosticLog; @@ -407,6 +410,19 @@ private OAuth2AccessTokenReqDTO buildAccessTokenReqDTO(CarbonOAuthTokenRequest o tokenReqDTO.setWindowsToken(oauthRequest.getWindowsToken()); } tokenReqDTO.addAuthenticationMethodReference(grantType); + + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(oauthRequest)) { + final String encodedAuthorizationDetailsJson = oauthRequest + .getParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS); + final String authorizationDetailsJson = AuthorizationDetailsUtils + .getUrlDecodedAuthorizationDetails(encodedAuthorizationDetailsJson); + + if (log.isDebugEnabled()) { + log.debug("Adding requested authorization details to tokenReqDTO: " + authorizationDetailsJson); + } + tokenReqDTO.setAuthorizationDetails(new AuthorizationDetails(authorizationDetailsJson)); + } + return tokenReqDTO; } } diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/EndpointUtil.java b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/EndpointUtil.java index 4129b752b78..4d0d9a3722c 100644 --- a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/EndpointUtil.java +++ b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/EndpointUtil.java @@ -103,7 +103,8 @@ import org.wso2.carbon.identity.oauth2.model.CarbonOAuthAuthzRequest; import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; import org.wso2.carbon.identity.oauth2.model.OAuth2ScopeConsentResponse; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; import org.wso2.carbon.identity.oauth2.scopeservice.OAuth2Resource; import org.wso2.carbon.identity.oauth2.scopeservice.ScopeMetadataService; @@ -872,8 +873,9 @@ public static String getUserConsentURL(OAuth2Parameters params, String loggedInU // Append authorization details to consent page url if (AuthorizationDetailsUtils.isRichAuthorizationRequest(params)) { - consentPageUrl = consentPageUrl + "&" + AuthorizationDetailsConstants.AUTHORIZATION_DETAILS + "=" - + URLEncoder.encode(params.getAuthorizationDetails().toJsonString(), UTF_8); + additionalQueryParams = additionalQueryParams + "&" + + AuthorizationDetailsConstants.AUTHORIZATION_DETAILS + "=" + AuthorizationDetailsUtils + .getUrlEncodedAuthorizationDetails(filterConsentRequiredAuthorizationDetails(user, params)); } // Append scope metadata to additionalQueryParams. @@ -2153,4 +2155,26 @@ public static void preHandleParRequest(HttpServletRequest request, Map + @@ -103,4 +104,5 @@ + diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/pom.xml b/components/org.wso2.carbon.identity.oauth.rar.common/pom.xml deleted file mode 100644 index 1ac995d59f0..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar.common/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - org.wso2.carbon.identity.inbound.auth.oauth2 - identity-inbound-auth-oauth - 7.0.107-SNAPSHOT - ../../pom.xml - - - 4.0.0 - org.wso2.carbon.identity.oauth.rar.common - jar - WSO2 Carbon - Rich Authorization Requests Common - http://wso2.org - - - UTF-8 - - - - - org.wso2.carbon.identity.framework - org.wso2.carbon.identity.core - provided - - - - com.fasterxml.jackson.core - jackson-databind - provided - - - - junit - junit - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - - - - com.github.spotbugs - spotbugs-maven-plugin - - High - 2048 - - - - - - diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAO.java b/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAO.java deleted file mode 100644 index a21b68b4d79..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAO.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.carbon.identity.oauth2.rar.common.dao; - -import org.wso2.carbon.identity.oauth2.rar.common.dto.AuthorizationDetailsConsentDTO; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; - -import java.sql.SQLException; -import java.util.List; -import java.util.Set; - -/** - * Provides methods to interact with the database to manage authorization details. - */ -public interface AuthorizationDetailsDAO { - - /** - * Adds authorization details against a given OAuth2 code. - * - * @param authorizationCodeID The ID of the authorization code. - * @param authorizationDetails The authorization details to store. - * @param tenantId The tenant ID. - * @return An array of positive integers indicating the number of rows affected for each batch operation, - * or negative integers if any of the batch operations fail. - * @throws SQLException If a database access error occurs. - */ - int[] addOAuth2CodeAuthorizationDetails(String authorizationCodeID, AuthorizationDetails authorizationDetails, - int tenantId) throws SQLException; - - /** - * Adds user consented authorization details. - * - * @param authorizationDetailsConsentDTOs List of user consented authorization details DTOs. - * {@link AuthorizationDetailsConsentDTO } - * @return An array of positive integers indicating the number of rows affected for each batch operation, - * or negative integers if any of the batch operations fail. - * @throws SQLException If a database access error occurs. - */ - int[] addUserConsentedAuthorizationDetails(List authorizationDetailsConsentDTOs) - throws SQLException; - - int deleteUserConsentedAuthorizationDetails(String consentId, int tenantId) - throws SQLException; - - // add a todo and mention to move this to consent module - String getConsentIdByUserIdAndAppId(String userId, String appId, int tenantId) throws SQLException; - - Set getUserConsentedAuthorizationDetails(String consentId, int tenantId) - throws SQLException; - - int[] updateUserConsentedAuthorizationDetails(List authorizationDetailsConsentDTOs) - throws SQLException; -} diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsCodeDTO.java b/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsCodeDTO.java deleted file mode 100644 index bc143a93257..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsCodeDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.wso2.carbon.identity.oauth2.rar.common.dto; - -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; - -/** - * - */ -public class AuthorizationDetailsCodeDTO extends AuthorizationDetailsDTO { - - final String codeId; - - public AuthorizationDetailsCodeDTO(final String id, final String codeId, final int typeId, - final AuthorizationDetail authorizationDetail, final int tenantId) { - - super(id, typeId, authorizationDetail, tenantId); - this.codeId = codeId; - } - - public String getCodeId() { - return codeId; - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsConsentDTO.java b/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsConsentDTO.java deleted file mode 100644 index 0cf61dc2306..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsConsentDTO.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.wso2.carbon.identity.oauth2.rar.common.dto; - -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; - -/** - * - */ -public class AuthorizationDetailsConsentDTO extends AuthorizationDetailsDTO { - - final String consentId; - final boolean isConsentActive; - - public AuthorizationDetailsConsentDTO(final String id, final String consentId, final int typeId, - final String authorizationDetail, - final boolean isConsentActive, final int tenantId) { - - super(id, typeId, authorizationDetail, tenantId); - this.consentId = consentId; - this.isConsentActive = isConsentActive; - } - - public AuthorizationDetailsConsentDTO(final String consentId, - final AuthorizationDetail authorizationDetail, - final boolean isConsentActive, final int tenantId) { - - super(authorizationDetail, tenantId); - this.consentId = consentId; - this.isConsentActive = isConsentActive; - } - - public boolean isConsentActive() { - return isConsentActive; - } - - public String getConsentId() { - return consentId; - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsDTO.java b/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsDTO.java deleted file mode 100644 index fa73865c8b4..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dto/AuthorizationDetailsDTO.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.wso2.carbon.identity.oauth2.rar.common.dto; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsCommonUtils; - -/** - * - */ -public class AuthorizationDetailsDTO { - - final String id; - final int typeId; - final AuthorizationDetail authorizationDetail; - final int tenantId; - - public AuthorizationDetailsDTO(final String id, final int typeId, final AuthorizationDetail authorizationDetail, - final int tenantId) { - - this.id = id; - this.typeId = typeId; - this.authorizationDetail = authorizationDetail; - this.tenantId = tenantId; - } - - public AuthorizationDetailsDTO(final String id, final int typeId, final String authorizationDetailJson, - final int tenantId) { - - this(id, typeId, AuthorizationDetailsCommonUtils - .fromJSON(authorizationDetailJson, AuthorizationDetail.class, new ObjectMapper()), tenantId); - } - - public AuthorizationDetailsDTO(final AuthorizationDetail authorizationDetail, final int tenantId) { - - this(null, 0, authorizationDetail, tenantId); - } - - public String getId() { - return this.id; - } - - public int getTypeId() { - return this.typeId; - } - - public AuthorizationDetail getAuthorizationDetail() { - return this.authorizationDetail; - } - - public int getTenantId() { - return this.tenantId; - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar/pom.xml b/components/org.wso2.carbon.identity.oauth.rar/pom.xml index bb6bc7c8617..ad80a06a349 100644 --- a/components/org.wso2.carbon.identity.oauth.rar/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.rar/pom.xml @@ -29,7 +29,7 @@ 4.0.0 org.wso2.carbon.identity.oauth.rar - bundle + jar WSO2 Carbon - Rich Authorization Requests http://wso2.org @@ -39,8 +39,8 @@ - org.wso2.carbon.identity.inbound.auth.oauth2 - org.wso2.carbon.identity.oauth + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.core provided @@ -50,42 +50,20 @@ - org.wso2.carbon.identity.inbound.auth.oauth2 - org.wso2.carbon.identity.oauth.rar.common + com.fasterxml.jackson.core + jackson-databind + provided + + junit + junit + test + - - org.apache.felix - maven-bundle-plugin - true - - - - ${project.artifactId} - - - org.wso2.carbon.identity.oauth2.rar.internal - - - org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}", - org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}", - org.wso2.carbon.identity.oauth2.*; version="${identity.inbound.auth.oauth.exp.pkg.version}", - - - !org.wso2.carbon.identity.oauth2.rar.internal, - org.wso2.carbon.identity.oauth2.rar.*; - version="${identity.inbound.auth.oauth.exp.pkg.version}", - - * - <_dsannotations>* - - - - org.apache.maven.plugins maven-compiler-plugin diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java deleted file mode 100644 index 00f4c97c965..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java +++ /dev/null @@ -1,278 +0,0 @@ -package org.wso2.carbon.identity.oauth2.rar; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; -import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; -import org.wso2.carbon.identity.application.common.model.ServiceProvider; -import org.wso2.carbon.identity.oauth2.IdentityOAuth2AuthorizationDetailsService; -import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; -import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory; -import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; -import org.wso2.carbon.identity.oauth2.rar.common.dao.AuthorizationDetailsDAO; -import org.wso2.carbon.identity.oauth2.rar.common.dto.AuthorizationDetailsConsentDTO; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; -import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProvider; -import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProviderFactory; -import org.wso2.carbon.identity.oauth2.util.OAuth2Util; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsConstants.AUTHORIZATION_DETAILS_ID_PREFIX; - -/** - * - */ -public class AuthorizationDetailsService extends IdentityOAuth2AuthorizationDetailsService { - - private static final Log log = LogFactory.getLog(AuthorizationDetailsService.class); - private final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory; - - public AuthorizationDetailsService() { - - this(OAuthTokenPersistenceFactory.getInstance().getAuthorizationDetailsDAO(), - AuthorizationDetailsProviderFactory.getInstance()); - } - - public AuthorizationDetailsService(final AuthorizationDetailsDAO authorizationDetailsDAO, - final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory) { - - super(authorizationDetailsDAO); - this.authorizationDetailsProviderFactory = authorizationDetailsProviderFactory; - } - - public void storeUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, final String clientId, - final OAuth2Parameters oAuth2Parameters, - final AuthorizationDetails userConsentedAuthorizationDetails) - throws OAuthSystemException { - - if (!AuthorizationDetailsService.isRichAuthorizationRequest(oAuth2Parameters)) { - return; - } - - try { - final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); - final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); - - if (consentId.isPresent()) { - - super.authorizationDetailsDAO.addUserConsentedAuthorizationDetails( - generateAuthorizationDetailsConsentDTOs(consentId.get(), - userConsentedAuthorizationDetails, tenantId)); - } - } catch (SQLException | IdentityOAuth2Exception e) { - log.error("Error occurred while storing user consented authorization details. Caused by, ", e); - throw new OAuthSystemException("Error occurred while storing authorization details", e); - } - } - - public void updateUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, - final String clientId, final OAuth2Parameters oAuth2Parameters, - final AuthorizationDetails userConsentedAuthorizationDetails) - throws OAuthSystemException { - - if (!AuthorizationDetailsService.isRichAuthorizationRequest(oAuth2Parameters)) { - return; - } - - try { - final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); - final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); - - if (consentId.isPresent()) { - - super.authorizationDetailsDAO.updateUserConsentedAuthorizationDetails( - generateAuthorizationDetailsConsentDTOs(consentId.get(), - userConsentedAuthorizationDetails, tenantId)); - } - } catch (SQLException | IdentityOAuth2Exception e) { - log.error("Error occurred while updating user consented authorization details. Caused by, ", e); - throw new OAuthSystemException("Error occurred while updating authorization details", e); - } - } - - public void deleteUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, - final String clientId, final OAuth2Parameters oAuth2Parameters) - throws OAuthSystemException { - - if (!AuthorizationDetailsService.isRichAuthorizationRequest(oAuth2Parameters)) { - return; - } - - try { - final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); - final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); - - if (consentId.isPresent()) { - - super.authorizationDetailsDAO.deleteUserConsentedAuthorizationDetails(consentId.get(), tenantId); - } - } catch (SQLException | IdentityOAuth2Exception e) { - log.error("Error occurred while deleting user consented authorization details. Caused by, ", e); - throw new OAuthSystemException("Error occurred while storing authorization details", e); - } - } - - public void storeOrReplaceUserConsentedAuthorizationDetails( - final AuthenticatedUser authenticatedUser, final String clientId, final OAuth2Parameters oAuth2Parameters, - final AuthorizationDetails userConsentedAuthorizationDetails) throws OAuthSystemException { - - this.deleteUserConsentedAuthorizationDetails(authenticatedUser, clientId, oAuth2Parameters); - this.storeUserConsentedAuthorizationDetails(authenticatedUser, clientId, oAuth2Parameters, - userConsentedAuthorizationDetails); - } - - /** - * Check if the user has already given consent to requested authorization details. - * - * @param authenticatedUser Authenticated user. - * @param oAuth2Parameters OAuth2 parameters. - * @return True if user has given consent to all the requested authorization details. - */ - public boolean isUserAlreadyConsentedForAuthorizationDetails(final AuthenticatedUser authenticatedUser, - final OAuth2Parameters oAuth2Parameters) - throws OAuthSystemException { - - try { - final String userId = this.getUserId(authenticatedUser); - final String appId = this.getApplicationResourceIdFromClientId(oAuth2Parameters.getClientId()); - final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); - final Optional consentId = this.getConsentIdByUserIdAndAppId(userId, appId, tenantId); - - if (consentId.isPresent()) { - final Set consentedAuthorizationDetailsDTOs = - super.authorizationDetailsDAO.getUserConsentedAuthorizationDetails(consentId.get(), tenantId); - - final Map> consentedAuthorizationDetailsByType = - consentedAuthorizationDetailsDTOs - .stream() - .filter(AuthorizationDetailsConsentDTO::isConsentActive) - .map(AuthorizationDetailsConsentDTO::getAuthorizationDetail) - .collect(Collectors.groupingBy(AuthorizationDetail::getType, - Collectors.mapping(Function.identity(), Collectors.toSet()))); - - for (final AuthorizationDetail requestedAuthorizationDetail : - oAuth2Parameters.getAuthorizationDetails().getDetails()) { - - if (consentedAuthorizationDetailsByType.containsKey(requestedAuthorizationDetail.getType())) { - - final Optional provider = authorizationDetailsProviderFactory - .getProviderByType(requestedAuthorizationDetail.getType()); - if (provider.isPresent()) { - - final AuthorizationDetails existingAuthorizationDetails = new AuthorizationDetails( - consentedAuthorizationDetailsByType.get(requestedAuthorizationDetail.getType())); - if (!provider.get() - .isEqualOrSubset(requestedAuthorizationDetail, existingAuthorizationDetails)) { - - if (log.isDebugEnabled()) { - log.debug("User has not consented for the requested authorization details type: " - + requestedAuthorizationDetail.getType()); - - } - return false; - } - } - } - } - return true; - } - return false; - } catch (IdentityOAuth2Exception | SQLException e) { - log.error("Error occurred while extracting user consented authorization details. Caused by, ", e); - throw new OAuthSystemException("Error occurred while extracting user consented authorization details", e); - } - } - - /** - * Retrieves the user-consented authorization details based on the provided parameter map and OAuth2 parameters. - *

- * This method is used to extract and return the authorization details that the user has consented to, - * filtering them based on a provided authorization details in the parameter map. - *

- * - * @param parameterMap A map of query parameters. - * @param oAuth2Parameters The OAuth2 parameters that include the details of the authorization request. - * @return The {@link AuthorizationDetails} object containing the details the user has consented to. - */ - public AuthorizationDetails getUserConsentedAuthorizationDetails( - final Map parameterMap, final OAuth2Parameters oAuth2Parameters) { - - if (!isRichAuthorizationRequest(oAuth2Parameters)) { - return new AuthorizationDetails(); - } - - // Extract consented authorization detail IDs from the parameter map - final Set consentedAuthorizationDetailIDs = parameterMap.keySet().stream() - .filter(parameterName -> parameterName.startsWith(AUTHORIZATION_DETAILS_ID_PREFIX)) - .map(parameterName -> parameterName.substring(AUTHORIZATION_DETAILS_ID_PREFIX.length())) - .collect(Collectors.toSet()); - - // Filter and collect the consented authorization details - final Set consentedAuthorizationDetails = oAuth2Parameters.getAuthorizationDetails() - .stream() - .filter(authorizationDetail -> consentedAuthorizationDetailIDs.contains(authorizationDetail.getId())) - .collect(Collectors.toSet()); - - return new AuthorizationDetails(consentedAuthorizationDetails); - } - - public Optional getConsentIdByUserIdAndAppId(final String userId, final String appId, final int tenantId) - throws OAuthSystemException { - - try { - return Optional - .ofNullable(super.authorizationDetailsDAO.getConsentIdByUserIdAndAppId(userId, appId, tenantId)); - } catch (SQLException e) { - log.error(String.format("Error occurred while retrieving user consent by " + - "userId: %s and appId: %s. Caused by, ", userId, appId), e); - throw new OAuthSystemException("Error occurred while retrieving user consent", e); - } - } - - private String getApplicationResourceIdFromClientId(final String clientId) throws IdentityOAuth2Exception { - - final ServiceProvider serviceProvider = OAuth2Util.getServiceProvider(clientId); - if (serviceProvider != null) { - return serviceProvider.getApplicationResourceId(); - } - throw new IdentityOAuth2Exception("Unable to find a service provider for client Id: " + clientId); - } - - private String getUserId(final AuthenticatedUser authenticatedUser) throws OAuthSystemException { - try { - return authenticatedUser.getUserId(); - } catch (UserIdNotFoundException e) { - log.error("Error occurred while extracting userId from authenticated user. Caused by, ", e); - throw new OAuthSystemException( - "User id is not found for user: " + authenticatedUser.getLoggableMaskedUserId(), e); - } - } - - private List generateAuthorizationDetailsConsentDTOs( - final String consentId, final AuthorizationDetails userConsentedAuthorizationDetails, final int tenantId) { - - return userConsentedAuthorizationDetails.stream() - .map(authorizationDetail -> - new AuthorizationDetailsConsentDTO(consentId, authorizationDetail, true, tenantId)) - .collect(Collectors.toList()); - } - - private Optional getConsentId(final AuthenticatedUser authenticatedUser, final String clientId, - final int tenantId) - throws OAuthSystemException, IdentityOAuth2Exception { - - final String userId = this.getUserId(authenticatedUser); - final String appId = this.getApplicationResourceIdFromClientId(clientId); - - return this.getConsentIdByUserIdAndAppId(userId, appId, tenantId); - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsValidator.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsValidator.java deleted file mode 100644 index 6b67f226d83..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsValidator.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.carbon.identity.oauth2.rar; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; -import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; -import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsConstants; -import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProvider; -import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProviderFactory; -import org.wso2.carbon.identity.oauth2.rar.exception.AuthorizationDetailsProcessingException; -import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetailsContext; -import org.wso2.carbon.identity.oauth2.rar.model.ValidationResult; - -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -/** - * {@code AuthorizationDetailsValidator} class responsible for managing and validating authorization details. - */ -public class AuthorizationDetailsValidator { - - private static final Log log = LogFactory.getLog(AuthorizationDetailsValidator.class); - private final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory; - - public AuthorizationDetailsValidator() { - - this(AuthorizationDetailsProviderFactory.getInstance()); - } - - public AuthorizationDetailsValidator(AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory) { - - this.authorizationDetailsProviderFactory = authorizationDetailsProviderFactory; - } - - /** - * Retrieves and validates the authorization details for a given OAuth2 parameters context. - * - * @param oAuth2Parameters The OAuth2 parameters associated with the request. - * @param oAuthAppDO The OAuth application details. - * @param authenticatedUser The authenticated user information. - * @return An {@link AuthorizationDetails} object containing the validated authorization details. - */ - public AuthorizationDetails getValidatedAuthorizationDetails( - final OAuth2Parameters oAuth2Parameters, final OAuthAppDO oAuthAppDO, - final AuthenticatedUser authenticatedUser) throws AuthorizationDetailsProcessingException { - - final Set validatedAuthorizationDetails = new HashSet<>(); - final Set authorizedAuthorizationDetailsTypes = this.getAuthorizedAuthorizationDetailsTypes( - oAuth2Parameters.getClientId(), oAuth2Parameters.getTenantDomain()); - for (AuthorizationDetail authorizationDetail : oAuth2Parameters.getAuthorizationDetails().getDetails()) { - - if (!isSupportedAuthorizationDetailType(authorizationDetail.getType())) { - throw new AuthorizationDetailsProcessingException(String.format(AuthorizationDetailsConstants - .TYPE_NOT_SUPPORTED_ERR_MSG_FORMAT, authorizationDetail.getType())); - } - - if (isAuthorizedAuthorizationDetail(authorizationDetail, authorizedAuthorizationDetailsTypes)) { - - final AuthorizationDetailsContext authorizationDetailsContext = new AuthorizationDetailsContext( - oAuth2Parameters, oAuthAppDO, authenticatedUser, authorizationDetail); - - if (isValidAuthorizationDetail(authorizationDetailsContext)) { - validatedAuthorizationDetails.add(getEnrichedAuthorizationDetail(authorizationDetailsContext)); - } - } - } - - return new AuthorizationDetails(validatedAuthorizationDetails); - } - - private boolean isAuthorizedAuthorizationDetail(final AuthorizationDetail authorizationDetail, - final Set authorizedAuthorizationDetailsTypes) { - - return authorizedAuthorizationDetailsTypes.contains(authorizationDetail.getType()); - } - - private boolean isSupportedAuthorizationDetailType(final String authorizationDetailType) { - - return this.authorizationDetailsProviderFactory - .isSupportedAuthorizationDetailsType(authorizationDetailType); - } - - /** - * Checks if the provided authorization details context is valid. - * - * @param authorizationDetailsContext The context containing authorization details. - * @return {@code true} if the authorization details are valid; {@code false} otherwise. - */ - private boolean isValidAuthorizationDetail(final AuthorizationDetailsContext authorizationDetailsContext) - throws AuthorizationDetailsProcessingException { - - Optional optionalProvider = this.authorizationDetailsProviderFactory - .getProviderByType(authorizationDetailsContext.getAuthorizationDetail().getType()); - - if (optionalProvider.isPresent()) { - - final ValidationResult validationResult = optionalProvider.get().validate(authorizationDetailsContext); - if (log.isDebugEnabled() && validationResult.isInvalid()) { - - log.debug(String.format("Authorization details validation failed for type %s. Caused by, %s", - authorizationDetailsContext.getAuthorizationDetail().getType(), validationResult.getReason())); - - } - return validationResult.isValid(); - } - throw new AuthorizationDetailsProcessingException(String.format( - AuthorizationDetailsConstants.TYPE_NOT_SUPPORTED_ERR_MSG_FORMAT, - authorizationDetailsContext.getAuthorizationDetail().getType())); - } - - /** - * Enriches the authorization details using the provided context. - * - * @param authorizationDetailsContext The context containing authorization details. - * @return An enriched {@link AuthorizationDetail} object. - */ - private AuthorizationDetail getEnrichedAuthorizationDetail( - final AuthorizationDetailsContext authorizationDetailsContext) { - - return this.authorizationDetailsProviderFactory - .getProviderByType(authorizationDetailsContext.getAuthorizationDetail().getType()) - .map(authorizationDetailsService -> authorizationDetailsService.enrich(authorizationDetailsContext)) - // If provider is missing, return the original authorization detail instance - .orElse(authorizationDetailsContext.getAuthorizationDetail()); - } - - /** - * Retrieves the set of authorized authorization types for the given client and tenant domain. - * - * @param clientID The client ID. - * @param tenantDomain The tenant domain. - * @return A set of strings representing the authorized authorization types. - */ - private Set getAuthorizedAuthorizationDetailsTypes(final String clientID, final String tenantDomain) { - -// try { -// final String appId = OAuth2Util -// .getApplicationResourceIDByClientId(clientID, tenantDomain, this.applicationMgtService); -// -//// OAuth2ServiceComponentHolder.getInstance().getAuthorizedAPIManagementService() -// .getAuthorizedAuthorizationDetailsTypes(appId, tenantDomain); -// } catch (IdentityOAuth2Exception e) { -// throw new RuntimeException(e); -// } - Set authorizedAuthorizationDetailsTypes = new HashSet<>(); - authorizedAuthorizationDetailsTypes.add("payment_initiation"); - return authorizedAuthorizationDetailsTypes; - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAO.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAO.java new file mode 100644 index 00000000000..5c467f47bf0 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAO.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth2.rar.dao; + +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsConsentDTO; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsTokenDTO; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +/** + * Provides methods to interact with the database to manage rich authorization requests. + * + *

{@link AuthorizationDetailsDAO} provides methods to add, update, retrieve, and delete authorization details + * associated with user consent and access tokens. + */ +public interface AuthorizationDetailsDAO { + + /** + * Adds user consented authorization details to the database. + * + * @param authorizationDetailsConsentDTOs List of user consented authorization details DTOs. + * {@link AuthorizationDetailsConsentDTO } + * @return An array of positive integers indicating the number of rows affected for each batch operation, + * or negative integers if any of the batch operations fail. + * @throws SQLException If a database access error occurs. + */ + int[] addUserConsentedAuthorizationDetails(List authorizationDetailsConsentDTOs) + throws SQLException; + + /** + * Retrieves user consented authorization details from the database. + * + * @param consentId The ID of the consent. + * @param tenantId The tenant ID. + * @return A set of user consented authorization details DTOs. + * @throws SQLException If a database access error occurs. + */ + Set getUserConsentedAuthorizationDetails(String consentId, int tenantId) + throws SQLException; + + /** + * Updates user consented authorization details in the database. + * + * @param authorizationDetailsConsentDTOs A list of user consented authorization details DTOs. + * {@link AuthorizationDetailsConsentDTO} + * @return An array of integers indicating the number of rows affected for each batch operation. + * Positive values indicate success, negative values indicate failure. + * @throws SQLException If a database access error occurs. + */ + int[] updateUserConsentedAuthorizationDetails(List authorizationDetailsConsentDTOs) + throws SQLException; + + /** + * Deletes user consented authorization details from the database. + * + * @param consentId The ID of the consent. + * @param tenantId The tenant ID. + * @return The number of rows affected by the delete operation. + * @throws SQLException If a database access error occurs. + */ + int deleteUserConsentedAuthorizationDetails(String consentId, int tenantId) throws SQLException; + + /** + * Adds access token authorization details to the database. + * + * @param authorizationDetailsTokenDTOs A list of access token authorization details DTOs. + * {@link AuthorizationDetailsTokenDTO} + * @return An array of integers indicating the number of rows affected for each batch operation. + * Positive values indicate success, negative values indicate failure. + * @throws SQLException If a database access error occurs. + */ + int[] addAccessTokenAuthorizationDetails(List authorizationDetailsTokenDTOs) + throws SQLException; + + /** + * Retrieves access token authorization details from the database. + * + * @param accessTokenId The ID of the access token. + * @param tenantId The tenant ID. + * @return A set of access token authorization details DTOs. + * @throws SQLException If a database access error occurs. + */ + Set getAccessTokenAuthorizationDetails(String accessTokenId, int tenantId) + throws SQLException; + + /** + * Deletes access token authorization details from the database. + * + * @param accessTokenId The ID of the access token. + * @param tenantId The tenant ID. + * @return The number of rows affected by the delete operation. + * @throws SQLException If a database access error occurs. + */ + int deleteAccessTokenAuthorizationDetails(String accessTokenId, int tenantId) throws SQLException; + + /** + * Retrieves the consent ID associated with a specific user ID and application ID. + * + * @param userId The user ID. + * @param appId The application ID. + * @param tenantId The tenant ID. + * @return The consent ID as a string. + * @throws SQLException If a database access error occurs. + */ + // TODO: Move this method to the consent module + String getConsentIdByUserIdAndAppId(String userId, String appId, int tenantId) throws SQLException; +} diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAOImpl.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAOImpl.java similarity index 64% rename from components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAOImpl.java rename to components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAOImpl.java index 839f23c4642..b7a561d0f59 100644 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/AuthorizationDetailsDAOImpl.java +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/AuthorizationDetailsDAOImpl.java @@ -16,12 +16,11 @@ * under the License. */ -package org.wso2.carbon.identity.oauth2.rar.common.dao; +package org.wso2.carbon.identity.oauth2.rar.dao; import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; -import org.wso2.carbon.identity.oauth2.rar.common.dto.AuthorizationDetailsConsentDTO; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsConsentDTO; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsTokenDTO; import java.sql.Connection; import java.sql.PreparedStatement; @@ -32,36 +31,31 @@ import java.util.Set; /** - * Implementation of {@link AuthorizationDetailsDAO}. - * This class provides methods to add authorization details to the database. + * Implements the {@link AuthorizationDetailsDAO} interface to manage rich authorization requests. + * + *

{@link AuthorizationDetailsDAO} provides methods to add, update, retrieve, and delete authorization details + * associated with user consent and access tokens. */ public class AuthorizationDetailsDAOImpl implements AuthorizationDetailsDAO { /** - * Stores authorization details against the provided OAuth2 authorization code. - * - * @param authorizationCodeID The ID of the authorization code. - * @param authorizationDetails The details to be added. - * @param tenantId The tenant ID. - * @return An array of positive integers indicating the number of rows affected for each batch operation, - * or negative integers if any of the batch operations fail. - * @throws SQLException If a database access error occurs. + * {@inheritDoc} */ @Override - public int[] addOAuth2CodeAuthorizationDetails(final String authorizationCodeID, - final AuthorizationDetails authorizationDetails, - final int tenantId) throws SQLException { + public int[] addUserConsentedAuthorizationDetails( + final List authorizationDetailsConsentDTOs) throws SQLException { try (final Connection connection = IdentityDatabaseUtil.getDBConnection(false); final PreparedStatement ps = - connection.prepareStatement(SQLQueries.ADD_OAUTH2_CODE_AUTHORIZATION_DETAILS)) { - - for (AuthorizationDetail authorizationDetail : authorizationDetails.getDetails()) { - ps.setString(1, authorizationCodeID); - ps.setString(2, authorizationDetail.getType()); - ps.setInt(3, tenantId); - ps.setString(4, authorizationDetail.toJsonString()); - ps.setInt(5, tenantId); + connection.prepareStatement(SQLQueries.ADD_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS)) { + + for (AuthorizationDetailsConsentDTO consentDTO : authorizationDetailsConsentDTOs) { + ps.setString(1, consentDTO.getConsentId()); + ps.setString(2, consentDTO.getAuthorizationDetail().toJsonString()); + ps.setBoolean(3, consentDTO.isConsentActive()); + ps.setString(4, consentDTO.getAuthorizationDetail().getType()); + ps.setInt(5, consentDTO.getTenantId()); + ps.setInt(6, consentDTO.getTenantId()); ps.addBatch(); } return ps.executeBatch(); @@ -69,34 +63,39 @@ public int[] addOAuth2CodeAuthorizationDetails(final String authorizationCodeID, } /** - * Stores user consented authorization details. - * - * @param authorizationDetailsConsentDTOs The user consented authorization details DTOs - * @return An array of positive integers indicating the number of rows affected for each batch operation, - * or negative integers if any of the batch operations fail. - * @throws SQLException If a database access error occurs. + * {@inheritDoc} */ @Override - public int[] addUserConsentedAuthorizationDetails( - final List authorizationDetailsConsentDTOs) throws SQLException { + public Set getUserConsentedAuthorizationDetails(final String consentId, + final int tenantId) + throws SQLException { try (final Connection connection = IdentityDatabaseUtil.getDBConnection(false); final PreparedStatement ps = - connection.prepareStatement(SQLQueries.ADD_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS)) { + connection.prepareStatement(SQLQueries.GET_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS)) { - for (AuthorizationDetailsConsentDTO consentDTO : authorizationDetailsConsentDTOs) { - ps.setString(1, consentDTO.getConsentId()); - ps.setString(2, consentDTO.getAuthorizationDetail().getType()); - ps.setInt(3, consentDTO.getTenantId()); - ps.setString(4, consentDTO.getAuthorizationDetail().toJsonString()); - ps.setBoolean(5, consentDTO.isConsentActive()); - ps.setInt(6, consentDTO.getTenantId()); - ps.addBatch(); + ps.setString(1, consentId); + ps.setInt(2, tenantId); + try (ResultSet rs = ps.executeQuery()) { + + final Set authorizationDetailsConsentDTOs = new HashSet<>(); + while (rs.next()) { + final String id = rs.getString(1); + final int typeId = rs.getInt(2); + final String authorizationDetail = rs.getString(3); + final boolean isConsentActive = rs.getBoolean(4); + + authorizationDetailsConsentDTOs.add(new AuthorizationDetailsConsentDTO(id, consentId, typeId, + authorizationDetail, isConsentActive, tenantId)); + } + return authorizationDetailsConsentDTOs; } - return ps.executeBatch(); } } + /** + * {@inheritDoc} + */ @Override public int[] updateUserConsentedAuthorizationDetails( final List authorizationDetailsConsentDTOs) throws SQLException { @@ -116,6 +115,9 @@ public int[] updateUserConsentedAuthorizationDetails( } } + /** + * {@inheritDoc} + */ @Override public int deleteUserConsentedAuthorizationDetails(final String consentId, final int tenantId) throws SQLException { @@ -130,42 +132,76 @@ public int deleteUserConsentedAuthorizationDetails(final String consentId, final } } + /** + * {@inheritDoc} + */ @Override - public Set getUserConsentedAuthorizationDetails(final String consentId, - final int tenantId) - throws SQLException { + public int[] addAccessTokenAuthorizationDetails(final List + authorizationDetailsTokenDTOs) throws SQLException { + try (final Connection connection = IdentityDatabaseUtil.getDBConnection(false); + final PreparedStatement ps = + connection.prepareStatement(SQLQueries.ADD_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS)) { + + for (AuthorizationDetailsTokenDTO tokenDTO : authorizationDetailsTokenDTOs) { + ps.setString(1, tokenDTO.getAccessTokenId()); + ps.setString(2, tokenDTO.getAuthorizationDetail().toJsonString()); + ps.setString(3, tokenDTO.getAuthorizationDetail().getType()); + ps.setInt(4, tokenDTO.getTenantId()); + ps.setInt(5, tokenDTO.getTenantId()); + ps.addBatch(); + } + return ps.executeBatch(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getAccessTokenAuthorizationDetails( + final String accessTokenId, final int tenantId) throws SQLException { try (final Connection connection = IdentityDatabaseUtil.getDBConnection(false); final PreparedStatement ps = - connection.prepareStatement(SQLQueries.GET_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS)) { + connection.prepareStatement(SQLQueries.GET_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS)) { - ps.setString(1, consentId); + ps.setString(1, accessTokenId); ps.setInt(2, tenantId); try (ResultSet rs = ps.executeQuery()) { - final Set authorizationDetailsConsentDTOs = new HashSet<>(); + final Set authorizationDetailsTokenDTO = new HashSet<>(); while (rs.next()) { final String id = rs.getString(1); final int typeId = rs.getInt(2); final String authorizationDetail = rs.getString(3); - final boolean isConsentActive = rs.getBoolean(4); - authorizationDetailsConsentDTOs.add(new AuthorizationDetailsConsentDTO(id, consentId, typeId, - authorizationDetail, isConsentActive, tenantId)); + authorizationDetailsTokenDTO.add( + new AuthorizationDetailsTokenDTO(id, accessTokenId, typeId, authorizationDetail, tenantId)); } - return authorizationDetailsConsentDTOs; + return authorizationDetailsTokenDTO; } } } /** - * Retrieves the first consent ID for a given user ID and application ID. - * - * @param userId The ID of the user. - * @param appId The ID of the application. - * @param tenantId The tenant ID. - * @return The first consent ID found, or null if no consent ID is found. - * @throws SQLException If a database access error occurs. + * {@inheritDoc} + */ + @Override + public int deleteAccessTokenAuthorizationDetails(final String accessTokenId, final int tenantId) + throws SQLException { + + try (final Connection connection = IdentityDatabaseUtil.getDBConnection(false); + final PreparedStatement ps = + connection.prepareStatement(SQLQueries.DELETE_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS)) { + + ps.setString(1, accessTokenId); + ps.setInt(2, tenantId); + return ps.executeUpdate(); + } + } + + /** + * {@inheritDoc} */ @Override public String getConsentIdByUserIdAndAppId(final String userId, final String appId, final int tenantId) diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/SQLQueries.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/SQLQueries.java similarity index 58% rename from components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/SQLQueries.java rename to components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/SQLQueries.java index 79faa31c9ec..9cbed273657 100644 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/dao/SQLQueries.java +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dao/SQLQueries.java @@ -16,7 +16,7 @@ * under the License. */ -package org.wso2.carbon.identity.oauth2.rar.common.dao; +package org.wso2.carbon.identity.oauth2.rar.dao; /** * The {@code SQLQueries} class contains SQL query constants used for performing @@ -28,25 +28,11 @@ private SQLQueries() { // Private constructor to prevent instantiation } - public static final String ADD_OAUTH2_CODE_AUTHORIZATION_DETAILS = - "INSERT INTO IDN_OAUTH2_AUTHZ_CODE_AUTHORIZATION_DETAILS " + - "(CODE_ID, TYPE_ID, AUTHORIZATION_DETAILS, TENANT_ID) VALUES " + - "(?, (SELECT ID FROM IDN_OAUTH2_AUTHORIZATION_DETAILS_TYPES WHERE TYPE=? AND TENANT_ID=?), " + - "? FORMAT JSON, ?)"; - - public static final String GET_OAUTH2_CODE_AUTHORIZATION_DETAILS = - "SELECT IDN_OAUTH2_AUTHZ_CODE_AUTHORIZATION_DETAILS.AUTHORIZATION_DETAILS " + - "FROM IDN_OAUTH2_AUTHZ_CODE_AUTHORIZATION_DETAILS " + - "INNER JOIN IDN_OAUTH2_AUTHORIZATION_CODE " + - "ON IDN_OAUTH2_AUTHZ_CODE_AUTHORIZATION_DETAILS.CODE_ID = IDN_OAUTH2_AUTHORIZATION_CODE.CODE_ID " + - "WHERE IDN_OAUTH2_AUTHORIZATION_CODE.AUTHORIZATION_CODE=? " + - "AND IDN_OAUTH2_AUTHZ_CODE_AUTHORIZATION_DETAILS.TENANT_ID=?"; - public static final String ADD_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS = "INSERT INTO IDN_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS " + - "(CONSENT_ID, TYPE_ID, AUTHORIZATION_DETAILS, CONSENT, TENANT_ID) VALUES " + - "(?,(SELECT ID FROM IDN_OAUTH2_AUTHORIZATION_DETAILS_TYPES WHERE TYPE=? AND TENANT_ID=?), " + - "? FORMAT JSON, ?, ?)"; + "(CONSENT_ID, AUTHORIZATION_DETAILS, CONSENT, TYPE_ID, TENANT_ID) " + + "VALUES (?, ? FORMAT JSON, ?," + + "(SELECT ID FROM IDN_OAUTH2_AUTHORIZATION_DETAILS_TYPES WHERE TYPE=? AND TENANT_ID=?), ?)"; public static final String UPDATE_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS = "UPDATE IDN_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS " + @@ -59,9 +45,18 @@ private SQLQueries() { public static final String DELETE_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS = "DELETE FROM IDN_OAUTH2_USER_CONSENTED_AUTHORIZATION_DETAILS WHERE CONSENT_ID=? AND TENANT_ID=?"; - public static final String CREATE_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS = + public static final String ADD_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS = "INSERT INTO IDN_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS " + - "(AUTHORIZATION_DETAILS_TYPE, AUTHORIZATION_DETAILS, TOKEN_ID, TENANT_ID) VALUES (?, ?, ?, ?)"; + "(TOKEN_ID, AUTHORIZATION_DETAILS, TYPE_ID, TENANT_ID) " + + "VALUES (?, ? FORMAT JSON, " + + "(SELECT ID FROM IDN_OAUTH2_AUTHORIZATION_DETAILS_TYPES WHERE TYPE=? AND TENANT_ID=?), ?)"; + + public static final String DELETE_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS = + "DELETE FROM IDN_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS WHERE TOKEN_ID=? AND TENANT_ID=?"; + + public static final String GET_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS = + "SELECT ID, TYPE_ID, AUTHORIZATION_DETAILS FROM IDN_OAUTH2_ACCESS_TOKEN_AUTHORIZATION_DETAILS " + + "WHERE TOKEN_ID=? AND TENANT_ID=?"; public static final String GET_IDN_OAUTH2_USER_CONSENT_CONSENT_ID = "SELECT CONSENT_ID FROM IDN_OAUTH2_USER_CONSENT WHERE USER_ID=? AND APP_ID=? AND TENANT_ID=?"; diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsConsentDTO.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsConsentDTO.java new file mode 100644 index 00000000000..c70619615f1 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsConsentDTO.java @@ -0,0 +1,67 @@ +package org.wso2.carbon.identity.oauth2.rar.dto; + +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; + +/** + * Data Transfer Object (DTO) for representing authorization details along with consent information. + * This class extends {@link AuthorizationDetailsDTO} to include additional fields for consent ID and consent status. + */ +public class AuthorizationDetailsConsentDTO extends AuthorizationDetailsDTO { + + final String consentId; + final boolean isConsentActive; + + /** + * Constructs an {@link AuthorizationDetailsConsentDTO} with all required fields. + * + * @param id the ID of the authorization detail DTO. + * @param consentId the consent ID associated with the authorization detail. + * @param typeId the type ID of the authorization detail. + * @param authorizationDetailJson the JSON string of the authorization detail. + * @param isConsentActive the consent status. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsConsentDTO(final String id, final String consentId, final int typeId, + final String authorizationDetailJson, + final boolean isConsentActive, final int tenantId) { + + super(id, typeId, authorizationDetailJson, tenantId); + this.consentId = consentId; + this.isConsentActive = isConsentActive; + } + + /** + * Constructs an {@link AuthorizationDetailsConsentDTO} with essential fields. + * + * @param consentId the consent ID associated with the authorization detail. + * @param authorizationDetail the {@link AuthorizationDetail} object. + * @param isConsentActive the consent status. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsConsentDTO(final String consentId, + final AuthorizationDetail authorizationDetail, + final boolean isConsentActive, final int tenantId) { + + super(authorizationDetail, tenantId); + this.consentId = consentId; + this.isConsentActive = isConsentActive; + } + + /** + * Checks if the consent is active. + * + * @return {@code true} if consent is active, {@code false} otherwise. + */ + public boolean isConsentActive() { + return isConsentActive; + } + + /** + * Gets the consent ID associated with the authorization detail. + * + * @return the consent ID. + */ + public String getConsentId() { + return consentId; + } +} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsDTO.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsDTO.java new file mode 100644 index 00000000000..fe6a85b8efc --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsDTO.java @@ -0,0 +1,98 @@ +package org.wso2.carbon.identity.oauth2.rar.dto; + +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsCommonUtils; + +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsCommonUtils.getDefaultObjectMapper; + +/** + * Data Transfer Object (DTO) for representing authorization details. + *

This class encapsulates the details of authorization, including the ID, type ID, + * authorization detail object, and tenant ID. + */ +public class AuthorizationDetailsDTO { + + final String id; + final int typeId; + final AuthorizationDetail authorizationDetail; + final int tenantId; + + /** + * Constructs an AuthorizationDetailsDTO with all fields. + * + * @param id the ID of the authorization detail DTO. + * @param typeId the type ID of the authorization detail. + * @param authorizationDetail the authorization detail object. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsDTO(final String id, final int typeId, final AuthorizationDetail authorizationDetail, + final int tenantId) { + + this.id = id; + this.typeId = typeId; + this.authorizationDetail = authorizationDetail; + this.tenantId = tenantId; + } + + /** + * Constructs an AuthorizationDetailsDTO from authorization detail JSON string. + * + * @param id the ID of the authorization detail DTO. + * @param typeId the type ID of the authorization detail. + * @param authorizationDetailJson the JSON string of the authorization detail. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsDTO(final String id, final int typeId, final String authorizationDetailJson, + final int tenantId) { + + this(id, typeId, AuthorizationDetailsCommonUtils + .fromJSON(authorizationDetailJson, AuthorizationDetail.class, getDefaultObjectMapper()), tenantId); + } + + /** + * Constructs an AuthorizationDetailsDTO with an authorization detail object and tenant ID. + * + * @param authorizationDetail the authorization detail object. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsDTO(final AuthorizationDetail authorizationDetail, final int tenantId) { + + this(null, 0, authorizationDetail, tenantId); + } + + /** + * Gets the ID of the authorization detail. + * + * @return the ID of the authorization detail. + */ + public String getId() { + return this.id; + } + + /** + * Gets the type ID of the authorization detail. + * + * @return the type ID of the authorization detail. + */ + public int getTypeId() { + return this.typeId; + } + + /** + * Gets the authorization detail object. + * + * @return the authorization detail object. + */ + public AuthorizationDetail getAuthorizationDetail() { + return this.authorizationDetail; + } + + /** + * Gets the tenant ID. + * + * @return the tenant ID. + */ + public int getTenantId() { + return this.tenantId; + } +} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsTokenDTO.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsTokenDTO.java new file mode 100644 index 00000000000..a75d7b8681d --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/dto/AuthorizationDetailsTokenDTO.java @@ -0,0 +1,51 @@ +package org.wso2.carbon.identity.oauth2.rar.dto; + +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; + +/** + * Data Transfer Object (DTO) for representing authorization details along with access token information. + * This class extends {@link AuthorizationDetailsDTO} to include additional fields for access token ID. + */ +public class AuthorizationDetailsTokenDTO extends AuthorizationDetailsDTO { + + final String accessTokenId; + + /** + * Constructs an {@link AuthorizationDetailsTokenDTO} with all required fields. + * + * @param id the ID of the authorization detail DTO. + * @param accessTokenId the access token ID associated with the authorization detail. + * @param typeId the type ID of the authorization detail. + * @param authorizationDetail the {@link AuthorizationDetail} object. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsTokenDTO(final String id, final String accessTokenId, final int typeId, + final String authorizationDetail, final int tenantId) { + + super(id, typeId, authorizationDetail, tenantId); + this.accessTokenId = accessTokenId; + } + + /** + * Constructs an {@link AuthorizationDetailsTokenDTO} with essential fields. + * + * @param accessTokenId the access token ID associated with the authorization detail. + * @param authorizationDetail the {@link AuthorizationDetail} object. + * @param tenantId the tenant ID. + */ + public AuthorizationDetailsTokenDTO(final String accessTokenId, final AuthorizationDetail authorizationDetail, + final int tenantId) { + + super(authorizationDetail, tenantId); + this.accessTokenId = accessTokenId; + } + + /** + * Gets the access token ID associated with the authorization detail. + * + * @return the access token ID. + */ + public String getAccessTokenId() { + return this.accessTokenId; + } +} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java deleted file mode 100644 index 62a65d9859c..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.wso2.carbon.identity.oauth2.rar.exception; - -/** - * - */ -public class AuthorizationDetailsProcessingException extends RuntimeException { - - private static final long serialVersionUID = -206212512259482200L; - - /** - * Constructs a new exception with an error message. - * - * @param message The detail message. - */ - public AuthorizationDetailsProcessingException(String message) { - - super(message); - } - - /** - * Constructs a new exception with the message and cause. - * - * @param message The detail message. - * @param cause The cause. - */ - public AuthorizationDetailsProcessingException(String message, Throwable cause) { - - super(message, cause); - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/handler/RARAccessTokenResponseHandler.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/handler/RARAccessTokenResponseHandler.java deleted file mode 100644 index a35e8bd0ae5..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/handler/RARAccessTokenResponseHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.wso2.carbon.identity.oauth2.rar.handler; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONArray; -import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; -import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; -import org.wso2.carbon.identity.oauth2.token.handlers.response.AccessTokenResponseHandler; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * - */ -public class RARAccessTokenResponseHandler implements AccessTokenResponseHandler { - private static final ObjectMapper OBJECT_MAPPER = new JsonMapper(); - private static final Log LOG = LogFactory.getLog(RARAccessTokenResponseHandler.class); - - @Override -// public Map getAdditionalTokenResponseAttributes(OAuthTokenReqMessageContext tokReqMsgCtx) -// throws IdentityOAuth2Exception { -// -// List authorizationDetails = AuthorizationDetailService.getInstance() -// .getConsentedAuthorizationDetailsByAuthzCode(tokReqMsgCtx.getOauth2AccessTokenReqDTO() -// .getAuthorizationCode()); -// -// Map additionalAttributes = new HashMap<>(); -// if (isNotEmptyList(authorizationDetails)) { -// additionalAttributes.put(AUTHORIZATION_DETAILS, convertToJsonArray(authorizationDetails)); -// } -// return additionalAttributes; -// } - - public Map getAdditionalTokenResponseAttributes(OAuthTokenReqMessageContext tokReqMsgCtx) - throws IdentityOAuth2Exception { - - Map additionalAttributes = new HashMap<>(); - - return additionalAttributes; - } - - - private boolean isNotEmptyList(List list) { - return list != null && !list.isEmpty(); - } - - private JSONArray convertToJsonArray(List authorizationDetails) { - try { - return OBJECT_MAPPER.convertValue(OBJECT_MAPPER.writeValueAsString(authorizationDetails), JSONArray.class); - } catch (JsonProcessingException e) { - LOG.error("Serialization error. Caused by, ", e); - } - return new JSONArray(authorizationDetails); - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsDataHolder.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsDataHolder.java deleted file mode 100644 index bd0c92f5f3f..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsDataHolder.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.carbon.identity.oauth2.rar.internal; - -import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProvider; - -import java.util.HashSet; -import java.util.Set; - -/** - * Singleton class that holds rich authorization details data. - *

This class uses the singleton design pattern to ensure there is only one instance - * managing the authorization details providers. The instance is lazily initialized - * with double-checked locking to ensure thread safety.

- *

The class provides methods to retrieve and set the authorization details data, - * which can be used in different parts of the application to manage rich authorization details.

- */ -public class AuthorizationDetailsDataHolder { - - private static volatile AuthorizationDetailsDataHolder instance; - private Set authorizationDetailsProviders; - - /** - * Private constructor to prevent instantiation from outside the class. - */ - private AuthorizationDetailsDataHolder() { - - this.authorizationDetailsProviders = new HashSet<>(); - } - - /** - * Returns the singleton instance of {@link AuthorizationDetailsDataHolder}. - * - *

This method uses double-checked locking to ensure that the instance is initialized - * only once and in a thread-safe manner. If the instance is not already created, it - * will be created and returned; otherwise, the existing instance will be returned.

- * - * @return The singleton instance of {@link AuthorizationDetailsDataHolder}. - */ - public static AuthorizationDetailsDataHolder getInstance() { - - if (instance == null) { - synchronized (AuthorizationDetailsDataHolder.class) { - if (instance == null) { - instance = new AuthorizationDetailsDataHolder(); - } - } - } - return instance; - } - - /** - * Returns the current set of {@link AuthorizationDetailsProvider} instances. - * - *

This method provides access to the authorization details providers. - * The returned set can be used to query or modify the authorization details providers.

- * - * @return A {@link Set} of {@link AuthorizationDetailsProvider} instances. - */ - public Set getAuthorizationDetailsProviders() { - - return this.authorizationDetailsProviders; - } - - /** - * Sets the set of {@link AuthorizationDetailsProvider} instances to the provided value. - * - *

This method replaces the current set of authorization details providers with the - * provided set. It can be used to update the list of providers that the application - * uses to manage authorization details.

- * - * @param authorizationDetailsProviders The new {@link Set} of {@link AuthorizationDetailsProvider} instances. - */ - public void setAuthorizationDetailsProviders(Set authorizationDetailsProviders) { - - this.authorizationDetailsProviders = authorizationDetailsProviders; - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsServiceComponent.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsServiceComponent.java deleted file mode 100644 index 2cd272dc71d..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/internal/AuthorizationDetailsServiceComponent.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.carbon.identity.oauth2.rar.internal; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProvider; - -import java.util.ServiceLoader; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -/** - * Authorization Details OSGI service component. - */ -@Component(name = "org.wso2.carbon.identity.oauth.rar.internal.AuthorizationDetailsServiceComponent") -public class AuthorizationDetailsServiceComponent { - private static final Log log = LogFactory.getLog(AuthorizationDetailsServiceComponent.class); - - @Activate - protected void activate(ComponentContext context) { - - AuthorizationDetailsDataHolder.getInstance().setAuthorizationDetailsProviders( - loadAuthorizationDetailsProviders(ServiceLoader.load(AuthorizationDetailsProvider.class, - this.getClass().getClassLoader()))); - - log.debug("AuthorizationDetailsServiceComponent is activated"); - } - - @Deactivate - protected void deactivate(ComponentContext context) { - - log.debug("AuthorizationDetailsServiceComponent is deactivated"); - } - - /** - * Loads supported authorization details providers from the provided {@link ServiceLoader}. - * - * @param serviceLoader {@link ServiceLoader} for {@link AuthorizationDetailsProvider}. - * @return Set of authorization details providers. - */ - private Set loadAuthorizationDetailsProviders( - final ServiceLoader serviceLoader) { - - return StreamSupport.stream(serviceLoader.spliterator(), false) - .collect(Collectors.toSet()); - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/model/AuthorizationDetail.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetail.java similarity index 91% rename from components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/model/AuthorizationDetail.java rename to components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetail.java index 96194b34856..9d3c87af10a 100644 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/model/AuthorizationDetail.java +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetail.java @@ -16,24 +16,23 @@ * under the License. */ -package org.wso2.carbon.identity.oauth2.rar.common.model; +package org.wso2.carbon.identity.oauth2.rar.model; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.StringUtils; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsCommonUtils; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsCommonUtils; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.function.Function; +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsCommonUtils.getDefaultObjectMapper; + /** * Represents an individual authorization details object which specifies the authorization requirements for a * specific resource type within the {@code authorization_details} request parameter used in OAuth 2.0 flows @@ -71,15 +70,13 @@ * *

Refer to * OAuth 2.0 Rich Authorization Requests for detailed information on the Authorization Details structure.

- * - * @since 7.0.26.9 */ @JsonInclude(JsonInclude.Include.NON_NULL) public class AuthorizationDetail implements Serializable { private static final long serialVersionUID = -3928636285264078857L; - @JsonIgnore + @JsonProperty("_id") private String id; private String type; private List locations; @@ -91,16 +88,21 @@ public class AuthorizationDetail implements Serializable { private Map details; private String consentDescription; - public AuthorizationDetail() { - this.setId(UUID.randomUUID().toString()); - } - + /** + * Gets the unique ID of the authorization detail. + * + * @return the ID of the authorization detail. + */ public String getId() { + return this.id; } - @JsonProperty("_id") + /** + * Sets a unique temporary ID for a given authorization detail instance. + */ public void setId(final String id) { + this.id = id; } @@ -112,13 +114,15 @@ public void setId(final String id) { * * @return The String value of the type field * @see - * Authorization Details Types + * Authorization Details Types */ public String getType() { + return this.type; } public void setType(final String type) { + this.type = type; } @@ -131,10 +135,12 @@ public void setType(final String type) { * @return A list of locations or {@code null} if the {@code locations} field is not present. */ public List getLocations() { + return this.locations; } public void setLocations(final List locations) { + this.locations = locations; } @@ -146,10 +152,12 @@ public void setLocations(final List locations) { * @return A list of actions or {@code null} if the {@code actions} field is not present. */ public List getActions() { + return this.actions; } public void setActions(final List actions) { + this.actions = actions; } @@ -161,10 +169,12 @@ public void setActions(final List actions) { * @return A list of datatypes or {@code null} if the {@code datatypes} field is not present. */ public List getDataTypes() { + return this.dataTypes; } public void setDataTypes(final List dataTypes) { + this.dataTypes = dataTypes; } @@ -176,10 +186,12 @@ public void setDataTypes(final List dataTypes) { * @return The String value of the identifier or {@code null} if the {@code identifier} field is not present. */ public String getIdentifier() { + return this.identifier; } public void setIdentifier(final String identifier) { + this.identifier = identifier; } @@ -192,10 +204,12 @@ public void setIdentifier(final String identifier) { * @return The String value of the privileges or {@code null} if the {@code privileges} field is not present. */ public List getPrivileges() { + return this.privileges; } public void setPrivileges(final List privileges) { + this.privileges = privileges; } @@ -207,15 +221,18 @@ public void setPrivileges(final List privileges) { */ @JsonAnyGetter public Map getDetails() { + return this.details; } public void setDetails(final Map details) { + this.details = details; } @JsonAnySetter public void setDetail(final String key, final Object value) { + if (this.details == null) { setDetails(new HashMap<>()); } @@ -243,6 +260,7 @@ public String getConsentDescription() { * explaining what they are consenting to. */ public void setConsentDescription(final String consentDescription) { + this.consentDescription = consentDescription; } @@ -269,6 +287,7 @@ public void setConsentDescription(final String consentDescription) { * @return the consent description if present, otherwise the value from the Function */ public String getConsentDescriptionOrDefault(Function defaultFunction) { + return StringUtils.isNotEmpty(this.getConsentDescription()) ? this.getConsentDescription() : defaultFunction.apply(this); } @@ -279,11 +298,23 @@ public String getConsentDescriptionOrDefault(Function} representation of the authorization detail. + */ + public Map toMap() { + + return AuthorizationDetailsCommonUtils.toMap(this, getDefaultObjectMapper()); } @Override public String toString() { + return "AuthorizationDetails {" + "type='" + this.type + '\'' + ", locations=" + this.locations + diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/model/AuthorizationDetails.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetails.java similarity index 80% rename from components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/model/AuthorizationDetails.java rename to components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetails.java index 0befab02b11..11c16e87fba 100644 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/model/AuthorizationDetails.java +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetails.java @@ -16,21 +16,23 @@ * under the License. */ -package org.wso2.carbon.identity.oauth2.rar.common.model; +package org.wso2.carbon.identity.oauth2.rar.model; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.StringUtils; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsCommonUtils; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsCommonUtils; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; import java.io.Serializable; import java.util.Collections; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsCommonUtils.getDefaultObjectMapper; + /** * Represents a set of {@link AuthorizationDetail} objects which specifies the authorization requirements for a * specific resource type within the {@code authorization_details} request parameter used in OAuth 2.0 flows @@ -52,6 +54,7 @@ public class AuthorizationDetails implements Serializable { * Constructs an empty set of {@link AuthorizationDetail}. */ public AuthorizationDetails() { + this(Collections.emptySet()); } @@ -61,6 +64,7 @@ public AuthorizationDetails() { * @param authorizationDetails The set of authorization details. If null, an empty set is assigned. */ public AuthorizationDetails(final Set authorizationDetails) { + this.authorizationDetails = Optional.ofNullable(authorizationDetails) .map(Collections::unmodifiableSet) .orElse(Collections.emptySet()); @@ -72,8 +76,9 @@ public AuthorizationDetails(final Set authorizationDetails) * @param authorizationDetailsJson The JSON string representing the authorization details. */ public AuthorizationDetails(final String authorizationDetailsJson) { + this(AuthorizationDetailsCommonUtils.fromJSONArray( - authorizationDetailsJson, AuthorizationDetail.class, new ObjectMapper())); + authorizationDetailsJson, AuthorizationDetail.class, getDefaultObjectMapper())); } /** @@ -82,15 +87,30 @@ public AuthorizationDetails(final String authorizationDetailsJson) { * @return A set of {@link AuthorizationDetail}. */ public Set getDetails() { + return this.authorizationDetails; } + /** + * Converts a stream of AuthorizationDetail objects into a {@link Set} of {@link Map}. + * + *

Each AuthorizationDetail object is transformed into a Map representation using + * the {@link AuthorizationDetail#toMap} method. + * + * @return a Set of Maps representing the AuthorizationDetail objects. + */ + public Set> toSet() { + + return this.stream().map(AuthorizationDetail::toMap).collect(Collectors.toSet()); + } + /** * Returns a set of the {@code authorization_details} filtered by provided type. * * @return A set of {@link AuthorizationDetail}. */ public Set getDetailsByType(final String type) { + return this.stream() .filter(Objects::nonNull) .filter(authorizationDetail -> StringUtils.equals(authorizationDetail.getType(), type)) @@ -103,7 +123,8 @@ public Set getDetailsByType(final String type) { * @return The JSON representation of the authorization details. */ public String toJsonString() { - return AuthorizationDetailsCommonUtils.toJSON(this.getDetails(), new ObjectMapper()); + + return AuthorizationDetailsCommonUtils.toJSON(this.getDetails(), getDefaultObjectMapper()); } /** @@ -120,7 +141,13 @@ public String toReadableText() { .collect(Collectors.joining(AuthorizationDetailsConstants.PARAM_SEPARATOR)); } + /** + * Converts the current set of authorization details to a {@link Stream}. + * + * @return The Stream representation of the {@code authorization_details}. + */ public Stream stream() { + return this.getDetails().stream(); } } diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java deleted file mode 100644 index 00cd4de10d6..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.carbon.identity.oauth2.rar.model; - -import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; -import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; -import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; - -import java.util.Objects; - -/** - * Represents the context for rich authorization requests in an OAuth2 flow. - *

- * This class holds relevant details such as OAuth2 parameters, application details, the authenticated user, - * and specific authorization details. It is immutable to ensure that the context remains consistent throughout its use. - *

- */ -public class AuthorizationDetailsContext { - - private final OAuth2Parameters oAuth2Parameters; - private final OAuthAppDO oAuthAppDO; - private final AuthenticatedUser authenticatedUser; - private final AuthorizationDetail authorizationDetail; - - /** - * Constructs a new {@code AuthorizationDetailsContext}. - *

- * This constructor ensures that all necessary details for an authorization context are provided. - *

- * - * @param oAuth2Parameters the OAuth2 parameters. - * @param oAuthAppDO the OAuth application details. - * @param authenticatedUser the authenticated user. - * @param authorizationDetail the specific authorization detail. - * @throws NullPointerException if any of the arguments are {@code null}. - */ - public AuthorizationDetailsContext(final OAuth2Parameters oAuth2Parameters, final OAuthAppDO oAuthAppDO, - final AuthenticatedUser authenticatedUser, - final AuthorizationDetail authorizationDetail) { - this.oAuth2Parameters = Objects.requireNonNull(oAuth2Parameters, "oAuth2Parameters cannot be null"); - this.oAuthAppDO = Objects.requireNonNull(oAuthAppDO, "oAuthAppDO cannot be null"); - this.authenticatedUser = Objects.requireNonNull(authenticatedUser, "authenticatedUser cannot be null"); - this.authorizationDetail = Objects.requireNonNull(authorizationDetail, "authorizationDetail cannot be null"); - } - - /** - * Returns the {@code AuthorizationDetail} instance. - * - * @return the {@link AuthorizationDetail} instance. - */ - public AuthorizationDetail getAuthorizationDetail() { - return this.authorizationDetail; - } - - /** - * Returns the OAuth2 parameters. - * - * @return the {@link OAuth2Parameters} instance. - */ - public OAuth2Parameters getOAuth2Parameters() { - return this.oAuth2Parameters; - } - - /** - * Returns the OAuth application details. - * - * @return the {@link OAuthAppDO} instance. - */ - public OAuthAppDO getoAuthAppDO() { - return this.oAuthAppDO; - } - - /** - * Returns the authenticated user. - * - * @return the {@link AuthenticatedUser} instance. - */ - public AuthenticatedUser getAuthenticatedUser() { - return this.authenticatedUser; - } -} diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/util/AuthorizationDetailsCommonUtils.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsCommonUtils.java similarity index 68% rename from components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/util/AuthorizationDetailsCommonUtils.java rename to components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsCommonUtils.java index c908656d18f..d34bfedb27a 100644 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/util/AuthorizationDetailsCommonUtils.java +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsCommonUtils.java @@ -16,16 +16,21 @@ * under the License. */ -package org.wso2.carbon.identity.oauth2.rar.common.util; +package org.wso2.carbon.identity.oauth2.rar.util; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** @@ -36,6 +41,7 @@ public class AuthorizationDetailsCommonUtils { private static final Log log = LogFactory.getLog(AuthorizationDetailsCommonUtils.class); private static final String empty_json = "{}"; private static final String empty_json_array = "[]"; + private static final ObjectMapper objectMapper = createDefaultObjectMapper(); private AuthorizationDetailsCommonUtils() { // Private constructor to prevent instantiation @@ -51,7 +57,7 @@ private AuthorizationDetailsCommonUtils() { * @param the type parameter extending {@code AuthorizationDetail} * @return an immutable set of {@link AuthorizationDetail} objects parsed from the given JSON string, * or an empty set if parsing fails - * @see org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails + * @see AuthorizationDetails */ public static Set fromJSONArray( final String authorizationDetailsJson, final Class clazz, final ObjectMapper objectMapper) { @@ -71,12 +77,12 @@ public static Set fromJSONArray( * Parses the given JSON object string into an {@link AuthorizationDetail} object. * * @param authorizationDetailJson A JSON string containing authorization detail object - * @param objectMapper A Jackson {@link ObjectMapper} used for parsing - * @param clazz A Class that extends {@link AuthorizationDetail} to be parsed - * @param the type parameter extending {@code AuthorizationDetail} + * @param objectMapper A Jackson {@link ObjectMapper} used for parsing + * @param clazz A Class that extends {@link AuthorizationDetail} to be parsed + * @param the type parameter extending {@code AuthorizationDetail} * @return an {@link AuthorizationDetail} objects parsed from the given JSON string, * or null if parsing fails - * @see org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail + * @see AuthorizationDetail */ public static T fromJSON( final String authorizationDetailJson, final Class clazz, final ObjectMapper objectMapper) { @@ -103,8 +109,8 @@ public static T fromJSON( * @param the type parameter extending {@code AuthorizationDetail} * @return a JSON string representation of the authorization details set, * or an empty JSON array if null or an error occurs - * @see org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail - * @see org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails + * @see AuthorizationDetail + * @see AuthorizationDetails */ public static String toJSON( final Set authorizationDetails, final ObjectMapper objectMapper) { @@ -131,8 +137,8 @@ public static String toJSON( * @param the type parameter extending {@code AuthorizationDetail} * @return a JSON string representation of the authorization detail, * or an empty JSON object if null or an error occurs - * @see org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail - * @see org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails + * @see AuthorizationDetail + * @see AuthorizationDetails */ public static String toJSON( final T authorizationDetail, final ObjectMapper objectMapper) { @@ -146,4 +152,52 @@ public static String toJSON( } return empty_json; } + + /** + * Converts a single {@code AuthorizationDetail} object into a {@link Map}. + *

+ * If the input object is {@code null} or an exception occurs during the conversion, + * an empty {@link HashMap} is returned. + *

+ * + * @param authorizationDetail the {@code AuthorizationDetail} object to convert + * @param objectMapper the {@code ObjectMapper} instance to use for serialization + * @param the type parameter extending {@code AuthorizationDetail} + * @return a {@code Map} representation of the authorization detail, + * or an empty {@code HashMap} if null or an error occurs + * @see AuthorizationDetail + * @see AuthorizationDetails + */ + public static Map toMap( + final T authorizationDetail, final ObjectMapper objectMapper) { + + if (authorizationDetail != null) { + return objectMapper.convertValue(authorizationDetail, new TypeReference>() { + }); + } + return new HashMap<>(); + } + + /** + * Creates a singleton instance of {@link ObjectMapper}. + * + * @return the singleton {@link ObjectMapper} instance. + */ + private static ObjectMapper createDefaultObjectMapper() { + + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return objectMapper; + } + + /** + * Returns a configured default {@link ObjectMapper} instance. + * + *

This singleton ObjectMapper is configured to exclude properties with null values from the JSON output. + * + * @return a configured {@link ObjectMapper} instance. + */ + public static ObjectMapper getDefaultObjectMapper() { + return objectMapper; + } } diff --git a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/util/AuthorizationDetailsConstants.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsConstants.java similarity index 96% rename from components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/util/AuthorizationDetailsConstants.java rename to components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsConstants.java index 5b1ab3d97f4..62128f272d4 100644 --- a/components/org.wso2.carbon.identity.oauth.rar.common/src/main/java/org/wso2/carbon/identity/oauth2/rar/common/util/AuthorizationDetailsConstants.java +++ b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsConstants.java @@ -16,7 +16,7 @@ * under the License. */ -package org.wso2.carbon.identity.oauth2.rar.common.util; +package org.wso2.carbon.identity.oauth2.rar.util; /** * Stores constants related to OAuth2 Rich Authorization Requests. diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java b/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java deleted file mode 100644 index 34af49529c6..00000000000 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.carbon.identity.oauth2.rar.util; - -import org.apache.commons.lang.StringUtils; -import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; -import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; -import org.wso2.carbon.identity.oauth2.rar.common.util.AuthorizationDetailsConstants; - -/** - * Utility class for handling and validating authorization details in OAuth2 requests. - */ -public class AuthorizationDetailsUtils { - - /** - * Determines if the given {@link OAuth2Parameters} object contains - * {@link org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails AuthorizationDetails}. - * - * @param oAuth2Parameters The requested OAuth2 parameters to check. - * @return {@code true} if the OAuth2 parameters contain non-empty authorization details array, - * {@code false} otherwise. - */ - public static boolean isRichAuthorizationRequest(final OAuth2Parameters oAuth2Parameters) { - - return oAuth2Parameters.getAuthorizationDetails() != null && - !oAuth2Parameters.getAuthorizationDetails().getDetails().isEmpty(); - } - - /** - * Determines if the given {@link OAuthAuthzRequest} object contains {@code authorization_details}. - * - * @param oauthRequest The OAuth Authorization Request to check. - * @return {@code true} if the OAuth authorization request contains a non-blank authorization details parameter, - * {@code false} otherwise. - */ - public static boolean isRichAuthorizationRequest(final OAuthAuthzRequest oauthRequest) { - - return StringUtils.isNotBlank(oauthRequest.getParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS)); - } -} diff --git a/components/org.wso2.carbon.identity.oauth/pom.xml b/components/org.wso2.carbon.identity.oauth/pom.xml index 92f3e93171e..99450b2ae1c 100644 --- a/components/org.wso2.carbon.identity.oauth/pom.xml +++ b/components/org.wso2.carbon.identity.oauth/pom.xml @@ -260,7 +260,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 - org.wso2.carbon.identity.oauth.rar.common + org.wso2.carbon.identity.oauth.rar provided diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/IdentityOAuth2AuthorizationDetailsService.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/IdentityOAuth2AuthorizationDetailsService.java deleted file mode 100644 index 21ac2f67fd4..00000000000 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/IdentityOAuth2AuthorizationDetailsService.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.wso2.carbon.identity.oauth2; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; -import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory; -import org.wso2.carbon.identity.oauth2.model.AuthzCodeDO; -import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; -import org.wso2.carbon.identity.oauth2.rar.common.dao.AuthorizationDetailsDAO; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; -import org.wso2.carbon.identity.oauth2.util.OAuth2Util; - -import java.sql.SQLException; -import java.util.Objects; - -/** - * IdentityOAuth2AuthorizationDetailsService is responsible for managing and handling OAuth2 authorization details, - * specifically in the context of rich authorization requests. - *

- * This class integrates with the {@link AuthorizationDetailsDAO} to persist these details in the underlying data store. - * It also provides utility methods to check if a request contains rich authorization details. - *

- * - * @see AuthorizationDetailsDAO - * @see AuthorizationDetails - */ -public class IdentityOAuth2AuthorizationDetailsService { - - private static final Log log = LogFactory.getLog(IdentityOAuth2AuthorizationDetailsService.class); - protected final AuthorizationDetailsDAO authorizationDetailsDAO; - - /** - * Default constructor that initializes the service with the default {@link AuthorizationDetailsDAO}. - *

- * This constructor uses the default DAO provided by the {@link OAuthTokenPersistenceFactory} - * to handle the persistence of authorization details. - *

- */ - public IdentityOAuth2AuthorizationDetailsService() { - - this(OAuthTokenPersistenceFactory.getInstance().getAuthorizationDetailsDAO()); - } - - /** - * Constructor that initializes the service with a given {@link AuthorizationDetailsDAO}. - * - * @param authorizationDetailsDAO The {@link AuthorizationDetailsDAO} instance to be used for - * handling authorization details persistence. Must not be {@code null}. - */ - public IdentityOAuth2AuthorizationDetailsService(final AuthorizationDetailsDAO authorizationDetailsDAO) { - - this.authorizationDetailsDAO = Objects - .requireNonNull(authorizationDetailsDAO, "AuthorizationDetailsDAO must not be null"); - } - - /** - * Determines if the given {@link OAuthAuthzReqMessageContext} object contains {@link AuthorizationDetails}. - * - * @param oAuthAuthzReqMessageContext The requested OAuthAuthzReqMessageContext to check. - * @return {@code true} if the OAuthAuthzReqMessageContext contains non-empty authorization details set, - * {@code false} otherwise. - */ - public static boolean isRichAuthorizationRequest(final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) { - - return isRichAuthorizationRequest(oAuthAuthzReqMessageContext.getAuthorizationDetails()); - } - - /** - * Determines if the request is a rich authorization request using provided {@link AuthorizationDetails} object. - *

- * This method checks if the specified {@link AuthorizationDetails} instance is not {@code null} - * and has a non-empty details set. - * - * @param authorizationDetails The {@link AuthorizationDetails} to check. - * @return {@code true} if the {@link AuthorizationDetails} is not {@code null} and has a non-empty details set, - * {@code false} otherwise. - */ - public static boolean isRichAuthorizationRequest(final AuthorizationDetails authorizationDetails) { - - return authorizationDetails != null && !authorizationDetails.getDetails().isEmpty(); - } - - /** - * Determines if the given {@link OAuth2Parameters} object contains {@link AuthorizationDetails}. - * - * @param oAuth2Parameters The requested OAuth2Parameters to check. - * @return {@code true} if the OAuth2Parameters contains non-empty authorization details set, - * {@code false} otherwise. - */ - public static boolean isRichAuthorizationRequest(final OAuth2Parameters oAuth2Parameters) { - - return isRichAuthorizationRequest(oAuth2Parameters.getAuthorizationDetails()); - } - - /** - * Stores the OAuth2 code authorization details if the request is a rich authorization request. - *

- * This method checks whether the given {@link OAuthAuthzReqMessageContext} contains {@link AuthorizationDetails}. - * If it does, it retrieves the tenant ID from the request context and stores the authorization - * details using the {@link AuthorizationDetailsDAO}. - *

- * - * @param authzCodeDO The {@link AuthzCodeDO} object containing the authorization code details. - * @param oAuthAuthzReqMessageContext The {@link OAuthAuthzReqMessageContext} containing the request context. - * @throws IdentityOAuth2Exception If an error occurs while storing the authorization details. - */ - public void storeOAuth2CodeAuthorizationDetails(final AuthzCodeDO authzCodeDO, - final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) - throws IdentityOAuth2Exception { - - if (!isRichAuthorizationRequest(oAuthAuthzReqMessageContext)) { - log.debug("Request is not a rich authorization request. Skipping storage of code authorization details."); - return; - } - - try { - final int tenantID = OAuth2Util.getTenantId( - oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getTenantDomain()); - // Storing the authorization details. - this.authorizationDetailsDAO.addOAuth2CodeAuthorizationDetails( - authzCodeDO.getAuthzCodeId(), - oAuthAuthzReqMessageContext.getAuthorizationDetails(), - tenantID); - - if (log.isDebugEnabled()) { - log.debug("Successfully stored OAuth2 Code authorization details for code Id: " + - authzCodeDO.getAuthzCodeId()); - } - } catch (SQLException e) { - log.error("Error occurred while storing OAuth2 Code authorization details. Caused by, ", e); - throw new IdentityOAuth2Exception("Error occurred while storing authorization details", e); - } - } -} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java index a654475ddb4..705f4a58a46 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java @@ -40,7 +40,10 @@ import org.wso2.carbon.identity.oauth2.authz.handlers.ResponseTypeHandler; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeRespDTO; +import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; import org.wso2.carbon.identity.oauth2.util.AuthzUtil; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; @@ -255,6 +258,7 @@ public OAuth2AuthorizeRespDTO handleAuthorization(OAuthAuthzReqMessageContext au // set the authorization request context to be used by downstream handlers. This is introduced as a fix for // IDENTITY-4111 OAuth2Util.setAuthzRequestContext(authzReqMsgCtx); + this.setUserConsentedAuthorizationDetails(authzReqMsgCtx); authorizeRespDTO = authzHandler.issue(authzReqMsgCtx); } finally { // clears authorization request context @@ -723,4 +727,22 @@ public OAuthErrorDTO handleAuthenticationFailure(OAuth2Parameters oAuth2Paramete ResponseTypeHandler responseTypeHandler = responseHandlers.get(oAuth2Parameters.getResponseType()); return responseTypeHandler.handleAuthenticationFailure(oAuth2Parameters); } + + private void setUserConsentedAuthorizationDetails(final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) + throws IdentityOAuth2Exception { + + OAuth2AuthorizeReqDTO oAuth2AuthorizeReqDTO = oAuthAuthzReqMessageContext.getAuthorizationReqDTO(); + if (!AuthorizationDetailsUtils.isRichAuthorizationRequest(oAuth2AuthorizeReqDTO)) { + return; + } + + final AuthorizationDetails authorizationDetails = OAuth2ServiceComponentHolder.getInstance() + .getAuthorizationDetailsService() + .getUserConsentedAuthorizationDetails( + oAuth2AuthorizeReqDTO.getUser(), + oAuth2AuthorizeReqDTO.getConsumerKey(), + IdentityTenantUtil.getTenantId(oAuth2AuthorizeReqDTO.getTenantDomain()) + ); + oAuthAuthzReqMessageContext.setAuthorizationDetails(authorizationDetails); + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/OAuthAuthzReqMessageContext.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/OAuthAuthzReqMessageContext.java index 2df67714c2e..8b7cc822f43 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/OAuthAuthzReqMessageContext.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/OAuthAuthzReqMessageContext.java @@ -19,7 +19,7 @@ package org.wso2.carbon.identity.oauth2.authz; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import java.io.Serializable; import java.util.Properties; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/AbstractResponseTypeHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/AbstractResponseTypeHandler.java index 287d2d8b57c..4be8704f12e 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/AbstractResponseTypeHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/AbstractResponseTypeHandler.java @@ -32,7 +32,6 @@ import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; -import org.wso2.carbon.identity.oauth2.IdentityOAuth2AuthorizationDetailsService; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; @@ -60,7 +59,6 @@ public abstract class AbstractResponseTypeHandler implements ResponseTypeHandler protected boolean cacheEnabled; protected OAuthCache oauthCache; private OAuthCallbackManager callbackManager; - protected IdentityOAuth2AuthorizationDetailsService identityOAuth2AuthorizationDetailsService; @Override public void init() throws IdentityOAuth2Exception { @@ -70,7 +68,6 @@ public void init() throws IdentityOAuth2Exception { if (cacheEnabled) { oauthCache = OAuthCache.getInstance(); } - this.identityOAuth2AuthorizationDetailsService = new IdentityOAuth2AuthorizationDetailsService(); } @Override @@ -228,6 +225,7 @@ public OAuth2AuthorizeRespDTO initResponse(OAuthAuthzReqMessageContext oauthAuth OAuth2AuthorizeReqDTO authorizationReqDTO = oauthAuthzMsgCtx.getAuthorizationReqDTO(); respDTO.setCallbackURI(authorizationReqDTO.getCallbackUrl()); respDTO.setScope(oauthAuthzMsgCtx.getApprovedScope()); + respDTO.setAuthorizationDetails(oauthAuthzMsgCtx.getAuthorizationDetails()); return respDTO; } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/CodeResponseTypeHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/CodeResponseTypeHandler.java index 489226250f3..adb9dfefd11 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/CodeResponseTypeHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/CodeResponseTypeHandler.java @@ -47,8 +47,6 @@ public OAuth2AuthorizeRespDTO issue(OAuthAuthzReqMessageContext oauthAuthzMsgCtx AuthzCodeDO authorizationCode = ResponseTypeHandlerUtil.generateAuthorizationCode(oauthAuthzMsgCtx, cacheEnabled); - super.identityOAuth2AuthorizationDetailsService - .storeOAuth2CodeAuthorizationDetails(authorizationCode, oauthAuthzMsgCtx); String sessionDataKey = oauthAuthzMsgCtx.getAuthorizationReqDTO().getSessionDataKey(); if (log.isDebugEnabled()) { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java index 188b70cc8a5..dc85dbcbd14 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java @@ -610,6 +610,9 @@ private static AccessTokenDO generateNewAccessToken(OAuthAuthzReqMessageContext // Persist the access token in database persistAccessTokenInDB(oauthAuthzMsgCtx, existingTokenBean, newTokenBean); deactivateCurrentAuthorizationCode(newTokenBean.getAuthorizationCode(), newTokenBean.getTokenId()); + // Persist access token authorization details in database + OAuth2ServiceComponentHolder.getInstance().getAuthorizationDetailsService() + .storeAccessTokenAuthorizationDetails(newTokenBean, oauthAuthzMsgCtx); //update cache with newly added token if (isHashDisabled && cacheEnabled) { addTokenToCache(getOAuthCacheKey(consumerKey, scope, authorizedUserId, authenticatedIDP), diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/OAuthTokenPersistenceFactory.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/OAuthTokenPersistenceFactory.java index 68aa46f7d82..3737e523114 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/OAuthTokenPersistenceFactory.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/OAuthTokenPersistenceFactory.java @@ -21,8 +21,8 @@ package org.wso2.carbon.identity.oauth2.dao; import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; -import org.wso2.carbon.identity.oauth2.rar.common.dao.AuthorizationDetailsDAO; -import org.wso2.carbon.identity.oauth2.rar.common.dao.AuthorizationDetailsDAOImpl; +import org.wso2.carbon.identity.oauth2.rar.dao.AuthorizationDetailsDAO; +import org.wso2.carbon.identity.oauth2.rar.dao.AuthorizationDetailsDAOImpl; import org.wso2.carbon.identity.openidconnect.dao.CacheBackedScopeClaimMappingDAOImpl; import org.wso2.carbon.identity.openidconnect.dao.RequestObjectDAO; import org.wso2.carbon.identity.openidconnect.dao.RequestObjectDAOImpl; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AccessTokenReqDTO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AccessTokenReqDTO.java index 193a61fd304..d6f98c73b37 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AccessTokenReqDTO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AccessTokenReqDTO.java @@ -22,6 +22,7 @@ import org.wso2.carbon.identity.oauth2.model.AccessTokenExtendedAttributes; import org.wso2.carbon.identity.oauth2.model.HttpRequestHeader; import org.wso2.carbon.identity.oauth2.model.RequestParameter; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import java.util.ArrayList; import java.util.Collections; @@ -62,6 +63,8 @@ public class OAuth2AccessTokenReqDTO { private AccessTokenExtendedAttributes accessTokenExtendedAttributes; + private AuthorizationDetails authorizationDetails; + public String getClientId() { return clientId; } @@ -252,4 +255,26 @@ public HttpServletResponseWrapper getHttpServletResponseWrapper() { public void setHttpServletResponseWrapper(HttpServletResponseWrapper httpServletResponseWrapper) { this.httpServletResponseWrapper = httpServletResponseWrapper; } + + /** + * Retrieves the authorization details requested in the token request. + * + * @return the {@link AuthorizationDetails} instance representing the rich authorization requests. + * If no authorization details are requested by the client, the method will return {@code null}. + */ + public AuthorizationDetails getAuthorizationDetails() { + + return this.authorizationDetails; + } + + /** + * Sets the authorization details. + * This method updates the authorization details with the provided {@link AuthorizationDetails} instance. + * + * @param authorizationDetails the {@link AuthorizationDetails} to set. + */ + public void setAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + this.authorizationDetails = authorizationDetails; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeReqDTO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeReqDTO.java index e3db5c13814..438810665a5 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeReqDTO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeReqDTO.java @@ -20,6 +20,7 @@ import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.oauth2.model.HttpRequestHeader; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import org.wso2.carbon.identity.openidconnect.model.RequestObject; import java.util.LinkedHashSet; @@ -61,6 +62,7 @@ public class OAuth2AuthorizeReqDTO { private boolean isRequestObjectFlow; private String state; private String requestedSubjectId; + private AuthorizationDetails authorizationDetails; public String getRequestedSubjectId() { @@ -303,4 +305,26 @@ public void setHttpServletRequestWrapper(HttpServletRequestWrapper httpServletRe this.httpServletRequestWrapper = httpServletRequestWrapper; } + + /** + * Retrieves the authorization details requested by the client. + * + * @return the {@link AuthorizationDetails} instance representing the {@code authorization_details} requested + * by the client. If no authorization details are available, it will return {@code null}. + */ + public AuthorizationDetails getAuthorizationDetails() { + + return this.authorizationDetails; + } + + /** + * Sets the authorization details requested by the client. + * This method updates the authorization details with the provided {@link AuthorizationDetails} instance. + * + * @param authorizationDetails the {@link AuthorizationDetails} to set. + */ + public void setAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + this.authorizationDetails = authorizationDetails; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeRespDTO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeRespDTO.java index 6cb706807c4..8c248bfc4b8 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeRespDTO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dto/OAuth2AuthorizeRespDTO.java @@ -18,6 +18,8 @@ package org.wso2.carbon.identity.oauth2.dto; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; + import java.util.Properties; /** @@ -39,6 +41,7 @@ public class OAuth2AuthorizeRespDTO { private String pkceCodeChallenge; private String pkceCodeChallengeMethod; private String oidcSessionId; + private AuthorizationDetails authorizationDetails; private String subjectToken; public String getAuthorizationCode() { @@ -200,4 +203,26 @@ public void setSubjectToken(String subjectToken) { this.subjectToken = subjectToken; } + + /** + * Retrieves the validated authorization details to be included in the authorize response. + * + * @return the {@link AuthorizationDetails} instance representing the validated authorization information. + * If no authorization details are available, it will return {@code null}. + */ + public AuthorizationDetails getAuthorizationDetails() { + + return this.authorizationDetails; + } + + /** + * Sets the authorization details. + * This method sets {@link AuthorizationDetails} that can potentially be included in the authorization response. + * + * @param authorizationDetails the {@link AuthorizationDetails} to set. + */ + public void setAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + this.authorizationDetails = authorizationDetails; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java index 2c4e41025b4..c1f54cc5447 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java @@ -56,6 +56,7 @@ import org.wso2.carbon.identity.oauth.tokenprocessor.OAuth2RevocationProcessor; import org.wso2.carbon.identity.oauth.tokenprocessor.RefreshTokenGrantProcessor; import org.wso2.carbon.identity.oauth.tokenprocessor.TokenProvider; +import org.wso2.carbon.identity.oauth2.IntrospectionDataProvider; import org.wso2.carbon.identity.oauth2.OAuth2ScopeService; import org.wso2.carbon.identity.oauth2.OAuth2Service; import org.wso2.carbon.identity.oauth2.OAuth2TokenValidationService; @@ -82,6 +83,9 @@ import org.wso2.carbon.identity.oauth2.keyidprovider.KeyIDProvider; import org.wso2.carbon.identity.oauth2.listener.TenantCreationEventListener; import org.wso2.carbon.identity.oauth2.model.ResourceAccessControlKey; +import org.wso2.carbon.identity.oauth2.rar.token.AccessTokenResponseRARHandler; +import org.wso2.carbon.identity.oauth2.rar.token.IntrospectionRARDataProvider; +import org.wso2.carbon.identity.oauth2.rar.token.JWTAccessTokenRARClaimProvider; import org.wso2.carbon.identity.oauth2.scopeservice.APIResourceBasedScopeMetadataService; import org.wso2.carbon.identity.oauth2.scopeservice.ScopeMetadataService; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinder; @@ -401,6 +405,11 @@ protected void activate(ComponentContext context) { bundleContext.registerService(ImpersonationConfigMgtService.class, new ImpersonationConfigMgtServiceImpl(), null); + bundleContext.registerService(AccessTokenResponseHandler.class, new AccessTokenResponseRARHandler(), null); + bundleContext.registerService(JWTAccessTokenClaimProvider.class, + new JWTAccessTokenRARClaimProvider(), null); + bundleContext.registerService(IntrospectionDataProvider.class, new IntrospectionRARDataProvider(), null); + // Note : DO NOT add any activation related code below this point, // to make sure the server doesn't start up if any activation failures occur } catch (Throwable e) { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java index c22e3f9ffdc..28845ea0ce7 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java @@ -44,6 +44,9 @@ import org.wso2.carbon.identity.oauth2.impersonation.services.ImpersonationMgtService; import org.wso2.carbon.identity.oauth2.impersonation.validators.ImpersonationValidator; import org.wso2.carbon.identity.oauth2.keyidprovider.KeyIDProvider; +import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsService; +import org.wso2.carbon.identity.oauth2.rar.validator.AuthorizationDetailsValidator; +import org.wso2.carbon.identity.oauth2.rar.validator.DefaultAuthorizationDetailsValidator; import org.wso2.carbon.identity.oauth2.responsemode.provider.ResponseModeProvider; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinder; import org.wso2.carbon.identity.oauth2.token.handlers.claims.JWTAccessTokenClaimProvider; @@ -122,7 +125,8 @@ public class OAuth2ServiceComponentHolder { private List impersonationValidators = new ArrayList<>(); private ConfigurationManager configurationManager; - + private AuthorizationDetailsService authorizationDetailsService; + private AuthorizationDetailsValidator authorizationDetailsValidator; private OAuth2ServiceComponentHolder() { @@ -889,4 +893,30 @@ public void setConfigurationManager(ConfigurationManager configurationManager) { this.configurationManager = configurationManager; } + + /** + * Get an {@link AuthorizationDetailsService} instance. + * + * @return A {@link AuthorizationDetailsService} singleton instance. + */ + public AuthorizationDetailsService getAuthorizationDetailsService() { + + if (this.authorizationDetailsService == null) { + this.authorizationDetailsService = new AuthorizationDetailsService(); + } + return this.authorizationDetailsService; + } + + /** + * Get an {@link AuthorizationDetailsValidator} instance. + * + * @return A {@link AuthorizationDetailsValidator} singleton instance. + */ + public AuthorizationDetailsValidator getAuthorizationDetailsValidator() { + + if (this.authorizationDetailsValidator == null) { + this.authorizationDetailsValidator = new DefaultAuthorizationDetailsValidator(); + } + return this.authorizationDetailsValidator; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/OAuth2Parameters.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/OAuth2Parameters.java index 868a5ff1640..520e9a4f31e 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/OAuth2Parameters.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/OAuth2Parameters.java @@ -18,7 +18,7 @@ package org.wso2.carbon.identity.oauth2.model; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import java.io.Serializable; import java.util.LinkedHashSet; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java new file mode 100644 index 00000000000..1170f330705 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/AuthorizationDetailsService.java @@ -0,0 +1,558 @@ +package org.wso2.carbon.identity.oauth2.rar; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory; +import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; +import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; +import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProcessor; +import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProviderFactory; +import org.wso2.carbon.identity.oauth2.rar.dao.AuthorizationDetailsDAO; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsConsentDTO; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsTokenDTO; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils.getAuthorizationDetailsConsentDTOs; +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils.isRichAuthorizationRequest; + +/** + * AuthorizationDetailsService is responsible for managing and handling OAuth2 authorization details, + * specifically in the context of rich authorization requests. + *

+ * This class integrates with the {@link AuthorizationDetailsDAO} to persist these details in the underlying data store. + * It also provides utility methods to check if a request contains rich authorization details. + *

+ * + * @see AuthorizationDetailsDAO + * @see AuthorizationDetails + */ +public class AuthorizationDetailsService { + + private static final Log log = LogFactory.getLog(AuthorizationDetailsService.class); + private final AuthorizationDetailsDAO authorizationDetailsDAO; + private final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory; + + /** + * Default constructor that initializes the service with the default {@link AuthorizationDetailsDAO} and + * {@link AuthorizationDetailsProviderFactory}. + *

+ * This constructor uses the default DAO provided by the {@link OAuthTokenPersistenceFactory} + * to handle the persistence of authorization details. + *

+ */ + public AuthorizationDetailsService() { + + this( + AuthorizationDetailsProviderFactory.getInstance(), + OAuthTokenPersistenceFactory.getInstance().getAuthorizationDetailsDAO() + ); + } + + /** + * Constructor that initializes the service with a given {@link AuthorizationDetailsDAO}. + * + * @param authorizationDetailsProviderFactory Factory instance for providing authorization details. + * @param authorizationDetailsDAO The {@link AuthorizationDetailsDAO} instance to be used for + * handling authorization details persistence. Must not be {@code null}. + */ + public AuthorizationDetailsService(final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory, + final AuthorizationDetailsDAO authorizationDetailsDAO) { + + this.authorizationDetailsDAO = Objects + .requireNonNull(authorizationDetailsDAO, "AuthorizationDetailsDAO must not be null"); + this.authorizationDetailsProviderFactory = Objects.requireNonNull(authorizationDetailsProviderFactory, + "AuthorizationDetailsProviderFactory must not be null"); + } + + /** + * Stores user-consented authorization details. + * + * @param authenticatedUser The authenticated user. + * @param clientId The client ID. + * @param oAuth2Parameters Requested OAuth2 parameters. + * @param userConsentedAuthorizationDetails User consented authorization details. + * @throws OAuthSystemException if an error occurs while storing user consented authorization details. + */ + public void storeUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, final String clientId, + final OAuth2Parameters oAuth2Parameters, + final AuthorizationDetails userConsentedAuthorizationDetails) + throws OAuthSystemException { + + if (!isRichAuthorizationRequest(oAuth2Parameters)) { + log.debug("Request is not a rich authorization request. Skipping storage of authorization details."); + return; + } + + try { + final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); + final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); + + if (consentId.isPresent()) { + final AuthorizationDetails trimmedAuthorizationDetails = AuthorizationDetailsUtils + .getTrimmedAuthorizationDetails(userConsentedAuthorizationDetails); + + final List authorizationDetailsConsentDTOs = + getAuthorizationDetailsConsentDTOs(consentId.get(), trimmedAuthorizationDetails, tenantId); + + this.authorizationDetailsDAO.addUserConsentedAuthorizationDetails(authorizationDetailsConsentDTOs); + if (log.isDebugEnabled()) { + log.debug("User consented authorization details stored successfully. consentId: " + + consentId.get()); + } + } + } catch (SQLException | IdentityOAuth2Exception e) { + log.error("Error occurred while storing user consented authorization details. Caused by, ", e); + throw new OAuthSystemException("Error occurred while storing authorization details", e); + } + } + + public void updateUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, + final String clientId, final OAuth2Parameters oAuth2Parameters, + final AuthorizationDetails userConsentedAuthorizationDetails) + throws OAuthSystemException { + + if (!isRichAuthorizationRequest(oAuth2Parameters)) { + return; + } + + try { + final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); + final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); + + if (consentId.isPresent()) { + + final List authorizationDetailsConsentDTOs = + getAuthorizationDetailsConsentDTOs(consentId.get(), + userConsentedAuthorizationDetails, tenantId); + + this.authorizationDetailsDAO.updateUserConsentedAuthorizationDetails(authorizationDetailsConsentDTOs); + } + } catch (SQLException | IdentityOAuth2Exception e) { + log.error("Error occurred while updating user consented authorization details. Caused by, ", e); + throw new OAuthSystemException("Error occurred while updating authorization details", e); + } + } + + /** + * Deletes user-consented authorization details. + * + * @param authenticatedUser The authenticated user. + * @param clientId The client ID. + * @param oAuth2Parameters Requested OAuth2 parameters. + * @throws OAuthSystemException if an error occurs while deleting authorization details. + */ + public void deleteUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, + final String clientId, final OAuth2Parameters oAuth2Parameters) + throws OAuthSystemException { + + if (!isRichAuthorizationRequest(oAuth2Parameters)) { + log.debug("Request is not a rich authorization request. Skipping deletion of authorization details."); + return; + } + + try { + final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); + final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); + + if (consentId.isPresent()) { + + this.authorizationDetailsDAO.deleteUserConsentedAuthorizationDetails(consentId.get(), tenantId); + + if (log.isDebugEnabled()) { + log.debug("User consented authorization details deleted successfully. consentId: " + + consentId.get()); + } + } + } catch (SQLException | IdentityOAuth2Exception e) { + log.error("Error occurred while deleting user consented authorization details. Caused by, ", e); + throw new OAuthSystemException("Error occurred while storing authorization details", e); + } + } + + /** + * Replaces the user consented authorization details. + * + * @param authenticatedUser The authenticated user. + * @param clientId The client ID. + * @param oAuth2Parameters Requested OAuth2 parameters. + * @param userConsentedAuthorizationDetails User consented authorization details. + * @throws OAuthSystemException if an error occurs while storing or replacing authorization details. + */ + public void replaceUserConsentedAuthorizationDetails( + final AuthenticatedUser authenticatedUser, final String clientId, final OAuth2Parameters oAuth2Parameters, + final AuthorizationDetails userConsentedAuthorizationDetails) throws OAuthSystemException { + + this.deleteUserConsentedAuthorizationDetails(authenticatedUser, clientId, oAuth2Parameters); + this.storeUserConsentedAuthorizationDetails(authenticatedUser, clientId, oAuth2Parameters, + userConsentedAuthorizationDetails); + } + + /** + * Check if the user has already given consent to requested authorization details. + * + * @param authenticatedUser Authenticated user. + * @param oAuth2Parameters OAuth2 parameters. + * @return {@code true} if user has given consent to all the requested authorization details, + * {@code false} otherwise. + */ + public boolean isUserAlreadyConsentedForAuthorizationDetails(final AuthenticatedUser authenticatedUser, + final OAuth2Parameters oAuth2Parameters) + throws IdentityOAuth2Exception { + + if (!isRichAuthorizationRequest(oAuth2Parameters)) { + return true; + } + + return this.getConsentRequiredAuthorizationDetails(authenticatedUser, oAuth2Parameters).getDetails().isEmpty(); + } + + /** + * Retrieves the user consented authorization details for a given user, client, and tenant. + * + * @param authenticatedUser The authenticated user. + * @param clientId The client ID. + * @param tenantId The tenant ID. + * @return The user consented authorization details. + * @throws IdentityOAuth2Exception If an error occurs while retrieving the details. + */ + public AuthorizationDetails getUserConsentedAuthorizationDetails( + final AuthenticatedUser authenticatedUser, final String clientId, final int tenantId) + throws IdentityOAuth2Exception { + + try { + final Set consentedAuthorizationDetails = new HashSet<>(); + final Optional consentId = this.getConsentId(authenticatedUser, clientId, tenantId); + if (consentId.isPresent()) { + final Set consentedAuthorizationDetailsDTOs = + this.authorizationDetailsDAO.getUserConsentedAuthorizationDetails(consentId.get(), tenantId); + + consentedAuthorizationDetailsDTOs + .stream() + .filter(AuthorizationDetailsConsentDTO::isConsentActive) + .map(AuthorizationDetailsConsentDTO::getAuthorizationDetail) + .forEach(consentedAuthorizationDetails::add); + } + return new AuthorizationDetails(consentedAuthorizationDetails); + } catch (SQLException e) { + log.error("Error occurred while retrieving user consented authorization details. Caused by, ", e); + throw new IdentityOAuth2Exception("Unable to retrieve user consented authorization details", e); + } + } + + /** + * Retrieves the user consented authorization details for a given user and OAuth2 parameters. + * + * @param authenticatedUser The authenticated user. + * @param oAuth2Parameters The OAuth2 parameters. + * @return The user consented authorization details. + * @throws IdentityOAuth2Exception If an error occurs while retrieving the details. + */ + public AuthorizationDetails getUserConsentedAuthorizationDetails(final AuthenticatedUser authenticatedUser, + final OAuth2Parameters oAuth2Parameters) + throws IdentityOAuth2Exception { + + final int tenantId = OAuth2Util.getTenantId(oAuth2Parameters.getTenantDomain()); + return this.getUserConsentedAuthorizationDetails(authenticatedUser, oAuth2Parameters.getClientId(), tenantId); + } + + /** + * Retrieves the authorization details associated with a given access token. + * + * @param accessTokenId The access token ID. + * @param tenantId The tenant ID. + * @return The access token authorization details. + * @throws IdentityOAuth2Exception If an error occurs while retrieving the details. + */ + public AuthorizationDetails getAccessTokenAuthorizationDetails(final String accessTokenId, final int tenantId) + throws IdentityOAuth2Exception { + + try { + final Set authorizationDetailsTokenDTOs = + this.authorizationDetailsDAO.getAccessTokenAuthorizationDetails(accessTokenId, tenantId); + + final Set accessTokenAuthorizationDetails = new HashSet<>(); + authorizationDetailsTokenDTOs + .stream() + .map(AuthorizationDetailsTokenDTO::getAuthorizationDetail) + .forEach(accessTokenAuthorizationDetails::add); + + return new AuthorizationDetails(accessTokenAuthorizationDetails); + } catch (SQLException e) { + log.error("Error occurred while retrieving access token authorization details. Caused by, ", e); + throw new IdentityOAuth2Exception("Unable to retrieve access token authorization details", e); + } + } + + /** + * Stores the authorization details for a given access token and OAuth authorization request context. + * + * @param accessTokenDO The access token data object. + * @param oAuthAuthzReqMessageContext The OAuth authorization request message context. + * @throws IdentityOAuth2Exception If an error occurs while storing the details. + */ + public void storeAccessTokenAuthorizationDetails(final AccessTokenDO accessTokenDO, + final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) + throws IdentityOAuth2Exception { + + if (!isRichAuthorizationRequest(oAuthAuthzReqMessageContext)) { + log.debug("Request is not a rich authorization request. Skipping storage of token authorization details."); + return; + } + + this.storeAccessTokenAuthorizationDetails(accessTokenDO, oAuthAuthzReqMessageContext.getAuthorizationDetails()); + } + + /** + * Stores the authorization details for a given access token and OAuth token request context. + * + * @param accessTokenDO The access token data object. + * @param oAuthTokenReqMessageContext The OAuth token request message context. + * @throws IdentityOAuth2Exception If an error occurs while storing the details. + */ + public void storeAccessTokenAuthorizationDetails(final AccessTokenDO accessTokenDO, + final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) + throws IdentityOAuth2Exception { + + if (!isRichAuthorizationRequest(oAuthTokenReqMessageContext)) { + log.debug("Request is not a rich authorization request. Skipping storage of token authorization details."); + return; + } + + this.storeAccessTokenAuthorizationDetails(accessTokenDO, oAuthTokenReqMessageContext.getAuthorizationDetails()); + } + + /** + * Stores the authorization details for a given access token and authorization details. + * + * @param accessTokenDO The access token data object. + * @param authorizationDetails The authorization details. + * @throws IdentityOAuth2Exception If an error occurs while storing the details. + */ + public void storeAccessTokenAuthorizationDetails(final AccessTokenDO accessTokenDO, + final AuthorizationDetails authorizationDetails) + throws IdentityOAuth2Exception { + + try { + final AuthorizationDetails trimmedAuthorizationDetails = AuthorizationDetailsUtils + .getTrimmedAuthorizationDetails(authorizationDetails); + + final List authorizationDetailsTokenDTOs = AuthorizationDetailsUtils + .getAccessTokenAuthorizationDetailsDTOs(accessTokenDO, trimmedAuthorizationDetails); + + // Storing the authorization details. + this.authorizationDetailsDAO.addAccessTokenAuthorizationDetails(authorizationDetailsTokenDTOs); + + if (log.isDebugEnabled()) { + log.debug("Successfully stored access token authorization details for tokenId: " + + accessTokenDO.getTokenId()); + } + } catch (SQLException e) { + log.error("Error occurred while storing access token authorization details. Caused by, ", e); + throw new IdentityOAuth2Exception("Error occurred while storing access token authorization details", e); + } + } + + /** + * Stores or replaces the authorization details for a new access token and + * optionally deletes the old token's details. + * + * @param newAccessTokenDO The new access token data object. + * @param oldAccessTokenDO The old access token data object. + * @param oAuthTokenReqMessageContext The OAuth token request message context. + * @throws IdentityOAuth2Exception If an error occurs while storing or replacing the details. + */ + public void storeOrReplaceAccessTokenAuthorizationDetails( + final AccessTokenDO newAccessTokenDO, final AccessTokenDO oldAccessTokenDO, + final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) throws IdentityOAuth2Exception { + + if (!isRichAuthorizationRequest(oAuthTokenReqMessageContext)) { + log.debug("Request is not a rich authorization request. Skipping storage of token authorization details."); + return; + } + + if (Objects.nonNull(oldAccessTokenDO)) { + this.deleteAccessTokenAuthorizationDetails(oldAccessTokenDO.getTokenId(), oldAccessTokenDO.getTenantID()); + } + + this.storeAccessTokenAuthorizationDetails(newAccessTokenDO, + oAuthTokenReqMessageContext.getAuthorizationDetails()); + } + + /** + * Replaces the authorization details for an old access token with the details of a new access token. + * + * @param oldAccessTokenId The old access token ID. + * @param newAccessTokenDO The new access token data object. + * @param oAuthTokenReqMessageContext The OAuth token request message context. + * @throws IdentityOAuth2Exception If an error occurs while replacing the details. + */ + public void replaceAccessTokenAuthorizationDetails(final String oldAccessTokenId, + final AccessTokenDO newAccessTokenDO, + final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) + throws IdentityOAuth2Exception { + + if (!isRichAuthorizationRequest(oAuthTokenReqMessageContext)) { + log.debug("Request is not a rich authorization request. Skipping replacement of authorization details."); + return; + } + this.deleteAccessTokenAuthorizationDetails(oldAccessTokenId, newAccessTokenDO.getTenantID()); + this.storeAccessTokenAuthorizationDetails(newAccessTokenDO, oAuthTokenReqMessageContext); + } + + /** + * Deletes the authorization details associated with a given access token. + * + * @param accessTokenId The access token ID. + * @param tenantId The tenant ID. + * @throws IdentityOAuth2Exception If an error occurs while deleting the details. + */ + public void deleteAccessTokenAuthorizationDetails(final String accessTokenId, final int tenantId) + throws IdentityOAuth2Exception { + + try { + this.authorizationDetailsDAO.deleteAccessTokenAuthorizationDetails(accessTokenId, tenantId); + if (log.isDebugEnabled()) { + log.debug("Access token authorization details deleted successfully. accessTokenId: " + accessTokenId); + } + } catch (SQLException e) { + log.error("Error occurred while deleting access token authorization details. Caused by, ", e); + throw new IdentityOAuth2Exception("Error occurred while deleting access token authorization details", e); + } + } + + /** + * Retrieves the consent ID for the given user, client, and tenant. + * + * @param authenticatedUser The authenticated user. + * @param clientId The client ID. + * @param tenantId The tenant ID. + * @return An {@link Optional} containing the consent ID if present. + * @throws IdentityOAuth2Exception if an error occurs related to OAuth2 identity. + */ + private Optional getConsentId(final AuthenticatedUser authenticatedUser, final String clientId, + final int tenantId) + throws IdentityOAuth2Exception { + + final String userId = AuthorizationDetailsUtils.getIdFromAuthenticatedUser(authenticatedUser); + final String appId = AuthorizationDetailsUtils.getApplicationResourceIdFromClientId(clientId); + + return this.getConsentIdByUserIdAndAppId(userId, appId, tenantId); + } + + /** + * Retrieves the consent ID by user ID and application ID. + * + * @param userId The user ID. + * @param appId The application ID. + * @param tenantId The tenant ID. + * @return An {@link Optional} containing the consent ID if present. + * @throws IdentityOAuth2Exception if an error occurs while retrieving the consent ID. + */ + public Optional getConsentIdByUserIdAndAppId(final String userId, final String appId, final int tenantId) + throws IdentityOAuth2Exception { + + try { + return Optional + .ofNullable(this.authorizationDetailsDAO.getConsentIdByUserIdAndAppId(userId, appId, tenantId)); + } catch (SQLException e) { + log.error(String.format("Error occurred while retrieving user consent by " + + "userId: %s and appId: %s. Caused by, ", userId, appId), e); + throw new IdentityOAuth2Exception("Error occurred while retrieving user consent", e); + } + } + + public AuthorizationDetails getConsentRequiredAuthorizationDetails(final AuthenticatedUser authenticatedUser, + final OAuth2Parameters oAuth2Parameters) + throws IdentityOAuth2Exception { + + if (!isRichAuthorizationRequest(oAuth2Parameters)) { + log.debug("Request is not a rich authorization request. Skipping the authorization details retrieval."); + return new AuthorizationDetails(); + } + + final Map> consentedAuthorizationDetailsByType = + getUserConsentedAuthorizationDetailsByType(authenticatedUser, oAuth2Parameters); + + final Set consentRequiredAuthorizationDetails = new HashSet<>(); + oAuth2Parameters.getAuthorizationDetails().stream() + .filter(requestedDetail -> + !this.isUserConsentedAuthorizationDetail(consentedAuthorizationDetailsByType, requestedDetail)) + .forEach(consentRequiredAuthorizationDetails::add); + + return new AuthorizationDetails(consentRequiredAuthorizationDetails); + } + + private Map> getUserConsentedAuthorizationDetailsByType( + final AuthenticatedUser authenticatedUser, final OAuth2Parameters oAuth2Parameters) + throws IdentityOAuth2Exception { + + return this.getUserConsentedAuthorizationDetails(authenticatedUser, oAuth2Parameters) + .stream() + .collect(Collectors.groupingBy(AuthorizationDetail::getType, + Collectors.mapping(Function.identity(), Collectors.toSet()))); + } + + /** + * Checks if the user has already consented to the requested authorization detail. + * + *

This method validates if the requested authorization detail is part of the consented authorization details. + * It uses the appropriate provider to compare the requested detail with the existing consented details.

+ * + * @param consentedAuthorizationDetailsByType a map of consented authorization details grouped by type + * @param requestedAuthorizationDetail the authorization detail to be checked + * @return {@code true} if the user has consented to the requested authorization detail, {@code false} otherwise + */ + public boolean isUserConsentedAuthorizationDetail( + final Map> consentedAuthorizationDetailsByType, + final AuthorizationDetail requestedAuthorizationDetail) { + + if (!consentedAuthorizationDetailsByType.containsKey(requestedAuthorizationDetail.getType())) { + log.debug("Request is not a rich authorization request. Skipping the validation."); + return false; + } + + final Optional provider = this.authorizationDetailsProviderFactory + .getProviderByType(requestedAuthorizationDetail.getType()); + if (provider.isPresent()) { + + if (log.isDebugEnabled()) { + log.debug("Validating equality of requested and existing authorization details " + + "using provider class: " + provider.get().getClass().getSimpleName()); + } + + final AuthorizationDetails existingAuthorizationDetails = new AuthorizationDetails( + consentedAuthorizationDetailsByType.get(requestedAuthorizationDetail.getType())); + boolean isEqualOrSubset = provider.get() + .isEqualOrSubset(requestedAuthorizationDetail, existingAuthorizationDetails); + + if (log.isDebugEnabled() && isEqualOrSubset) { + log.debug("User has already consented for the requested authorization details type: " + + requestedAuthorizationDetail.getType()); + } + return isEqualOrSubset; + } + if (log.isDebugEnabled()) { + log.debug(String.format("Ignores unsupported authorization details type: %s", + requestedAuthorizationDetail.getType())); + } + return true; + } +} diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProvider.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProcessor.java similarity index 75% rename from components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProvider.java rename to components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProcessor.java index aa05dec6250..44b68875a48 100644 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProvider.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProcessor.java @@ -18,14 +18,15 @@ package org.wso2.carbon.identity.oauth2.rar.core; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetail; -import org.wso2.carbon.identity.oauth2.rar.common.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ServerException; import org.wso2.carbon.identity.oauth2.rar.exception.AuthorizationDetailsProcessingException; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetailsContext; import org.wso2.carbon.identity.oauth2.rar.model.ValidationResult; /** - * The {@code AuthorizationDetailsProvider} interface defines a contract for implementing + * The {@code AuthorizationDetailsProcessor} interface defines a contract for implementing * different types of authorization detail providers in a Service Provider Interface (SPI) setup. *

* Implementing classes are expected to provide mechanisms to validate, enrich, and identify @@ -34,7 +35,7 @@ * * @see Java SPI */ -public interface AuthorizationDetailsProvider { +public interface AuthorizationDetailsProcessor { /** * Validates the provided authorization details context when a new Rich Authorization Request is received. @@ -48,13 +49,15 @@ public interface AuthorizationDetailsProvider { * @return a {@code ValidationResult} indicating the outcome of the validation process. Returns a valid result * if the authorization details are correct and meet the criteria, otherwise returns an invalid result with an * appropriate error message. - * @throws AuthorizationDetailsProcessingException if the validation fails and the authorization flow needs - * to be interrupted. + * @throws AuthorizationDetailsProcessingException if the validation fails due to a request error and the + * authorization flow needs to be interrupted. + * @throws IdentityOAuth2ServerException if the validation fails due to a server error and the + * authorization flow needs to be interrupted. * @see AuthorizationDetailsContext * @see ValidationResult */ ValidationResult validate(AuthorizationDetailsContext authorizationDetailsContext) - throws AuthorizationDetailsProcessingException; + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException; /** * Retrieves the type of authorization details handled by this provider. @@ -69,7 +72,18 @@ ValidationResult validate(AuthorizationDetailsContext authorizationDetailsContex */ String getType(); - // The existing authorization detail that was previously accepted by the resource owner. + /** + * Checks if the requested authorization detail is equal to or a subset of the existing authorization details. + * + *

This method verifies if the provided {@code requestedAuthorizationDetail} is either exactly the same as or + * a subset of the {@code existingAuthorizationDetails} that have been previously accepted by the resource owner. + * + * @param requestedAuthorizationDetail The {@link AuthorizationDetail} being requested by the client. + * @param existingAuthorizationDetails The set of {@link AuthorizationDetail} that have been previously accepted + * by the resource owner. + * @return {@code true} if the requested authorization detail is equal to or a subset of the existing + * authorization details, {@code false} otherwise. + */ boolean isEqualOrSubset(AuthorizationDetail requestedAuthorizationDetail, AuthorizationDetails existingAuthorizationDetails); diff --git a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java similarity index 75% rename from components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java rename to components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java index 123e3cc3dd2..64ca86d097f 100644 --- a/components/org.wso2.carbon.identity.oauth.rar/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/core/AuthorizationDetailsProviderFactory.java @@ -18,8 +18,6 @@ package org.wso2.carbon.identity.oauth2.rar.core; -import org.wso2.carbon.identity.oauth2.rar.internal.AuthorizationDetailsDataHolder; - import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -27,11 +25,12 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** - * A factory class to manage and provide instances of {@link AuthorizationDetailsProvider} Service Provider Interface. + * A factory class to manage and provide instances of {@link AuthorizationDetailsProcessor} Service Provider Interface. * This class follows the Singleton pattern to ensure only one instance is created. - * It uses {@link ServiceLoader} to dynamically load and manage {@link AuthorizationDetailsProvider} implementations. + * It uses {@link ServiceLoader} to dynamically load and manage {@link AuthorizationDetailsProcessor} implementations. *

Example usage: *

 {@code
  * // Get a specific provider by type
@@ -43,15 +42,14 @@
  *     );
  * } 

* - * @see AuthorizationDetailsProvider AuthorizationDetailsService + * @see AuthorizationDetailsProcessor AuthorizationDetailsService * @see * Request Parameter "authorization_details" - * @since 7.0.26.9 */ public class AuthorizationDetailsProviderFactory { private static volatile AuthorizationDetailsProviderFactory instance; - private final Map supportedAuthorizationDetailsTypes; + private final Map supportedAuthorizationDetailsTypes; /** * Private constructor to initialize the factory. @@ -65,16 +63,17 @@ private AuthorizationDetailsProviderFactory() { } /** - * Loads supported authorization details types from the provided {@link ServiceLoader}. + * Loads supported authorization details Processors from the provided {@link ServiceLoader}. * - * @return Map of authorization details types with their corresponding services. + * @return Map of authorization details types with their corresponding SPI services. */ - private Map loadSupportedAuthorizationDetailsTypes() { + private Map loadSupportedAuthorizationDetailsTypes() { + + final ServiceLoader serviceLoader = ServiceLoader + .load(AuthorizationDetailsProcessor.class, this.getClass().getClassLoader()); - return AuthorizationDetailsDataHolder.getInstance() - .getAuthorizationDetailsProviders() - .stream() - .collect(Collectors.toMap(AuthorizationDetailsProvider::getType, Function.identity())); + return StreamSupport.stream(serviceLoader.spliterator(), false) + .collect(Collectors.toMap(AuthorizationDetailsProcessor::getType, Function.identity())); } /** @@ -95,13 +94,13 @@ public static AuthorizationDetailsProviderFactory getInstance() { } /** - * Returns the {@link AuthorizationDetailsProvider} provider for the given type. + * Returns the {@link AuthorizationDetailsProcessor} provider for the given type. * * @param type A supported authorization details type. - * @return {@link Optional} containing the {@link AuthorizationDetailsProvider} if present, otherwise empty. - * @see AuthorizationDetailsProvider#getType() getAuthorizationDetailsType + * @return {@link Optional} containing the {@link AuthorizationDetailsProcessor} if present, otherwise empty. + * @see AuthorizationDetailsProcessor#getType() getAuthorizationDetailsType */ - public Optional getProviderByType(final String type) { + public Optional getProviderByType(final String type) { return Optional.ofNullable(this.supportedAuthorizationDetailsTypes.get(type)); } @@ -111,7 +110,7 @@ public Optional getProviderByType(final String typ * * @param type The type to check. * @return {@code true} if the type is supported, {@code false} otherwise. - * @see AuthorizationDetailsProvider AuthorizationDetailsService + * @see AuthorizationDetailsProcessor AuthorizationDetailsService */ public boolean isSupportedAuthorizationDetailsType(final String type) { @@ -121,7 +120,7 @@ public boolean isSupportedAuthorizationDetailsType(final String type) { /** * Returns a {@link Collections#unmodifiableSet} of all supported authorization details types. *

To be included as a supported authorization details type, there must be a custom implementation - * of the {@link AuthorizationDetailsProvider} Service Provider Interface (SPI) available in the classpath + * of the {@link AuthorizationDetailsProcessor} Service Provider Interface (SPI) available in the classpath * for the specified type.

* * @return A set of supported authorization details types. diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java new file mode 100644 index 00000000000..3ccc2784cc8 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/exception/AuthorizationDetailsProcessingException.java @@ -0,0 +1,37 @@ +package org.wso2.carbon.identity.oauth2.rar.exception; + +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException; + +/** + * Exception class to represent failures related to Rich Authorization Requests in OAuth 2.0 clients. + * + *

This exception is thrown when there are errors in processing authorization details during the OAuth 2.0 + * authorization flow. It extends the {@link IdentityOAuth2ClientException} class, providing more specific + * context for authorization-related issues.

+ */ +public class AuthorizationDetailsProcessingException extends IdentityOAuth2ClientException { + + private static final long serialVersionUID = -206212512259482200L; + + /** + * Constructs a new exception with the specified detail message. + * + * @param message The detail message. It provides information about the cause of the exception. + */ + public AuthorizationDetailsProcessingException(final String message) { + + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + * + * @param message The detail message. It provides information about the cause of the exception. + * @param cause The cause of the exception. It can be used to retrieve the stack trace or other information + * about the root cause of the exception. + */ + public AuthorizationDetailsProcessingException(final String message, final Throwable cause) { + + super(message, cause); + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java new file mode 100644 index 00000000000..998ea794ac9 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/model/AuthorizationDetailsContext.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth2.rar.model; + +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; + +import java.util.Objects; + +import javax.servlet.http.HttpServletRequestWrapper; + +import static org.wso2.carbon.identity.oauth2.authz.AuthorizationHandlerManager.OAUTH_APP_PROPERTY; + +/** + * Represents the context for rich authorization requests in an OAuth2 flow. + *

+ * This class holds relevant details such as OAuth2 parameters, application details, the authenticated user, + * and specific authorization details. It is immutable to ensure that the context remains consistent throughout its use. + *

+ */ +public class AuthorizationDetailsContext { + + private final AuthenticatedUser authenticatedUser; + private final AuthorizationDetail authorizationDetail; + private final HttpServletRequestWrapper httpServletRequestWrapper; + private final OAuthAppDO oAuthAppDO; + private final String[] scopes; + + /** + * Constructs a new {@code AuthorizationDetailsContext}. + *

+ * This constructor ensures that all necessary details for an authorization context are provided. + *

+ * + * @param authenticatedUser the {@link AuthenticatedUser}. + * @param authorizationDetail the specific {@link AuthorizationDetail} to be validated. + * @param httpServletRequestWrapper the {@link HttpServletRequestWrapper} instance containing request details. + * @param oAuthAppDO the {@link OAuthAppDO} containing application details. + * @param scopes the array of scopes requested. + * @throws NullPointerException if any of the arguments are {@code null}. + */ + public AuthorizationDetailsContext(final AuthenticatedUser authenticatedUser, + final AuthorizationDetail authorizationDetail, + final HttpServletRequestWrapper httpServletRequestWrapper, + final OAuthAppDO oAuthAppDO, + final String[] scopes) { + + this.authenticatedUser = Objects.requireNonNull(authenticatedUser, "authenticatedUser cannot be null"); + this.authorizationDetail = Objects.requireNonNull(authorizationDetail, "authorizationDetail cannot be null"); + this.httpServletRequestWrapper = Objects + .requireNonNull(httpServletRequestWrapper, "httpServletRequestWrapper cannot be null"); + this.oAuthAppDO = Objects.requireNonNull(oAuthAppDO, "oAuthAppDO cannot be null"); + this.scopes = Objects.requireNonNull(scopes, "scopes cannot be null"); + } + + /** + * Constructs a new {@code AuthorizationDetailsContext}. + * + * @param authorizationDetail the specific {@link AuthorizationDetail} to be validated. + * @param oAuthAuthzReqMessageContext the {@link OAuthAuthzReqMessageContext} instance which represent + * the authorization request context. + * @throws NullPointerException if any of the arguments are {@code null}. + */ + public AuthorizationDetailsContext(final AuthorizationDetail authorizationDetail, + final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) { + + this(oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getUser(), + authorizationDetail, + oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getHttpServletRequestWrapper(), + (OAuthAppDO) oAuthAuthzReqMessageContext.getProperty(OAUTH_APP_PROPERTY), + oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getScopes()); + } + + /** + * Constructs a new {@code AuthorizationDetailsContext}. + * + * @param authorizationDetail the specific {@link AuthorizationDetail} to be validated. + * @param oAuthTokenReqMessageContext the {@link OAuthTokenReqMessageContext} instance which represent + * the token request context. + * @throws NullPointerException if any of the arguments are {@code null}. + */ + public AuthorizationDetailsContext(final AuthorizationDetail authorizationDetail, + final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) { + + this(oAuthTokenReqMessageContext.getAuthorizedUser(), + authorizationDetail, + oAuthTokenReqMessageContext.getOauth2AccessTokenReqDTO().getHttpServletRequestWrapper(), + (OAuthAppDO) oAuthTokenReqMessageContext.getProperty(OAUTH_APP_PROPERTY), + oAuthTokenReqMessageContext.getScope()); + } + + /** + * Returns the {@code AuthorizationDetail} instance. + * + * @return the {@link AuthorizationDetail} instance. + */ + public AuthorizationDetail getAuthorizationDetail() { + return this.authorizationDetail; + } + + /** + * Returns the OAuth application details. + * + * @return the {@link OAuthAppDO} instance. + */ + public OAuthAppDO getoAuthAppDO() { + return this.oAuthAppDO; + } + + /** + * Returns the authenticated user. + * + * @return the {@link AuthenticatedUser} instance. + */ + public AuthenticatedUser getAuthenticatedUser() { + return this.authenticatedUser; + } + + /** + * Returns the HTTP servlet request user. + * + * @return the {@link HttpServletRequestWrapper} instance containing HTTP request details. + */ + public HttpServletRequestWrapper getHttpServletRequestWrapper() { + return this.httpServletRequestWrapper; + } + + /** + * Returns the valid scopes requested by the client. + * + * @return the {@link String} array of scopes. + */ + public String[] getScopes() { + return this.scopes; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/AccessTokenResponseRARHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/AccessTokenResponseRARHandler.java new file mode 100644 index 00000000000..a6d4baa1cd1 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/AccessTokenResponseRARHandler.java @@ -0,0 +1,49 @@ +package org.wso2.carbon.identity.oauth2.rar.token; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.token.handlers.response.AccessTokenResponseHandler; + +import java.util.HashMap; +import java.util.Map; + +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils.isRichAuthorizationRequest; + +/** + * Class responsible for modifying the access token response to include user-consented authorization details. + * + *

This class enhances the access token response by appending user-consented authorization details. + * It is invoked by the {@link org.wso2.carbon.identity.oauth2.token.AccessTokenIssuer#issue} method during + * the OAuth 2.0 token issuance process.

+ */ +public class AccessTokenResponseRARHandler implements AccessTokenResponseHandler { + + private static final Log log = LogFactory.getLog(AccessTokenResponseRARHandler.class); + + /** + * Returns Rich Authorization Request attributes to be added to the access token response. + * + * @param oAuthTokenReqMessageContext {@link OAuthTokenReqMessageContext} token request message context. + * @return Map of additional attributes to be added to the token response. + * @throws IdentityOAuth2Exception Error while constructing additional token response attributes. + */ + @Override + public Map getAdditionalTokenResponseAttributes( + final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) throws IdentityOAuth2Exception { + + Map additionalAttributes = new HashMap<>(); + if (isRichAuthorizationRequest(oAuthTokenReqMessageContext.getAuthorizationDetails())) { + + if (log.isDebugEnabled()) { + log.debug("Processing Rich Authorization Request in token flow. authorization_details: " + + oAuthTokenReqMessageContext.getAuthorizationDetails().toJsonString()); + } + additionalAttributes.put(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS, + oAuthTokenReqMessageContext.getAuthorizationDetails().toSet()); + } + return additionalAttributes; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/IntrospectionRARDataProvider.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/IntrospectionRARDataProvider.java new file mode 100644 index 00000000000..e2399aff363 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/IntrospectionRARDataProvider.java @@ -0,0 +1,118 @@ +package org.wso2.carbon.identity.oauth2.rar.token; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.IntrospectionDataProvider; +import org.wso2.carbon.identity.oauth2.dto.OAuth2IntrospectionResponseDTO; +import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationRequestDTO; +import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationResponseDTO; +import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.validator.AuthorizationDetailsValidator; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.oauth2.validators.OAuth2TokenValidationMessageContext; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants.AUTHORIZATION_DETAILS; + +/** + * Class responsible for modifying the introspection response to include user-consented authorization details. + * + *

This class enhances the introspection response by appending user-consented authorization details. + * It is invoked by the /introspect endpoint of the oauth.endpoint webapp during the token introspection process.

+ */ +public class IntrospectionRARDataProvider implements IntrospectionDataProvider { + + private static final Log log = LogFactory.getLog(IntrospectionRARDataProvider.class); + private final AuthorizationDetailsValidator authorizationDetailsValidator; + + public IntrospectionRARDataProvider() { + + this.authorizationDetailsValidator = + OAuth2ServiceComponentHolder.getInstance().getAuthorizationDetailsValidator(); + } + + /** + * Provides additional Rich Authorization Requests data for OAuth token introspection. + * + * @param tokenValidationRequestDTO Token validation request DTO. + * @param introspectionResponseDTO Token introspection response DTO. + * @return Map of additional data to be added to the introspection response. + * @throws IdentityOAuth2Exception If an error occurs while setting additional introspection data. + */ + @Override + public Map getIntrospectionData( + final OAuth2TokenValidationRequestDTO tokenValidationRequestDTO, + final OAuth2IntrospectionResponseDTO introspectionResponseDTO) throws IdentityOAuth2Exception { + + final Map introspectionData = new HashMap<>(); + final OAuth2TokenValidationMessageContext tokenValidationMessageContext = + generateOAuth2TokenValidationMessageContext(tokenValidationRequestDTO, introspectionResponseDTO); + + if (Objects.nonNull(tokenValidationMessageContext)) { + final AuthorizationDetails validatedAuthorizationDetails = this.authorizationDetailsValidator + .getValidatedAuthorizationDetails(tokenValidationMessageContext); + introspectionData.put(AUTHORIZATION_DETAILS, validatedAuthorizationDetails.toSet()); + } + return introspectionData; + } + + /** + * Generates an OAuth2TokenValidationMessageContext based on the token validation request and + * introspection response. + * + * @param tokenValidationRequestDTO The OAuth2 token validation request DTO. + * @param introspectionResponseDTO The OAuth2 introspection response DTO. + * @return The generated OAuth2TokenValidationMessageContext. + * @throws IdentityOAuth2Exception If an error occurs during the generation of the context. + */ + private OAuth2TokenValidationMessageContext generateOAuth2TokenValidationMessageContext( + final OAuth2TokenValidationRequestDTO tokenValidationRequestDTO, + final OAuth2IntrospectionResponseDTO introspectionResponseDTO) throws IdentityOAuth2Exception { + + // Check if the introspection response contains a validation message context + if (introspectionResponseDTO.getProperties().containsKey(OAuth2Util.OAUTH2_VALIDATION_MESSAGE_CONTEXT)) { + log.debug("Introspection response contains a validation message context."); + + final Object oAuth2TokenValidationMessageContext = introspectionResponseDTO.getProperties() + .get(OAuth2Util.OAUTH2_VALIDATION_MESSAGE_CONTEXT); + + if (oAuth2TokenValidationMessageContext instanceof OAuth2TokenValidationMessageContext) { + return (OAuth2TokenValidationMessageContext) oAuth2TokenValidationMessageContext; + } + } else { + // Create a new validation message context + final OAuth2TokenValidationMessageContext oAuth2TokenValidationMessageContext = + new OAuth2TokenValidationMessageContext(tokenValidationRequestDTO, + generateOAuth2TokenValidationResponseDTO(introspectionResponseDTO)); + + final AccessTokenDO accessTokenDO = OAuth2ServiceComponentHolder.getInstance().getTokenProvider() + .getVerifiedAccessToken(tokenValidationRequestDTO.getAccessToken().getIdentifier(), false); + + oAuth2TokenValidationMessageContext.addProperty(OAuthConstants.ACCESS_TOKEN_DO, accessTokenDO); + + return oAuth2TokenValidationMessageContext; + } + + log.debug("OAuth2TokenValidationMessageContext could not be generated. returning null"); + return null; + } + + private OAuth2TokenValidationResponseDTO generateOAuth2TokenValidationResponseDTO( + final OAuth2IntrospectionResponseDTO oAuth2IntrospectionResponseDTO) { + + final OAuth2TokenValidationResponseDTO tokenValidationResponseDTO = new OAuth2TokenValidationResponseDTO(); + tokenValidationResponseDTO.setValid(oAuth2IntrospectionResponseDTO.isActive()); + tokenValidationResponseDTO.setErrorMsg(oAuth2IntrospectionResponseDTO.getError()); + tokenValidationResponseDTO.setScope(OAuth2Util.buildScopeArray(oAuth2IntrospectionResponseDTO.getScope())); + tokenValidationResponseDTO.setExpiryTime(oAuth2IntrospectionResponseDTO.getExp()); + + return tokenValidationResponseDTO; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/JWTAccessTokenRARClaimProvider.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/JWTAccessTokenRARClaimProvider.java new file mode 100644 index 00000000000..d429390131f --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/token/JWTAccessTokenRARClaimProvider.java @@ -0,0 +1,70 @@ +package org.wso2.carbon.identity.oauth2.rar.token; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.token.handlers.claims.JWTAccessTokenClaimProvider; + +import java.util.HashMap; +import java.util.Map; + +/** + * Provides additional claims related to Rich Authorization Requests to be included in JWT Access Tokens. + * This implementation supports both the OAuth2 authorization and token flows. + */ +public class JWTAccessTokenRARClaimProvider implements JWTAccessTokenClaimProvider { + + private static final Log log = LogFactory.getLog(JWTAccessTokenRARClaimProvider.class); + + /** + * Returns a map of additional claims related to Rich Authorization Requests to be included in + * JWT Access Tokens issued in the OAuth2 authorize flow. + * + * @param oAuthAuthzReqMessageContext The OAuth authorization request message context. + * @return A map of additional claims. + * @throws IdentityOAuth2Exception If an error occurs during claim retrieval. + */ + @Override + public Map getAdditionalClaims(final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) + throws IdentityOAuth2Exception { + + final Map additionalClaims = new HashMap<>(); + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(oAuthAuthzReqMessageContext)) { + if (log.isDebugEnabled()) { + log.debug("Processing Rich Authorization Request in authorization flow. authorization_details: " + + oAuthAuthzReqMessageContext.getAuthorizationDetails().toJsonString()); + } + additionalClaims.put(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS, + oAuthAuthzReqMessageContext.getAuthorizationDetails().toSet()); + } + return additionalClaims; + } + + /** + * Returns a map of additional claims related to Rich Authorization Requests to be included in + * JWT Access Tokens issued in the OAuth2 token flow. + * + * @param oAuthTokenReqMessageContext The OAuth token request message context. + * @return A map of additional claims. + * @throws IdentityOAuth2Exception If an error occurs during claim retrieval. + */ + @Override + public Map getAdditionalClaims(final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) + throws IdentityOAuth2Exception { + + final Map additionalClaims = new HashMap<>(); + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(oAuthTokenReqMessageContext)) { + if (log.isDebugEnabled()) { + log.debug("Processing Rich Authorization Request in token flow.authorization_details: " + + oAuthTokenReqMessageContext.getAuthorizationDetails().toJsonString()); + } + additionalClaims.put(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS, + oAuthTokenReqMessageContext.getAuthorizationDetails().toSet()); + } + return additionalClaims; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java new file mode 100644 index 00000000000..eaf24b4c397 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/util/AuthorizationDetailsUtils.java @@ -0,0 +1,341 @@ +package org.wso2.carbon.identity.oauth2.rar.util; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; +import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.application.common.model.ServiceProvider; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; +import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; +import org.wso2.carbon.identity.oauth2.model.CarbonOAuthTokenRequest; +import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsConsentDTO; +import org.wso2.carbon.identity.oauth2.rar.dto.AuthorizationDetailsTokenDTO; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletRequest; + +import static org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants.AUTHORIZATION_DETAILS_ID_PREFIX; + +/** + * Utility class for handling and validating authorization details in OAuth2 requests. + */ +public class AuthorizationDetailsUtils { + + private static final Log log = LogFactory.getLog(AuthorizationDetailsUtils.class); + + /** + * Determines if the given {@link OAuthAuthzReqMessageContext} object contains {@link AuthorizationDetails}. + * + * @param oAuthAuthzReqMessageContext The requested OAuthAuthzReqMessageContext to check. + * @return {@code true} if the OAuthAuthzReqMessageContext contains non-empty authorization details set, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) { + + return isRichAuthorizationRequest(oAuthAuthzReqMessageContext.getAuthorizationDetails()); + } + + /** + * Determines if the given {@link OAuthAuthzRequest} object contains {@code authorization_details}. + * + * @param oauthRequest The OAuth Authorization Request to check. + * @return {@code true} if the OAuth authorization request contains a non-blank authorization details parameter, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final OAuthAuthzRequest oauthRequest) { + + return StringUtils.isNotBlank(oauthRequest.getParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS)); + } + + /** + * Determines if the given {@link CarbonOAuthTokenRequest} object contains {@code authorization_details}. + * + * @param carbonOAuthTokenRequest The OAuth Token Request to check. + * @return {@code true} if the OAuth token request contains a non-blank authorization details parameter, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final CarbonOAuthTokenRequest carbonOAuthTokenRequest) { + + return StringUtils + .isNotBlank(carbonOAuthTokenRequest.getParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS)); + } + + /** + * Determines if the request is a rich authorization request using provided {@link AuthorizationDetails} object. + *

+ * This method checks if the specified {@link AuthorizationDetails} instance is not {@code null} + * and has a non-empty details set. + * + * @param authorizationDetails The {@link AuthorizationDetails} to check. + * @return {@code true} if the {@link AuthorizationDetails} is not {@code null} and has a non-empty details set, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final AuthorizationDetails authorizationDetails) { + + return authorizationDetails != null && !authorizationDetails.getDetails().isEmpty(); + } + + /** + * Determines if the given {@link OAuth2Parameters} object contains {@link AuthorizationDetails}. + * + * @param oAuth2Parameters The requested OAuth2Parameters to check. + * @return {@code true} if the OAuth2Parameters contains non-empty authorization details set, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final OAuth2Parameters oAuth2Parameters) { + + return isRichAuthorizationRequest(oAuth2Parameters.getAuthorizationDetails()); + } + + /** + * Determines if the given {@link OAuthTokenReqMessageContext} object or the + * {@link OAuthTokenReqMessageContext#getOauth2AccessTokenReqDTO} contains {@link AuthorizationDetails}. + * + * @param oAuthTokenReqMessageContext The requested oAuthTokenReqMessageContext to check. + * @return {@code true} if the oAuthTokenReqMessageContext contains non-empty authorization details set, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) { + + return isRichAuthorizationRequest(oAuthTokenReqMessageContext.getAuthorizationDetails()) || + isRichAuthorizationRequest(oAuthTokenReqMessageContext + .getOauth2AccessTokenReqDTO().getAuthorizationDetails()); + } + + /** + * Determines if the given {@link OAuth2AuthorizeReqDTO} object contains {@link AuthorizationDetails}. + * + * @param oAuth2AuthorizeReqDTO The requested oAuth2AuthorizeReqDTO to check. + * @return {@code true} if the oAuth2AuthorizeReqDTO contains non-empty authorization details set, + * {@code false} otherwise. + */ + public static boolean isRichAuthorizationRequest(final OAuth2AuthorizeReqDTO oAuth2AuthorizeReqDTO) { + + return isRichAuthorizationRequest(oAuth2AuthorizeReqDTO.getAuthorizationDetails()); + } + + /** + * Retrieves the application resource ID from the client ID. + * + * @param clientId The client ID. + * @return The application resource ID. + * @throws IdentityOAuth2Exception if an error occurs while retrieving the application resource ID. + */ + public static String getApplicationResourceIdFromClientId(final String clientId) throws IdentityOAuth2Exception { + + final ServiceProvider serviceProvider = OAuth2Util.getServiceProvider(clientId); + if (serviceProvider != null) { + return serviceProvider.getApplicationResourceId(); + } + throw new IdentityOAuth2Exception("Unable to find a service provider for client Id: " + clientId); + } + + /** + * Retrieves the user ID from the authenticated user. + * + * @param authenticatedUser The authenticated user. + * @return The user ID. + * @throws IdentityOAuth2Exception if an error occurs while retrieving the user ID. + */ + public static String getIdFromAuthenticatedUser(final AuthenticatedUser authenticatedUser) + throws IdentityOAuth2Exception { + + try { + return authenticatedUser.getUserId(); + } catch (UserIdNotFoundException e) { + log.error("Error occurred while extracting userId from authenticated user. Caused by, ", e); + throw new IdentityOAuth2Exception( + "User id is not found for user: " + authenticatedUser.getLoggableMaskedUserId(), e); + } + } + + /** + * Generates a list of {@link AuthorizationDetailsConsentDTO} from the provided consent ID, + * authorization details, and tenant ID. + * + * @param consentId The consent ID. + * @param userConsentedAuthorizationDetails The user-consented authorization details. + * @param tenantId The tenant ID. + * @return A list of {@link AuthorizationDetailsConsentDTO}. + */ + public static List getAuthorizationDetailsConsentDTOs( + final String consentId, final AuthorizationDetails userConsentedAuthorizationDetails, final int tenantId) { + + return userConsentedAuthorizationDetails.stream() + .map(detail -> new AuthorizationDetailsConsentDTO(consentId, detail, true, tenantId)) + .collect(Collectors.toList()); + } + + /** + * Generates a list of {@link AuthorizationDetailsTokenDTO} from the provided access token and + * authorization details. + * + * @param accessTokenDO The access token data object. + * @param authorizationDetails The user-consented authorization details. + * @return A list of {@link AuthorizationDetailsTokenDTO}. + */ + public static List getAccessTokenAuthorizationDetailsDTOs( + final AccessTokenDO accessTokenDO, final AuthorizationDetails authorizationDetails) { + + return authorizationDetails + .stream() + .map(authorizationDetail -> new AuthorizationDetailsTokenDTO( + accessTokenDO.getTokenId(), authorizationDetail, accessTokenDO.getTenantID())) + .collect(Collectors.toList()); + } + + /** + * Extracts the user-consented authorization details from the request parameters and OAuth2 parameters. + * + * @param httpServletRequest The HTTP servlet request containing the authorization details. + * @param oAuth2Parameters The OAuth2 parameters that include the authorization details. + * @return The {@link AuthorizationDetails} containing the user-consented authorization details. + */ + public static AuthorizationDetails extractAuthorizationDetailsFromRequest( + final HttpServletRequest httpServletRequest, final OAuth2Parameters oAuth2Parameters) { + + if (!AuthorizationDetailsUtils.isRichAuthorizationRequest(oAuth2Parameters)) { + log.debug("Request is not a rich authorization request. Returning empty authorization details."); + return new AuthorizationDetails(); + } + + // Extract consented authorization detail IDs from the parameter map + final Set consentedAuthorizationDetailIDs = httpServletRequest.getParameterMap().keySet().stream() + .filter(parameterName -> parameterName.startsWith(AUTHORIZATION_DETAILS_ID_PREFIX)) + .map(parameterName -> parameterName.substring(AUTHORIZATION_DETAILS_ID_PREFIX.length())) + .collect(Collectors.toSet()); + + // Filter and collect the consented authorization details + final Set consentedAuthorizationDetails = oAuth2Parameters.getAuthorizationDetails() + .stream() + .filter(authorizationDetail -> consentedAuthorizationDetailIDs.contains(authorizationDetail.getId())) + .collect(Collectors.toSet()); + + log.debug("User consented authorization details extracted successfully."); + return new AuthorizationDetails(consentedAuthorizationDetails); + } + + /** + * Transforms the given {@link AuthorizationDetails} by creating a new set of {@link AuthorizationDetail} objects + * with only the displayable fields ({@code type}, {@code id}, {@code consentDescription}) copied over. + * + * @param authorizationDetails The original AuthorizationDetails to be transformed. + * @return A new {@link AuthorizationDetails} object containing the displayable authorization details. + */ + public static AuthorizationDetails getDisplayableAuthorizationDetails( + final AuthorizationDetails authorizationDetails) { + + final Set displayableAuthorizationDetails = authorizationDetails.stream() + .map(protectedAuthorizationDetail -> { + final AuthorizationDetail authorizationDetail = new AuthorizationDetail(); + authorizationDetail.setType(protectedAuthorizationDetail.getType()); + authorizationDetail.setId(protectedAuthorizationDetail.getId()); + authorizationDetail.setConsentDescription(protectedAuthorizationDetail.getConsentDescription()); + return authorizationDetail; + }).collect(Collectors.toSet()); + + return new AuthorizationDetails(displayableAuthorizationDetails); + } + + /** + * Trims the given {@link AuthorizationDetails} by setting the temporary {@code id} and {@code consentDescription} + * fields to null for each {@link AuthorizationDetail}. + * + * @param authorizationDetails The original AuthorizationDetails to be trimmed. + * @return The same AuthorizationDetails object with trimmed fields. + */ + public static AuthorizationDetails getTrimmedAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + authorizationDetails.stream().forEach(authorizationDetail -> { + authorizationDetail.setId(null); + authorizationDetail.setConsentDescription(null); + }); + return authorizationDetails; + } + + /** + * Generates an {@link AuthorizationDetails} instance from given JSON string + * and assigns unique IDs for each {@link AuthorizationDetail}. + * + * @param authorizationDetailsJson The JSON string representing the authorization details. + * @return The AuthorizationDetails object with unique IDs assigned to each AuthorizationDetail. + */ + public static AuthorizationDetails generateAndAssignUniqueIDs(final String authorizationDetailsJson) { + + return assignUniqueIDsToAuthorizationDetails(new AuthorizationDetails(authorizationDetailsJson)); + } + + /** + * Generates unique IDs for each {@link AuthorizationDetail} within the given {@link AuthorizationDetails} object. + * + * @param authorizationDetails The AuthorizationDetails object containing a set of AuthorizationDetail objects. + * @return The AuthorizationDetails object with unique IDs assigned to each AuthorizationDetail. + */ + public static AuthorizationDetails assignUniqueIDsToAuthorizationDetails(final AuthorizationDetails + authorizationDetails) { + + authorizationDetails.stream().filter(Objects::nonNull) + .forEach(authorizationDetail -> authorizationDetail.setId(UUID.randomUUID().toString())); + return authorizationDetails; + } + + /** + * Encodes the given AuthorizationDetails object to a URL-encoded JSON string. + * + * @param authorizationDetails The AuthorizationDetails object to be encoded. + * @return A URL-encoded JSON string representing the authorization details. + */ + public static String getUrlEncodedAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + try { + if (log.isDebugEnabled()) { + log.debug("Starts URL encoding authorization details: " + authorizationDetails.toJsonString()); + } + if (isRichAuthorizationRequest(authorizationDetails)) { + return URLEncoder.encode(authorizationDetails.toJsonString(), StandardCharsets.UTF_8.toString()); + } + } catch (UnsupportedEncodingException e) { + log.error("Error occurred while URL encoding authorization details. Caused by, ", e); + } + return StringUtils.EMPTY; + } + + /** + * Decodes the given URL-encoded AuthorizationDetails JSON String. + * + * @param encodedAuthorizationDetails The encoded AuthorizationDetails String to be decoded. + * @return A URL-decoded JSON string representing the authorization details. + */ + public static String getUrlDecodedAuthorizationDetails(final String encodedAuthorizationDetails) { + + try { + if (log.isDebugEnabled()) { + log.debug("Starts decoding URL encoded authorization details JSON: " + encodedAuthorizationDetails); + } + if (StringUtils.isNotEmpty(encodedAuthorizationDetails)) { + return URLDecoder.decode(encodedAuthorizationDetails, StandardCharsets.UTF_8.toString()); + } + } catch (UnsupportedEncodingException e) { + log.error("Error occurred while URL decoding authorization details Json. Caused by, ", e); + } + return StringUtils.EMPTY; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/AuthorizationDetailsValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/AuthorizationDetailsValidator.java new file mode 100644 index 00000000000..2cdf2afd7f0 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/AuthorizationDetailsValidator.java @@ -0,0 +1,62 @@ +package org.wso2.carbon.identity.oauth2.rar.validator; + +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ServerException; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.rar.exception.AuthorizationDetailsProcessingException; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.validators.OAuth2TokenValidationMessageContext; + +/** + * Interface for validating {@link AuthorizationDetails} in different OAuth2 message contexts. + * + *

This interface provides methods to validate {@link AuthorizationDetails} across various OAuth2 message contexts, + * including authorization requests, token requests, and token validation requests. Implementations of this + * interface should handle the validation logic specific to the type of request and ensure that the returned + * AuthorizationDetails are accurate and compliant with the application's security policies.

+ */ +public interface AuthorizationDetailsValidator { + + /** + * Validates and returns the {@link AuthorizationDetails} for the given {@link OAuthAuthzReqMessageContext}. + *

+ * Validates the {@link AuthorizationDetails} during the authorization request phase. + * This is typically invoked when an authorization request is received and needs to be processed. + * + * @param oAuthAuthzReqMessageContext The OAuth authorization request message context. + * @return The validated {@link AuthorizationDetails}. + * @throws AuthorizationDetailsProcessingException If an error occurs during the processing of authorization details + * @throws IdentityOAuth2ServerException if the validation fails due to a server error. + */ + AuthorizationDetails getValidatedAuthorizationDetails(OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException; + + /** + * Validates and returns the {@link AuthorizationDetails} for the given {@link OAuthTokenReqMessageContext}. + *

+ * Validates the AuthorizationDetails during the token request phase. This is usually called when an authorization + * code is exchanged for an access token, or when a refresh token request is made. + * + * @param oAuthTokenReqMessageContext The OAuth token request message context. + * @return The validated {@link AuthorizationDetails}. + * @throws AuthorizationDetailsProcessingException If an error occurs during the processing of authorization details + * @throws IdentityOAuth2ServerException if the validation fails due to a server error. + */ + AuthorizationDetails getValidatedAuthorizationDetails(OAuthTokenReqMessageContext oAuthTokenReqMessageContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException; + + /** + * Validates and returns the {@link AuthorizationDetails} for the given {@link OAuth2TokenValidationMessageContext}. + *

+ * Validates the {@link AuthorizationDetails} during the token validation phase. This method is often used when an + * access token is being introspected to ensure its legitimacy and the associated AuthorizationDetails. + * + * @param oAuth2TokenValidationMessageContext The OAuth2 token validation message context. + * @return The validated {@link AuthorizationDetails}. + * @throws AuthorizationDetailsProcessingException If an error occurs during the processing of authorization details + * @throws IdentityOAuth2ServerException If an error occurs related to the OAuth2 server. + */ + AuthorizationDetails getValidatedAuthorizationDetails(OAuth2TokenValidationMessageContext + oAuth2TokenValidationMessageContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException; +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/DefaultAuthorizationDetailsValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/DefaultAuthorizationDetailsValidator.java new file mode 100644 index 00000000000..52fce6d3e8a --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/rar/validator/DefaultAuthorizationDetailsValidator.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth2.rar.validator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.oltu.oauth2.common.message.types.GrantType; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ServerException; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; +import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; +import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsService; +import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProcessor; +import org.wso2.carbon.identity.oauth2.rar.core.AuthorizationDetailsProviderFactory; +import org.wso2.carbon.identity.oauth2.rar.exception.AuthorizationDetailsProcessingException; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetail; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetailsContext; +import org.wso2.carbon.identity.oauth2.rar.model.ValidationResult; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.oauth2.validators.OAuth2TokenValidationMessageContext; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Default implementation class responsible for validating {@link AuthorizationDetails} in different + * OAuth2 message contexts. + */ +public class DefaultAuthorizationDetailsValidator implements AuthorizationDetailsValidator { + + private static final Log log = LogFactory.getLog(DefaultAuthorizationDetailsValidator.class); + private final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory; + private final AuthorizationDetailsService authorizationDetailsService; + + public DefaultAuthorizationDetailsValidator() { + + this( + AuthorizationDetailsProviderFactory.getInstance(), + OAuth2ServiceComponentHolder.getInstance().getAuthorizationDetailsService() + ); + } + + public DefaultAuthorizationDetailsValidator( + final AuthorizationDetailsProviderFactory authorizationDetailsProviderFactory, + final AuthorizationDetailsService authorizationDetailsService) { + + this.authorizationDetailsProviderFactory = authorizationDetailsProviderFactory; + this.authorizationDetailsService = authorizationDetailsService; + } + + /** + * {@inheritDoc} + */ + @Override + public AuthorizationDetails getValidatedAuthorizationDetails(final OAuthAuthzReqMessageContext + oAuthAuthzReqMessageContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException { + + try { + return this.getValidatedAuthorizationDetails( + oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getConsumerKey(), + OAuth2Util.getTenantId(oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getTenantDomain()), + oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getAuthorizationDetails(), + authorizationDetail -> + new AuthorizationDetailsContext(authorizationDetail, oAuthAuthzReqMessageContext) + ); + } catch (IdentityOAuth2Exception e) { + log.error("Unable find the tenant ID of the domain: " + + oAuthAuthzReqMessageContext.getAuthorizationReqDTO().getTenantDomain() + " Caused by, ", e); + throw new AuthorizationDetailsProcessingException("Invalid tenant domain", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public AuthorizationDetails getValidatedAuthorizationDetails(final OAuthTokenReqMessageContext + oAuthTokenReqMessageContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException { + + final OAuth2AccessTokenReqDTO accessTokenReqDTO = oAuthTokenReqMessageContext.getOauth2AccessTokenReqDTO(); + + if (GrantType.AUTHORIZATION_CODE.toString().equals(accessTokenReqDTO.getGrantType())) { + if (log.isDebugEnabled()) { + log.debug("Skipping the authorization_details validation for authorization code flow " + + "as this validation has already happened in the authorize flow."); + } + return oAuthTokenReqMessageContext.getAuthorizationDetails(); + } + + if (!AuthorizationDetailsUtils.isRichAuthorizationRequest(accessTokenReqDTO.getAuthorizationDetails())) { + if (log.isDebugEnabled()) { + log.debug("Client application does not request new authorization details. " + + "Returning previously validated authorization details."); + + } + return oAuthTokenReqMessageContext.getAuthorizationDetails(); + } + + return this.getValidatedAuthorizationDetails( + accessTokenReqDTO.getClientId(), + oAuthTokenReqMessageContext.getTenantID(), + accessTokenReqDTO.getAuthorizationDetails(), + authorizationDetail -> new AuthorizationDetailsContext(authorizationDetail, oAuthTokenReqMessageContext) + ); + } + + /** + * {@inheritDoc} + */ + @Override + public AuthorizationDetails getValidatedAuthorizationDetails( + final OAuth2TokenValidationMessageContext oAuth2TokenValidationMessageContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException { + + try { + final AccessTokenDO accessTokenDO = (AccessTokenDO) oAuth2TokenValidationMessageContext + .getProperty(OAuthConstants.ACCESS_TOKEN_DO); + + final AuthorizationDetails accessTokenAuthorizationDetails = this.authorizationDetailsService + .getAccessTokenAuthorizationDetails(accessTokenDO.getTokenId(), accessTokenDO.getTenantID()); + + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(accessTokenAuthorizationDetails)) { + final Set authorizedAuthorizationDetails = + this.getAuthorizedAuthorizationDetails( + accessTokenDO.getConsumerKey(), + accessTokenDO.getTenantID(), + accessTokenAuthorizationDetails); + return new AuthorizationDetails(authorizedAuthorizationDetails); + } + } catch (IdentityOAuth2Exception e) { + log.error("Error occurred while retrieving access token authorization details. Caused by, ", e); + throw new AuthorizationDetailsProcessingException("Unable to retrieve token authorization details", e); + } + return new AuthorizationDetails(); + } + + /** + * Validates the authorization details for OAuthTokenReqMessageContext. + * + * @param clientId The client ID. + * @param tenantId The tenant ID. + * @param authorizationDetails The set of authorization details to validate. + * @param contextProvider A lambda function to create the AuthorizationDetailsContext. + * @return An {@link AuthorizationDetails} object containing the validated authorization details. + * @throws AuthorizationDetailsProcessingException if validation fails. + */ + private AuthorizationDetails getValidatedAuthorizationDetails( + final String clientId, final int tenantId, final AuthorizationDetails authorizationDetails, + final Function contextProvider) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException { + + final Set validatedAuthorizationDetails = new HashSet<>(); + for (final AuthorizationDetail authorizationDetail : + this.getAuthorizedAuthorizationDetails(clientId, tenantId, authorizationDetails)) { + + if (!isSupportedAuthorizationDetailType(authorizationDetail.getType())) { + throw new AuthorizationDetailsProcessingException(String.format(AuthorizationDetailsConstants + .TYPE_NOT_SUPPORTED_ERR_MSG_FORMAT, authorizationDetail.getType())); + } + + if (log.isDebugEnabled()) { + log.debug("Validation started for authorization detail of type: " + authorizationDetail.getType()); + } + + final AuthorizationDetailsContext authorizationDetailsContext = contextProvider.apply(authorizationDetail); + + if (this.isValidAuthorizationDetail(authorizationDetailsContext)) { + validatedAuthorizationDetails.add(getEnrichedAuthorizationDetail(authorizationDetailsContext)); + } + } + + return new AuthorizationDetails(validatedAuthorizationDetails); + } + + private Set getAuthorizedAuthorizationDetails( + final String clientId, final int tenantId, final AuthorizationDetails authorizationDetails) { + + final Set authorizedAuthorizationDetailsTypes = + this.getAuthorizedAuthorizationDetailsTypes(clientId, tenantId); + + return authorizationDetails.stream() + .filter(authorizationDetail -> + authorizedAuthorizationDetailsTypes.contains(authorizationDetail.getType())) + .collect(Collectors.toSet()); + } + + private boolean isSupportedAuthorizationDetailType(final String authorizationDetailType) { + + return this.authorizationDetailsProviderFactory + .isSupportedAuthorizationDetailsType(authorizationDetailType); + } + + /** + * Checks if the provided authorization details context is valid. + * + * @param authorizationDetailsContext The context containing authorization details. + * @return {@code true} if the authorization details are valid; {@code false} otherwise. + */ + private boolean isValidAuthorizationDetail(final AuthorizationDetailsContext authorizationDetailsContext) + throws AuthorizationDetailsProcessingException, IdentityOAuth2ServerException { + + Optional optionalProvider = this.authorizationDetailsProviderFactory + .getProviderByType(authorizationDetailsContext.getAuthorizationDetail().getType()); + + if (optionalProvider.isPresent()) { + + final ValidationResult validationResult = optionalProvider.get().validate(authorizationDetailsContext); + if (log.isDebugEnabled() && validationResult.isInvalid()) { + + log.debug(String.format("Authorization details validation failed for type %s. Caused by, %s", + authorizationDetailsContext.getAuthorizationDetail().getType(), validationResult.getReason())); + + } + return validationResult.isValid(); + } + throw new AuthorizationDetailsProcessingException(String.format( + AuthorizationDetailsConstants.TYPE_NOT_SUPPORTED_ERR_MSG_FORMAT, + authorizationDetailsContext.getAuthorizationDetail().getType())); + } + + /** + * Enriches the authorization details using the provided context. + * + * @param authorizationDetailsContext The context containing authorization details. + * @return An enriched {@link AuthorizationDetail} object. + */ + private AuthorizationDetail getEnrichedAuthorizationDetail( + final AuthorizationDetailsContext authorizationDetailsContext) { + + return this.authorizationDetailsProviderFactory + .getProviderByType(authorizationDetailsContext.getAuthorizationDetail().getType()) + .map(authorizationDetailsProcessor -> authorizationDetailsProcessor.enrich(authorizationDetailsContext)) + // If provider is missing, return the original authorization detail instance + .orElse(authorizationDetailsContext.getAuthorizationDetail()); + } + + /** + * Retrieves the set of authorized authorization types for the given client and tenant domain. + * + * @param clientId The client ID. + * @param tenantId The tenant ID. + * @return A set of strings representing the authorized authorization types. + */ + private Set getAuthorizedAuthorizationDetailsTypes(final String clientId, final int tenantId) { + +// try { +// final String appId = OAuth2Util +// .getApplicationResourceIDByClientId(clientID, tenantDomain, this.applicationMgtService); +// +//// OAuth2ServiceComponentHolder.getInstance().getAuthorizedAPIManagementService() +// .getAuthorizedAuthorizationDetailsTypes(appId, tenantDomain); +// } catch (IdentityOAuth2Exception e) { +// throw new RuntimeException(e); +// } + Set authorizedAuthorizationDetailsTypes = new HashSet<>(); + authorizedAuthorizationDetailsTypes.add("payment_initiation"); + return authorizedAuthorizationDetailsTypes; + } + + +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/SuccessResponseDTO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/SuccessResponseDTO.java index 4c96445fe54..524e5ce2ab1 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/SuccessResponseDTO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/SuccessResponseDTO.java @@ -18,6 +18,7 @@ package org.wso2.carbon.identity.oauth2.responsemode.provider; import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import java.util.Set; @@ -34,6 +35,7 @@ public class SuccessResponseDTO { private String formPostBody; private String subjectToken; private Set scope = null; + private AuthorizationDetails authorizationDetails; public String getAuthorizationCode() { @@ -117,4 +119,25 @@ public void setSubjectToken(String subjectToken) { this.subjectToken = subjectToken; } + + /** + * Retrieves the authorization details to be included in the successful authorization response. + * + * @return the {@link AuthorizationDetails} instance representing the current authorization information. + * If no authorization details are available, it will return {@code null}. + */ + public AuthorizationDetails getAuthorizationDetails() { + + return this.authorizationDetails; + } + + /** + * Sets the authorization details to be included in the successful authorization response. + * + * @param authorizationDetails the {@link AuthorizationDetails} to set. + */ + public void setAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + this.authorizationDetails = authorizationDetails; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/impl/FragmentResponseModeProvider.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/impl/FragmentResponseModeProvider.java index 51c52372342..313353d82e0 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/impl/FragmentResponseModeProvider.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/responsemode/provider/impl/FragmentResponseModeProvider.java @@ -20,6 +20,9 @@ import org.apache.commons.lang.StringUtils; import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; import org.wso2.carbon.identity.oauth2.responsemode.provider.AbstractResponseModeProvider; import org.wso2.carbon.identity.oauth2.responsemode.provider.AuthorizationResponseDTO; @@ -55,6 +58,8 @@ public String getAuthResponseRedirectUrl(AuthorizationResponseDTO authorizationR String scope = authorizationResponseDTO.getSuccessResponseDTO().getScope(); String authenticatedIdPs = authorizationResponseDTO.getAuthenticatedIDPs(); String subjectToken = authorizationResponseDTO.getSuccessResponseDTO().getSubjectToken(); + final AuthorizationDetails authorizationDetails = authorizationResponseDTO.getSuccessResponseDTO() + .getAuthorizationDetails(); List params = new ArrayList<>(); if (accessToken != null) { params.add(OAuthConstants.ACCESS_TOKEN_RESPONSE_PARAM + "=" + accessToken); @@ -93,6 +98,11 @@ public String getAuthResponseRedirectUrl(AuthorizationResponseDTO authorizationR params.add(OAuthConstants.SUBJECT_TOKEN + "=" + subjectToken); } + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(authorizationDetails)) { + params.add(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS + "=" + + AuthorizationDetailsUtils.getUrlEncodedAuthorizationDetails(authorizationDetails)); + } + redirectUrl += "#" + String.join("&", params); } else { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java index 6d6f9862904..ee5054ee999 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java @@ -64,6 +64,12 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.rar.exception.AuthorizationDetailsProcessingException; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsConstants; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; +import org.wso2.carbon.identity.oauth2.rar.validator.AuthorizationDetailsValidator; +import org.wso2.carbon.identity.oauth2.rar.validator.DefaultAuthorizationDetailsValidator; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinder; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding; import org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationGrantHandler; @@ -116,6 +122,7 @@ public class AccessTokenIssuer { private Map authzGrantHandlers; public static final String OAUTH_APP_DO = "OAuthAppDO"; private static final String SERVICE_PROVIDERS_SUB_CLAIM = "ServiceProviders.UseUsernameAsSubClaim"; + private final AuthorizationDetailsValidator authorizationDetailsValidator; /** * Private constructor which will not allow to create objects of this class from outside @@ -123,6 +130,7 @@ public class AccessTokenIssuer { private AccessTokenIssuer() throws IdentityOAuth2Exception { authzGrantHandlers = OAuthServerConfiguration.getInstance().getSupportedGrantTypes(); + this.authorizationDetailsValidator = new DefaultAuthorizationDetailsValidator(); AppInfoCache appInfoCache = AppInfoCache.getInstance(); if (appInfoCache != null) { if (log.isDebugEnabled()) { @@ -449,6 +457,35 @@ private OAuth2AccessTokenRespDTO validateGrantAndIssueToken(OAuth2AccessTokenReq return tokenRespDTO; } + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(tokReqMsgCtx)) { + try { + final AuthorizationDetails validatedAuthorizationDetails = this.authorizationDetailsValidator + .getValidatedAuthorizationDetails(tokReqMsgCtx); + tokReqMsgCtx.setAuthorizationDetails(validatedAuthorizationDetails); + } catch (AuthorizationDetailsProcessingException e) { + if (log.isDebugEnabled()) { + log.debug("Invalid authorization details requested by client Id: " + tokenReqDTO.getClientId()); + } + + if (LoggerUtils.isDiagnosticLogsEnabled()) { + LoggerUtils.triggerDiagnosticLogEvent(new DiagnosticLog.DiagnosticLogBuilder( + OAuthConstants.LogConstants.OAUTH_INBOUND_SERVICE, + OAuthConstants.LogConstants.ActionIDs.VALIDATE_AUTHORIZATION_DETAILS) + .inputParam(LogConstants.InputKeys.CLIENT_ID, tokenReqDTO.getClientId()) + .inputParam(OAuthConstants.LogConstants.InputKeys.REQUESTED_AUTHORIZATION_DETAILS, + tokenReqDTO.getAuthorizationDetails().toSet()) + .resultMessage(AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_MSG) + .logDetailLevel(DiagnosticLog.LogDetailLevel.APPLICATION) + .resultStatus(DiagnosticLog.ResultStatus.FAILED)); + } + tokenRespDTO = handleError(AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_CODE, + AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_MSG, tokenReqDTO); + setResponseHeaders(tokReqMsgCtx, tokenRespDTO); + triggerPostListeners(tokenReqDTO, tokenRespDTO, tokReqMsgCtx, isRefreshRequest); + return tokenRespDTO; + } + } + handleTokenBinding(tokenReqDTO, grantType, tokReqMsgCtx, oAuthAppDO); try { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java index 4f1a55358ae..693b7fe8abf 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java @@ -21,6 +21,7 @@ import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding; import java.util.Properties; @@ -56,6 +57,8 @@ public class OAuthTokenReqMessageContext { private boolean isImpersonationRequest; + private AuthorizationDetails authorizationDetails; + public OAuthTokenReqMessageContext(OAuth2AccessTokenReqDTO oauth2AccessTokenReqDTO) { this.oauth2AccessTokenReqDTO = oauth2AccessTokenReqDTO; @@ -183,4 +186,26 @@ public void setImpersonationRequest(boolean impersonationRequest) { isImpersonationRequest = impersonationRequest; } + + /** + * Retrieves the user consented or authorized authorization details. + * + * @return the {@link AuthorizationDetails} instance representing the rich authorization requests. + * If no authorization details are available, it will return {@code null}. + */ + public AuthorizationDetails getAuthorizationDetails() { + + return this.authorizationDetails; + } + + /** + * Sets the validated authorization details. + * This method updates the authorization details with the provided {@link AuthorizationDetails} instance. + * + * @param authorizationDetails the {@link AuthorizationDetails} to set. + */ + public void setAuthorizationDetails(final AuthorizationDetails authorizationDetails) { + + this.authorizationDetails = authorizationDetails; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java index 4fefb780b8b..1ab7b78a5cf 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java @@ -54,6 +54,9 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; +import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsService; +import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; +import org.wso2.carbon.identity.oauth2.rar.util.AuthorizationDetailsUtils; import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; import org.wso2.carbon.identity.oauth2.token.OauthTokenIssuer; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; @@ -89,6 +92,7 @@ public abstract class AbstractAuthorizationGrantHandler implements Authorization protected static final String EXISTING_TOKEN_ISSUED = "existingTokenUsed"; protected static final int SECONDS_TO_MILISECONDS_FACTOR = 1000; private boolean isHashDisabled = OAuth2Util.isHashDisabled(); + protected AuthorizationDetailsService authorizationDetailsService; @Override public void init() throws IdentityOAuth2Exception { @@ -98,6 +102,7 @@ public void init() throws IdentityOAuth2Exception { cacheEnabled = true; oauthCache = OAuthCache.getInstance(); } + this.authorizationDetailsService = OAuth2ServiceComponentHolder.getInstance().getAuthorizationDetailsService(); } @Override @@ -419,6 +424,11 @@ private OAuth2AccessTokenRespDTO issueExistingAccessToken(OAuthTokenReqMessageCo existingTokenBean.getTokenId(), true); } + if (AuthorizationDetailsUtils.isRichAuthorizationRequest(tokReqMsgCtx)) { + this.authorizationDetailsService.replaceAccessTokenAuthorizationDetails(existingTokenBean.getTokenId(), + existingTokenBean, tokReqMsgCtx); + } + setDetailsToMessageContext(tokReqMsgCtx, existingTokenBean); return createResponseWithTokenBean(existingTokenBean, expireTime, scope); } @@ -555,6 +565,8 @@ private void persistAccessTokenInDB(OAuthTokenReqMessageContext tokReqMsgCtx, Ac } storeAccessToken(tokenReq, getUserStoreDomain(tokReqMsgCtx.getAuthorizedUser()), newTokenBean, newAccessToken, existingTokenBean); + this.authorizationDetailsService + .storeOrReplaceAccessTokenAuthorizationDetails(newTokenBean, existingTokenBean, tokReqMsgCtx); } private void updateCacheIfEnabled(AccessTokenDO newTokenBean, String scope, OauthTokenIssuer oauthTokenIssuer) @@ -1176,4 +1188,19 @@ private boolean isFederatedUser(OAuthTokenReqMessageContext tokReqMsgCtx) { } return tokReqMsgCtx.getAuthorizedUser().isFederatedUser(); } + + protected void setRARPropertiesForTokenGeneration(final OAuthTokenReqMessageContext oAuthTokenReqMessageContext) + throws IdentityOAuth2Exception { + + final int tenantId = OAuth2Util + .getTenantId(oAuthTokenReqMessageContext.getOauth2AccessTokenReqDTO().getTenantDomain()); + + final AuthorizationDetails userConsentedAuthorizationDetails = + this.authorizationDetailsService.getUserConsentedAuthorizationDetails( + oAuthTokenReqMessageContext.getAuthorizedUser(), + oAuthTokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId(), + tenantId); + + oAuthTokenReqMessageContext.setAuthorizationDetails(userConsentedAuthorizationDetails); + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AuthorizationCodeGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AuthorizationCodeGrantHandler.java index 21b3ae46d03..e1115522487 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AuthorizationCodeGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AuthorizationCodeGrantHandler.java @@ -126,12 +126,15 @@ public String buildSyncLockString(OAuthTokenReqMessageContext tokReqMsgCtx) { } private void setPropertiesForTokenGeneration(OAuthTokenReqMessageContext tokReqMsgCtx, - OAuth2AccessTokenReqDTO tokenReq, AuthzCodeDO authzCodeBean) { + OAuth2AccessTokenReqDTO tokenReq, AuthzCodeDO authzCodeBean) + throws IdentityOAuth2Exception { + tokReqMsgCtx.setAuthorizedUser(authzCodeBean.getAuthorizedUser()); tokReqMsgCtx.setScope(authzCodeBean.getScope()); // keep the pre processed authz code as a OAuthTokenReqMessageContext property to avoid // calculating it again when issuing the access token. tokReqMsgCtx.addProperty(AUTHZ_CODE, tokenReq.getAuthorizationCode()); + super.setRARPropertiesForTokenGeneration(tokReqMsgCtx); } private boolean validateCallbackUrlFromRequest(String callbackUrlFromRequest, diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java index 8a2752997c5..ccebaa26503 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java @@ -131,6 +131,8 @@ public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) // sets accessToken, refreshToken and validity data setTokenData(accessTokenBean, tokReqMsgCtx, validationBean, tokenReq, accessTokenBean.getIssuedTime()); persistNewToken(tokReqMsgCtx, accessTokenBean, tokenReq.getClientId()); + super.authorizationDetailsService + .replaceAccessTokenAuthorizationDetails(validationBean.getTokenId(), accessTokenBean, tokReqMsgCtx); if (log.isDebugEnabled()) { log.debug("Persisted an access token for the refresh token, " + @@ -222,6 +224,7 @@ private void setPropertiesForTokenGeneration(OAuthTokenReqMessageContext tokReqM // Store the old access token as a OAuthTokenReqMessageContext property, this is already // a preprocessed token. tokReqMsgCtx.addProperty(PREV_ACCESS_TOKEN, validationBean); + super.setRARPropertiesForTokenGeneration(tokReqMsgCtx); } private boolean validateRefreshTokenInRequest(OAuth2AccessTokenReqDTO tokenReq, diff --git a/pom.xml b/pom.xml index 278ca1bfc96..e9dec24b8d3 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,6 @@ features/org.wso2.carbon.identity.oauth.server.feature features/org.wso2.carbon.identity.oauth.ui.feature features/org.wso2.carbon.identity.oauth.dcr.server.feature - components/org.wso2.carbon.identity.oauth.rar.common components/org.wso2.carbon.identity.oauth.rar @@ -559,11 +558,6 @@ org.wso2.carbon.identity.oauth.rar ${project.version} - - org.wso2.carbon.identity.inbound.auth.oauth2 - org.wso2.carbon.identity.oauth.rar.common - ${project.version} -