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..2790609f4c4e 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; @@ -92,7 +94,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); } } @@ -519,7 +521,7 @@ public void postGetAssociatedApplicationIdsByRoleId(List associatedAppli * @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 +538,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 (app.getSpProperties() != null && Arrays.stream(app.getSpProperties()) + .anyMatch(property -> ApplicationConstants.IS_FRAGMENT_APP.equals(property.getName()) + && Boolean.parseBoolean(property.getValue()))) { + if (IdentityUtil.threadLocalProperties.get().get(ApplicationConstants.IS_FRAGMENT_APP) != null) { + IdentityUtil.threadLocalProperties.get().remove(ApplicationConstants.IS_FRAGMENT_APP); + } + 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); 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 a7c12941f469..eb61b95fd784 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 @@ -213,6 +213,7 @@ public class RoleDAOImpl implements RoleDAO { private static final String GROUPS = "groups"; private static final String PERMISSIONS = "permissions"; private static final String ASSOCIATED_APPLICATIONS = "associatedApplications"; + private static final String IS_FRAGMENT_APP = "isFragmentApp"; @Override public RoleBasicInfo addRole(String roleName, List userList, List groupList, @@ -1649,8 +1650,8 @@ private void addRoleInfo(String roleId, String roleName, List permis try { 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); @@ -1666,6 +1667,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 7c130f5d7b0f..3e0f32b942d2 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 @@ -127,6 +127,9 @@ public class RoleDAOTest { private static final String UPDATED_SHARED_ORG_ROLE_NAME = "new-sharing-org-role-001"; private static final String SUB_ORG_ROLE_NAME = "sub-org-role-200"; private static final String MOCKED_EXCEPTION = "Mocked Exception"; + 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<>(); @@ -242,6 +245,34 @@ 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()); + // Remove the mocked threadLocalProperties. + Map threadLocalProperties = new HashMap<>(); + IdentityUtil.threadLocalProperties.set(threadLocalProperties); + } + @Test public void testAddRoles() throws Exception {