Skip to content

Commit

Permalink
Introduce the fragment app check when adding the app role association
Browse files Browse the repository at this point in the history
  • Loading branch information
ShanChathusanda93 committed Dec 14, 2024
1 parent 65ae6fe commit 2b0de15
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -92,7 +94,7 @@ public void preAddRole(String roleName, List<String> userList, List<String> grou
throws IdentityRoleManagementException {

if (APPLICATION.equalsIgnoreCase(audience)) {
validateApplicationRoleAudience(audienceId, tenantDomain);
validateApplicationTypeAndRoleAudience(audienceId, tenantDomain);
validatePermissionsForApplication(permissions, audienceId, tenantDomain);
}
}
Expand Down Expand Up @@ -519,7 +521,7 @@ public void postGetAssociatedApplicationIdsByRoleId(List<String> 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 {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> userList, List<String> groupList,
List<Permission> 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<RoleManagementListener> 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<RoleManagementListener> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> userList, List<String> groupList,
Expand Down Expand Up @@ -1649,8 +1650,8 @@ private void addRoleInfo(String roleId, String roleName, List<Permission> 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);
Expand All @@ -1666,6 +1667,23 @@ private void addRoleInfo(String roleId, String roleName, List<Permission> 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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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> roleManagementEventPublisherProxy;

@BeforeClass
public static void setUpClass() throws Exception {

roleManagementEventPublisherProxy = mockStatic(RoleManagementEventPublisherProxy.class);
}

@BeforeMethod
public void setUp() {

Expand Down Expand Up @@ -103,9 +115,7 @@ public Object[][] addRoleWithSystemPrefixData() {
public void testAddRoleWithSystemPrefix(boolean allowSystemPrefix, String roleName, boolean errorScenario)
throws IdentityRoleManagementException {

try (MockedStatic<IdentityUtil> identityUtil = mockStatic(IdentityUtil.class);
MockedStatic<RoleManagementEventPublisherProxy> roleManagementEventPublisherProxy
= mockStatic(RoleManagementEventPublisherProxy.class)) {
try (MockedStatic<IdentityUtil> identityUtil = mockStatic(IdentityUtil.class)) {
identityUtil.when(() -> IdentityUtil.getProperty(ALLOW_SYSTEM_PREFIX_FOR_ROLES))
.thenReturn(String.valueOf(allowSystemPrefix));

Expand Down Expand Up @@ -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<String, Object> 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();
Expand Down
Loading

0 comments on commit 2b0de15

Please sign in to comment.