From 06b64b5d9f3f603ce2e03a57d0674d92a7565139 Mon Sep 17 00:00:00 2001 From: Shan Chathusanda Jayathilaka Date: Sat, 25 Jan 2025 15:23:02 +0530 Subject: [PATCH] Allow sub organization applications to issue tokens to access the resources in sub organizations --- .../carbon/identity/oauth2/OAuth2Service.java | 27 ++++++++++-- .../BasicAuthClientAuthenticator.java | 25 ++++++++++- .../OAuthClientAuthnService.java | 20 ++++++++- .../oauth2/dao/AccessTokenDAOImpl.java | 20 ++++++++- .../oauth2/dao/TokenManagementDAOImpl.java | 25 +++++++++-- .../oauth2/token/AccessTokenIssuer.java | 31 ++++++++++++- .../identity/oauth2/token/JWTTokenIssuer.java | 43 ++++++++++++++++-- .../AbstractAuthorizationGrantHandler.java | 35 +++++++++++++-- .../handlers/grant/PasswordGrantHandler.java | 34 +++++++++++++- .../handlers/grant/RefreshGrantHandler.java | 12 ++--- .../identity/oauth2/util/AuthzUtil.java | 18 +++++--- .../carbon/identity/oauth2/util/JWTUtils.java | 26 ++++++++++- .../DefaultOAuth2ScopeValidator.java | 12 +++-- .../validators/TokenValidationHandler.java | 26 ++++++++++- .../openidconnect/DefaultIDTokenBuilder.java | 16 +++++-- .../identity/oauth2/OAuth2ServiceTest.java | 25 ++++++++--- .../BasicAuthClientAuthenticatorTest.java | 8 ++-- .../OAuthClientAuthnServiceTest.java | 4 +- .../oauth2/token/JWTTokenIssuerTest.java | 44 +++++++++++++++---- ...AbstractAuthorizationGrantHandlerTest.java | 23 ++++++++-- .../grant/PasswordGrantHandlerTest.java | 13 ++++++ 21 files changed, 423 insertions(+), 64 deletions(-) diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java index d32a2abc8c1..c99a58a8771 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2013-2025, WSO2 LLC. (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 @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.owasp.encoder.Encode; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.core.AbstractAdmin; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.central.log.mgt.utils.LogConstants; @@ -63,6 +64,7 @@ import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinder; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.openidconnect.model.Constants; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import org.wso2.carbon.user.api.Claim; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.utils.DiagnosticLog; @@ -676,7 +678,9 @@ public OAuthRevocationResponseDTO revokeTokenByOAuthClient(OAuthRevocationReques } else if (accessTokenDO != null) { if (revokeRequestDTO.getConsumerKey().equals(accessTokenDO.getConsumerKey())) { - if ((OAuth2Util.getAppInformationByClientId(accessTokenDO.getConsumerKey()). + // Extracting the application details with consumer key and tenant domain. + String tenantDomain = IdentityTenantUtil.getTenantDomain(accessTokenDO.getTenantID()); + if ((OAuth2Util.getAppInformationByClientId(accessTokenDO.getConsumerKey(), tenantDomain). isTokenBindingValidationEnabled()) && (!isValidTokenBinding(accessTokenDO. getTokenBinding(), revokeRequestDTO.getRequest()))) { if (LoggerUtils.isDiagnosticLogsEnabled()) { @@ -981,7 +985,24 @@ public Claim[] getUserClaims(String accessTokenIdentifier) { public String getOauthApplicationState(String consumerKey) { try { - OAuthAppDO appDO = OAuth2Util.getAppInformationByClientId(consumerKey); + String tenantDomain = IdentityTenantUtil.getTenantDomain(IdentityTenantUtil.getLoginTenantId()); + String appOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If appOrgId is not empty, then the request comes for an application which is registered directly in the + organization of the appOrgId. Therefore, we need to resolve the tenant domain of the organization. + */ + if (StringUtils.isNotEmpty(appOrgId)) { + try { + tenantDomain = OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(appOrgId); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain for the organization ID: " + + appOrgId, e); + } + } + // Getting the application information by consumer key and tenant domain. + OAuthAppDO appDO = OAuth2Util.getAppInformationByClientId(consumerKey, tenantDomain); return appDO.getState(); } catch (IdentityOAuth2Exception e) { log.error("Error while finding application state for application with client_id: " + consumerKey, e); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticator.java index 891fe5f1b15..e282643502c 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticator.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2018-2025, 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 @@ -24,13 +24,17 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.OAuth; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.oauth.IdentityOAuthAdminException; import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes; import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.bean.OAuthClientAuthnContext; import org.wso2.carbon.identity.oauth2.model.ClientAuthenticationMethodModel; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import java.util.ArrayList; import java.util.Base64; @@ -89,8 +93,25 @@ public boolean authenticateClient(HttpServletRequest request, Map log.debug("Authenticating client : " + oAuthClientAuthnContext.getClientId() + " with client " + "secret."); } + String tenantDomain = IdentityTenantUtil.resolveTenantDomain(); + String appOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If appOrgId is not empty, then the request comes for an application which is registered directly in the + organization of the appOrgId. Therefore, we need to resolve the tenant domain of the organization. + */ + if (StringUtils.isNotEmpty(appOrgId)) { + try { + tenantDomain = OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(appOrgId); + } catch (OrganizationManagementException e) { + throw new InvalidOAuthClientException("Error while resolving tenant domain for the organization " + + "ID: " + appOrgId, e); + } + } + // Authenticating the client with the client id, the client secret and the extracted tenant domain. return OAuth2Util.authenticateClient(oAuthClientAuthnContext.getClientId(), - (String) oAuthClientAuthnContext.getParameter(OAuth.OAUTH_CLIENT_SECRET)); + (String) oAuthClientAuthnContext.getParameter(OAuth.OAUTH_CLIENT_SECRET), tenantDomain); } catch (IdentityOAuthAdminException e) { throw new OAuthClientAuthnException("Error while authenticating client", OAuth2ErrorCodes.INVALID_CLIENT, e); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnService.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnService.java index a36fe3f1251..bed9461bbab 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnService.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2018-2025, 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 @@ -21,16 +21,19 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes; import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.bean.OAuthClientAuthnContext; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.model.ClientAuthenticationMethodModel; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import java.util.ArrayList; import java.util.Arrays; @@ -321,6 +324,21 @@ private List getConfiguredClientAuthMethods(String cli throws OAuthClientAuthnException, InvalidOAuthClientException { String tenantDomain = IdentityTenantUtil.resolveTenantDomain(); + String appOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If appOrgId is not empty, then the request comes for an application which is registered directly in the + organization of the appOrgId. Therefore, we need to resolve the tenant domain of the organization. + */ + if (StringUtils.isNotEmpty(appOrgId)) { + try { + tenantDomain = OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(appOrgId); + } catch (OrganizationManagementException e) { + throw new InvalidOAuthClientException("Error while resolving tenant domain for the organization ID: " + + appOrgId, e); + } + } List configuredClientAuthMethods = new ArrayList<>(); try { OAuthAppDO oAuthAppDO = OAuth2Util.getAppInformationByClientId(clientId, tenantDomain); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/AccessTokenDAOImpl.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/AccessTokenDAOImpl.java index 64fac73b8e2..c3e8017dffb 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/AccessTokenDAOImpl.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/AccessTokenDAOImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -24,6 +24,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.database.utils.jdbc.JdbcTemplate; import org.wso2.carbon.database.utils.jdbc.exceptions.DataAccessException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; @@ -251,6 +252,23 @@ private void insertAccessToken(String accessToken, String consumerKey, AccessTok insertTokenPrepStmt.setString(19, authorizedOrganization); int appTenantId = IdentityTenantUtil.getLoginTenantId(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. Therefore, we need to resolve the + tenant domain of the organization to get the application tenant id. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + try { + String tenantDomain = OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + appTenantId = OAuth2Util.getTenantId(tenantDomain); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: " + + applicationResidentOrgId, e); + } + } if (OAuth2ServiceComponentHolder.isIDPIdColumnEnabled()) { if (OAuth2ServiceComponentHolder.isConsentedTokenColumnEnabled()) { insertTokenPrepStmt.setString(20, Boolean.toString(accessTokenDO.isConsentedToken())); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/TokenManagementDAOImpl.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/TokenManagementDAOImpl.java index aa7fe011a8c..cda4960b59a 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/TokenManagementDAOImpl.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/dao/TokenManagementDAOImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -24,6 +24,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; import org.wso2.carbon.identity.application.common.model.ServiceProvider; @@ -163,7 +164,25 @@ public RefreshTokenValidationDataDO validateRefreshToken(String consumerKey, Str prepStmt = connection.prepareStatement(sql); prepStmt.setString(1, getPersistenceProcessor().getProcessedClientId(consumerKey)); - prepStmt.setInt(2, IdentityTenantUtil.getLoginTenantId()); + int tenantId = IdentityTenantUtil.getLoginTenantId(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. Therefore, we need to resolve the + tenant domain of the organization to get the application tenant id. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + try { + String tenantDomain = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: " + + applicationResidentOrgId, e); + } + } + prepStmt.setInt(2, tenantId); if (refreshToken != null) { prepStmt.setString(3, getHashingPersistenceProcessor().getProcessedRefreshToken(refreshToken)); } @@ -183,7 +202,7 @@ public RefreshTokenValidationDataDO validateRefreshToken(String consumerKey, Str validationDataDO.setAccessToken(resultSet.getString(1)); } String userName = resultSet.getString(2); - int tenantId = resultSet.getInt(3); + tenantId = resultSet.getInt(3); String userDomain = resultSet.getString(4); String tenantDomain = OAuth2Util.getTenantDomain(tenantId); validationDataDO.setRefreshToken(refreshToken); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java index 9cf398b186f..bc77a0e8592 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -26,6 +26,7 @@ import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.owasp.encoder.Encode; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; @@ -316,6 +317,16 @@ public OAuth2AccessTokenRespDTO issue(OAuth2AccessTokenReqDTO tokenReqDTO) if (!isOfTypeApplicationUser) { tokReqMsgCtx.setAuthorizedUser(oAuthAppDO.getAppOwner()); tokReqMsgCtx.addProperty(OAuthConstants.UserType.USER_TYPE, OAuthConstants.UserType.APPLICATION); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. Therefore, we are setting the authorized + user's accessing organization as the applicationResidentOrgId. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + tokReqMsgCtx.getAuthorizedUser().setAccessingOrganization(applicationResidentOrgId); + } } else { tokReqMsgCtx.addProperty(OAuthConstants.UserType.USER_TYPE, OAuthConstants.UserType.APPLICATION_USER); } @@ -1398,7 +1409,23 @@ private void setResponseHeaders(OAuthTokenReqMessageContext tokReqMsgCtx, private OAuthAppDO getOAuthApplication(String consumerKey) throws InvalidOAuthClientException, IdentityOAuth2Exception { - OAuthAppDO authAppDO = OAuth2Util.getAppInformationByClientId(consumerKey); + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If the applicationResidentOrgId is not null, resolve the tenant domain from the organization id to get the + application information by passing the consumer key and the tenant domain. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + try { + tenantDomain = OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: " + + applicationResidentOrgId, e); + } + } + OAuthAppDO authAppDO = OAuth2Util.getAppInformationByClientId(consumerKey, tenantDomain); String appState = authAppDO.getState(); if (StringUtils.isEmpty(appState)) { if (log.isDebugEnabled()) { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java index 72b81f1e694..0bc3cf85f43 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -35,6 +35,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.base.IdentityConstants; @@ -55,6 +56,7 @@ import org.wso2.carbon.identity.openidconnect.CustomClaimsCallbackHandler; import org.wso2.carbon.identity.openidconnect.OIDCClaimUtil; import org.wso2.carbon.identity.openidconnect.util.ClaimHandlerUtil; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import java.security.Key; import java.security.cert.Certificate; @@ -418,7 +420,16 @@ private String getSigningTenantDomain(String clientID, AuthenticatedUser authent throws IdentityOAuth2Exception { String tenantDomain; - if (OAuthServerConfiguration.getInstance().getUseSPTenantDomainValue()) { + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. In this scenario, the signing tenant domain + should be the root tenant domain of the applicationResidentOrgId. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + } else if (OAuthServerConfiguration.getInstance().getUseSPTenantDomainValue()) { if (log.isDebugEnabled()) { log.debug("Using the tenant domain of the SP to sign the token"); } @@ -566,8 +577,25 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe // loading the stored application data OAuthAppDO oAuthAppDO; + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); try { - oAuthAppDO = OAuth2Util.getAppInformationByClientId(consumerKey); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. Therefore, the tenant domain should be + extracted from the organization id to get the information of the application. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + try { + tenantDomain = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: " + + applicationResidentOrgId, e); + } + } + oAuthAppDO = OAuth2Util.getAppInformationByClientId(consumerKey, tenantDomain); } catch (InvalidOAuthClientException e) { throw new IdentityOAuth2Exception("Error while retrieving app information for clientId: " + consumerKey, e); } @@ -584,6 +612,15 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe spTenantDomain = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getTenantDomain(); } + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. spTenantDomain is used to get the idTokenIssuer + for the token. In this scenario, the tenant domain that needs to be used as the issuer is the root tenant. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + spTenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();; + } + boolean isMTLSrequest; if (authAuthzReqMessageContext != null) { /* If the auth request is originated from a request object reference(ex: PAR), then that endpoint should be diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java index 02aa8ddfa52..c039d87e2c9 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2013-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -68,6 +68,7 @@ import org.wso2.carbon.identity.oauth2.validators.OAuth2ScopeHandler; import org.wso2.carbon.identity.oauth2.validators.scope.ScopeValidator; import org.wso2.carbon.identity.openidconnect.OIDCClaimUtil; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import org.wso2.carbon.utils.DiagnosticLog; import java.sql.Timestamp; @@ -848,10 +849,24 @@ private OAuth2AccessTokenRespDTO createResponseWithTokenBean(AccessTokenDO exist OAuthAppDO oAuthAppDO; String consumerKey = existingAccessTokenDO.getConsumerKey(); try { - oAuthAppDO = OAuth2Util.getAppInformationByClientId(consumerKey); + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. Therefore, the tenant domain should be + extracted from the organization id to get the information of the application. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + tenantDomain = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + } + oAuthAppDO = OAuth2Util.getAppInformationByClientId(consumerKey, tenantDomain); } catch (InvalidOAuthClientException e) { throw new IdentityOAuth2Exception("Error while retrieving app information for client_id : " + consumerKey, e); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: ", e); } if (issueRefreshToken(existingAccessTokenDO.getTokenType()) && @@ -895,7 +910,19 @@ private OAuthCacheKey getOAuthCacheKey(String scope, String consumerKey, String private OAuthAppDO getoAuthApp(String consumerKey) throws IdentityOAuth2Exception { OAuthAppDO oAuthAppBean; try { - oAuthAppBean = OAuth2Util.getAppInformationByClientId(consumerKey); + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. Therefore, the tenant domain should be + extracted from the organization id to get the information of the application. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + tenantDomain = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + } + oAuthAppBean = OAuth2Util.getAppInformationByClientId(consumerKey, tenantDomain); if (log.isDebugEnabled()) { log.debug("Service Provider specific expiry time enabled for application : " + consumerKey + ". Application access token expiry time : " + oAuthAppBean.getApplicationAccessTokenExpiryTime() @@ -904,6 +931,8 @@ private OAuthAppDO getoAuthApp(String consumerKey) throws IdentityOAuth2Exceptio } } catch (InvalidOAuthClientException e) { throw new IdentityOAuth2Exception("Error while retrieving app information for clientId: " + consumerKey, e); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: ", e); } return oAuthAppBean; } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandler.java index 5822cd1f35a..031600057d6 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2013-2025, 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 @@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory; import org.wso2.carbon.CarbonConstants; import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationDataPublisher; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationService; import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorFlowStatus; @@ -66,6 +67,7 @@ import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreClientException; @@ -273,13 +275,30 @@ private void setPropertiesForTokenGeneration(OAuthTokenReqMessageContext tokReqM tokReqMsgCtx.setScope(tokenReq.getScope()); } - private String getFullQualifiedUsername(OAuth2AccessTokenReqDTO tokenReq, ServiceProvider serviceProvider) { + private String getFullQualifiedUsername(OAuth2AccessTokenReqDTO tokenReq, ServiceProvider serviceProvider) + throws IdentityOAuth2Exception { boolean isEmailUserNameEnabled = MultitenantUtils.isEmailUserName(); boolean isSaasApp = serviceProvider.isSaasApp(); boolean isLegacySaaSAuthenticationEnabled = IdentityTenantUtil.isLegacySaaSAuthenticationEnabled(); String usernameFromRequest = tokenReq.getResourceOwnerUsername(); String tenantDomainFromContext = IdentityTenantUtil.resolveTenantDomain(); + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is registered + directly in the organization of the applicationResidentOrgId. In this scenario the user is also in the + organization level and the tenant domain of the user should be resolved from the organization id. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + try { + tenantDomainFromContext = OAuth2ServiceComponentHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(applicationResidentOrgId); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving tenant domain from the organization id: " + + applicationResidentOrgId, e); + } + } if (!isSaasApp) { /* @@ -393,6 +412,17 @@ private AuthenticatedUser validateUserCredentials(OAuth2AccessTokenReqDTO tokenR log.debug(PASSWORD_GRANT_POST_AUTHENTICATION_EVENT + " event is triggered"); } if (authenticatedUser.isPresent()) { + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is + registered directly in the organization of the applicationResidentOrgId. In this scenario the user's + accessing and resident organization will be the organization of the applicationResidentOrgId. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + authenticatedUser.get().setAccessingOrganization(applicationResidentOrgId); + authenticatedUser.get().setUserResidentOrganization(applicationResidentOrgId); + } return authenticatedUser.get(); } if (isPublishPasswordGrantLoginEnabled) { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java index db2788c89dc..e5f3fb6d82c 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2013-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -581,11 +581,11 @@ private ResponseHeader[] getResponseHeaders(OAuthTokenReqMessageContext tokReqMs return respHeaders; } - private OAuthAppDO getOAuthApp(String clientId) throws IdentityOAuth2Exception { + private OAuthAppDO getOAuthApp(String clientId, String tenantDomain) throws IdentityOAuth2Exception { OAuthAppDO oAuthAppDO; try { - oAuthAppDO = OAuth2Util.getAppInformationByClientId(clientId); + oAuthAppDO = OAuth2Util.getAppInformationByClientId(clientId, tenantDomain); } catch (InvalidOAuthClientException e) { throw new IdentityOAuth2Exception("Error while retrieving app information for clientId: " + clientId, e); @@ -649,7 +649,8 @@ private void setTokenData(AccessTokenDO accessTokenDO, OAuthTokenReqMessageConte RefreshTokenValidationDataDO validationBean, OAuth2AccessTokenReqDTO tokenReq, Timestamp timestamp) throws IdentityOAuth2Exception { - OAuthAppDO oAuthAppDO = getOAuthApp(tokenReq.getClientId()); + OAuthAppDO oAuthAppDO = getOAuthApp(tokenReq.getClientId(), validationBean.getAuthorizedUser(). + getTenantDomain()); createTokens(accessTokenDO, tokReqMsgCtx); setRefreshTokenData(accessTokenDO, tokenReq, validationBean, oAuthAppDO, accessTokenDO.getRefreshToken(), timestamp, tokReqMsgCtx); @@ -869,7 +870,8 @@ private boolean checkExecutePreIssueAccessTokensActions(RefreshTokenValidationDa OAuthTokenReqMessageContext tokenReqMessageContext) throws IdentityOAuth2Exception { - OAuthAppDO oAuthAppBean = getOAuthApp(tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId()); + OAuthAppDO oAuthAppBean = getOAuthApp(tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId(), + refreshTokenValidationDataDO.getAuthorizedUser().getTenantDomain()); String grantType = refreshTokenValidationDataDO.getGrantType(); // Allow if refresh token is issued for token requests from following grant types and, diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java index 29620bd069d..466cf79bbf4 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -255,13 +255,17 @@ public static boolean isUserAuthorized(AuthenticatedUser authenticatedUser, List // Application id is not required for basic authentication flow. List roleIds = getUserRoles(authenticatedUser, null); - String tenantDomain = authenticatedUser.getTenantDomain(); - if (StringUtils.isNotBlank(authenticatedUser.getAccessingOrganization()) && - !authenticatedUser.getAccessingOrganization(). - equals(authenticatedUser.getUserResidentOrganization())) { - tenantDomain = getAccessingTenantDomain(authenticatedUser); + List permissions; + /* + If the authenticatedUser contains an accessing organization, the the scopes should be checked against the + accessing organization. + */ + if (StringUtils.isNotEmpty(authenticatedUser.getAccessingOrganization())) { + permissions = getAssociatedScopesForRoles(roleIds, + authenticatedUser.getAccessingOrganization()); + } else { + permissions = getAssociatedScopesForRoles(roleIds, authenticatedUser.getTenantDomain()); } - List permissions = getAssociatedScopesForRoles(roleIds, tenantDomain); if (OAuthServerConfiguration.getInstance().isUseLegacyPermissionAccessForUserBasedAuth()) { // Handling backward compatibility for previous access level. List internalScopes = getInternalScopes(authenticatedUser.getTenantDomain()); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/JWTUtils.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/JWTUtils.java index af002c3fdc3..5be02c855ca 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/JWTUtils.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/JWTUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2023-2025, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -30,6 +30,7 @@ import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig; import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.application.common.model.ServiceProviderProperty; import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; @@ -54,12 +55,14 @@ import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.IS_FRAGMENT_APP; import static org.wso2.carbon.identity.organization.management.service.constant .OrganizationManagementConstants.DEFAULT_SUB_ORG_LEVEL; import static org.wso2.carbon.identity.organization.management.service.constant @@ -298,6 +301,16 @@ public static String getSigningTenantDomain(JWTClaimsSet claimsSet, AccessTokenD if (log.isDebugEnabled()) { log.debug("Getting signing tenant domain from OAuth app."); } + /* + Check if the OAuth application is a fragment application. Based on that we can define what + is the tenant that signed the JWT. In this case the signing tenant is the root organization. + */ + String appTenantDomain = IdentityTenantUtil.getTenantDomain(accessTokenDO.getTenantID()); + ServiceProviderProperty[] serviceProviderProperties = OAuth2Util.getServiceProvider( + accessTokenDO.getConsumerKey(), appTenantDomain).getSpProperties(); + if (!isFragmentApp(serviceProviderProperties)) { + return PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + } return OAuth2Util.getTenantDomainOfOauthApp(accessTokenDO.getConsumerKey()); } catch (InvalidOAuthClientException e) { throw new IdentityOAuth2Exception("Error while getting tenant domain from OAuth app with consumer key: " @@ -327,6 +340,17 @@ private static String getTenantDomain() { return tenantDomain; } + private static boolean isFragmentApp(ServiceProviderProperty[] serviceProviderProperties) { + + if (serviceProviderProperties == null) { + return false; + } + + return Arrays.stream(serviceProviderProperties). + anyMatch(property -> IS_FRAGMENT_APP.equals(property.getName()) && + Boolean.parseBoolean(property.getValue())); + } + /** * Validates that the provided token's "Not Before" time has passed, considering the configured timestamp skew. * If the token is used before the "Not Before" time, an IdentityOAuth2Exception is thrown. diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java index 1025c723f34..8108c4e1b2f 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -23,6 +23,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.api.resource.mgt.APIResourceMgtException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; @@ -129,8 +130,13 @@ public List validateScope(OAuthTokenReqMessageContext tokenReqMessageCon String tenantDomain = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getTenantDomain(); String clientId = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId(); String appId = getApplicationId(clientId, tenantDomain); - // When user is not accessing the resident organization, resolve the application id from the shared app table. - if (!AuthzUtil.isUserAccessingResidentOrganization(tokenReqMessageContext.getAuthorizedUser())) { + /* + When user is not accessing the resident organization and if the user is not accessing an application in + the organization level, resolve the application id from the shared app table. + */ + if (!AuthzUtil.isUserAccessingResidentOrganization(tokenReqMessageContext.getAuthorizedUser()) && + StringUtils.isEmpty(PrivilegedCarbonContext.getThreadLocalCarbonContext(). + getApplicationResidentOrganizationId())) { String orgId = tokenReqMessageContext.getAuthorizedUser().getAccessingOrganization(); String appResideOrgId = resolveOrgIdByTenantDomain(tenantDomain); appId = SharedAppResolveDAO.resolveSharedApplication(appResideOrgId, appId, orgId); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/TokenValidationHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/TokenValidationHandler.java index fabb785dbba..3b76f0cf29d 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/TokenValidationHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/TokenValidationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2019-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -27,6 +27,7 @@ import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; import org.wso2.carbon.identity.application.common.model.ServiceProvider; +import org.wso2.carbon.identity.application.common.model.ServiceProviderProperty; import org.wso2.carbon.identity.application.mgt.ApplicationConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LogConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; @@ -50,10 +51,12 @@ import org.wso2.carbon.utils.DiagnosticLog; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; +import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.IS_FRAGMENT_APP; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.isParsableJWT; /** @@ -507,6 +510,16 @@ private OAuth2IntrospectionResponseDTO validateAccessToken(OAuth2TokenValidation String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); accessTokenDO = OAuth2ServiceComponentHolder.getInstance().getTokenProvider() .getVerifiedAccessToken(validationRequest.getAccessToken().getIdentifier(), false); + /* + Check if the OAuth application is a fragment application. If that is not a fragment application, + then getting the tenant domain from the token. + */ + String appTenantDomain = IdentityTenantUtil.getTenantDomain(accessTokenDO.getTenantID()); + ServiceProviderProperty[] serviceProviderProperties = OAuth2Util.getServiceProvider( + accessTokenDO.getConsumerKey(), appTenantDomain).getSpProperties(); + if (!isFragmentApp(serviceProviderProperties)) { + tenantDomain = appTenantDomain; + } boolean isCrossTenantTokenIntrospectionAllowed = OAuthServerConfiguration.getInstance().isCrossTenantTokenIntrospectionAllowed(); if (!isCrossTenantTokenIntrospectionAllowed && accessTokenDO != null && @@ -684,6 +697,17 @@ private OAuth2IntrospectionResponseDTO validateAccessToken(OAuth2TokenValidation return introResp; } + private boolean isFragmentApp(ServiceProviderProperty[] serviceProviderProperties) { + + if (serviceProviderProperties == null) { + return false; + } + + return Arrays.stream(serviceProviderProperties). + anyMatch(property -> IS_FRAGMENT_APP.equals(property.getName()) && + Boolean.parseBoolean(property.getValue())); + } + private String getAuthzUser(AccessTokenDO accessTokenDO) throws IdentityOAuth2Exception { AuthenticatedUser user = accessTokenDO.getAuthzUser(); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/DefaultIDTokenBuilder.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/DefaultIDTokenBuilder.java index 614539abd96..6beb79b8077 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/DefaultIDTokenBuilder.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/DefaultIDTokenBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2017-2025, 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 @@ -30,6 +30,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationMethodNameTranslator; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCache; @@ -121,7 +122,7 @@ public String buildIDToken(OAuthTokenReqMessageContext tokenReqMsgCtxt, // Initialize OAuthAppDO using the client ID. OAuthAppDO oAuthAppDO; try { - oAuthAppDO = OAuth2Util.getAppInformationByClientId(clientId); + oAuthAppDO = OAuth2Util.getAppInformationByClientId(clientId, spTenantDomain); } catch (InvalidOAuthClientException e) { String error = "Error occurred while getting app information for client_id: " + clientId; throw new IdentityOAuth2Exception(error, e); @@ -421,7 +422,16 @@ private JWTClaimsSet handleOIDCCustomClaims(OAuthTokenReqMessageContext tokReqMs private String getSigningTenantDomain(OAuthTokenReqMessageContext tokReqMsgCtx) { boolean isJWTSignedWithSPKey = OAuthServerConfiguration.getInstance().isJWTSignedWithSPKey(); - if (isJWTSignedWithSPKey) { + String applicationResidentOrgId = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getApplicationResidentOrganizationId(); + /* + If applicationResidentOrgId is not empty, then the request comes for an application which is + registered directly in the organization of the applicationResidentOrgId. In this case, the tenant domain + that needs to be signing the token should be the root tenant of the organization in applicationResidentOrgId. + */ + if (StringUtils.isNotEmpty(applicationResidentOrgId)) { + return PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + } else if (isJWTSignedWithSPKey) { return (String) tokReqMsgCtx.getProperty(MultitenantConstants.TENANT_DOMAIN); } else { return tokReqMsgCtx.getAuthorizedUser().getTenantDomain(); diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java index 424d6b6f793..41b100efe02 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -73,9 +73,11 @@ import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO; import org.wso2.carbon.identity.oauth2.token.AccessTokenIssuer; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding; +import org.wso2.carbon.identity.oauth2.util.AuthzUtil; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.user.core.UserRealm; import org.wso2.carbon.user.core.UserStoreManager; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.lang.reflect.Field; @@ -667,7 +669,8 @@ public void testRevokeTokenByOAuthClientWithAccessToken() throws Exception { try (MockedStatic oAuthComponentServiceHolder = mockStatic(OAuthComponentServiceHolder.class); MockedStatic oAuth2Util = mockStatic(OAuth2Util.class); - MockedStatic oAuthUtil = mockStatic(OAuthUtil.class)) { + MockedStatic oAuthUtil = mockStatic(OAuthUtil.class); + MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { setUpRevokeToken(oAuthComponentServiceHolder, oAuth2Util, oAuthUtil); AccessTokenDO accessTokenDO = getAccessToken(); TokenBinding tokenBinding = new TokenBinding(); @@ -680,9 +683,11 @@ public void testRevokeTokenByOAuthClientWithAccessToken() throws Exception { setPrivateField(oAuthTokenPersistenceFactory, "managementDAO", mockTokenManagementDAOImpl); AccessTokenDAO mockAccessTokenDAO = mock(AccessTokenDAO.class); setPrivateField(oAuthTokenPersistenceFactory, "tokenDAO", mockAccessTokenDAO); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(anyInt())).thenReturn( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); OAuthAppDO oAuthAppDO = new OAuthAppDO(); - when(OAuth2Util.getAppInformationByClientId(anyString())).thenReturn(oAuthAppDO); + when(OAuth2Util.getAppInformationByClientId(anyString(), anyString())).thenReturn(oAuthAppDO); OAuthRevocationRequestDTO revokeRequestDTO = getOAuthRevocationRequestDTO(); oAuth2Service.revokeTokenByOAuthClient(revokeRequestDTO); @@ -710,14 +715,17 @@ public void testRevokeTokenByOAuthClientWithAccessTokenWithInvalidBinding() thro try (MockedStatic oAuthComponentServiceHolder = mockStatic(OAuthComponentServiceHolder.class); MockedStatic oAuth2Util = mockStatic(OAuth2Util.class); - MockedStatic oAuthUtil = mockStatic(OAuthUtil.class)) { + MockedStatic oAuthUtil = mockStatic(OAuthUtil.class); + MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { setUpRevokeToken(oAuthComponentServiceHolder, oAuth2Util, oAuthUtil); AccessTokenDO accessTokenDO = getAccessToken(); when(OAuth2Util.findAccessToken(anyString(), anyBoolean())).thenReturn(accessTokenDO); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(anyInt())).thenReturn( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); OAuthAppDO oAuthAppDO = new OAuthAppDO(); oAuthAppDO.setTokenBindingValidationEnabled(true); - when(OAuth2Util.getAppInformationByClientId(anyString())).thenReturn(oAuthAppDO); + when(OAuth2Util.getAppInformationByClientId(anyString(), anyString())).thenReturn(oAuthAppDO); OAuthRevocationRequestDTO revokeRequestDTO = getOAuthRevocationRequestDTO(); OAuthRevocationResponseDTO oAuthRevocationResponseDTO = oAuth2Service @@ -958,7 +966,10 @@ private void setUpRevokeToken(MockedStatic oAuthCom @Test public void testGetOauthApplicationState() throws Exception { - try (MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { + try (MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class); + MockedStatic authzUtil = mockStatic(AuthzUtil.class)) { + + authzUtil.when(AuthzUtil::isLegacyAuthzRuntime).thenReturn(false); String id = "clientId1"; OAuthAppDO oAuthAppDO = new OAuthAppDO(); oAuthAppDO.setState("ACTIVE"); @@ -979,6 +990,7 @@ public void testGetOauthApplicationState() throws Exception { public void testGetOauthApplicationStateWithIdentityOAuth2Exception() throws Exception { try (MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { + identityTenantUtil.when(IdentityTenantUtil::getLoginTenantId).thenReturn(1); identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(1); identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(1)).thenReturn("test.tenant"); @@ -998,6 +1010,7 @@ public void testGetOauthApplicationStateWithIdentityOAuth2Exception() throws Exc public void testGetOauthApplicationStateWithInvalidOAuthClientException() throws Exception { try (MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { + identityTenantUtil.when(IdentityTenantUtil::getLoginTenantId).thenReturn(1); identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(1); identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(1)).thenReturn("test.tenant"); diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticatorTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticatorTest.java index cda43492f2f..f60dbf7b1a7 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticatorTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/BasicAuthClientAuthenticatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2018-2025, 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 @@ -115,7 +115,7 @@ public void testAuthenticateClient(String headerName, String headerValue, HashMa OAuthClientAuthnContext oAuthClientAuthnContext = (OAuthClientAuthnContext) oAuthClientAuthnContextObj; HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); - oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString())).thenReturn + oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString(), anyString())).thenReturn (isAuthenticated); when(httpServletRequest.getHeader(headerName)).thenReturn(headerValue); assertEquals(basicAuthClientAuthenticator.authenticateClient(httpServletRequest, bodyContent, @@ -157,10 +157,10 @@ public void testAuthenticateClientExeption(String headerName, String headerValue HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); if (exception instanceof IdentityOAuthAdminException) { - oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString())).thenThrow( + oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString(), anyString())).thenThrow( (IdentityOAuthAdminException) exception); } else if (exception instanceof IdentityOAuth2Exception) { - oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString())).thenThrow( + oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString(), anyString())).thenThrow( (IdentityOAuth2Exception) exception); } diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnServiceTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnServiceTest.java index 3c8a67416d8..ac080c38b29 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnServiceTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/client/authentication/OAuthClientAuthnServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2018-2025, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -153,7 +153,7 @@ public void testAuthenticateClient(Map headers, Map OAuth2Util.authenticateClient(anyString(), anyString())).thenReturn + oAuth2Util.when(() -> OAuth2Util.authenticateClient(anyString(), anyString(), anyString())).thenReturn (isBasicAuthenticated); HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); setHeaders(httpServletRequest, headers); diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java index d0ff62f9808..7710576c7a9 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -42,7 +42,9 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.wso2.carbon.base.CarbonBaseConstants; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.common.testng.WithCarbonHome; import org.wso2.carbon.identity.common.testng.WithH2Database; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; @@ -61,6 +63,7 @@ import org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationGrantHandler; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.openidconnect.CustomClaimsCallbackHandler; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -98,6 +101,7 @@ import static org.testng.Assert.fail; import static org.wso2.carbon.identity.openidconnect.util.TestUtils.getKeyStoreFromFile; +@WithCarbonHome @WithH2Database(files = {"dbScripts/identity.sql", "dbScripts/insert_consumer_app.sql", "dbScripts/insert_local_idp.sql"}) public class JWTTokenIssuerTest { @@ -147,6 +151,10 @@ public class JWTTokenIssuerTest { @BeforeMethod public void setUp() throws Exception { initMocks(this); + System.setProperty( + CarbonBaseConstants.CARBON_HOME, + Paths.get(System.getProperty("user.dir"), "src", "test", "resources").toString() + ); oAuthServerConfiguration = mockStatic(OAuthServerConfiguration.class); oAuthServerConfiguration.when(OAuthServerConfiguration::getInstance) .thenReturn(this.mockOAuthServerConfiguration); @@ -176,7 +184,11 @@ public Object[][] provideRequestScopes() { public void testBuildJWTTokenFromTokenMsgContext(String requestScopes[], List expectedJWTAudiences) throws Exception { - try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class)) { + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain("DUMMY_TENANT.COM"); + try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class); + MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { + + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(-1234); OAuth2AccessTokenReqDTO accessTokenReqDTO = new OAuth2AccessTokenReqDTO(); accessTokenReqDTO.setGrantType(USER_ACCESS_TOKEN_GRANT_TYPE); accessTokenReqDTO.setClientId(DUMMY_CLIENT_ID); @@ -231,7 +243,11 @@ public void testBuildJWTTokenFromTokenMsgContext(String requestScopes[], public void testBuildJWTTokenFromAuthzMsgContext(String requestScopes[], List expectedJWTAudiences) throws Exception { - try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class)) { + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain("DUMMY_TENANT.COM"); + try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class); + MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { + + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(-1234); OAuth2AuthorizeReqDTO authorizeReqDTO = new OAuth2AuthorizeReqDTO(); OAuthAuthzReqMessageContext authzReqMessageContext = new OAuthAuthzReqMessageContext(authorizeReqDTO); authzReqMessageContext.setApprovedScope(requestScopes); @@ -284,13 +300,15 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable { @Test(expectedExceptions = IdentityOAuth2Exception.class) public void testCreateJWTClaimSetForInvalidClient() throws Exception { + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class)) { - oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(null)) + oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString(), anyString())) .thenThrow(new InvalidOAuthClientException("INVALID_CLIENT")); oAuth2Util.when(OAuth2Util::isTokenPersistenceEnabled).thenReturn(true); when(mockOAuthServerConfiguration.getSignatureAlgorithm()).thenReturn(SHA256_WITH_HMAC); JWTTokenIssuer jwtTokenIssuer = new JWTTokenIssuer(); - jwtTokenIssuer.createJWTClaimSet(null, null, null); + jwtTokenIssuer.createJWTClaimSet(null, null, DUMMY_CLIENT_ID); } } @@ -370,15 +388,19 @@ public void testCreateJWTClaimSet(Object authzReqMessageContext, String sub, long expectedExpiry, boolean ppidEnabled) throws Exception { + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class); - MockedStatic identityUtil = mockStatic(IdentityUtil.class)) { + MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { + + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(-1234); OAuthAppDO appDO = spy(new OAuthAppDO()); appDO.setSubjectType("pairwise"); appDO.setSectorIdentifierURI(DUMMY_SECTOR_IDENTIFIER); appDO.setOauthConsumerKey(DUMMY_CLIENT_ID); mockGrantHandlers(); mockCustomClaimsCallbackHandler(); - oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString())).thenReturn(appDO); + oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString(), anyString())).thenReturn(appDO); oAuth2Util.when(OAuth2Util::getIDTokenIssuer).thenReturn(ID_TOKEN_ISSUER); oAuth2Util.when(() -> OAuth2Util.getIdTokenIssuer(anyString(), anyBoolean())).thenReturn(ID_TOKEN_ISSUER); oAuth2Util.when(() -> OAuth2Util.getOIDCAudience(anyString(), any())).thenReturn(Collections.singletonList @@ -460,6 +482,8 @@ public void testSignJWTWithRSA(Object authzReqMessageContext, String sub, long expectedExpiry, boolean ppidEnabled) throws Exception { + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class, Mockito.CALLS_REAL_METHODS); MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class); MockedStatic identityUtil = mockStatic(IdentityUtil.class)) { @@ -470,7 +494,7 @@ public void testSignJWTWithRSA(Object authzReqMessageContext, mockCustomClaimsCallbackHandler(); identityUtil.when(() -> IdentityUtil.getProperty(OAuthConstants.MTLS_HOSTNAME)) .thenReturn(DUMMY_MTLS_TOKEN_ENDPOINT); - oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString())).thenReturn(appDO); + oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString(), anyString())).thenReturn(appDO); oAuth2Util.when(OAuth2Util::isTokenPersistenceEnabled).thenReturn(true); System.setProperty(CarbonBaseConstants.CARBON_HOME, @@ -780,7 +804,7 @@ private void prepareForBuildJWTToken(MockedStatic oAuth2Util) OAuthAppDO appDO = spy(new OAuthAppDO()); mockGrantHandlers(); mockCustomClaimsCallbackHandler(); - oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString())).thenReturn(appDO); + oAuth2Util.when(() -> OAuth2Util.getAppInformationByClientId(anyString(), anyString())).thenReturn(appDO); oAuth2Util.when(() -> OAuth2Util.getTenantDomain(anyInt())).thenReturn("super.wso2"); oAuth2Util.when(OAuth2Util::isTokenPersistenceEnabled).thenReturn(true); } @@ -822,6 +846,8 @@ public Map getAdditionalClaims(OAuthTokenReqMessageContext conte @Test public void testIssueSubjectToken() throws Exception { + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); when(mockOAuthServerConfiguration.getSignatureAlgorithm()).thenReturn(SHA256_WITH_RSA); try (MockedStatic oAuth2Util = mockStatic(OAuth2Util.class, Mockito.CALLS_REAL_METHODS); MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java index b23e74675a9..ed4eaa75059 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2017-2025, 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 @@ -27,6 +27,8 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.action.execution.ActionExecutorService; import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException; import org.wso2.carbon.identity.action.execution.model.ActionType; @@ -74,6 +76,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; @@ -166,6 +169,9 @@ public void tearDown() { boolean dbEntryAvailable, String dbTokenState, boolean tokenLoggable, boolean isIDPIdColumnEnabled, boolean setBindingReference) throws Exception { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext() + .setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); OAuth2ServiceComponentHolder.setIDPIdColumnEnabled(isIDPIdColumnEnabled); Map supportedGrantTypes = new HashMap<>(); @@ -189,7 +195,8 @@ public void tearDown() { // Mocking static methods using try-with-resources try (MockedStatic identityUtil = mockStatic(IdentityUtil.class); - MockedStatic oauth2Util = mockStatic(OAuth2Util.class)) { + MockedStatic oauth2Util = mockStatic(OAuth2Util.class); + MockedStatic identityTenantUtil = mockStatic(IdentityTenantUtil.class)) { identityUtil.when(() -> IdentityUtil.getProperty(anyString())) .thenReturn(Boolean.TRUE.toString()); @@ -201,13 +208,17 @@ public void tearDown() { OauthTokenIssuer oauthTokenIssuer = mock(JWTTokenIssuer.class); when(oauthTokenIssuer.getAccessTokenType()).thenReturn("jwt"); oauth2Util.when(() -> OAuth2Util.getOAuthTokenIssuerForOAuthApp(clientId)).thenReturn(oauthTokenIssuer); - oauth2Util.when(() -> OAuth2Util.getAppInformationByClientId(clientId)).thenReturn(oAuthAppDO); + oauth2Util.when(() -> OAuth2Util.getAppInformationByClientId(eq(clientId), anyString())). + thenReturn(oAuthAppDO); + identityTenantUtil.when(IdentityTenantUtil::getLoginTenantId).thenReturn(-1234); // Set allowed grant types (ensure PASSWORD_GRANT is allowed for renewal) OAuth2ServiceComponentHolder.setJwtRenewWithoutRevokeAllowedGrantTypes( Collections.singletonList("password")); // This allows PASSWORD_GRANT OAuth2AccessTokenRespDTO tokenRespDTO = handler.issue(tokReqMsgCtx); + } finally { + PrivilegedCarbonContext.endTenantFlow(); } } @@ -258,6 +269,11 @@ public void testIssue(boolean cacheEnabled, boolean cacheEntryAvailable, long ca boolean isIDPIdColumnEnabled) throws Exception { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + OAuthComponentServiceHolder.getInstance().setActionExecutorService(mockActionExecutionService); + OAuth2ServiceComponentHolder.setIDPIdColumnEnabled(isIDPIdColumnEnabled); Map supportedGrantTypes = new HashMap<>(); @@ -273,6 +289,7 @@ public void testIssue(boolean cacheEnabled, boolean cacheEntryAvailable, long ca OAuth2AccessTokenRespDTO tokenRespDTO = handler.issue(tokReqMsgCtx); assertNotNull(tokenRespDTO.getAccessToken()); + PrivilegedCarbonContext.endTenantFlow(); } @DataProvider(name = "AuthorizeAccessDelegationDataProvider") diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandlerTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandlerTest.java index f197e8bc3db..9d73fb44e7e 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandlerTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandlerTest.java @@ -20,9 +20,11 @@ import org.apache.commons.logging.Log; import org.mockito.MockedStatic; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.application.authentication.framework.config.builder.FileBasedConfigurationBuilder; import org.wso2.carbon.identity.application.authentication.framework.config.model.AuthenticatorConfig; import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils; @@ -31,6 +33,7 @@ import org.wso2.carbon.identity.application.common.model.ServiceProvider; import org.wso2.carbon.identity.application.common.model.ServiceProviderProperty; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.common.testng.WithCarbonHome; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.multi.attribute.login.mgt.ResolvedUserResult; @@ -70,6 +73,7 @@ import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.SHOW_AUTHFAILURE_RESON_CONFIG; import static org.wso2.carbon.user.core.UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME; +@WithCarbonHome public class PasswordGrantHandlerTest { private OAuthTokenReqMessageContext tokReqMsgCtx; @@ -116,6 +120,15 @@ public void init() throws Exception { // Set the static field to the mock object logField.set(null, mockLog); + + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setApplicationResidentOrganizationId(null); + } + + @AfterMethod + public void tearDown() { + + PrivilegedCarbonContext.endTenantFlow(); } @DataProvider(name = "ValidateGrantDataProvider")