Skip to content

Commit

Permalink
accept and persist
Browse files Browse the repository at this point in the history
- Accept 'authorization_details' field in the authorization request.
- Persist code and consent authorization details in the database.
- Add support for oauth.rar and oauth.rar.common modules.
- Read custom implementations of AuthorizationDetailsProvider from SPI.
- Display rich authorization details in the consent UI.
  • Loading branch information
VimukthiRajapaksha committed Jul 2, 2024
1 parent e4620e8 commit f9d06d5
Show file tree
Hide file tree
Showing 36 changed files with 2,802 additions and 2 deletions.
5 changes: 5 additions & 0 deletions components/org.wso2.carbon.identity.oauth.endpoint/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@
<artifactId>jackson-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.inbound.auth.oauth2</groupId>
<artifactId>org.wso2.carbon.identity.oauth.rar</artifactId>
<scope>provided</scope>
</dependency>

<!--Test Dependencies-->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
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 @@ -134,6 +135,12 @@
import org.wso2.carbon.identity.oauth2.model.FederatedTokenDO;
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.util.AuthorizationDetailsUtils;
import org.wso2.carbon.identity.oauth2.responsemode.provider.AuthorizationResponseDTO;
import org.wso2.carbon.identity.oauth2.responsemode.provider.ResponseModeProvider;
import org.wso2.carbon.identity.oauth2.scopeservice.ScopeMetadataService;
Expand Down Expand Up @@ -280,6 +287,9 @@ public class OAuth2AuthzEndpoint {
private static ScopeMetadataService scopeMetadataService;

private static DeviceAuthService deviceAuthService;

private static AuthorizationDetailsService authorizationDetailsService;

private static final String AUTH_SERVICE_RESPONSE = "authServiceResponse";
private static final String IS_API_BASED_AUTH_HANDLED = "isApiBasedAuthHandled";
private static final ApiAuthnHandler API_AUTHN_HANDLER = new ApiAuthnHandler();
Expand All @@ -304,6 +314,16 @@ public static void setScopeMetadataService(ScopeMetadataService scopeMetadataSer
OAuth2AuthzEndpoint.scopeMetadataService = scopeMetadataService;
}

public static AuthorizationDetailsService getAuthorizationDetailsService() {

return authorizationDetailsService;
}

public static void setAuthorizationDetailsService(AuthorizationDetailsService authorizationDetailsService) {

OAuth2AuthzEndpoint.authorizationDetailsService = authorizationDetailsService;
}

private static Class<? extends OAuthAuthzRequest> oAuthAuthzRequestClass;

@GET
Expand Down Expand Up @@ -1697,10 +1717,18 @@ 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);

if (hasPromptContainsConsent(oauth2Params)) {
EndpointUtil.storeOAuthScopeConsent(loggedInUser, oauth2Params, true);
authorizationDetailsService.storeOrReplaceUserConsentedAuthorizationDetails(loggedInUser,
clientId, oauth2Params, userConsentedAuthorizationDetails);
} else {
EndpointUtil.storeOAuthScopeConsent(loggedInUser, oauth2Params, false);
authorizationDetailsService.storeUserConsentedAuthorizationDetails(loggedInUser,
clientId, oauth2Params, userConsentedAuthorizationDetails);
}
}
}
Expand Down Expand Up @@ -2577,6 +2605,12 @@ private String populateOauthParameters(OAuth2Parameters params, OAuthMessage oAu
params.setEssentialClaims(oauthRequest.getParam(CLAIMS));
}

if (AuthorizationDetailsUtils.isRichAuthorizationRequest(oauthRequest)) {
final String authorizationDetailsJson = oauthRequest
.getParam(AuthorizationDetailsConstants.AUTHORIZATION_DETAILS);
params.setAuthorizationDetails(new AuthorizationDetails(authorizationDetailsJson));
}

handleMaxAgeParameter(oauthRequest, params);

Object isMtls = oAuthMessage.getRequest().getAttribute(OAuthConstants.IS_MTLS_REQUEST);
Expand Down Expand Up @@ -2980,6 +3014,22 @@ private String doUserAuthorization(OAuthMessage oAuthMessage, String sessionData
return handleAuthorizationFailureBeforeConsent(oAuthMessage, oauth2Params, authorizeRespDTO);
}

try {
validateAuthorizationDetailsBeforeConsent(oAuthMessage, oauth2Params, authzReqDTO);
} catch (AuthorizationDetailsProcessingException e) {
log.debug("Error occurred while validating authorization details. Caused by, ", e);

authorizationResponseDTO.setError(HttpServletResponse.SC_FOUND,
AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_MSG,
AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_CODE);

OAuth2AuthorizeRespDTO oAuth2AuthorizeRespDTO = new OAuth2AuthorizeRespDTO();
oAuth2AuthorizeRespDTO.setErrorMsg(AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_MSG);
oAuth2AuthorizeRespDTO.setErrorCode(AuthorizationDetailsConstants.VALIDATION_FAILED_ERR_CODE);
oAuth2AuthorizeRespDTO.setCallbackURI(authzReqDTO.getCallbackUrl());
return handleAuthorizationFailureBeforeConsent(oAuthMessage, oauth2Params, oAuth2AuthorizeRespDTO);
}

boolean hasUserApproved = isUserAlreadyApproved(oauth2Params, authenticatedUser);

if (hasPromptContainsConsent(oauth2Params)) {
Expand Down Expand Up @@ -3673,7 +3723,8 @@ private boolean isUserAlreadyApproved(OAuth2Parameters oauth2Params, Authenticat
throws OAuthSystemException {

try {
return EndpointUtil.isUserAlreadyConsentedForOAuthScopes(user, oauth2Params);
return EndpointUtil.isUserAlreadyConsentedForOAuthScopes(user, oauth2Params) &&
authorizationDetailsService.isUserAlreadyConsentedForAuthorizationDetails(user, oauth2Params);
} catch (IdentityOAuth2ScopeException | IdentityOAuthAdminException e) {
throw new OAuthSystemException("Error occurred while checking user has already approved the consent " +
"required OAuth scopes.", e);
Expand Down Expand Up @@ -4746,4 +4797,52 @@ private Response handleUnsupportedGrantForApiBasedAuth() {
new AuthServiceClientException(AuthServiceConstants.ErrorMessage.ERROR_INVALID_AUTH_REQUEST.code(),
"App native authentication is only supported with code response type."), log);
}

/**
* Validates the authorization details in the provided OAuth message before user consent.
*
* <p>This method checks if the request is a rich authorization request. If it is, it
* retrieves and validates the authorization details, updating the parameters and context
* accordingly. If any validation errors occur, it logs the issue and throws an appropriate
* exception.</p>
*
* @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.
*/
private void validateAuthorizationDetailsBeforeConsent(final OAuthMessage oAuthMessage,
final OAuth2Parameters oAuth2Parameters,
final OAuth2AuthorizeReqDTO oAuth2AuthorizeReqDTO)
throws OAuthSystemException, AuthorizationDetailsProcessingException {

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);
// Validate the authorization details
final AuthorizationDetails validatedAuthorizationDetails = new AuthorizationDetailsValidator()
.getValidatedAuthorizationDetails(oAuth2Parameters, oAuthAppDO, oAuth2AuthorizeReqDTO.getUser());

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) {
log.error("Error occurred while validating authorization details. Caused by, ", e);
throw new OAuthSystemException("Error occurred while validating requested authorization details", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +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.util.AuthorizationDetailsUtils;
import org.wso2.carbon.identity.oauth2.scopeservice.OAuth2Resource;
import org.wso2.carbon.identity.oauth2.scopeservice.ScopeMetadataService;
import org.wso2.carbon.identity.oauth2.util.AuthzUtil;
Expand Down Expand Up @@ -868,6 +870,12 @@ public static String getUserConsentURL(OAuth2Parameters params, String loggedInU
(consentRequiredScopes, UTF_8) + "&" + OAuthConstants.SESSION_DATA_KEY_CONSENT
+ "=" + URLEncoder.encode(sessionDataKeyConsent, UTF_8) + "&" + "&spQueryParams=" + queryString;

// Append authorization details to consent page url
if (AuthorizationDetailsUtils.isRichAuthorizationRequest(params)) {
consentPageUrl = consentPageUrl + "&" + AuthorizationDetailsConstants.AUTHORIZATION_DETAILS + "="
+ URLEncoder.encode(params.getAuthorizationDetails().toJsonString(), UTF_8);
}

// Append scope metadata to additionalQueryParams.
String scopeMetadataQueryParam = getScopeMetadataQueryParam(params.getConsentRequiredScopes(),
params.getTenantDomain());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
import org.wso2.carbon.identity.oauth2.dto.OAuth2ClientValidationResponseDTO;
import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder;
import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters;
import org.wso2.carbon.identity.oauth2.rar.AuthorizationDetailsService;
import org.wso2.carbon.identity.oauth2.responsemode.provider.ResponseModeProvider;
import org.wso2.carbon.identity.oauth2.responsemode.provider.impl.DefaultResponseModeProvider;
import org.wso2.carbon.identity.oauth2.responsemode.provider.impl.FormPostResponseModeProvider;
Expand Down Expand Up @@ -284,6 +285,9 @@ public class OAuth2AuthzEndpointTest extends TestOAuthEndpointBase {
@Mock
private CentralLogMgtServiceComponentHolder centralLogMgtServiceComponentHolderMock;

@Mock
private AuthorizationDetailsService authorizationDetailsService;

private static final String ERROR_PAGE_URL = "https://localhost:9443/authenticationendpoint/oauth2_error.do";
private static final String LOGIN_PAGE_URL = "https://localhost:9443/authenticationendpoint/login.do";
private static final String USER_CONSENT_URL =
Expand Down Expand Up @@ -803,6 +807,10 @@ public void testAuthorizeForAuthenticationResponse(boolean isResultInRequest, bo
when(oAuth2ScopeService.hasUserProvidedConsentForAllRequestedScopes(
anyString(), isNull(), anyInt(), anyList())).thenReturn(true);

when(authorizationDetailsService.isUserAlreadyConsentedForAuthorizationDetails(
any(AuthenticatedUser.class), any(OAuth2Parameters.class))).thenReturn(true);
OAuth2AuthzEndpoint.setAuthorizationDetailsService(authorizationDetailsService);

mockServiceURLBuilder(serviceURLBuilder);
setSupportedResponseModes();
Response response = oAuth2AuthzEndpoint.authorize(httpServletRequest, httpServletResponse);
Expand Down Expand Up @@ -1644,6 +1652,8 @@ public void testHandleUserConsent(boolean isRespDTONull, String consent, boolean
OAuthAuthzReqMessageContext authzReqMsgCtx = new OAuthAuthzReqMessageContext(authorizeReqDTO);
when(consentCacheEntry.getAuthzReqMsgCtx()).thenReturn(authzReqMsgCtx);

OAuth2AuthzEndpoint.setAuthorizationDetailsService(authorizationDetailsService);

Response response;
try {
setSupportedResponseModes();
Expand Down
60 changes: 60 additions & 0 deletions components/org.wso2.carbon.identity.oauth.rar.common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.wso2.carbon.identity.inbound.auth.oauth2</groupId>
<artifactId>identity-inbound-auth-oauth</artifactId>
<version>7.0.107-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>org.wso2.carbon.identity.oauth.rar.common</artifactId>
<packaging>jar</packaging>
<name>WSO2 Carbon - Rich Authorization Requests Common</name>
<url>http://wso2.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.core</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>8</release>
</configuration>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<configuration>
<threshold>High</threshold>
<maxHeap>2048</maxHeap>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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<AuthorizationDetailsConsentDTO> 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<AuthorizationDetailsConsentDTO> getUserConsentedAuthorizationDetails(String consentId, int tenantId)
throws SQLException;

int[] updateUserConsentedAuthorizationDetails(List<AuthorizationDetailsConsentDTO> authorizationDetailsConsentDTOs)
throws SQLException;
}
Loading

0 comments on commit f9d06d5

Please sign in to comment.