-
Notifications
You must be signed in to change notification settings - Fork 368
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
c8d568e
f7544ce
dd8f27f
824163c
3360d4e
3031c32
376e5cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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(); | ||
|
@@ -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 | ||
|
@@ -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); | ||
} | ||
} | ||
} | ||
|
@@ -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); | ||
|
@@ -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)) { | ||
|
@@ -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); | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
|
@@ -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; | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
|
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; | ||
} |
There was a problem hiding this comment.
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