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

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure to add diagnostic logs

} 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this validation logic to service layer where we validate scopes?

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we catch client exception and throw system exception here?

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it good to encode an entire JSON string and send it as a parameter? This can cause several issues

}

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