Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rich authorization requests #2511

Merged
merged 29 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c8d568e
accept and persist authorization_details of authorize request
VimukthiRajapaksha Jul 2, 2024
f7544ce
Accept and persist authorization_details of token request
VimukthiRajapaksha Jul 26, 2024
dd8f27f
resolve merge conflicts
VimukthiRajapaksha Jul 31, 2024
824163c
Add unit tests for the rar module
VimukthiRajapaksha Aug 7, 2024
3360d4e
Merge remote-tracking branch 'origin/master' into master_rar
VimukthiRajapaksha Aug 7, 2024
3031c32
Add unit tests
VimukthiRajapaksha Sep 3, 2024
376e5cc
Fix merge conflicts
VimukthiRajapaksha Sep 3, 2024
b12db7c
Add schema validation
VimukthiRajapaksha Oct 8, 2024
8256664
Merge branch 'master' into master_rar
VimukthiRajapaksha Oct 8, 2024
c3c9fe9
Add unit tests to discovery endpoint
VimukthiRajapaksha Oct 9, 2024
7e5ac32
Change schema type to CLOB
VimukthiRajapaksha Oct 25, 2024
d5249a2
Resolve merge conflicts
VimukthiRajapaksha Oct 25, 2024
ca2f054
Resolve merge conflicts
VimukthiRajapaksha Oct 28, 2024
9c8fe25
Resolve merge conflicts
VimukthiRajapaksha Oct 28, 2024
ffba19a
Add license headers
VimukthiRajapaksha Oct 28, 2024
4c56858
Add a config method to check if RAR is enabled
VimukthiRajapaksha Nov 13, 2024
7a90b09
Resolve merge conflicts
VimukthiRajapaksha Nov 13, 2024
faa5e31
Fix refresh token introspection issue
VimukthiRajapaksha Nov 13, 2024
4a156b2
Resolve merge conflicts
VimukthiRajapaksha Dec 18, 2024
bc0ac7d
resolve merge conflicts
VimukthiRajapaksha Jan 9, 2025
95792de
Resolve test failures
VimukthiRajapaksha Jan 17, 2025
d5fde99
Merge remote-tracking branch 'origin/master' into master_rar
VimukthiRajapaksha Jan 17, 2025
61bf493
Improve unit tests coverage
VimukthiRajapaksha Jan 17, 2025
f523061
bump carbon.identity.framework.version to support authorization details
VimukthiRajapaksha Jan 17, 2025
be2070a
Add logic to check RAR is enabled
VimukthiRajapaksha Jan 17, 2025
0c03aa3
Update license to 2025
VimukthiRajapaksha Jan 17, 2025
7cf9f62
Change org.wso2.carbon.identity.oauth.rar packaging type to bundle
VimukthiRajapaksha Jan 22, 2025
0a80f81
Merge remote-tracking branch 'origin/master' into master_rar
VimukthiRajapaksha Jan 22, 2025
4959016
Bump RAR version to 7.0.219-SNAPSHOT
VimukthiRajapaksha Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
VimukthiRajapaksha marked this conversation as resolved.
Show resolved Hide resolved
} 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,
VimukthiRajapaksha marked this conversation as resolved.
Show resolved Hide resolved
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) {
VimukthiRajapaksha marked this conversation as resolved.
Show resolved Hide resolved
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);
VimukthiRajapaksha marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
Loading