From f7449609bc125117f07d9b4f2e8745331ee221fb Mon Sep 17 00:00:00 2001 From: Shan Chathusanda Jayathilaka Date: Sun, 8 Dec 2024 11:43:27 +0530 Subject: [PATCH] Introduce the fragment app check when adding the app role association --- .../DefaultRoleManagementListener.java | 17 ++- .../mgt/core/RoleManagementServiceImpl.java | 110 ++++++++++-------- .../role/v2/mgt/core/dao/RoleDAOImpl.java | 22 +++- .../role/v2/mgt/core/dao/RoleDAOTest.java | 31 +++++ 4 files changed, 125 insertions(+), 55 deletions(-) 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 a0b8b65e903a..2065519d0779 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 @@ -75,70 +75,78 @@ 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 (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 (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 c5b708e5e997..4adb68f4e07e 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, @@ -1609,8 +1610,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); @@ -1626,6 +1627,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/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 69739368b0f8..573d396d5102 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 {