From 03e663dda649ec01921aa5f91c3f2f57de8a521b Mon Sep 17 00:00:00 2001 From: Shan Chathusanda Jayathilaka Date: Mon, 16 Dec 2024 16:28:49 +0530 Subject: [PATCH] Introduce the fragment app check when adding the app role association --- .../DefaultRoleManagementListener.java | 24 +- .../DefaultRoleManagementListenerTest.java | 268 ++++++++++++++++++ .../mgt/core/RoleManagementServiceImpl.java | 112 ++++---- .../role/v2/mgt/core/dao/RoleDAOImpl.java | 20 +- .../core/RoleManagementServiceImplTest.java | 49 +++- .../role/v2/mgt/core/dao/RoleDAOTest.java | 30 ++ 6 files changed, 441 insertions(+), 62 deletions(-) create mode 100644 components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/DefaultRoleManagementListenerTest.java diff --git a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/listener/DefaultRoleManagementListener.java b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/listener/DefaultRoleManagementListener.java index 58a1976827c7..cdf9a56df19e 100644 --- a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/listener/DefaultRoleManagementListener.java +++ b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/listener/DefaultRoleManagementListener.java @@ -24,6 +24,7 @@ import org.wso2.carbon.identity.application.common.model.ApplicationBasicInfo; import org.wso2.carbon.identity.application.common.model.AuthorizedScopes; import org.wso2.carbon.identity.application.common.model.ServiceProvider; +import org.wso2.carbon.identity.application.mgt.ApplicationConstants; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementService; import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementServiceImpl; @@ -34,6 +35,7 @@ import org.wso2.carbon.identity.application.mgt.internal.cache.ServiceProviderByResourceIdCache; import org.wso2.carbon.identity.application.mgt.internal.cache.ServiceProviderIDCacheKey; import org.wso2.carbon.identity.application.mgt.internal.cache.ServiceProviderResourceIdCacheKey; +import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.role.v2.mgt.core.exception.IdentityRoleManagementClientException; import org.wso2.carbon.identity.role.v2.mgt.core.exception.IdentityRoleManagementException; import org.wso2.carbon.identity.role.v2.mgt.core.exception.IdentityRoleManagementServerException; @@ -65,9 +67,6 @@ */ public class DefaultRoleManagementListener extends AbstractApplicationMgtListener implements RoleManagementListener { - private static final AuthorizedAPIManagementService authorizedAPIManagementService = - new AuthorizedAPIManagementServiceImpl(); - @Override public int getExecutionOrderId() { @@ -92,7 +91,7 @@ public void preAddRole(String roleName, List userList, List grou throws IdentityRoleManagementException { if (APPLICATION.equalsIgnoreCase(audience)) { - validateApplicationRoleAudience(audienceId, tenantDomain); + validateApplicationTypeAndRoleAudience(audienceId, tenantDomain); validatePermissionsForApplication(permissions, audienceId, tenantDomain); } } @@ -513,13 +512,14 @@ public void postGetAssociatedApplicationIdsByRoleId(List associatedAppli } /** - * Validate application role audience. + * Validate application type and the role audience of the application. The application type will be set to the + * thread local properties. * * @param applicationId Application ID. * @param tenantDomain Tenant domain. * @throws IdentityRoleManagementException Error occurred while validating application role audience. */ - private void validateApplicationRoleAudience(String applicationId, String tenantDomain) + private void validateApplicationTypeAndRoleAudience(String applicationId, String tenantDomain) throws IdentityRoleManagementException { try { @@ -536,6 +536,17 @@ private void validateApplicationRoleAudience(String applicationId, String tenant throw new IdentityRoleManagementClientException(INVALID_AUDIENCE.getCode(), "Application: " + applicationId + " does not have Application role audience type"); } + + // Set thread local property to identify that the application is a fragment application. This property + // will be used in the role management component to identify the application type. + if (IdentityUtil.threadLocalProperties.get().get(ApplicationConstants.IS_FRAGMENT_APP) != null) { + IdentityUtil.threadLocalProperties.get().remove(ApplicationConstants.IS_FRAGMENT_APP); + } + if (app.getSpProperties() != null && Arrays.stream(app.getSpProperties()) + .anyMatch(property -> ApplicationConstants.IS_FRAGMENT_APP.equals(property.getName()) + && Boolean.parseBoolean(property.getValue()))) { + IdentityUtil.threadLocalProperties.get().put(ApplicationConstants.IS_FRAGMENT_APP, Boolean.TRUE); + } } catch (IdentityApplicationManagementException e) { String errorMessage = "Error while retrieving the application for the given id: " + applicationId; throw new IdentityRoleManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), errorMessage, e); @@ -579,6 +590,7 @@ private List getAuthorizedScopes(String appId, String tenantDomain) List authorizedScopesList; try { + AuthorizedAPIManagementService authorizedAPIManagementService = new AuthorizedAPIManagementServiceImpl(); authorizedScopesList = authorizedAPIManagementService.getAuthorizedScopes(appId, tenantDomain); } catch (IdentityApplicationManagementException e) { throw new IdentityRoleManagementException("Error while retrieving authorized scopes.", diff --git a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/DefaultRoleManagementListenerTest.java b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/DefaultRoleManagementListenerTest.java new file mode 100644 index 000000000000..3160d5a9b3ea --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/DefaultRoleManagementListenerTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 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 + * 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.application.mgt; + +import org.mockito.MockedStatic; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +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.listener.DefaultRoleManagementListener; +import org.wso2.carbon.identity.core.util.IdentityUtil; +import org.wso2.carbon.identity.role.v2.mgt.core.exception.IdentityRoleManagementClientException; +import org.wso2.carbon.identity.role.v2.mgt.core.exception.IdentityRoleManagementServerException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * Contains the unit tests for the default role management listener. + */ +public class DefaultRoleManagementListenerTest { + + private DefaultRoleManagementListener defaultRoleManagementListener; + private static final String ROLE_NAME = "test_role"; + private static final String APPLICATION_NAME = "app_name"; + private static final String APPLICATION_RES_ID = "app_id"; + private static final String IS_FRAGMENT_APP = "isFragmentApp"; + private static final String APPLICATION_AUD = "APPLICATION"; + private static final String ORGANIZATION_AUD = "ORGANIZATION"; + private static final String TENANT_DOMAIN = "wso2.com"; + + @BeforeClass + public void setUp() { + + defaultRoleManagementListener = spy(new DefaultRoleManagementListener()); + } + + @DataProvider(name = "fragmentAppPropertyProvider") + public Object[][] fragmentAppPropertyProvider() { + + // Creating main application object. + ServiceProvider mainApplication = createServiceProvider(); + mainApplication.setSpProperties(null); + + // Creating shared application object. + ServiceProvider fragmentApplication = createServiceProvider(); + ServiceProviderProperty isFragmentAppSpProp = buildServiceProviderProperty(IS_FRAGMENT_APP, + Boolean.TRUE.toString()); + fragmentApplication.setSpProperties(new ServiceProviderProperty[]{isFragmentAppSpProp}); + + return new Object[][] { + {false, mainApplication}, + {true, fragmentApplication} + }; + } + + @Test(priority = 1, dataProvider = "fragmentAppPropertyProvider") + public void testPreAddRoleForFragmentApp(boolean isFragmentApp, ServiceProvider application) throws Exception { + + try (MockedStatic applicationManagementServiceMockedStatic = + mockStatic(ApplicationManagementService.class)) { + + ApplicationManagementService applicationManagementService = mock(ApplicationManagementService.class); + applicationManagementServiceMockedStatic.when(ApplicationManagementService::getInstance). + thenReturn(applicationManagementService); + when(applicationManagementService.getApplicationByResourceId(anyString(), anyString())). + thenReturn(application); + when(applicationManagementService.getAllowedAudienceForRoleAssociation(anyString(), anyString())). + thenReturn(APPLICATION_AUD); + + // Calling the preAddRole method. + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), APPLICATION_AUD, APPLICATION_RES_ID, TENANT_DOMAIN); + + Map threadLocalProps = IdentityUtil.threadLocalProperties.get(); + if (!isFragmentApp) { + assertFalse(threadLocalProps.containsKey(IS_FRAGMENT_APP)); + } else { + // If the application is a fragment app, then we add the IS_FRAGMENT_APP property to the thread local. + assertTrue(threadLocalProps.containsKey(IS_FRAGMENT_APP)); + } + // Clearing the thread local properties. + IdentityUtil.threadLocalProperties.set(new HashMap<>()); + } + } + + @Test(priority = 2, dataProvider = "fragmentAppPropertyProvider") + public void testPreAddRoleWhenPreviousFragmentPropertyNotCleared(boolean isFragmentApp, + ServiceProvider application) throws Exception { + + try (MockedStatic applicationManagementServiceMockedStatic = + mockStatic(ApplicationManagementService.class)) { + + ApplicationManagementService applicationManagementService = mock(ApplicationManagementService.class); + applicationManagementServiceMockedStatic.when(ApplicationManagementService::getInstance). + thenReturn(applicationManagementService); + when(applicationManagementService.getApplicationByResourceId(anyString(), anyString())). + thenReturn(application); + when(applicationManagementService.getAllowedAudienceForRoleAssociation(anyString(), anyString())). + thenReturn(APPLICATION_AUD); + + // Mimicking the scenario where the previous fragment property is not cleared in the thread local. + Map threadLocalProps = new HashMap<>(); + threadLocalProps.put(IS_FRAGMENT_APP, Boolean.TRUE); + IdentityUtil.threadLocalProperties.set(threadLocalProps); + + // Calling the preAddRole method. + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), APPLICATION_AUD, APPLICATION_RES_ID, TENANT_DOMAIN); + + if (!isFragmentApp) { + assertFalse(threadLocalProps.containsKey(IS_FRAGMENT_APP)); + } else { + // If the application is a fragment app, then we add the IS_FRAGMENT_APP property to the thread local. + assertTrue(threadLocalProps.containsKey(IS_FRAGMENT_APP)); + } + // Clearing the thread local properties. + IdentityUtil.threadLocalProperties.set(new HashMap<>()); + } + } + + @Test(priority = 3) + public void testPreAddRoleWithOtherSPProperties() throws Exception { + + try (MockedStatic applicationManagementServiceMockedStatic = + mockStatic(ApplicationManagementService.class)) { + + // Creating service provider object. + ServiceProvider serviceProvider = createServiceProvider(); + + // Creating service provider property object. + ServiceProviderProperty isFragmentAppSpProp = buildServiceProviderProperty("test-prop-name", + "test-prop-value"); + serviceProvider.setSpProperties(new ServiceProviderProperty[]{isFragmentAppSpProp}); + + ApplicationManagementService applicationManagementService = mock(ApplicationManagementService.class); + applicationManagementServiceMockedStatic.when(ApplicationManagementService::getInstance). + thenReturn(applicationManagementService); + when(applicationManagementService.getApplicationByResourceId(anyString(), anyString())). + thenReturn(serviceProvider); + when(applicationManagementService.getAllowedAudienceForRoleAssociation(anyString(), anyString())). + thenReturn(APPLICATION_AUD); + + // Calling the preAddRole method. + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), APPLICATION_AUD, APPLICATION_RES_ID, TENANT_DOMAIN); + + assertFalse(IdentityUtil.threadLocalProperties.get().containsKey(IS_FRAGMENT_APP)); + } + } + + @Test(priority = 4) + public void testPreAddRoleForOrgAudience() throws Exception { + + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), ORGANIZATION_AUD, "org-id", TENANT_DOMAIN); + // If the audience is not application, then in the preAddRole method, it will return without going to the + // other methods. So the thread local properties will not be set. + Map threadLocalProps = IdentityUtil.threadLocalProperties.get(); + assertFalse(threadLocalProps.containsKey(IS_FRAGMENT_APP)); + } + + @Test(priority = 5, expectedExceptions = {IdentityRoleManagementClientException.class}, + expectedExceptionsMessageRegExp = "Invalid audience. No application found with application id: " + + APPLICATION_RES_ID + " and tenant domain : " + TENANT_DOMAIN) + public void testPreAddRoleWithNullApplication() throws Exception { + + try (MockedStatic applicationManagementServiceMockedStatic = + mockStatic(ApplicationManagementService.class)) { + + ApplicationManagementService applicationManagementService = mock(ApplicationManagementService.class); + applicationManagementServiceMockedStatic.when(ApplicationManagementService::getInstance). + thenReturn(applicationManagementService); + when(applicationManagementService.getApplicationByResourceId(anyString(), anyString())). + thenReturn(null); + // Calling the preAddRole method. + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), APPLICATION_AUD, APPLICATION_RES_ID, TENANT_DOMAIN); + } + } + + @Test(priority = 6, expectedExceptions = {IdentityRoleManagementClientException.class}, + expectedExceptionsMessageRegExp = "Application: " + APPLICATION_RES_ID + " does not have Application " + + "role audience type") + public void testPreAddRoleWithWrongAudience() throws Exception { + + try (MockedStatic applicationManagementServiceMockedStatic = + mockStatic(ApplicationManagementService.class)) { + + // Creating service provider object. + ServiceProvider serviceProvider = createServiceProvider(); + + ApplicationManagementService applicationManagementService = mock(ApplicationManagementService.class); + applicationManagementServiceMockedStatic.when(ApplicationManagementService::getInstance). + thenReturn(applicationManagementService); + when(applicationManagementService.getApplicationByResourceId(anyString(), anyString())). + thenReturn(serviceProvider); + when(applicationManagementService.getAllowedAudienceForRoleAssociation(anyString(), anyString())). + thenReturn(ORGANIZATION_AUD); + // Calling the preAddRole method. + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), APPLICATION_AUD, APPLICATION_RES_ID, TENANT_DOMAIN); + } + } + + @Test(priority = 7, expectedExceptions = {IdentityRoleManagementServerException.class}, + expectedExceptionsMessageRegExp = "Error while retrieving the application for the given id: " + + APPLICATION_RES_ID) + public void testPreAddRoleWithApplicationRetrievingException() throws Exception { + + try (MockedStatic applicationManagementServiceMockedStatic = + mockStatic(ApplicationManagementService.class)) { + + ApplicationManagementService applicationManagementService = mock(ApplicationManagementService.class); + applicationManagementServiceMockedStatic.when(ApplicationManagementService::getInstance). + thenReturn(applicationManagementService); + when(applicationManagementService.getApplicationByResourceId(anyString(), anyString())). + thenThrow(IdentityApplicationManagementException.class); + + // Calling the preAddRole method. + defaultRoleManagementListener.preAddRole(ROLE_NAME, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), APPLICATION_AUD, APPLICATION_RES_ID, TENANT_DOMAIN); + } + } + + private static ServiceProviderProperty buildServiceProviderProperty(String name, String value) { + + ServiceProviderProperty isFragmentAppSpProp = new ServiceProviderProperty(); + isFragmentAppSpProp.setName(name); + isFragmentAppSpProp.setValue(value); + return isFragmentAppSpProp; + } + + private static ServiceProvider createServiceProvider() { + + ServiceProvider serviceProvider = new ServiceProvider(); + serviceProvider.setApplicationName(APPLICATION_NAME); + serviceProvider.setApplicationResourceId(APPLICATION_RES_ID); + return serviceProvider; + } +} diff --git a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImpl.java b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImpl.java index 5ec1aed50444..85bfab183292 100644 --- a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImpl.java +++ b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImpl.java @@ -76,71 +76,79 @@ public class RoleManagementServiceImpl implements RoleManagementService { private static final Log log = LogFactory.getLog(RoleManagementServiceImpl.class); private final RoleDAO roleDAO = RoleMgtDAOFactory.getInstance().getRoleDAO(); private final UserIDResolver userIDResolver = new UserIDResolver(); + private static final String IS_FRAGMENT_APP = "isFragmentApp"; @Override public RoleBasicInfo addRole(String roleName, List userList, List groupList, List permissions, String audience, String audienceId, String tenantDomain) throws IdentityRoleManagementException { - if (!RoleManagementUtils.isAllowSystemPrefixForRole() && - StringUtils.startsWithIgnoreCase(roleName, UserCoreConstants.INTERNAL_SYSTEM_ROLE_PREFIX)) { - String errorMessage = String.format("Invalid role name: %s. Role names with the prefix: %s, is not allowed" - + " to be created from externally in the system.", roleName, - UserCoreConstants.INTERNAL_SYSTEM_ROLE_PREFIX); - throw new IdentityRoleManagementClientException(INVALID_REQUEST.getCode(), errorMessage); - } - if (isDomainSeparatorPresent(roleName)) { - // SCIM2 API only adds roles to the internal domain. - throw new IdentityRoleManagementClientException(INVALID_REQUEST.getCode(), "Invalid character: " - + UserCoreConstants.DOMAIN_SEPARATOR + " contains in the role name: " + roleName + "."); - } - List roleManagementListenerList = RoleManagementServiceComponentHolder.getInstance() - .getRoleManagementListenerList(); - for (RoleManagementListener roleManagementListener : roleManagementListenerList) { - if (roleManagementListener.isEnable()) { - roleManagementListener.preAddRole(roleName, userList, groupList, - permissions, audience, audienceId, tenantDomain); + try { + if (!RoleManagementUtils.isAllowSystemPrefixForRole() && + StringUtils.startsWithIgnoreCase(roleName, UserCoreConstants.INTERNAL_SYSTEM_ROLE_PREFIX)) { + String errorMessage = String.format("Invalid role name: %s. Role names with the prefix: %s, is not " + + "allowed to be created from externally in the system.", roleName, + UserCoreConstants.INTERNAL_SYSTEM_ROLE_PREFIX); + throw new IdentityRoleManagementClientException(INVALID_REQUEST.getCode(), errorMessage); + } + if (isDomainSeparatorPresent(roleName)) { + // SCIM2 API only adds roles to the internal domain. + throw new IdentityRoleManagementClientException(INVALID_REQUEST.getCode(), "Invalid character: " + + UserCoreConstants.DOMAIN_SEPARATOR + " contains in the role name: " + roleName + "."); + } + List roleManagementListenerList = RoleManagementServiceComponentHolder. + getInstance().getRoleManagementListenerList(); + for (RoleManagementListener roleManagementListener : roleManagementListenerList) { + if (roleManagementListener.isEnable()) { + roleManagementListener.preAddRole(roleName, userList, groupList, + permissions, audience, audienceId, tenantDomain); + } } - } - RoleManagementEventPublisherProxy roleManagementEventPublisherProxy = RoleManagementEventPublisherProxy - .getInstance(); - roleManagementEventPublisherProxy.publishPreAddRoleWithException(roleName, userList, groupList, permissions, - audience, audienceId, tenantDomain); + RoleManagementEventPublisherProxy roleManagementEventPublisherProxy = RoleManagementEventPublisherProxy + .getInstance(); + roleManagementEventPublisherProxy.publishPreAddRoleWithException(roleName, userList, groupList, + permissions, audience, audienceId, tenantDomain); - // Validate audience. - if (StringUtils.isNotEmpty(audience)) { - if (!(ORGANIZATION.equalsIgnoreCase(audience) || APPLICATION.equalsIgnoreCase(audience))) { - throw new IdentityRoleManagementClientException(INVALID_AUDIENCE.getCode(), "Invalid role audience"); - } - if (ORGANIZATION.equalsIgnoreCase(audience)) { - validateOrganizationRoleAudience(audienceId, tenantDomain); + // Validate audience. + if (StringUtils.isNotEmpty(audience)) { + if (!(ORGANIZATION.equalsIgnoreCase(audience) || APPLICATION.equalsIgnoreCase(audience))) { + throw new IdentityRoleManagementClientException(INVALID_AUDIENCE.getCode(), + "Invalid role audience"); + } + if (ORGANIZATION.equalsIgnoreCase(audience)) { + validateOrganizationRoleAudience(audienceId, tenantDomain); + audience = ORGANIZATION; + } + if (APPLICATION.equalsIgnoreCase(audience)) { + // audience validation done using listener. + audience = APPLICATION; + } + } else { audience = ORGANIZATION; + audienceId = getOrganizationIdByTenantDomain(tenantDomain); + } + validatePermissions(permissions, audience, audienceId, tenantDomain); + RoleBasicInfo roleBasicInfo = roleDAO.addRole(roleName, userList, groupList, permissions, audience, + audienceId, tenantDomain); + roleManagementEventPublisherProxy.publishPostAddRole(roleBasicInfo.getId(), roleName, userList, groupList, + permissions, audience, audienceId, tenantDomain); + if (log.isDebugEnabled()) { + log.debug(String.format("%s added role of name : %s successfully.", getUser(tenantDomain), roleName)); + } + RoleBasicInfo role = roleDAO.getRoleBasicInfoById(roleBasicInfo.getId(), tenantDomain); + for (RoleManagementListener roleManagementListener : roleManagementListenerList) { + if (roleManagementListener.isEnable()) { + roleManagementListener.postAddRole(role, roleName, userList, + groupList, permissions, audience, audienceId, tenantDomain); + } } - if (APPLICATION.equalsIgnoreCase(audience)) { - // audience validation done using listener. - audience = APPLICATION; + return role; + } finally { + if (IdentityUtil.threadLocalProperties.get().get(IS_FRAGMENT_APP) != null) { + IdentityUtil.threadLocalProperties.get().remove(IS_FRAGMENT_APP); } - } else { - audience = ORGANIZATION; - audienceId = getOrganizationIdByTenantDomain(tenantDomain); } - validatePermissions(permissions, audience, audienceId, tenantDomain); - RoleBasicInfo roleBasicInfo = roleDAO.addRole(roleName, userList, groupList, permissions, audience, audienceId, - tenantDomain); - roleManagementEventPublisherProxy.publishPostAddRole(roleBasicInfo.getId(), roleName, userList, groupList, - permissions, audience, audienceId, tenantDomain); - if (log.isDebugEnabled()) { - log.debug(String.format("%s added role of name : %s successfully.", getUser(tenantDomain), roleName)); - } - RoleBasicInfo role = roleDAO.getRoleBasicInfoById(roleBasicInfo.getId(), tenantDomain); - for (RoleManagementListener roleManagementListener : roleManagementListenerList) { - if (roleManagementListener.isEnable()) { - roleManagementListener.postAddRole(role, roleName, userList, - groupList, permissions, audience, audienceId, tenantDomain); - } - } - return role; } @Override diff --git a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOImpl.java b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOImpl.java index 65bd819437d7..cfadce04c194 100644 --- a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOImpl.java +++ b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOImpl.java @@ -215,6 +215,7 @@ public class RoleDAOImpl implements RoleDAO { private static final String PERMISSIONS = "permissions"; private static final String ASSOCIATED_APPLICATIONS = "associatedApplications"; private static final String PROPERTIES = "properties"; + private static final String IS_FRAGMENT_APP = "isFragmentApp"; @Override public RoleBasicInfo addRole(String roleName, List userList, List groupList, @@ -1672,7 +1673,7 @@ private void addRoleInfo(String roleId, String roleName, List permis addRoleID(roleId, roleName, audienceRefId, tenantDomain, connection); addPermissions(roleId, permissions, tenantDomain, connection); - if (APPLICATION.equals(audience) && !isOrganization(tenantDomain)) { + if (APPLICATION.equals(audience) && !isFragmentApp()) { addAppRoleAssociation(roleId, audienceId, connection); } IdentityDatabaseUtil.commitTransaction(connection); @@ -1688,6 +1689,23 @@ private void addRoleInfo(String roleId, String roleName, List permis } } + /** + * Check whether the corresponding application is a fragment application. This check is using a thread local + * property which is set from the default role management listener. + * + * @return True if the application is a fragment application. + */ + private boolean isFragmentApp() { + + if (IdentityUtil.threadLocalProperties.get().get(IS_FRAGMENT_APP) != null) { + boolean isFragmentApp = Boolean.parseBoolean(IdentityUtil.threadLocalProperties.get(). + get(IS_FRAGMENT_APP).toString()); + IdentityUtil.threadLocalProperties.get().remove(IS_FRAGMENT_APP); + return isFragmentApp; + } + return false; + } + @Override public boolean isSharedRole(String roleId, String tenantDomain) throws IdentityRoleManagementException { diff --git a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImplTest.java b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImplTest.java index 04aa271fb7ce..8bea86eccb23 100644 --- a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImplTest.java +++ b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleManagementServiceImplTest.java @@ -22,6 +22,7 @@ import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -38,6 +39,8 @@ import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import static org.mockito.Mockito.anyList; import static org.mockito.Mockito.anyString; @@ -48,6 +51,7 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +import static org.testng.AssertJUnit.assertEquals; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.ALLOW_SYSTEM_PREFIX_FOR_ROLES; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.APPLICATION; import static org.wso2.carbon.utils.multitenancy.MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; @@ -66,6 +70,14 @@ public class RoleManagementServiceImplTest extends IdentityBaseTest { private static final String audienceId = "testId"; private static final String roleId = "testRoleId"; + private static MockedStatic roleManagementEventPublisherProxy; + + @BeforeClass + public static void setUpClass() throws Exception { + + roleManagementEventPublisherProxy = mockStatic(RoleManagementEventPublisherProxy.class); + } + @BeforeMethod public void setUp() { @@ -103,9 +115,7 @@ public Object[][] addRoleWithSystemPrefixData() { public void testAddRoleWithSystemPrefix(boolean allowSystemPrefix, String roleName, boolean errorScenario) throws IdentityRoleManagementException { - try (MockedStatic identityUtil = mockStatic(IdentityUtil.class); - MockedStatic roleManagementEventPublisherProxy - = mockStatic(RoleManagementEventPublisherProxy.class)) { + try (MockedStatic identityUtil = mockStatic(IdentityUtil.class)) { identityUtil.when(() -> IdentityUtil.getProperty(ALLOW_SYSTEM_PREFIX_FOR_ROLES)) .thenReturn(String.valueOf(allowSystemPrefix)); @@ -141,6 +151,39 @@ public void testAddRoleWithSystemPrefix(boolean allowSystemPrefix, String roleNa } } + @Test + public void testAddRoleWithIsFragmentAppProperty() throws Exception { + + String roleName = "validRole"; + String audience = "APPLICATION"; + String audienceId = "application_id_01"; + String tenantDomain = "tenantDomain"; + + Map mockThreadLocalProperties = new HashMap<>(); + mockThreadLocalProperties.put("isFragmentApp", Boolean.TRUE.toString()); + IdentityUtil.threadLocalProperties.set(mockThreadLocalProperties); + + RoleBasicInfo expectedRole = new RoleBasicInfo("roleId", roleName); + when(roleDAO.addRole(anyString(), anyList(), anyList(), anyList(), anyString(), anyString(), anyString())) + .thenReturn(expectedRole); + + when(roleDAO.getRoleBasicInfoById(anyString(), anyString())).thenReturn(expectedRole); + + RoleManagementEventPublisherProxy mockRoleMgtEventPublisherProxy = mock( + RoleManagementEventPublisherProxy.class); + roleManagementEventPublisherProxy.when(RoleManagementEventPublisherProxy::getInstance) + .thenReturn(mockRoleMgtEventPublisherProxy); + lenient().doNothing().when(mockRoleMgtEventPublisherProxy).publishPreAddRoleWithException(anyString(), + anyList(), anyList(), anyList(), anyString(), anyString(), anyString()); + lenient().doNothing().when(mockRoleMgtEventPublisherProxy).publishPostAddRole(anyString(), anyString(), + anyList(), anyList(), anyList(), anyString(), anyString(), anyString()); + + RoleBasicInfo result = roleManagementService.addRole(roleName, new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), audience, audienceId, tenantDomain); + + assertEquals(expectedRole, result); + } + private void mockCarbonContextForTenant() { String carbonHome = Paths.get(System.getProperty("user.dir"), "target", "test-classes").toString(); diff --git a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOTest.java b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOTest.java index 37c583bf85bc..c50c08ef8494 100644 --- a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOTest.java +++ b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/test/java/org/wso2/carbon/identity/role/v2/mgt/core/dao/RoleDAOTest.java @@ -130,6 +130,9 @@ public class RoleDAOTest { private static final String MOCKED_EXCEPTION = "Mocked Exception"; private static final String ROLE_PROPERTIES = "properties"; private static final String IS_SHARED_ROLE = "isSharedRole"; + private static final String IS_FRAGMENT_APP = "isFragmentApp"; + private static final String SHARED_APP_ROLE_NAME = "shared-app-role-name-01"; + private static final String SHARED_APP_ID = "shared-app-id"; private static Map dataSourceMap = new HashMap<>(); private List userNamesList = new ArrayList<>(); @@ -245,6 +248,33 @@ public void testAddAppRole() throws Exception { SAMPLE_TENANT_DOMAIN)); } + @Test + public void testAddAppRoleToFragmentApp() throws Exception { + + RoleDAOImpl roleDAO = spy(new RoleDAOImpl()); + mockCacheClearing(roleDAO); + identityDatabaseUtil.when(() -> IdentityDatabaseUtil.getUserDBConnection(anyBoolean())) + .thenAnswer(invocation -> getConnection()); + identityDatabaseUtil.when(() -> IdentityDatabaseUtil.getDBConnection(anyBoolean())) + .thenAnswer(invocation -> getConnection()); + identityUtil.when(IdentityUtil::getPrimaryDomainName).thenReturn(USER_DOMAIN_PRIMARY); + identityUtil.when(() -> IdentityUtil.extractDomainFromName(anyString())).thenCallRealMethod(); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(SAMPLE_TENANT_ID); + // Mock the threadLocalProperties static variable. + Map mockThreadLocalProperties = new HashMap<>(); + mockThreadLocalProperties.put(IS_FRAGMENT_APP, Boolean.TRUE.toString()); + IdentityUtil.threadLocalProperties.set(mockThreadLocalProperties); + // Add role to a fragment application. WHen doing this we need to stop adding the role to the + // application mapping. + RoleBasicInfo sharedRoleBasicInfo = addRole(SHARED_APP_ROLE_NAME, APPLICATION_AUD, SHARED_APP_ID, roleDAO); + List appIDs = roleDAO.getAssociatedApplicationIdsByRoleId(sharedRoleBasicInfo.getId(), + SAMPLE_TENANT_DOMAIN); + // The role should not be associated with any application. + assertTrue(appIDs.isEmpty()); + // Assert the thread local properties to check whether the IS_FRAGMENT_APP is removed. + assertFalse(IdentityUtil.threadLocalProperties.get().containsKey(IS_FRAGMENT_APP)); + } + @Test public void testAddRoles() throws Exception {