diff --git a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/ApplicationConstants.java b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/ApplicationConstants.java index c18c4c2d38c2..1f682915d550 100644 --- a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/ApplicationConstants.java +++ b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/ApplicationConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2014-2026, 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 @@ -123,6 +123,10 @@ private ApplicationConstants() { public static final String APPLICATION_NAME_CONFIG_ELEMENT = "ApplicationName"; public static final String PORTAL_NAMES_CONFIG_ELEMENT = "SystemPortals.PortalName"; + // Application API Authorization Management Service Configurations. + public static final String APPLICATION_API_AUTHORIZATION_BLOCKED_API_RESOURCES = + "APIResourceManagement.ApplicationAPIAuthorization.BlockedAPIResources.Identifier"; + // Application Management Service Configurations. public static final String ENABLE_APPLICATION_ROLE_VALIDATION_PROPERTY = "ApplicationMgt.EnableRoleValidation"; public static final String TRUSTED_APP_CONSENT_REQUIRED_PROPERTY = "ApplicationMgt.TrustedAppConsentRequired"; diff --git a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImpl.java b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImpl.java index 02c2df384d8e..89ebf5665830 100644 --- a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImpl.java +++ b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImpl.java @@ -55,6 +55,7 @@ import static org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants.Error.INVALID_REQUEST; import static org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants.Error.UNEXPECTED_SERVER_ERROR; +import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.APPLICATION_API_AUTHORIZATION_BLOCKED_API_RESOURCES; import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.AUTHORIZE_ALL_SCOPES; import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.AUTHORIZE_INTERNAL_SCOPES; import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.ENABLE_CROSS_TENANT_AUTHORIZED_API_VALIDATION_PROPERTY; @@ -83,6 +84,7 @@ public void addAuthorizedAPI(String applicationId, AuthorizedAPI authorizedAPI, ApplicationManagementService applicationManagementService = ApplicationManagementServiceImpl.getInstance(); validateTenantDomain(applicationId, tenantDomain, applicationManagementService); + validateAPIResourceNotBlocked(authorizedAPI.getAPIId(), tenantDomain); ApplicationAuthorizedAPIManagementEventPublisherProxy publisherProxy = ApplicationAuthorizedAPIManagementEventPublisherProxy.getInstance(); @@ -323,6 +325,29 @@ private IdentityApplicationManagementClientException buildClientException( return new IdentityApplicationManagementClientException(errorMessage.getCode(), message); } + private void validateAPIResourceNotBlocked(String apiId, String tenantDomain) + throws IdentityApplicationManagementException { + + if (StringUtils.isBlank(apiId)) { + return; + } + try { + APIResource apiResource = ApplicationManagementServiceComponentHolder.getInstance() + .getAPIResourceManager().getAPIResourceById(apiId, tenantDomain); + if (apiResource == null || StringUtils.isBlank(apiResource.getIdentifier())) { + return; + } + List blockedIdentifiers = IdentityUtil.getPropertyAsList( + APPLICATION_API_AUTHORIZATION_BLOCKED_API_RESOURCES); + if (blockedIdentifiers.contains(apiResource.getIdentifier())) { + throw buildClientException(INVALID_REQUEST, "API resource '" + apiResource.getIdentifier() + + "' is blocked from being authorized to applications."); + } + } catch (APIResourceMgtException e) { + throw buildServerException("Error while retrieving API resource for apiId: " + apiId, e); + } + } + private IdentityApplicationManagementServerException buildServerException(String message, Throwable ex) { return new IdentityApplicationManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), message, ex); @@ -370,6 +395,7 @@ public void patchAuthorizedAPI(String appId, String apiId, List addedSco ApplicationManagementService applicationManagementService = ApplicationManagementServiceImpl.getInstance(); validateTenantDomain(appId, tenantDomain, applicationManagementService); + validateAPIResourceNotBlocked(apiId, tenantDomain); ApplicationAuthorizedAPIManagementEventPublisherProxy publisherProxy = ApplicationAuthorizedAPIManagementEventPublisherProxy.getInstance(); publisherProxy.publishPreUpdateAuthorizedAPIForApplication(appId, apiId, addedScopes, removedScopes, diff --git a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/dao/impl/AuthorizedAPIDAOImpl.java b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/dao/impl/AuthorizedAPIDAOImpl.java index b44a9fb39098..6b62436c4573 100644 --- a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/dao/impl/AuthorizedAPIDAOImpl.java +++ b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/main/java/org/wso2/carbon/identity/application/mgt/dao/impl/AuthorizedAPIDAOImpl.java @@ -28,6 +28,7 @@ import org.wso2.carbon.identity.api.resource.mgt.util.AuthorizationDetailsTypesUtil; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementClientException; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.common.model.APIResource; import org.wso2.carbon.identity.application.common.model.AuthorizationDetailsType; import org.wso2.carbon.identity.application.common.model.AuthorizedAPI; import org.wso2.carbon.identity.application.common.model.AuthorizedScopes; @@ -38,6 +39,7 @@ import org.wso2.carbon.identity.application.mgt.util.ScopeAuthorizationInfo; import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import org.wso2.carbon.identity.organization.management.service.util.OrganizationManagementUtil; @@ -412,6 +414,8 @@ private void authorizeScopesForSystemApis(Connection dbConnection, String applic throws SQLException, IdentityApplicationManagementException { List systemApiScopes = resolveSystemApiScopesByName(dbConnection, scopes); + // Silently drop other system APIs being auto authorized based on the server-wide blocked list. + systemApiScopes = filterBlockedAPIScopes(systemApiScopes, apiId, tenantId); boolean hasSharedScopes = systemApiScopes.stream().anyMatch(scopeInfo -> !scopeInfo.getApiId().equals(apiId)); if (hasSharedScopes) { @@ -595,6 +599,56 @@ private void authorizeApisWithReusedScopes(Connection dbConnection, String appli } } + private List filterBlockedAPIScopes(List systemApiScopes, + String primaryApiId, int tenantId) + throws IdentityApplicationManagementException { + + if (CollectionUtils.isEmpty(systemApiScopes)) { + return systemApiScopes; + } + List blockedIdentifiers = IdentityUtil.getPropertyAsList( + ApplicationConstants.APPLICATION_API_AUTHORIZATION_BLOCKED_API_RESOURCES); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Blocked API identifiers from the server config: %s", blockedIdentifiers)); + } + if (blockedIdentifiers.isEmpty()) { + return systemApiScopes; + } + // Identify other API resources that share scopes with the primary API resource. + Set nonPrimaryApiIds = systemApiScopes.stream() + .map(ScopeAuthorizationInfo::getApiId) + .filter(id -> !id.equals(primaryApiId)) + .collect(Collectors.toSet()); + if (nonPrimaryApiIds.isEmpty()) { + return systemApiScopes; + } + String tenantDomain = IdentityTenantUtil.getTenantDomain(tenantId); + Set blockedApiIds = new HashSet<>(); + for (String apiId : nonPrimaryApiIds) { + try { + APIResource apiResource = ApplicationManagementServiceComponentHolder.getInstance() + .getAPIResourceManager().getAPIResourceById(apiId, tenantDomain); + if (apiResource != null && !StringUtils.isBlank(apiResource.getIdentifier()) + && blockedIdentifiers.contains(apiResource.getIdentifier())) { + blockedApiIds.add(apiId); + } + } catch (APIResourceMgtException e) { + throw new IdentityApplicationManagementException("Error while retrieving API resource for apiId: " + + apiId, e); + } + } + if (blockedApiIds.isEmpty()) { + return systemApiScopes; + } + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Skipping implicit cross-API authorization for %d blocked API Id(s): %s", + blockedApiIds.size(), blockedApiIds)); + } + return systemApiScopes.stream() + .filter(scopeInfo -> !blockedApiIds.contains(scopeInfo.getApiId())) + .collect(Collectors.toList()); + } + /** * Get already authorized APIs from a set of API IDs in a single query. * diff --git a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImplTest.java b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImplTest.java index 30dd6c182e01..629b48ad35fd 100644 --- a/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImplTest.java +++ b/components/application-mgt/org.wso2.carbon.identity.application.mgt/src/test/java/org/wso2/carbon/identity/application/mgt/AuthorizedAPIManagementServiceImplTest.java @@ -112,6 +112,7 @@ public void setUp() throws Exception { identityEventService = mock(IdentityEventService.class); doNothing().when(identityEventService).handleEvent(any()); ApplicationManagementServiceComponentHolder.getInstance().setIdentityEventService(identityEventService); + ApplicationManagementServiceComponentHolder.getInstance().setAPIResourceManager(apiResourceManager); APIResourceManagementServiceComponentHolder.getInstance().setRichAuthorizationRequestsEnabled(true); APIResourceManagementServiceComponentHolder.getInstance().setIdentityEventService(identityEventService); CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false; @@ -148,7 +149,6 @@ public Object[][] createAuthorizedAPIDataProvider() throws Exception { public void testCreateAuthorizedAPI(AuthorizedAPI authorizedAPI, int expectedAPIs) throws Exception { - ApplicationManagementServiceComponentHolder.getInstance().setAPIResourceManager(apiResourceManager); authorizedAPIManagementService.addAuthorizedAPI(authorizedAPI.getAppId(), authorizedAPI, tenantDomain); List authorizedAPIS = authorizedAPIManagementService.getAuthorizedAPIs(authorizedAPI.getAppId(), tenantDomain); 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/RoleConstants.java b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleConstants.java index 7754f347fc9f..59f499e8a2ab 100644 --- a/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleConstants.java +++ b/components/role-mgt/org.wso2.carbon.identity.role.v2.mgt.core/src/main/java/org/wso2/carbon/identity/role/v2/mgt/core/RoleConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2026, 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 @@ -85,6 +85,8 @@ private RoleConstants() { // Role management service configurations. public static final String ALLOW_SYSTEM_PREFIX_FOR_ROLES = "RoleMgt.AllowSystemPrefixForRoles"; + public static final String ROLE_PERMISSION_ASSIGNMENT_BLOCKED_API_RESOURCES = + "APIResourceManagement.RolePermissionAssignment.BlockedAPIResources.Identifier"; // Role properties public static final String IS_SHARED_ROLE_PROP_NAME = "isSharedRole"; @@ -114,6 +116,7 @@ public static class RoleTableColumns { public static final String ROLE_ID = "ROLE_ID"; public static final String UM_ROLE_ID = "UM_ROLE_ID"; public static final String SCOPE_NAME = "SCOPE_NAME"; + public static final String SCOPE_ID = "SCOPE_ID"; public static final String APP_ID = "APP_ID"; public static final String UM_SHARED_ROLE_ID = "UM_SHARED_ROLE_ID"; public static final String UM_SHARED_ROLE_TENANT_ID = "UM_SHARED_ROLE_TENANT_ID"; 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 2e174a7cf496..5bbbfba82983 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2025, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2026, 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 @@ -27,8 +27,11 @@ import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.database.utils.jdbc.NamedPreparedStatement; +import org.wso2.carbon.identity.api.resource.mgt.APIResourceMgtException; +import org.wso2.carbon.identity.application.common.model.APIResource; import org.wso2.carbon.identity.application.common.model.IdPGroup; import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.application.common.model.Scope; import org.wso2.carbon.identity.base.IdentityConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LogConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; @@ -99,6 +102,7 @@ import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.DB2; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.Error.INVALID_LIMIT; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.Error.INVALID_OFFSET; +import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.Error.INVALID_PERMISSION; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.Error.INVALID_REQUEST; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.Error.OPERATION_FORBIDDEN; import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.Error.PERMISSION_ALREADY_ADDED; @@ -125,6 +129,7 @@ import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_GROUP_TO_ROLE_SQL_MSSQL; import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_IDP_GROUPS_SQL; import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_ROLE_AUDIENCE_SQL; +import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_ROLE_SCOPE_BY_ID_SQL; import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_ROLE_SCOPE_SQL; import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_ROLE_SQL; import static org.wso2.carbon.identity.role.v2.mgt.core.dao.SQLQueries.ADD_SCIM_ROLE_ID_SQL; @@ -297,7 +302,10 @@ public RoleBasicInfo addRole(String roleName, List userList, List addedPerm throws IdentityRoleManagementException { try (Connection connection = IdentityDatabaseUtil.getDBConnection(true)) { - addPermissions(roleId, addedPermissions, tenantDomain, connection); - for (Permission permission : deletedPermissions) { - try (NamedPreparedStatement statement = new NamedPreparedStatement(connection, - DELETE_ROLE_SCOPE_BY_SCOPE_NAME_SQL)) { - statement.setString(RoleConstants.RoleTableColumns.ROLE_ID, roleId); - statement.setString(RoleConstants.RoleTableColumns.SCOPE_NAME, permission.getName()); - statement.executeUpdate(); - } catch (SQLException e) { - IdentityDatabaseUtil.rollbackTransaction(connection); - String errorMessage = "Error while adding permissions to roleId : " + roleId; - throw new IdentityRoleManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), - errorMessage, e); + try { + addPermissions(roleId, addedPermissions, tenantDomain, connection); + for (Permission permission : deletedPermissions) { + try (NamedPreparedStatement statement = new NamedPreparedStatement(connection, + DELETE_ROLE_SCOPE_BY_SCOPE_NAME_SQL)) { + statement.setString(RoleConstants.RoleTableColumns.ROLE_ID, roleId); + statement.setString(RoleConstants.RoleTableColumns.SCOPE_NAME, permission.getName()); + statement.executeUpdate(); + } } + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException | IdentityRoleManagementException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw e; } - IdentityDatabaseUtil.commitTransaction(connection); + } catch (IdentityRoleManagementClientException e) { + throw e; } catch (SQLException | IdentityRoleManagementException e) { String errorMessage = "Error while adding permissions to roleId : " + roleId; throw new IdentityRoleManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), @@ -1674,6 +1684,9 @@ private void addRoleInfo(String roleId, String roleName, List permis IdentityDatabaseUtil.commitTransaction(connection); } catch (IdentityRoleManagementException e) { IdentityDatabaseUtil.rollbackTransaction(connection); + if (e instanceof IdentityRoleManagementClientException) { + throw e; + } String errorMessage = "Error while adding role info for role : " + roleName; throw new IdentityRoleManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), errorMessage, e); } @@ -1927,17 +1940,40 @@ private void addPermissions(String roleId, List permissions, String return; } checkPermissionsAlreadyAdded(roleId, permissions, tenantDomain, connection); - // Resolve the tenant ID to search the scope details. - int tenantIdToSearchScopes = IdentityTenantUtil.getTenantId( - isOrganization(tenantDomain) ? getPrimaryOrgTenantDomain(tenantDomain) : tenantDomain); - try (NamedPreparedStatement statement = new NamedPreparedStatement(connection, ADD_ROLE_SCOPE_SQL)) { - for (Permission permission : permissions) { - statement.setString(RoleConstants.RoleTableColumns.ROLE_ID, roleId); - statement.setString(RoleConstants.RoleTableColumns.SCOPE_NAME, permission.getName()); - statement.setInt(RoleConstants.RoleTableColumns.TENANT_ID, tenantIdToSearchScopes); - statement.addBatch(); + // Resolve the tenant domain to search the scope details. + String tenantDomainToSearchScopes = isOrganization(tenantDomain) ? + getPrimaryOrgTenantDomain(tenantDomain) : tenantDomain; + Set blockedAPIResourceIds = getBlockedAPIResourceIds(tenantDomainToSearchScopes); + try { + // If there are blocked API resources, resolve the scope IDs for the permissions while excluding the + // scopes of the blocked API resources, and add the permissions with scope IDs. + // Otherwise, add the permissions with scope names. + if (!blockedAPIResourceIds.isEmpty()) { + List addedScopeIds = resolveScopeIdsExcludingBlockedAPIResourceScopes(roleId, permissions, + blockedAPIResourceIds, tenantDomainToSearchScopes); + if (!addedScopeIds.isEmpty()) { + try (NamedPreparedStatement statement = new NamedPreparedStatement(connection, + ADD_ROLE_SCOPE_BY_ID_SQL)) { + for (String scopeId : addedScopeIds) { + statement.setString(RoleConstants.RoleTableColumns.ROLE_ID, roleId); + statement.setString(RoleConstants.RoleTableColumns.SCOPE_ID, scopeId); + statement.addBatch(); + } + statement.executeBatch(); + } + } + } else { + int tenantIdToSearchScopes = IdentityTenantUtil.getTenantId(tenantDomainToSearchScopes); + try (NamedPreparedStatement statement = new NamedPreparedStatement(connection, ADD_ROLE_SCOPE_SQL)) { + for (Permission permission : permissions) { + statement.setString(RoleConstants.RoleTableColumns.ROLE_ID, roleId); + statement.setString(RoleConstants.RoleTableColumns.SCOPE_NAME, permission.getName()); + statement.setInt(RoleConstants.RoleTableColumns.TENANT_ID, tenantIdToSearchScopes); + statement.addBatch(); + } + statement.executeBatch(); + } } - statement.executeBatch(); } catch (SQLException e) { String errorMessage = "Error while adding permissions to roleId : " + roleId; throw new IdentityRoleManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), @@ -1966,6 +2002,77 @@ private void checkPermissionsAlreadyAdded(String roleId, List permis } } + private List resolveScopeIdsExcludingBlockedAPIResourceScopes(String roleId, List permissions, + Set blockedApiResourceIds, + String tenantDomain) + throws IdentityRoleManagementException { + + if (permissions == null || permissions.isEmpty()) { + return new ArrayList<>(); + } + try { + List allScopes = RoleManagementServiceComponentHolder.getInstance().getApiResourceManager() + .getScopesByTenantDomain(tenantDomain, StringUtils.EMPTY); + Map> nameToScopes = allScopes.stream().collect(Collectors.groupingBy(Scope::getName)); + + List resolvedScopeIds = new ArrayList<>(); + for (Permission permission : permissions) { + List scopesOfSameName = nameToScopes.get(permission.getName()); + if (CollectionUtils.isEmpty(scopesOfSameName)) { + throw new IdentityRoleManagementServerException(UNEXPECTED_SERVER_ERROR.getCode(), + "No scope found for the permission: " + permission.getName()); + } + + for (Scope scope : scopesOfSameName) { + if (!blockedApiResourceIds.contains(scope.getApiID())) { + resolvedScopeIds.add(scope.getId()); + } else if (scopesOfSameName.size() == 1) { + // If there is only one scope with the given name, and it belongs to a blocked API resource, + // permission assignment should be blocked. + throw new IdentityRoleManagementClientException(INVALID_PERMISSION.getCode(), + "Permission: " + permission.getName() + " is blocked from being assigned to roles"); + } else if (LOG.isDebugEnabled()) { + LOG.debug(String.format("There are multiple scopes with the permission name: '%s' and the " + + "scope id: '%s' belongs to API resource id: '%s' which is on the blocked list. Hence " + + "skipping this scope during permission update of role id: '%s'. Other scopes may get " + + "added as permissions to the role.", permission.getName(), scope.getId(), + scope.getApiID(), roleId)); + } + } + } + return resolvedScopeIds; + } catch (APIResourceMgtException e) { + throw new IdentityRoleManagementServerException(RoleConstants.Error.UNEXPECTED_SERVER_ERROR.getCode(), + "Error while resolving permission scope IDs for tenant domain: " + tenantDomain, e); + } + } + + private Set getBlockedAPIResourceIds(String tenantDomain) throws IdentityRoleManagementException { + + List blockedIdentifiers = IdentityUtil.getPropertyAsList( + RoleConstants.ROLE_PERMISSION_ASSIGNMENT_BLOCKED_API_RESOURCES); + if (blockedIdentifiers.isEmpty()) { + return Collections.emptySet(); + } + Set blockedApiIds = new HashSet<>(); + for (String identifier : blockedIdentifiers) { + if (StringUtils.isBlank(identifier)) { + continue; + } + try { + APIResource apiResource = RoleManagementServiceComponentHolder.getInstance().getApiResourceManager() + .getAPIResourceByIdentifier(identifier, tenantDomain); + if (apiResource != null && StringUtils.isNotBlank(apiResource.getId())) { + blockedApiIds.add(apiResource.getId()); + } + } catch (APIResourceMgtException e) { + throw new IdentityRoleManagementException("Error while retrieving API resource by identifier: " + + identifier, e); + } + } + return blockedApiIds; + } + /** * Delete all permissions of the role. * 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/SQLQueries.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/SQLQueries.java index 0e1603b0b215..100bebcdeaff 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/SQLQueries.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/SQLQueries.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2026, 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 @@ -39,6 +39,9 @@ public class SQLQueries { " SCOPE.ID FROM SCOPE WHERE SCOPE.NAME = :SCOPE_NAME; AND ( SCOPE.TENANT_ID = :TENANT_ID; OR " + "SCOPE.TENANT_ID IS NULL)"; + public static final String ADD_ROLE_SCOPE_BY_ID_SQL = "INSERT INTO ROLE_SCOPE (ROLE_ID, SCOPE_ID) VALUES " + + "(:ROLE_ID;, :SCOPE_ID;)"; + public static final String DELETE_ROLE_SCOPE_BY_SCOPE_NAME_SQL = "DELETE FROM ROLE_SCOPE WHERE ROLE_ID =:ROLE_ID;" + " AND SCOPE_ID IN (SELECT ID FROM SCOPE WHERE NAME = :SCOPE_NAME;)"; diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 index 5029fd3e277d..c0bb07d3e449 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 @@ -2355,6 +2355,34 @@ {% endif %} + {% if api_resource_management is defined %} + + + {% if api_resource_management.application_api_authorization is defined %} + + {% if api_resource_management.application_api_authorization.blocked_api_resources is defined %} + + {% for identifier in api_resource_management.application_api_authorization.blocked_api_resources %} + {{identifier}} + {% endfor %} + + {% endif %} + + {% endif %} + {% if api_resource_management.role_permission_assignment is defined %} + + {% if api_resource_management.role_permission_assignment.blocked_api_resources is defined %} + + {% for identifier in api_resource_management.role_permission_assignment.blocked_api_resources %} + {{identifier}} + {% endfor %} + + {% endif %} + + {% endif %} + + {% endif %} + {% if outbound_provisioning_management.reset_provisioning_entities_on_config_update is defined %}