Skip to content

Commit

Permalink
Accept and persist authorization_details of token request
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
VimukthiRajapaksha committed Jul 26, 2024
1 parent c8d568e commit f7544ce
Show file tree
Hide file tree
Showing 63 changed files with 2,761 additions and 1,454 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}

/**
Expand All @@ -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";
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -4808,41 +4813,74 @@ 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.");
return;
}

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);
}
}
Original file line number Diff line number Diff line change
@@ -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<AuthorizationDetailsService> {

private AuthorizationDetailsService authorizationDetailsService;

@Override
public Class<AuthorizationDetailsService> getObjectType() {

return AuthorizationDetailsService.class;
}

@Override
protected AuthorizationDetailsService createInstance() throws Exception {

if (this.authorizationDetailsService == null) {
this.authorizationDetailsService =
OAuth2ServiceComponentHolder.getInstance().getAuthorizationDetailsService();
}
return this.authorizationDetailsService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -2153,4 +2155,26 @@ public static void preHandleParRequest(HttpServletRequest request, Map<String, S
throw new ParClientException(e.getErrorCode(), e.getMessage());
}
}

private static AuthorizationDetails filterConsentRequiredAuthorizationDetails(
final AuthenticatedUser authenticatedUser, final OAuth2Parameters oAuth2Parameters)
throws IdentityOAuth2Exception {

if (authenticatedUser != null && !isPromptContainsConsent(oAuth2Parameters)) {

final AuthorizationDetails consentRequiredAuthorizationDetails = OAuth2ServiceComponentHolder.getInstance()
.getAuthorizationDetailsService()
.getConsentRequiredAuthorizationDetails(authenticatedUser, oAuth2Parameters);

if (log.isDebugEnabled()) {
log.debug(String.format("Consent required authorization details for clientId %s and userId %s : %s",
oAuth2Parameters.getClientId(), authenticatedUser.getLoggableMaskedUserId(),
consentRequiredAuthorizationDetails.toJsonString()));
}
return AuthorizationDetailsUtils
.getDisplayableAuthorizationDetails(consentRequiredAuthorizationDetails);
}
return AuthorizationDetailsUtils
.getDisplayableAuthorizationDetails(oAuth2Parameters.getAuthorizationDetails());
}
}
Loading

0 comments on commit f7544ce

Please sign in to comment.