diff --git a/components/org.wso2.carbon.identity.organization.management.handler/src/main/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListener.java b/components/org.wso2.carbon.identity.organization.management.handler/src/main/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListener.java index 5b852ef5d..9cd1bba24 100644 --- a/components/org.wso2.carbon.identity.organization.management.handler/src/main/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListener.java +++ b/components/org.wso2.carbon.identity.organization.management.handler/src/main/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2024, 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 @@ -57,6 +57,7 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; +import static org.wso2.carbon.identity.organization.management.application.constant.OrgApplicationMgtConstants.IS_FRAGMENT_APP; import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.SUPER_ORG_ID; /** @@ -468,16 +469,26 @@ public boolean doPreDeleteApplication(String applicationName, String tenantDomai throws IdentityApplicationManagementException { try { - // If the deleting application is an application of tenant(i.e primary org) nothing to do here. + // If the tenant is not an organization, no need to handle shared roles. if (!OrganizationManagementUtil.isOrganization(tenantDomain)) { return true; } - ServiceProvider sharedApplication = getApplicationByName(applicationName, tenantDomain); - if (sharedApplication == null) { + ServiceProvider serviceProvider = getApplicationByName(applicationName, tenantDomain); + if (serviceProvider == null) { return false; } - String sharedAppId = sharedApplication.getApplicationResourceId(); + + // If the application is not a fragment app in the sub organization level, no need to handle shared roles. + boolean isFragmentApp = Arrays.stream(serviceProvider.getSpProperties()) + .anyMatch(property -> IS_FRAGMENT_APP.equals(property.getName()) && + Boolean.parseBoolean(property.getValue())); + if (!isFragmentApp) { + // Given app is a sub org level application. + return true; + } + + String sharedAppId = serviceProvider.getApplicationResourceId(); String sharedAppOrgId = organizationManager.resolveOrganizationId(tenantDomain); // Resolve the main application details. String mainAppId = orgApplicationManager.getMainApplicationIdForGivenSharedApp(sharedAppId, sharedAppOrgId); @@ -583,22 +594,18 @@ public boolean doPostGetAllowedAudienceForRoleAssociation(AssociatedRolesConfig String applicationUUID, String tenantDomain) throws IdentityApplicationManagementException { - try { - if (!OrganizationManagementUtil.isOrganization(tenantDomain)) { - return true; - } - // Resolve the allowed audience for associated roles of shared application from main application details. - String mainAppId = applicationManagementService.getMainAppId(applicationUUID); - int mainAppTenantId = applicationManagementService.getTenantIdByApp(mainAppId); - String mainAppTenantDomain = IdentityTenantUtil.getTenantDomain(mainAppTenantId); - String resolvedAllowedAudienceFromMainApp = - applicationManagementService.getAllowedAudienceForRoleAssociation(mainAppId, mainAppTenantDomain); - allowedAudienceForRoleAssociation.setAllowedAudience(resolvedAllowedAudienceFromMainApp); - } catch (OrganizationManagementException e) { - throw new IdentityApplicationManagementException(String.format( - "Error while fetching the allowed audience for role association of application with: %s.", - applicationUUID), e); + String mainAppId = applicationManagementService.getMainAppId(applicationUUID); + // If the main application id is null, then this is the main application. We can skip this operation + // based on that. + if (StringUtils.isEmpty(mainAppId)) { + return true; } + // Resolve the allowed audience for associated roles of shared application from main application details. + int mainAppTenantId = applicationManagementService.getTenantIdByApp(mainAppId); + String mainAppTenantDomain = IdentityTenantUtil.getTenantDomain(mainAppTenantId); + String resolvedAllowedAudienceFromMainApp = + applicationManagementService.getAllowedAudienceForRoleAssociation(mainAppId, mainAppTenantDomain); + allowedAudienceForRoleAssociation.setAllowedAudience(resolvedAllowedAudienceFromMainApp); return true; } diff --git a/components/org.wso2.carbon.identity.organization.management.handler/src/test/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListenerTest.java b/components/org.wso2.carbon.identity.organization.management.handler/src/test/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListenerTest.java new file mode 100644 index 000000000..e5cebf877 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.management.handler/src/test/java/org/wso2/carbon/identity/organization/management/handler/listener/SharedRoleMgtListenerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 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 + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.management.handler.listener; + +import org.apache.commons.lang3.StringUtils; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.application.common.model.AssociatedRolesConfig; +import org.wso2.carbon.identity.application.common.model.RoleV2; +import org.wso2.carbon.identity.application.common.model.ServiceProvider; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.organization.management.handler.internal.OrganizationManagementHandlerDataHolder; +import org.wso2.carbon.identity.organization.management.service.util.OrganizationManagementUtil; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Contains unit tests for SharedRoleMgtListener. + */ +public class SharedRoleMgtListenerTest { + + private static final String SAMPLE_APPLICATION_NAME = "sampleApplicationName"; + private static final String SAMPLE_TENANT_DOMAIN = "sampleTenantDomain"; + private static final int SAMPLE_TENANT_ID = 12345; + private static final String SAMPLE_USERNAME = "sampleUsername"; + private static final String SAMPLE_ROLE_NAME = "sampleRoleName"; + private static final String SAMPLE_MAIN_APP_ID = "main-app-id"; + private static final String SAMPLE_SHARED_APP_ID = "shared-app-id"; + private static final String ORGANIZATION_AUD = "organization"; + + @Mock + private ApplicationManagementService mockedApplicationManagementService; + + private MockedStatic organizationManagementUtilMockedStatic; + private MockedStatic identityTenantUtilMockedStatic; + + @BeforeClass + public void setUpClass() { + + MockitoAnnotations.openMocks(this); + OrganizationManagementHandlerDataHolder.getInstance(). + setApplicationManagementService(mockedApplicationManagementService); + organizationManagementUtilMockedStatic = mockStatic(OrganizationManagementUtil.class); + identityTenantUtilMockedStatic = mockStatic(IdentityTenantUtil.class); + } + + @DataProvider(name = "organizationTypeDataProvider") + public Object[][] organizationTypeDataProvider() { + + // Create a ServiceProvider object for a main application. + ServiceProvider serviceProvider = new ServiceProvider(); + serviceProvider.setApplicationName(SAMPLE_APPLICATION_NAME); + + return new Object[][]{ + {false, null, true}, + {true, serviceProvider, true}, + {true, null, false}, + }; + } + + @DataProvider(name = "applicationDataProvider") + public Object[][] applicationDataProvider() { + + return new Object[][] { + {null}, + {SAMPLE_MAIN_APP_ID} + }; + } + + @Test(dataProvider = "organizationTypeDataProvider") + public void testDoPreDeleteApplicationInOrgTypes(boolean isOrganization, ServiceProvider serviceProvider, + boolean expected) throws Exception { + + organizationManagementUtilMockedStatic.when(() -> OrganizationManagementUtil.isOrganization(anyString())) + .thenReturn(isOrganization); + SharedRoleMgtListener sharedRoleMgtListener = new SharedRoleMgtListener(); + if (!isOrganization) { + assertEquals(sharedRoleMgtListener.doPreDeleteApplication(SAMPLE_APPLICATION_NAME, SAMPLE_TENANT_DOMAIN, + SAMPLE_USERNAME), expected); + } else { + when(mockedApplicationManagementService.getServiceProvider(SAMPLE_APPLICATION_NAME, SAMPLE_TENANT_DOMAIN)) + .thenReturn(serviceProvider); + assertEquals(sharedRoleMgtListener.doPreDeleteApplication(SAMPLE_APPLICATION_NAME, SAMPLE_TENANT_DOMAIN, + SAMPLE_USERNAME), expected); + } + } + + @Test(dataProvider = "applicationDataProvider") + public void testDoPostGetAllowedAudienceForRoleAssociation(String mainAppId) throws Exception { + + AssociatedRolesConfig associatedRolesConfig = new AssociatedRolesConfig(); + RoleV2 roleV2 = new RoleV2(); + roleV2.setName(SAMPLE_ROLE_NAME); + associatedRolesConfig.setRoles(new RoleV2[]{roleV2}); + SharedRoleMgtListener sharedRoleMgtListener = new SharedRoleMgtListener(); + when(mockedApplicationManagementService.getMainAppId(SAMPLE_SHARED_APP_ID)).thenReturn(mainAppId); + when(mockedApplicationManagementService.getTenantIdByApp(mainAppId)).thenReturn(SAMPLE_TENANT_ID); + identityTenantUtilMockedStatic.when(() -> IdentityTenantUtil.getTenantDomain(SAMPLE_TENANT_ID)). + thenReturn(SAMPLE_TENANT_DOMAIN); + when(mockedApplicationManagementService.getAllowedAudienceForRoleAssociation(mainAppId, SAMPLE_TENANT_DOMAIN)). + thenReturn(ORGANIZATION_AUD); + assertTrue(sharedRoleMgtListener.doPostGetAllowedAudienceForRoleAssociation(associatedRolesConfig, + SAMPLE_SHARED_APP_ID, SAMPLE_USERNAME)); + if (StringUtils.isNotEmpty(mainAppId)) { + assertEquals(associatedRolesConfig.getAllowedAudience(), ORGANIZATION_AUD); + } + } +}