Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inherit parent user domain in shared token revoke flow #2664

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -753,6 +755,10 @@ private static AuthenticatedUser buildAuthenticatedUser(UserStoreManager userSto
.getUserAssociation(userId, accessingOrg).getAssociatedUserId();
authenticatedUser.setUserName(userId);
setOrganizationSSOUserDetails(authenticatedUser);
} else {
Optional<String> parentUserStoreDomain = getUserStoreDomainOfParentUser(
userId, accessingOrg, tenantDomain);
parentUserStoreDomain.ifPresent(authenticatedUser::setUserStoreDomain);
}
return authenticatedUser;
}
Expand Down Expand Up @@ -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<String> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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;
Expand All @@ -44,21 +47,34 @@
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.user.api.RealmConfiguration;
import org.wso2.carbon.user.api.UserRealm;
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.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.times;
Expand All @@ -68,17 +84,34 @@
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 organizationManagerMock;

@Mock
private OrganizationUserSharingService organizationUserSharingServiceMock;

@Mock
private TokenManagementDAO tokenManagementDAOMock;

@Mock
private RealmService realmService;

@Mock
RoleManagementService roleManagementService;

@Mock
ApplicationManagementService applicationManagementService;

Expand Down Expand Up @@ -335,7 +368,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);
Expand All @@ -355,19 +388,62 @@ 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(tokenManagementDAOMock);
Set<String> clientIds = new HashSet<>();
clientIds.add(clientId);
when(mockTokenManagementDao.getAllTimeAuthorizedClientIds(any())).thenReturn(clientIds);
when(tokenManagementDAOMock.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) {
@Test
public void testRevokeTokensInSharedUserFlow() throws Exception {

try (MockedStatic<UserCoreUtil> 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<String, String> 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(organizationManagerMock);
when(organizationManagerMock.isPrimaryOrganization(anyString())).thenReturn(true);

when(OrganizationManagementUtil.isOrganization(anyString())).thenReturn(true);
when(UserCoreUtil.removeDomainFromName(null)).thenReturn(CARBON_TENANT_DOMAIN);

UserAssociation userAssociation = new UserAssociation();
userAssociation.setAssociatedUserId(SAMPLE_ID);
when(mockOAuthComponentServiceHolder.getOrganizationUserSharingService())
.thenReturn(organizationUserSharingServiceMock);
when(organizationUserSharingServiceMock.getUserAssociation(null, null))
.thenReturn(userAssociation);

when(mockOAuthComponentServiceHolder.getRealmService()).thenReturn(realmService);
UserRealm userRealm = mock(UserRealm.class);
when(userRealm.getUserStoreManager()).thenReturn(userStoreManager);
when(realmService.getTenantUserRealm(anyInt())).thenReturn(userRealm);

OAuthTokenPersistenceFactory mockOAuthTokenPersistenceFactory = mock(OAuthTokenPersistenceFactory.class);
when(OAuthTokenPersistenceFactory.getInstance()).thenReturn(mockOAuthTokenPersistenceFactory);
when(mockOAuthTokenPersistenceFactory.getTokenManagementDAO()).thenReturn(tokenManagementDAOMock);

boolean result = OAuthUtil.revokeTokens(null, userStoreManager, null);
assertTrue(result);
}
}

private OAuthCache getOAuthCache(OAuthCacheKey oAuthCacheKey) {

// Add some value to OAuthCache.
DummyOAuthCacheEntry dummyOAuthCacheEntry = new DummyOAuthCacheEntry("identifier");
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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";
Expand All @@ -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";
Expand Down
Loading