diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java index f61db3580d..3725b1d4a2 100755 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java @@ -62,6 +62,7 @@ import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; import org.wso2.carbon.identity.oauth2.model.AuthzCodeDO; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.organization.management.organization.user.sharing.models.UserAssociation; import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import org.wso2.carbon.identity.organization.management.service.util.OrganizationManagementUtil; import org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants; @@ -70,6 +71,7 @@ import org.wso2.carbon.identity.role.v2.mgt.core.model.RoleBasicInfo; import org.wso2.carbon.idp.mgt.IdentityProviderManagementException; import org.wso2.carbon.user.api.Tenant; +import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.common.AbstractUserStoreManager; @@ -753,6 +755,10 @@ private static AuthenticatedUser buildAuthenticatedUser(UserStoreManager userSto .getUserAssociation(userId, accessingOrg).getAssociatedUserId(); authenticatedUser.setUserName(userId); setOrganizationSSOUserDetails(authenticatedUser); + } else { + Optional parentUserStoreDomain = getUserStoreDomainOfParentUser( + userId, accessingOrg, tenantDomain); + parentUserStoreDomain.ifPresent(authenticatedUser::setUserStoreDomain); } return authenticatedUser; } @@ -1327,4 +1333,43 @@ private static String readServerConfigurationPvtKeyJWTReuse() { } return tokenEPAllowReusePvtKeyJwtTenantConfig; } + + /** + * Retrieves the user store domain of the parent user for a shared user in a specific organization. + * + * @param userId ID of the shared user. + * @param accessingOrgId ID of the shared user's organization. + * @param tenantDomain Tenant domain of the shared user. + * @return Optional containing the parent user's user store domain, or empty if not found. + * @throws OrganizationManagementException If an error occurs retrieving user association. + * @throws UserStoreException If an error occurs retrieving the user store domain. + */ + private static Optional getUserStoreDomainOfParentUser(String userId, String accessingOrgId, + String tenantDomain) + throws OrganizationManagementException, UserStoreException { + + UserAssociation userAssociation = OAuthComponentServiceHolder.getInstance() + .getOrganizationUserSharingService() + .getUserAssociation(userId, accessingOrgId); + + if (userAssociation == null || userAssociation.getAssociatedUserId() == null) { + return Optional.empty(); + } + String parentUserId = userAssociation.getAssociatedUserId(); + + try { + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + UserRealm userRealm = OAuthComponentServiceHolder.getInstance() + .getRealmService() + .getTenantUserRealm(tenantId); + UserStoreManager userStoreManager = (AbstractUserStoreManager) userRealm.getUserStoreManager(); + + return Optional.ofNullable(((AbstractUserStoreManager) userStoreManager) + .getUser(parentUserId, null) + .getUserStoreDomain()); + } catch (org.wso2.carbon.user.api.UserStoreException e) { + throw new UserStoreException("Failed to retrieve the user store domain for the parent user with ID: " + + parentUserId + " in tenant domain: " + tenantDomain, e); + } + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java index a8c894b6b1..fa2b05ea4f 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java @@ -11,7 +11,7 @@ * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ @@ -21,10 +21,13 @@ import org.apache.commons.lang.StringUtils; import org.mockito.Mock; import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.application.common.model.InboundAuthenticationConfig; @@ -44,23 +47,40 @@ import org.wso2.carbon.identity.oauth2.dao.TokenManagementDAO; import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.organization.management.organization.user.sharing.OrganizationUserSharingService; +import org.wso2.carbon.identity.organization.management.organization.user.sharing.models.UserAssociation; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; import org.wso2.carbon.identity.organization.management.service.util.OrganizationManagementUtil; import org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants; import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService; import org.wso2.carbon.identity.role.v2.mgt.core.model.RoleBasicInfo; +import org.wso2.carbon.idp.mgt.IdpManager; import org.wso2.carbon.user.api.RealmConfiguration; +import org.wso2.carbon.user.api.UserRealm; +import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; +import org.wso2.carbon.user.core.jdbc.UniqueIDJDBCUserStoreManager; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.user.core.util.UserCoreUtil; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -68,17 +88,37 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import static org.wso2.carbon.identity.oauth2.TestConstants.CARBON_TENANT_DOMAIN; import static org.wso2.carbon.identity.oauth2.TestConstants.LOCAL_IDP; +import static org.wso2.carbon.identity.oauth2.TestConstants.MANAGED_ORG_CLAIM_URI; +import static org.wso2.carbon.identity.oauth2.TestConstants.SAMPLE_ID; /** * Unit tests for OAuthUtil class. */ @WithCarbonHome @WithRealmService +@Listeners(MockitoTestNGListener.class) public class OAuthUtilTest { + @Mock + private OrganizationManager organizationManager; + + @Mock + private OrganizationUserSharingService organizationUserSharingService; + + @Mock + private TokenManagementDAO tokenManagementDAO; + + @Mock + private IdpManager idpManager; + + @Mock + private RealmService realmService; + @Mock RoleManagementService roleManagementService; + @Mock ApplicationManagementService applicationManagementService; @@ -335,7 +375,7 @@ public void testRevokeTokensForOrganizationAudienceRoles() throws Exception { inboundAuthenticationRequestConfigs[0] = inboundAuthenticationRequestConfig; inboundAuthenticationConfig.setInboundAuthenticationRequestConfigs(inboundAuthenticationRequestConfigs); serviceProvider.setInboundAuthenticationConfig(inboundAuthenticationConfig); - when(applicationManagementService.getApplicationByResourceId( + lenient().when(applicationManagementService.getApplicationByResourceId( appId, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)).thenReturn(serviceProvider); when(applicationManagementService.getApplicationResourceIDByInboundKey(anyString(), anyString(), anyString())). thenReturn(appId); @@ -355,19 +395,99 @@ public void testRevokeTokensForOrganizationAudienceRoles() throws Exception { when(mockAccessTokenDAO.getAccessTokens(anyString(), any(AuthenticatedUser.class), nullable(String.class), anyBoolean())).thenReturn(accessTokens); - TokenManagementDAO mockTokenManagementDao = mock(TokenManagementDAO.class); - when(mockOAuthTokenPersistenceFactory.getTokenManagementDAO()).thenReturn(mockTokenManagementDao); + when(mockOAuthTokenPersistenceFactory.getTokenManagementDAO()).thenReturn(tokenManagementDAO); Set clientIds = new HashSet<>(); clientIds.add(clientId); - when(mockTokenManagementDao.getAllTimeAuthorizedClientIds(any())).thenReturn(clientIds); + when(tokenManagementDAO.getAllTimeAuthorizedClientIds(any())).thenReturn(clientIds); boolean result = OAuthUtil.revokeTokens(username, userStoreManager, roleId); verify(mockAccessTokenDAO, times(1)).revokeAccessTokens(any(), anyBoolean()); assertTrue(result, "Token revocation failed."); } - private OAuthCache getOAuthCache(OAuthCacheKey oAuthCacheKey) { + @DataProvider(name = "authenticatedSharedUserFlowDataProvider") + public Object[][] authenticatedSharedUserFlowDataProvider() { + + return new Object[][]{ + {false, true, false}, // Shared User Flow + {true, true, false}, // SSO Login User Shared Flow + {false, false, false}, // No user association found + {false, true, true} // Throws UserStoreException + }; + } + @Test(dataProvider = "authenticatedSharedUserFlowDataProvider") + public void testAuthenticatedUserInSharedUserFlow(boolean isSSOLoginUser, boolean isUserAssociationFound, + boolean shouldThrowUserStoreException) throws Exception { + + try (MockedStatic userCoreUtil = mockStatic(UserCoreUtil.class)) { + + UniqueIDJDBCUserStoreManager userStoreManager = Mockito.spy( + new UniqueIDJDBCUserStoreManager(new RealmConfiguration(), 1)); + + org.wso2.carbon.user.core.common.User mockUser = Mockito.mock(org.wso2.carbon.user.core.common.User.class); + doReturn(mockUser).when(userStoreManager).getUser(any(), eq(null)); + + Map claimsMap = new HashMap<>(); + claimsMap.put(MANAGED_ORG_CLAIM_URI, SAMPLE_ID); + doReturn(claimsMap).when(userStoreManager) + .getUserClaimValuesWithID(null, new String[]{MANAGED_ORG_CLAIM_URI}, null); + + OAuthComponentServiceHolder mockOAuthComponentServiceHolder = mock(OAuthComponentServiceHolder.class); + when(OAuthComponentServiceHolder.getInstance()).thenReturn(mockOAuthComponentServiceHolder); + when(mockOAuthComponentServiceHolder.getOrganizationManager()).thenReturn(organizationManager); + + if (isSSOLoginUser) { + when(organizationManager.isPrimaryOrganization(anyString())).thenReturn(false); + when(mockOAuthComponentServiceHolder.getIdpManager()).thenReturn(idpManager); + } else { + when(organizationManager.isPrimaryOrganization(anyString())).thenReturn(true); + } + when(OrganizationManagementUtil.isOrganization(anyString())).thenReturn(true); + when(UserCoreUtil.removeDomainFromName(null)).thenReturn(CARBON_TENANT_DOMAIN); + when(mockOAuthComponentServiceHolder.getOrganizationUserSharingService()) + .thenReturn(organizationUserSharingService); + + lenient().when(mockOAuthComponentServiceHolder.getRealmService()).thenReturn(realmService); + + if (isUserAssociationFound) { + UserAssociation userAssociation = new UserAssociation(); + userAssociation.setAssociatedUserId(SAMPLE_ID); + when(organizationUserSharingService.getUserAssociation(null, null)).thenReturn(userAssociation); + } + if (shouldThrowUserStoreException) { + when(realmService.getTenantUserRealm(anyInt())).thenThrow(new UserStoreException()); + } else { + UserRealm userRealm = mock(UserRealm.class); + lenient().when(userRealm.getUserStoreManager()).thenReturn(userStoreManager); + lenient().when(realmService.getTenantUserRealm(anyInt())).thenReturn(userRealm); + + OAuthTokenPersistenceFactory mockOAuthTokenPersistenceFactory = + mock(OAuthTokenPersistenceFactory.class); + when(OAuthTokenPersistenceFactory.getInstance()).thenReturn(mockOAuthTokenPersistenceFactory); + when(mockOAuthTokenPersistenceFactory.getTokenManagementDAO()).thenReturn(tokenManagementDAO); + } + if (isSSOLoginUser || !isUserAssociationFound) { + boolean result = OAuthUtil.revokeTokens(null, userStoreManager, null); + assertTrue(result); + verify(mockUser, never()).getUserStoreDomain(); + } else if (shouldThrowUserStoreException) { + try { + OAuthUtil.revokeTokens(null, userStoreManager, null); + fail(); + } catch (UserStoreException e) { + assertTrue(e.getMessage().contains("Failed to retrieve the user store domain"), + "Unexpected exception message: " + e.getMessage()); + } + } else { + boolean result = OAuthUtil.revokeTokens(null, userStoreManager, null); + assertTrue(result); + verify(mockUser, times(1)).getUserStoreDomain(); + } + } + } + + private OAuthCache getOAuthCache(OAuthCacheKey oAuthCacheKey) { // Add some value to OAuthCache. DummyOAuthCacheEntry dummyOAuthCacheEntry = new DummyOAuthCacheEntry("identifier"); diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/TestConstants.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/TestConstants.java index b07c1e8dd2..f9aca075ab 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/TestConstants.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/TestConstants.java @@ -1,7 +1,7 @@ /* - * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2017-2025, WSO2 LLC. (http://www.wso2.com). * - * WSO2 Inc. licenses this file to you under the Apache License, + * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at @@ -19,6 +19,7 @@ package org.wso2.carbon.identity.oauth2; public class TestConstants { + public static final String CARBON_TENANT_DOMAIN = "carbon.super"; public static final String LOACALHOST_DOMAIN = "localhost"; public static final String OAUTH2_TOKEN_EP = "https://localhost:9443/oauth2/token"; @@ -32,6 +33,8 @@ public class TestConstants { public static final String CLAIM_URI2 = "http://wso2.org/claimuri2"; public static final String CLAIM_VALUE1 = "ClaimValue1"; public static final String CLAIM_VALUE2 = "ClaimValue2"; + public static final String MANAGED_ORG_CLAIM_URI = "http://wso2.org/claims/identity/managedOrg"; + public static final String SAMPLE_ID = "76dedfb5-99ef-4bf3-92e6-56d296db55ec"; public static final String USERSTORE_DOMAIN = "user_store_domain"; public static final String NEW_ACCESS_TOKEN = "123456789";