-
Notifications
You must be signed in to change notification settings - Fork 400
Enable Authorization Code grant support for sub organization OAuth2 applications #2971
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
Enable Authorization Code grant support for sub organization OAuth2 applications #2971
Conversation
...ity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/AuthzUtil.java
Outdated
Show resolved
Hide resolved
...ity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/AuthzUtil.java
Outdated
Show resolved
Hide resolved
| */ | ||
| public static String getSPTenantDomainFromClientId(String clientId, String tenantDomain) { | ||
|
|
||
| try { |
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.
Log Improvement Suggestion No: 3
| */ | |
| public static String getSPTenantDomainFromClientId(String clientId, String tenantDomain) { | |
| try { | |
| public static String getSPTenantDomainFromClientId(String clientId, String tenantDomain) { | |
| if (log.isDebugEnabled()) { | |
| log.debug("Retrieving SP tenant domain for client ID: " + clientId + " from tenant: " + tenantDomain); | |
| } | |
| try { |
....oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/EndpointUtil.java
Outdated
Show resolved
Hide resolved
...c/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java
Show resolved
Hide resolved
...c/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java
Show resolved
Hide resolved
...a/org/wso2/carbon/identity/oauth2/authz/validators/AbstractResponseTypeRequestValidator.java
Show resolved
Hide resolved
...a/org/wso2/carbon/identity/oauth2/authz/validators/AbstractResponseTypeRequestValidator.java
Show resolved
Hide resolved
...entity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/AuthorizationCodeDAOImpl.java
Show resolved
Hide resolved
...entity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/AuthorizationCodeDAOImpl.java
Show resolved
Hide resolved
...java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AuthorizationCodeGrantHandler.java
Show resolved
Hide resolved
...java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AuthorizationCodeGrantHandler.java
Show resolved
Hide resolved
...so2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/OAuth2Util.java
Show resolved
Hide resolved
...so2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/OAuth2Util.java
Show resolved
Hide resolved
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.
AI Agent Log Improvement Checklist
- The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
- Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.
✅ Before merging this pull request:
- Review all AI-generated comments for accuracy and relevance.
- Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
8c609cb to
8cdfaa2
Compare
dcff15b to
1c25883
Compare
1c25883 to
42eae45
Compare
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.
Pull request overview
This pull request enables Authorization Code grant support for sub-organization OAuth2 applications by adding organization-aware context handling throughout the OAuth2/OIDC flows. The changes allow applications registered in sub-organizations to properly authenticate users and issue tokens while maintaining proper tenant and organization isolation.
Key Changes
- Updated OAuth2 utility methods to accept explicit tenant domain parameters instead of relying solely on thread-local context
- Added organization context resolution logic throughout authorization code generation, validation, and OIDC logout flows
- Modified OIDC session cookie path handling to support sub-organization applications with tenant-qualified paths
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pom.xml | Updated carbon.identity.framework.version to 7.8.598 to support the new sub-organization OAuth2 features |
| OIDCLogoutServlet.java | Added organization context resolution for logout flows, including tenant domain lookup from organization ID and proper error handling for OrganizationManagementException |
| DefaultOIDCSessionStateManager.java | Enhanced cookie path handling to support sub-organization applications by checking applicationResidentOrganizationId and setting appropriate tenant-qualified cookie paths |
| OAuth2ServiceTest.java | Updated test mocks to use the new two-parameter OAuth2Util methods that require explicit tenant domain |
| OAuth2Util.java | Modified getIdTokenIssuer to use root tenant domain when applicationResidentOrganizationId is present, ensuring correct issuer for sub-organization apps |
| AuthorizationCodeGrantHandler.java | Updated validatePKCECode to accept tenantDomain parameter and pass it to getOAuthAppDO for proper tenant-aware app retrieval |
| AuthorizationCodeDAOImpl.java | Added organization context handling in authorization code validation, resolving tenant ID from organization and setting user organization context on AuthenticatedUser |
| AbstractResponseTypeRequestValidator.java | Modified client validation to resolve login tenant ID and pass it to OAuth2Util methods for proper tenant-aware app lookups |
| ResponseTypeHandlerUtil.java | Added logic to set accessing organization and user resident organization IDs on authorization code generation for sub-org apps |
| AuthzUtilTest.java | Updated test to mock OAuth2Util methods with explicit tenant domain parameters for proper test isolation |
| OAuth2ParEndpointTest.java | Added tenant domain mocking and updated OAuth2Util method calls to use two-parameter versions |
| OAuth2AuthzEndpointTest.java | Added AuthzUtil.isLegacyAuthzRuntime mocking to support new authorization runtime behavior |
| EndpointUtil.java | Added new overloaded getSPTenantDomainFromClientId method that accepts explicit tenant domain parameter |
| AuthzUtil.java | Enhanced multiple methods to resolve tenant domain from organization context, including populateValidationResponseWithAppDetail, getSpTenantDomain, getLoginTenantDomain, and associateAuthenticationHistory |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| " and user resident organization in the authorization code data object."); | ||
| } | ||
| authzCodeDO.getAuthorizedUser().setAccessingOrganization(appResidentOrganizationId); | ||
| authzCodeDO.getAuthorizedUser().setUserResidentOrganization(appResidentOrganizationId); |
Copilot
AI
Nov 21, 2025
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.
The logic in this code block appears to set both setAccessingOrganization and setUserResidentOrganization to the same value (appResidentOrganizationId). This seems incorrect - the user resident organization should be different from the accessing organization (the app's organization).
Looking at similar logic in AuthorizationCodeDAOImpl.java (lines 278-283), the correct pattern is:
setAccessingOrganization(appResidentOrganizationId)- the organization where the app residessetUserResidentOrganization(userOrganizationId)- the organization where the user resides
The user resident organization should be resolved from the user's tenant domain, not set to the app's resident organization ID.
| authzCodeDO.getAuthorizedUser().setUserResidentOrganization(appResidentOrganizationId); | |
| String userTenantDomain = authzCodeDO.getAuthorizedUser().getTenantDomain(); | |
| String userResidentOrganizationId = OAuth2Util.getOrganizationIdByTenantDomain(userTenantDomain); | |
| authzCodeDO.getAuthorizedUser().setUserResidentOrganization(userResidentOrganizationId); |
| log.error(msg + " Client id from id token: " + clientId, e); | ||
| redirectURL = getErrorPageURL(OAuth2ErrorCodes.ACCESS_DENIED, msg); | ||
| } catch (OrganizationManagementException e) { | ||
| String msg = "Error occurred while resolving tenant domain from application resident org id."; |
Copilot
AI
Nov 21, 2025
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.
The error message "Error occurred while resolving tenant domain from application resident org id." is missing a specific detail. It should include the organization ID in the log message (similar to line 418 which has "for organization") to aid in debugging when issues occur.
| String msg = "Error occurred while resolving tenant domain from application resident org id."; | |
| String msg = "Error occurred while resolving tenant domain from application resident org id: " + | |
| appResidentOrgId + "."; |
| log.debug("Setting accessing organization id: " + appResidentOrganizationId + | ||
| " and user resident organization in the authorization code data object."); |
Copilot
AI
Nov 21, 2025
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.
The debug log message claims to be "Setting accessing organization id: ... and user resident organization" but only logs the appResidentOrganizationId. This is misleading. Since both values are being set to the same ID (which is incorrect per comment #1), this message should be updated when the bug is fixed to log both values distinctly, or the message should be corrected to reflect what's actually being done.
| log.debug("Setting accessing organization id: " + appResidentOrganizationId + | |
| " and user resident organization in the authorization code data object."); | |
| log.debug("Setting both accessing organization id and user resident organization to: " + | |
| appResidentOrganizationId + " in the authorization code data object."); |
| redirectURL = getErrorPageURL(OAuth2ErrorCodes.ACCESS_DENIED, msg); | ||
| return getRedirectURL(redirectURL, request); | ||
| } catch (OrganizationManagementException e) { | ||
| String msg = "Error occurred while resolving tenant domain for organization."; |
Copilot
AI
Nov 21, 2025
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.
The exception is caught but not logged. Unlike the other catch blocks (lines 400-401, 412-413) which log the error using log.debug(), this catch block for OrganizationManagementException doesn't log the exception. This makes debugging difficult when organization resolution fails. Consider adding:
if (log.isDebugEnabled()) {
log.debug(msg, e);
}| String msg = "Error occurred while resolving tenant domain for organization."; | |
| String msg = "Error occurred while resolving tenant domain for organization."; | |
| if (log.isDebugEnabled()) { | |
| log.debug(msg, e); | |
| } |
| } catch (OrganizationManagementException e) { | ||
| String msg = "Error occurred while resolving tenant domain from application resident org id."; | ||
| log.error(msg + " Client id from id token: " + clientId, e); | ||
| redirectURL = getErrorPageURL(OAuth2ErrorCodes.ACCESS_DENIED, msg); |
Copilot
AI
Nov 21, 2025
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.
The exception is caught but not logged. Unlike the other catch blocks in the same method (lines 1069-1071 which log using log.error()), this catch block for OrganizationManagementException doesn't log the exception. This makes debugging difficult when organization resolution fails. Consider adding:
log.error(msg + " Client id from id token: " + clientId, e);
...ity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/AuthzUtil.java
Show resolved
Hide resolved
| log.error("Error while getting oauth app for client Id: " + clientId, e); | ||
| return MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; | ||
| } catch (InvalidOAuthClientException e) { | ||
| if (log.isDebugEnabled()) { | ||
| log.debug("Error while getting oauth app for client Id: " + clientId, e); | ||
| } | ||
| return MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; | ||
| } |
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.
Are these fallbacks intentional?
...c/main/java/org/wso2/carbon/identity/oauth2/authz/handlers/util/ResponseTypeHandlerUtil.java
Show resolved
Hide resolved
42eae45 to
0b9c5d6
Compare
34d5b83 to
711f1c8
Compare
Codecov Report❌ Patch coverage is ❌ Your patch status has failed because the patch coverage (25.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## master #2971 +/- ##
============================================
- Coverage 58.07% 57.15% -0.92%
+ Complexity 9628 9483 -145
============================================
Files 669 669
Lines 53605 55101 +1496
Branches 12235 12640 +405
============================================
+ Hits 31131 31494 +363
- Misses 18118 19185 +1067
- Partials 4356 4422 +66
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| } | ||
| user.setAuthenticatedSubjectIdentifier(subjectIdentifier, serviceProvider); | ||
| if (StringUtils.isNotEmpty(appResidentOrganizationId)) { | ||
| String userOrganizationId = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() |
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.
here the user's org id is always the sub org app residing org righ?
Better to resolve the user resident org correctly as this logic will fail when the app is shared
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.
The user resident organization is resolved from the tenant domain. Tenant domain is extracted from the database result and it is used to create the authenticated user object as well (line 267).
| return MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; | ||
| } catch (InvalidOAuthClientException e) { | ||
| if (log.isDebugEnabled()) { | ||
| log.debug("Error while getting oauth app for client Id: " + clientId, e); | ||
| } | ||
| return MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; |
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.
Why it's fallback to carbon super tenant? Ideally it should be an error case or invalid tenant id (-1)
...ity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/util/AuthzUtil.java
Show resolved
Hide resolved
| try { | ||
| OAuthAppDO appDO = OAuth2Util.getAppInformationByClientId(clientId); | ||
| OAuthAppDO appDO; | ||
| String appResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() |
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.
Introduce a new method to get the application tenant domain and reuse it
| String appResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() | ||
| .getApplicationResidentOrganizationId(); | ||
| if (StringUtils.isNotEmpty(appResidentOrgId)) { | ||
| String appResidentTenantDomain; | ||
| try { | ||
| appResidentTenantDomain = FrameworkUtils.resolveTenantDomainFromOrganizationId(appResidentOrgId); | ||
| } catch (FrameworkException e) { | ||
| throw new InvalidRequestException("Error resolving tenant domain from organization id: " | ||
| + appResidentOrgId, OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.OAuth2SubErrorCodes | ||
| .INVALID_REQUEST); | ||
| } | ||
| return EndpointUtil.getSPTenantDomainFromClientId(clientId, appResidentTenantDomain); | ||
| } |
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.
Move this to line 2882
| * @param tenantDomain Tenant domain to which the client ID belongs. | ||
| * @return tenantDomain domain of the service provider. | ||
| */ | ||
| public static String getSPTenantDomainFromClientId(String clientId, String tenantDomain) { |
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.
Verifying method
481b62c to
b1bbf27
Compare
| session is stored against the primary organization. | ||
| */ | ||
| try { | ||
| String appTenantDomain = OAuth2Util.getTenantDomainOfOauthApp( |
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.
resultFromLogin.getoAuth2Parameters().getClientId() can this be null here?
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.
This is populated from the session. Based on the current path that the operation is hapenning, this is not getting null.
| if (OrganizationManagementUtil.isOrganization(appTenantDomain)) { | ||
| String appOrgId = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() | ||
| .resolveOrganizationId(appTenantDomain); | ||
| String primaryOrgId = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() |
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.
Is primary orgId and login org Id are same?
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.
No, primary org id is the org id of the root organization. Login org id is the application resident organization
| Boolean.parseBoolean(IdentityUtil.getProperty(ALLOW_SESSION_BOUND_TOKENS_AFTER_IDLE_SESSION_EXPIRY)); | ||
| } | ||
|
|
||
| public static String extractTenantDomain() throws IdentityOAuth2Exception { |
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.
Add javadoc comments. Can we rename this method? getLoginTenant?
| if (StringUtils.isNotBlank(appResidentOrgId)) { | ||
| // Handling the cookie path for request coming with the path `/t/<tenant-domain>/o/<org-id>`. | ||
| // The cookie will be set to the root tenant path. | ||
| loginTenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); |
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.
Shall we get the loginTenantDomain from the OAuth2Util.extractTenantDomain method and simply these logics?
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.
No we cannot. Here we need the root organization tenant domain since the cookies needs to be stored in the root tenant level.
1d1619e to
9b9d1bf
Compare
9b9d1bf to
41e01ac
Compare
| if (StringUtils.equals(extractedTenantDomain, appTenantDomain)) { | ||
| return appTenantDomain; | ||
| } | ||
| throw new OAuthSystemException("Provided tenant domain: " + extractedTenantDomain + " does not " + |
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.
Is this a new type of exception? Are we using this in other places? isn't this a client error.
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.
We have used this exception in the Endpoint utils
|
Please send the PRs for rest of the unit tests |
|
PR builder started |
|
PR builder completed |
jenkins-is-staging
left a comment
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.
Approving the pull request based on the successful pr build https://github.com/wso2/product-is/actions/runs/19668294722
fda8ddf
into
wso2-extensions:master
Proposed changes in this pull request