From 7187044c9a4b8498e7b925a0a70d9639b841ba67 Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Tue, 23 Jun 2026 11:45:47 +0530 Subject: [PATCH] Add granular scope feature. --- .../pom.xml | 1 + .../mgt/APIResourceCollectionManagerImpl.java | 40 +- ...ResourceCollectionManagementConstants.java | 11 + .../mgt/model/APIResourceCollection.java | 114 +++++ .../APIResourceCollectionManagementUtil.java | 12 + ...APIResourceCollectionMgtConfigBuilder.java | 73 +++- ...IResourceCollectionManagementUtilTest.java | 63 +++ .../mgt/APIResourceCollectionManagerTest.java | 232 ++++++++++- ...esourceCollectionMgtConfigBuilderTest.java | 230 ++++++++++- .../mgt/APIResourceCollectionTest.java | 96 +++++ .../conf/api-resource-collection.xml | 3 + .../src/test/resources/testng.xml | 2 + .../resources/api-resource-collection.xml | 192 +++++++++ .../resources/api-resource-collection.xml.j2 | 204 +++++++++ .../resources/system-api-resource.xml | 378 +++++++++++++++++ .../resources/system-api-resource.xml.j2 | 390 ++++++++++++++++++ .../resources/identity.xml | 5 + .../resources/identity.xml.j2 | 5 + ....identity.core.server.feature.default.json | 1 + ...on.identity.core.server.feature.infer.json | 16 +- 20 files changed, 2049 insertions(+), 19 deletions(-) create mode 100644 components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagementUtilTest.java create mode 100644 components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionTest.java diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/pom.xml b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/pom.xml index 1278210aa58a..e3384e0ba4e4 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/pom.xml +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/pom.xml @@ -109,6 +109,7 @@ org.wso2.carbon.identity.base; version="${carbon.identity.package.import.version.range}", org.wso2.carbon.identity.core; version="${carbon.identity.package.import.version.range}", org.wso2.carbon.identity.core.model; version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.core.util; version="${carbon.identity.package.import.version.range}", org.wso2.carbon.utils; version="${carbon.kernel.package.import.version.range}", org.wso2.carbon.identity.api.resource.mgt; version="${carbon.identity.package.import.version.range}", diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerImpl.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerImpl.java index d83ad3b0394e..21d6b7359f48 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerImpl.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerImpl.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; import static org.wso2.carbon.identity.api.resource.collection.mgt.util.APIResourceCollectionManagementUtil.handleServerException; +import static org.wso2.carbon.identity.api.resource.collection.mgt.util.APIResourceCollectionManagementUtil.isGranularConsolePermissionsEnabled; /** * API Resource Collection Manager Implementation. @@ -127,22 +128,51 @@ private APIResourceCollection populateAPIResourcesForCollection(APIResourceColle } try { APIResourceCollection clonedCollection = cloneAPIResourceCollection(collection); + boolean granularEnabled = isGranularConsolePermissionsEnabled(); + + Set writeScopes = new HashSet<>(); + Optional.ofNullable(clonedCollection.getReadScopes()).ifPresent(writeScopes::addAll); + Optional.ofNullable(clonedCollection.getWriteScopes()).ifPresent(writeScopes::addAll); - // Combine read and write scopes for a single fetch. Set combinedScopes = new HashSet<>(); Optional.ofNullable(clonedCollection.getReadScopes()).ifPresent(combinedScopes::addAll); Optional.ofNullable(clonedCollection.getWriteScopes()).ifPresent(combinedScopes::addAll); + if (granularEnabled) { + Optional.ofNullable(clonedCollection.getCreateScopes()).ifPresent(combinedScopes::addAll); + Optional.ofNullable(clonedCollection.getUpdateScopes()).ifPresent(combinedScopes::addAll); + Optional.ofNullable(clonedCollection.getDeleteScopes()).ifPresent(combinedScopes::addAll); + } List allAPIResources = APIResourceCollectionMgtServiceDataHolder.getInstance() .getAPIResourceManagementService().getScopeMetadata(new ArrayList<>(combinedScopes), tenantDomain); List readAPIResources = filterAPIResources(allAPIResources, clonedCollection.getReadScopes()); List writeAPIResources = - filterAPIResources(allAPIResources, new ArrayList<>(combinedScopes)); + filterAPIResources(allAPIResources, new ArrayList<>(writeScopes)); Map> apiResourcesMap = new HashMap<>(); apiResourcesMap.put(APIResourceCollectionManagementConstants.READ, readAPIResources); apiResourcesMap.put(APIResourceCollectionManagementConstants.WRITE, writeAPIResources); + if (granularEnabled) { + Set createScopes = new HashSet<>(); + Optional.ofNullable(clonedCollection.getReadScopes()).ifPresent(createScopes::addAll); + Optional.ofNullable(clonedCollection.getCreateScopes()).ifPresent(createScopes::addAll); + + Set updateScopes = new HashSet<>(); + Optional.ofNullable(clonedCollection.getReadScopes()).ifPresent(updateScopes::addAll); + Optional.ofNullable(clonedCollection.getUpdateScopes()).ifPresent(updateScopes::addAll); + + Set deleteScopes = new HashSet<>(); + Optional.ofNullable(clonedCollection.getReadScopes()).ifPresent(deleteScopes::addAll); + Optional.ofNullable(clonedCollection.getDeleteScopes()).ifPresent(deleteScopes::addAll); + + apiResourcesMap.put(APIResourceCollectionManagementConstants.CREATE, + filterAPIResources(allAPIResources, new ArrayList<>(createScopes))); + apiResourcesMap.put(APIResourceCollectionManagementConstants.UPDATE, + filterAPIResources(allAPIResources, new ArrayList<>(updateScopes))); + apiResourcesMap.put(APIResourceCollectionManagementConstants.DELETE, + filterAPIResources(allAPIResources, new ArrayList<>(deleteScopes))); + } clonedCollection.setApiResources(apiResourcesMap); return clonedCollection; } catch (APIResourceMgtException e) { @@ -199,10 +229,16 @@ private APIResourceCollection cloneAPIResourceCollection(APIResourceCollection a .type(apiResourceCollection.getType()) .readScopes(apiResourceCollection.getReadScopes()) .writeScopes(apiResourceCollection.getWriteScopes()) + .createScopes(apiResourceCollection.getCreateScopes()) + .updateScopes(apiResourceCollection.getUpdateScopes()) + .deleteScopes(apiResourceCollection.getDeleteScopes()) .legacyReadScopes(apiResourceCollection.getLegacyReadScopes()) .legacyWriteScopes(apiResourceCollection.getLegacyWriteScopes()) .viewFeatureScope(apiResourceCollection.getViewFeatureScope()) .editFeatureScope(apiResourceCollection.getEditFeatureScope()) + .createFeatureScope(apiResourceCollection.getCreateFeatureScope()) + .updateFeatureScope(apiResourceCollection.getUpdateFeatureScope()) + .deleteFeatureScope(apiResourceCollection.getDeleteFeatureScope()) .apiResources(apiResourceCollection.getApiResources()) .build(); } diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/constant/APIResourceCollectionManagementConstants.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/constant/APIResourceCollectionManagementConstants.java index 503c1e07d12b..b7e1bba02f79 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/constant/APIResourceCollectionManagementConstants.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/constant/APIResourceCollectionManagementConstants.java @@ -38,7 +38,12 @@ public class APIResourceCollectionManagementConstants { public static final String WRITE_SCOPES = "writeScopes"; public static final String READ = "read"; public static final String WRITE = "write"; + public static final String CREATE = "create"; + public static final String UPDATE = "update"; + public static final String DELETE = "delete"; public static final String API_RESOURCES = "apiResources"; + public static final String USE_GRANULAR_CONSOLE_PERMISSIONS_CONFIG = + "ConsoleSettings.UseGranularConsolePermissions"; /** * API resource collection configuration builder constants. @@ -53,10 +58,16 @@ public static class APIResourceCollectionConfigBuilderConstants { public static final String DISPLAY_NAME = "displayName"; public static final String TYPE = "type"; public static final String READ = "Read"; + public static final String CREATE = "Create"; + public static final String UPDATE = "Update"; + public static final String DELETE = "Delete"; public static final String FEATURE = "Feature"; public static final String VERSION = "version"; public static final String VIEW_FEATURE_SCOPE_SUFFIX = "_view"; public static final String EDIT_FEATURE_SCOPE_SUFFIX = "_edit"; + public static final String CREATE_FEATURE_SCOPE_SUFFIX = "_create"; + public static final String UPDATE_FEATURE_SCOPE_SUFFIX = "_update"; + public static final String DELETE_FEATURE_SCOPE_SUFFIX = "_delete"; public static final String CONSOLE_SCOPE_PREFIX = "console:"; public static final String COLLECTION_VERSION_V0 = "v0"; } diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/model/APIResourceCollection.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/model/APIResourceCollection.java index 05ed0daa07ab..5ebc9a699dac 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/model/APIResourceCollection.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/model/APIResourceCollection.java @@ -34,10 +34,16 @@ public class APIResourceCollection { private String type; private List readScopes; private List writeScopes; + private List createScopes; + private List updateScopes; + private List deleteScopes; private List legacyReadScopes; private List legacyWriteScopes; private String viewFeatureScope; private String editFeatureScope; + private String createFeatureScope; + private String updateFeatureScope; + private String deleteFeatureScope; private Map> apiResources; public APIResourceCollection() { @@ -52,10 +58,16 @@ public APIResourceCollection(APIResourceCollectionBuilder apiResourceCollectionB this.apiResources = apiResourceCollectionBuilder.apiResources; this.readScopes = apiResourceCollectionBuilder.readScopes; this.writeScopes = apiResourceCollectionBuilder.writeScopes; + this.createScopes = apiResourceCollectionBuilder.createScopes; + this.updateScopes = apiResourceCollectionBuilder.updateScopes; + this.deleteScopes = apiResourceCollectionBuilder.deleteScopes; this.legacyReadScopes = apiResourceCollectionBuilder.legacyReadScopes; this.legacyWriteScopes = apiResourceCollectionBuilder.legacyWriteScopes; this.viewFeatureScope = apiResourceCollectionBuilder.viewFeatureScope; this.editFeatureScope = apiResourceCollectionBuilder.editFeatureScope; + this.createFeatureScope = apiResourceCollectionBuilder.createFeatureScope; + this.updateFeatureScope = apiResourceCollectionBuilder.updateFeatureScope; + this.deleteFeatureScope = apiResourceCollectionBuilder.deleteFeatureScope; } public String getId() { @@ -108,6 +120,36 @@ public void setWriteScopes(List writeScopes) { this.writeScopes = writeScopes; } + public List getCreateScopes() { + + return createScopes; + } + + public void setCreateScopes(List createScopes) { + + this.createScopes = createScopes; + } + + public List getUpdateScopes() { + + return updateScopes; + } + + public void setUpdateScopes(List updateScopes) { + + this.updateScopes = updateScopes; + } + + public List getDeleteScopes() { + + return deleteScopes; + } + + public void setDeleteScopes(List deleteScopes) { + + this.deleteScopes = deleteScopes; + } + public List getLegacyReadScopes() { return legacyReadScopes; @@ -148,6 +190,36 @@ public void setEditFeatureScope(String editFeatureScope) { this.editFeatureScope = editFeatureScope; } + public String getCreateFeatureScope() { + + return createFeatureScope; + } + + public void setCreateFeatureScope(String createFeatureScope) { + + this.createFeatureScope = createFeatureScope; + } + + public String getUpdateFeatureScope() { + + return updateFeatureScope; + } + + public void setUpdateFeatureScope(String updateFeatureScope) { + + this.updateFeatureScope = updateFeatureScope; + } + + public String getDeleteFeatureScope() { + + return deleteFeatureScope; + } + + public void setDeleteFeatureScope(String deleteFeatureScope) { + + this.deleteFeatureScope = deleteFeatureScope; + } + /** * Builder class for API Resource Collection. */ @@ -159,10 +231,16 @@ public static class APIResourceCollectionBuilder { private String type; private List readScopes; private List writeScopes; + private List createScopes; + private List updateScopes; + private List deleteScopes; private List legacyReadScopes; private List legacyWriteScopes; private String viewFeatureScope; private String editFeatureScope; + private String createFeatureScope; + private String updateFeatureScope; + private String deleteFeatureScope; private Map> apiResources; public APIResourceCollectionBuilder() { @@ -204,6 +282,24 @@ public APIResourceCollectionBuilder writeScopes(List writeScopes) { return this; } + public APIResourceCollectionBuilder createScopes(List createScopes) { + + this.createScopes = createScopes; + return this; + } + + public APIResourceCollectionBuilder updateScopes(List updateScopes) { + + this.updateScopes = updateScopes; + return this; + } + + public APIResourceCollectionBuilder deleteScopes(List deleteScopes) { + + this.deleteScopes = deleteScopes; + return this; + } + public APIResourceCollectionBuilder legacyReadScopes(List legacyReadScopes) { @@ -229,6 +325,24 @@ public APIResourceCollectionBuilder editFeatureScope(String editFeatureScope) { return this; } + public APIResourceCollectionBuilder createFeatureScope(String createFeatureScope) { + + this.createFeatureScope = createFeatureScope; + return this; + } + + public APIResourceCollectionBuilder updateFeatureScope(String updateFeatureScope) { + + this.updateFeatureScope = updateFeatureScope; + return this; + } + + public APIResourceCollectionBuilder deleteFeatureScope(String deleteFeatureScope) { + + this.deleteFeatureScope = deleteFeatureScope; + return this; + } + public APIResourceCollectionBuilder apiResources(Map> apiResources) { this.apiResources = apiResources; diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionManagementUtil.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionManagementUtil.java index 207a2ca6ab10..9e9556d5b885 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionManagementUtil.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionManagementUtil.java @@ -22,6 +22,7 @@ import org.wso2.carbon.identity.api.resource.collection.mgt.constant.APIResourceCollectionManagementConstants; import org.wso2.carbon.identity.api.resource.collection.mgt.exception.APIResourceCollectionMgtClientException; import org.wso2.carbon.identity.api.resource.collection.mgt.exception.APIResourceCollectionMgtServerException; +import org.wso2.carbon.identity.core.util.IdentityUtil; /** * Utility class for API Resource Collection Management. @@ -63,4 +64,15 @@ public static APIResourceCollectionMgtServerException handleServerException( } return new APIResourceCollectionMgtServerException(error.getMessage(), description, error.getCode(), e); } + + /** + * Check whether the granular console permission model (create/update/delete feature scopes) is enabled. Controlled + * + * @return True if granular console permissions are enabled. + */ + public static boolean isGranularConsolePermissionsEnabled() { + + return Boolean.parseBoolean(IdentityUtil.getProperty( + APIResourceCollectionManagementConstants.USE_GRANULAR_CONSOLE_PERMISSIONS_CONFIG)); + } } diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionMgtConfigBuilder.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionMgtConfigBuilder.java index b6323d172aae..4e39a37bdab2 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionMgtConfigBuilder.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/main/java/org/wso2/carbon/identity/api/resource/collection/mgt/util/APIResourceCollectionMgtConfigBuilder.java @@ -127,12 +127,16 @@ private void buildAPIResourceCollectionConfig() { if (scopesElement != null) { Set readScopeSet = new HashSet<>(); Set writeScopeSet = new HashSet<>(); + Set createScopeSet = new HashSet<>(); + Set updateScopeSet = new HashSet<>(); + Set deleteScopeSet = new HashSet<>(); Iterator actionElements = scopesElement.getChildElements(); while (actionElements.hasNext()) { OMElement actionElement = (OMElement) actionElements.next(); if (actionElement == null) { continue; } + String actionName = actionElement.getLocalName(); Iterator scopes = actionElement.getChildrenWithName( new QName(APIResourceCollectionConfigBuilderConstants.SCOPE_ELEMENT)); while (scopes.hasNext()) { @@ -140,21 +144,40 @@ private void buildAPIResourceCollectionConfig() { String scopeName = scope.getAttributeValue( new QName(APIResourceCollectionConfigBuilderConstants.NAME)); // Read and old Feature scope are considered as read scopes. - boolean isReadAction = APIResourceCollectionConfigBuilderConstants.READ - .equals(actionElement.getLocalName()); - boolean isFeatureAction = APIResourceCollectionConfigBuilderConstants.FEATURE. - equals(actionElement.getLocalName()); + boolean isReadAction = APIResourceCollectionConfigBuilderConstants.READ.equals(actionName); + boolean isCreateAction = APIResourceCollectionConfigBuilderConstants.CREATE.equals(actionName); + boolean isUpdateAction = APIResourceCollectionConfigBuilderConstants.UPDATE.equals(actionName); + boolean isDeleteAction = APIResourceCollectionConfigBuilderConstants.DELETE.equals(actionName); + boolean isFeatureAction = APIResourceCollectionConfigBuilderConstants.FEATURE + .equals(actionName); if (APIResourceCollectionConfigBuilderConstants.COLLECTION_VERSION_V0 .equals(collectionVersion)) { if (isReadAction || isFeatureAction) { readScopeSet.add(scopeName); } else { + // Create / Update / Delete actions aggregate into write and into their own bucket. writeScopeSet.add(scopeName); + if (isCreateAction) { + createScopeSet.add(scopeName); + } else if (isUpdateAction) { + updateScopeSet.add(scopeName); + } else if (isDeleteAction) { + deleteScopeSet.add(scopeName); + } } } else { // Process new scopes with feature scopes. if (isReadAction) { readScopeSet.add(scopeName); + } else if (isCreateAction) { + createScopeSet.add(scopeName); + writeScopeSet.add(scopeName); + } else if (isUpdateAction) { + updateScopeSet.add(scopeName); + writeScopeSet.add(scopeName); + } else if (isDeleteAction) { + deleteScopeSet.add(scopeName); + writeScopeSet.add(scopeName); } else if (isFeatureAction) { if (isViewFeatureScope(scopeName)) { apiResourceCollectionObj.setViewFeatureScope(scopeName); @@ -162,6 +185,15 @@ private void buildAPIResourceCollectionConfig() { } else if (isEditFeatureScope(scopeName)) { apiResourceCollectionObj.setEditFeatureScope(scopeName); writeScopeSet.add(scopeName); + } else if (isCreateFeatureScope(scopeName)) { + apiResourceCollectionObj.setCreateFeatureScope(scopeName); + createScopeSet.add(scopeName); + } else if (isUpdateFeatureScope(scopeName)) { + apiResourceCollectionObj.setUpdateFeatureScope(scopeName); + updateScopeSet.add(scopeName); + } else if (isDeleteFeatureScope(scopeName)) { + apiResourceCollectionObj.setDeleteFeatureScope(scopeName); + deleteScopeSet.add(scopeName); } else { readScopeSet.add(scopeName); } @@ -181,9 +213,21 @@ private void buildAPIResourceCollectionConfig() { if (apiResourceCollectionObj.getWriteScopes() == null) { apiResourceCollectionObj.setWriteScopes(new ArrayList<>(writeScopeSet)); } + if (apiResourceCollectionObj.getCreateScopes() == null) { + apiResourceCollectionObj.setCreateScopes(new ArrayList<>(createScopeSet)); + } + if (apiResourceCollectionObj.getUpdateScopes() == null) { + apiResourceCollectionObj.setUpdateScopes(new ArrayList<>(updateScopeSet)); + } + if (apiResourceCollectionObj.getDeleteScopes() == null) { + apiResourceCollectionObj.setDeleteScopes(new ArrayList<>(deleteScopeSet)); + } } else { apiResourceCollectionObj.setReadScopes(new ArrayList<>(readScopeSet)); apiResourceCollectionObj.setWriteScopes(new ArrayList<>(writeScopeSet)); + apiResourceCollectionObj.setCreateScopes(new ArrayList<>(createScopeSet)); + apiResourceCollectionObj.setUpdateScopes(new ArrayList<>(updateScopeSet)); + apiResourceCollectionObj.setDeleteScopes(new ArrayList<>(deleteScopeSet)); } } apiResourceCollectionMgtConfigurations.put(apiResourceCollectionObj.getId(), apiResourceCollectionObj); @@ -222,4 +266,25 @@ private boolean isEditFeatureScope(String scope) { scope.startsWith(APIResourceCollectionConfigBuilderConstants.CONSOLE_SCOPE_PREFIX) && scope.endsWith(APIResourceCollectionConfigBuilderConstants.EDIT_FEATURE_SCOPE_SUFFIX); } + + private boolean isCreateFeatureScope(String scope) { + + return StringUtils.isNotBlank(scope) && + scope.startsWith(APIResourceCollectionConfigBuilderConstants.CONSOLE_SCOPE_PREFIX) && + scope.endsWith(APIResourceCollectionConfigBuilderConstants.CREATE_FEATURE_SCOPE_SUFFIX); + } + + private boolean isUpdateFeatureScope(String scope) { + + return StringUtils.isNotBlank(scope) && + scope.startsWith(APIResourceCollectionConfigBuilderConstants.CONSOLE_SCOPE_PREFIX) && + scope.endsWith(APIResourceCollectionConfigBuilderConstants.UPDATE_FEATURE_SCOPE_SUFFIX); + } + + private boolean isDeleteFeatureScope(String scope) { + + return StringUtils.isNotBlank(scope) && + scope.startsWith(APIResourceCollectionConfigBuilderConstants.CONSOLE_SCOPE_PREFIX) && + scope.endsWith(APIResourceCollectionConfigBuilderConstants.DELETE_FEATURE_SCOPE_SUFFIX); + } } diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagementUtilTest.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagementUtilTest.java new file mode 100644 index 000000000000..288b15266300 --- /dev/null +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagementUtilTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, 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.api.resource.collection.mgt; + +import org.mockito.MockedStatic; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.api.resource.collection.mgt.constant.APIResourceCollectionManagementConstants; +import org.wso2.carbon.identity.api.resource.collection.mgt.util.APIResourceCollectionManagementUtil; +import org.wso2.carbon.identity.core.util.IdentityUtil; + +import static org.mockito.Mockito.mockStatic; + +/** + * Unit tests for {@link APIResourceCollectionManagementUtil}. + */ +public class APIResourceCollectionManagementUtilTest { + + @DataProvider(name = "granularConsolePermissionsConfigProvider") + public Object[][] granularConsolePermissionsConfigProvider() { + + // {configured value of ConsoleSettings.UseGranularConsolePermissions, expected boolean} + return new Object[][]{ + {"true", true}, + {"TRUE", true}, + {"false", false}, + {"", false}, + {null, false}, + {"not-a-boolean", false}, + }; + } + + @Test(dataProvider = "granularConsolePermissionsConfigProvider") + public void testIsGranularConsolePermissionsEnabled(String configuredValue, boolean expected) { + + try (MockedStatic identityUtil = mockStatic(IdentityUtil.class)) { + identityUtil.when(() -> IdentityUtil.getProperty( + APIResourceCollectionManagementConstants.USE_GRANULAR_CONSOLE_PERMISSIONS_CONFIG)) + .thenReturn(configuredValue); + + Assert.assertEquals( + APIResourceCollectionManagementUtil.isGranularConsolePermissionsEnabled(), expected, + "Unexpected granular console permission resolution for configured value: " + configuredValue); + } + } +} diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerTest.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerTest.java index 82137d0e1a8a..966ef2805ea6 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerTest.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionManagerTest.java @@ -29,6 +29,7 @@ import org.wso2.carbon.identity.api.resource.collection.mgt.internal.APIResourceCollectionMgtServiceDataHolder; import org.wso2.carbon.identity.api.resource.collection.mgt.model.APIResourceCollection; import org.wso2.carbon.identity.api.resource.collection.mgt.model.APIResourceCollectionSearchResult; +import org.wso2.carbon.identity.api.resource.collection.mgt.util.APIResourceCollectionManagementUtil; import org.wso2.carbon.identity.api.resource.mgt.APIResourceManager; import org.wso2.carbon.identity.application.common.model.APIResource; import org.wso2.carbon.identity.application.common.model.Scope; @@ -38,13 +39,17 @@ import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; @@ -56,17 +61,24 @@ public class APIResourceCollectionManagerTest { private APIResourceCollectionManager apiResourceCollectionManager; private Map apiResourceCollectionMapMock; private MockedStatic apiResourceCollectionMgtServiceData; + private MockedStatic apiResourceCollectionMgtUtil; + private APIResourceManager apiResourceManagerMock; @BeforeMethod public void setUp() throws Exception { + // Exercise the granular console permission path by default; the granular-disabled case re-stubs this to false. + apiResourceCollectionMgtUtil = mockStatic(APIResourceCollectionManagementUtil.class, CALLS_REAL_METHODS); + apiResourceCollectionMgtUtil.when( + APIResourceCollectionManagementUtil::isGranularConsolePermissionsEnabled).thenReturn(true); + apiResourceCollectionMgtServiceData = mockStatic(APIResourceCollectionMgtServiceDataHolder.class); APIResourceCollectionMgtServiceDataHolder serviceDataHolderMock = mock(APIResourceCollectionMgtServiceDataHolder.class); apiResourceCollectionMgtServiceData.when( APIResourceCollectionMgtServiceDataHolder::getInstance).thenReturn(serviceDataHolderMock); - APIResourceManager apiResourceManagerMock = mock(APIResourceManager.class); + apiResourceManagerMock = mock(APIResourceManager.class); when(serviceDataHolderMock.getAPIResourceManagementService()).thenReturn(apiResourceManagerMock); when(apiResourceManagerMock.getScopeMetadata(anyList(), anyString())).thenReturn( getListOfAPIResources()); @@ -82,6 +94,7 @@ public void setUp() throws Exception { public void tearDown() { apiResourceCollectionMgtServiceData.close(); + apiResourceCollectionMgtUtil.close(); } @DataProvider(name = "getAPIResourceCollectionsDataProvider") @@ -114,6 +127,223 @@ public void testGetAPIResourceCollectionsById() Assert.assertNotNull(apiResourceCollection); } + @Test + public void testGetAPIResourceCollectionByIdPopulatesGranularApiResources() throws Exception { + + // Build a collection whose create/update/delete scopes line up with distinct mocked API resources. + String name = "granular"; + APIResourceCollection granularCollection = new APIResourceCollection.APIResourceCollectionBuilder() + .id(getEncodedName(name)) + .name(name) + .displayName("Display Name granular") + .type("BUSINESS") + .readScopes(new ArrayList<>()) + .writeScopes(new ArrayList<>()) + .createScopes(singletonScopeList("testScopeOne test1")) + .updateScopes(singletonScopeList("testScopeTwo test2")) + .deleteScopes(singletonScopeList("testScopeOne test3")) + .build(); + apiResourceCollectionMapMock.put(granularCollection.getId(), granularCollection); + + APIResourceCollection result = + apiResourceCollectionManager.getAPIResourceCollectionById(getEncodedName(name), "test"); + + Assert.assertNotNull(result); + Map> apiResources = result.getApiResources(); + Assert.assertNotNull(apiResources); + Assert.assertTrue(apiResources.containsKey(APIResourceCollectionManagementConstants.CREATE)); + Assert.assertTrue(apiResources.containsKey(APIResourceCollectionManagementConstants.UPDATE)); + Assert.assertTrue(apiResources.containsKey(APIResourceCollectionManagementConstants.DELETE)); + + assertSingleResourceWithScope(apiResources.get(APIResourceCollectionManagementConstants.CREATE), + "testScopeOne test1"); + assertSingleResourceWithScope(apiResources.get(APIResourceCollectionManagementConstants.UPDATE), + "testScopeTwo test2"); + assertSingleResourceWithScope(apiResources.get(APIResourceCollectionManagementConstants.DELETE), + "testScopeOne test3"); + } + + @Test + public void testGranularEnabledWithNoActionScopesYieldsEmptyBuckets() throws Exception { + + // Granular enabled, but the collection declares no create/update/delete scopes: the action buckets are + // emitted as present-but-empty lists (never null). + addTestAPIResourceCollections(); + String encodedName1 = getEncodedName("nametest1"); + APIResourceCollection result = + apiResourceCollectionManager.getAPIResourceCollectionById(encodedName1, "test"); + + Assert.assertNotNull(result); + Map> apiResources = result.getApiResources(); + Assert.assertNotNull(apiResources.get(APIResourceCollectionManagementConstants.CREATE)); + Assert.assertTrue(apiResources.get(APIResourceCollectionManagementConstants.CREATE).isEmpty()); + Assert.assertNotNull(apiResources.get(APIResourceCollectionManagementConstants.UPDATE)); + Assert.assertTrue(apiResources.get(APIResourceCollectionManagementConstants.UPDATE).isEmpty()); + Assert.assertNotNull(apiResources.get(APIResourceCollectionManagementConstants.DELETE)); + Assert.assertTrue(apiResources.get(APIResourceCollectionManagementConstants.DELETE).isEmpty()); + } + + @Test + public void testGranularDisabledYieldsOnlyReadWriteBuckets() throws Exception { + + // With granular console permissions disabled, only the legacy read/write buckets are emitted; the + // create/update/delete keys are absent altogether. + apiResourceCollectionMgtUtil.when( + APIResourceCollectionManagementUtil::isGranularConsolePermissionsEnabled).thenReturn(false); + + addTestAPIResourceCollections(); + String encodedName1 = getEncodedName("nametest1"); + APIResourceCollection result = + apiResourceCollectionManager.getAPIResourceCollectionById(encodedName1, "test"); + + Assert.assertNotNull(result); + Map> apiResources = result.getApiResources(); + Assert.assertTrue(apiResources.containsKey(APIResourceCollectionManagementConstants.READ)); + Assert.assertTrue(apiResources.containsKey(APIResourceCollectionManagementConstants.WRITE)); + Assert.assertFalse(apiResources.containsKey(APIResourceCollectionManagementConstants.CREATE)); + Assert.assertFalse(apiResources.containsKey(APIResourceCollectionManagementConstants.UPDATE)); + Assert.assertFalse(apiResources.containsKey(APIResourceCollectionManagementConstants.DELETE)); + } + + // Plain feature scope shared across every action bucket. + private static final String FEATURE_SCOPE = "console:apiResources"; + // Per-action feature scopes (console:*). + private static final String VIEW_FEATURE_SCOPE = "console:apiResources_view"; + private static final String EDIT_FEATURE_SCOPE = "console:apiResources_edit"; + private static final String CREATE_FEATURE_SCOPE = "console:apiResources_create"; + private static final String UPDATE_FEATURE_SCOPE = "console:apiResources_update"; + private static final String DELETE_FEATURE_SCOPE = "console:apiResources_delete"; + // Per-action normal (internal_*) scopes. + private static final String READ_NORMAL_SCOPE = "internal_api_resource_view"; + private static final String CREATE_NORMAL_SCOPE = "internal_api_resource_create"; + private static final String UPDATE_NORMAL_SCOPE = "internal_api_resource_update"; + private static final String DELETE_NORMAL_SCOPE = "internal_api_resource_delete"; + + /** + * Verifies the bucket contract enforced by {@code populateAPIResourcesForCollection}. The read scopes are merged + * into every other bucket, so each action bucket carries the read-side scopes (plain feature + view feature + + * read normal) on top of its own action and feature scopes. Granular actions do not bleed into one another. + *
    + *
  • view = plain feature + view feature + read normal
  • + *
  • write = read scopes + edit feature + write normal (create/update/delete normal)
  • + *
  • create = read scopes + create feature + create normal
  • + *
  • update = read scopes + update feature + update normal
  • + *
  • delete = read scopes + delete feature + delete normal
  • + *
+ */ + @Test + public void testPopulatedBucketsCarryFeatureAndActionScopes() throws Exception { + + // A single API resource that owns every scope; the manager trims it down per bucket. + APIResource allScopesResource = apiResourceWithScopes("collectionResource", + FEATURE_SCOPE, VIEW_FEATURE_SCOPE, EDIT_FEATURE_SCOPE, CREATE_FEATURE_SCOPE, UPDATE_FEATURE_SCOPE, + DELETE_FEATURE_SCOPE, READ_NORMAL_SCOPE, CREATE_NORMAL_SCOPE, UPDATE_NORMAL_SCOPE, DELETE_NORMAL_SCOPE); + List metadata = new ArrayList<>(); + metadata.add(allScopesResource); + when(apiResourceManagerMock.getScopeMetadata(anyList(), anyString())).thenReturn(metadata); + + // Buckets composed as the config builder emits them: the plain feature scope is read-side only and the + // manager merges the read scopes into every other bucket. + String name = "featureCollection"; + APIResourceCollection collection = new APIResourceCollection.APIResourceCollectionBuilder() + .id(getEncodedName(name)) + .name(name) + .displayName("Display Name " + name) + .type("tenant") + .readScopes(Arrays.asList(READ_NORMAL_SCOPE, VIEW_FEATURE_SCOPE, FEATURE_SCOPE)) + .writeScopes(Arrays.asList(CREATE_NORMAL_SCOPE, UPDATE_NORMAL_SCOPE, DELETE_NORMAL_SCOPE, + EDIT_FEATURE_SCOPE, VIEW_FEATURE_SCOPE, FEATURE_SCOPE)) + .createScopes(Arrays.asList(CREATE_NORMAL_SCOPE, CREATE_FEATURE_SCOPE, + READ_NORMAL_SCOPE, VIEW_FEATURE_SCOPE, FEATURE_SCOPE)) + .updateScopes(Arrays.asList(UPDATE_NORMAL_SCOPE, UPDATE_FEATURE_SCOPE, + READ_NORMAL_SCOPE, VIEW_FEATURE_SCOPE, FEATURE_SCOPE)) + .deleteScopes(Arrays.asList(DELETE_NORMAL_SCOPE, DELETE_FEATURE_SCOPE, + READ_NORMAL_SCOPE, VIEW_FEATURE_SCOPE, FEATURE_SCOPE)) + .build(); + apiResourceCollectionMapMock.put(collection.getId(), collection); + + APIResourceCollection result = + apiResourceCollectionManager.getAPIResourceCollectionById(getEncodedName(name), "test"); + + Assert.assertNotNull(result); + Map> apiResources = result.getApiResources(); + + // view (read) = plain feature + view feature + read normal. + assertBucketScopes(apiResources, APIResourceCollectionManagementConstants.READ, + FEATURE_SCOPE, VIEW_FEATURE_SCOPE, READ_NORMAL_SCOPE); + // create = read scopes (plain feature) + create feature + create normal; no other action scopes bleed in. + assertBucketScopes(apiResources, APIResourceCollectionManagementConstants.CREATE, + CREATE_FEATURE_SCOPE, CREATE_NORMAL_SCOPE, + VIEW_FEATURE_SCOPE, READ_NORMAL_SCOPE, FEATURE_SCOPE); + // update = read scopes (plain feature) + update feature + update normal; no other action scopes bleed in. + assertBucketScopes(apiResources, APIResourceCollectionManagementConstants.UPDATE, + FEATURE_SCOPE, UPDATE_FEATURE_SCOPE, UPDATE_NORMAL_SCOPE, + VIEW_FEATURE_SCOPE, READ_NORMAL_SCOPE, FEATURE_SCOPE); + // delete = read scopes (plain feature) + delete feature + delete normal; no other action scopes bleed in. + assertBucketScopes(apiResources, APIResourceCollectionManagementConstants.DELETE, + FEATURE_SCOPE, DELETE_FEATURE_SCOPE, DELETE_NORMAL_SCOPE, + VIEW_FEATURE_SCOPE, READ_NORMAL_SCOPE, FEATURE_SCOPE); + // write = read scopes (plain feature) + edit feature + write normal (create/update/delete normal). + assertBucketScopes(apiResources, APIResourceCollectionManagementConstants.WRITE, + CREATE_NORMAL_SCOPE, UPDATE_NORMAL_SCOPE, DELETE_NORMAL_SCOPE, READ_NORMAL_SCOPE, + VIEW_FEATURE_SCOPE, EDIT_FEATURE_SCOPE, FEATURE_SCOPE); + } + + /** + * Asserts that the given bucket exists and the union of scopes across its API resources contains every expected + * scope. The bucket may legitimately hold more (e.g. the write bucket also inherits read scopes), so this checks + * containment rather than equality. + */ + private static void assertBucketScopes(Map> apiResources, String bucketKey, + String... expectedScopes) { + + List bucket = apiResources.get(bucketKey); + Assert.assertNotNull(bucket, "Bucket should be present: " + bucketKey); + Set scopeNames = new HashSet<>(); + for (APIResource apiResource : bucket) { + for (Scope scope : apiResource.getScopes()) { + scopeNames.add(scope.getName()); + } + } + for (String expectedScope : expectedScopes) { + Assert.assertTrue(scopeNames.contains(expectedScope), + "Bucket '" + bucketKey + "' should expose scope '" + expectedScope + "' but had " + scopeNames); + } + } + + private static APIResource apiResourceWithScopes(String name, String... scopeNames) { + + List scopes = new ArrayList<>(); + for (String scopeName : scopeNames) { + scopes.add(createScope(scopeName)); + } + return new APIResource.APIResourceBuilder() + .name("testAPIResource name " + name) + .identifier("testAPIResource identifier " + name) + .description("testAPIResource description " + name) + .type("BUSINESS") + .requiresAuthorization(true) + .scopes(scopes) + .build(); + } + + private static List singletonScopeList(String scope) { + + List scopes = new ArrayList<>(); + scopes.add(scope); + return scopes; + } + + private static void assertSingleResourceWithScope(List apiResources, String expectedScope) { + + Assert.assertNotNull(apiResources); + Assert.assertEquals(apiResources.size(), 1, + "Exactly one API resource should match the granular scope: " + expectedScope); + List scopes = apiResources.get(0).getScopes(); + Assert.assertEquals(scopes.size(), 1, "Only the matching scope should be retained on the resource."); + Assert.assertEquals(scopes.get(0).getName(), expectedScope); + } + private void addTestAPIResourceCollections() { APIResourceCollection apiResourceCollection1 = createAPIResourceCollection("test1"); diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionMgtConfigBuilderTest.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionMgtConfigBuilderTest.java index e132adf3582f..4408a204e78a 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionMgtConfigBuilderTest.java +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionMgtConfigBuilderTest.java @@ -1,34 +1,244 @@ +/* + * Copyright (c) 2025, 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.api.resource.collection.mgt; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.wso2.carbon.identity.api.resource.collection.mgt.constant.APIResourceCollectionManagementConstants; import org.wso2.carbon.identity.api.resource.collection.mgt.model.APIResourceCollection; import org.wso2.carbon.identity.api.resource.collection.mgt.util.APIResourceCollectionMgtConfigBuilder; import org.wso2.carbon.identity.common.testng.WithAxisConfiguration; import org.wso2.carbon.identity.common.testng.WithCarbonHome; -import org.wso2.carbon.identity.event.IdentityEventException; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; import java.util.Map; +/** + * Unit tests for {@link APIResourceCollectionMgtConfigBuilder}. + *

+ * The builder parses {@code api-resource-collection.xml} from the test resources. That config declares two + * {@code apiResources} collections that share the same name (a {@code version="v0"} collection and a new one); + * because the name is base64-encoded into the id, both merge into a single parsed {@link APIResourceCollection}. + * The tests below pin the scope buckets produced for that merged collection, grouped by the concern they cover. + */ @WithCarbonHome @WithAxisConfiguration public class APIResourceCollectionMgtConfigBuilderTest { - APIResourceCollectionMgtConfigBuilder configBuilder; + private static final String COLLECTION_NAME = "apiResources"; + private static final String COLLECTION_DISPLAY_NAME = "API Resources"; + private static final String COLLECTION_TYPE = "tenant"; + + // Action scopes (internal_*) declared under ///. + private static final String READ_SCOPE = "internal_api_resource_view"; + private static final String CREATE_SCOPE = "internal_api_resource_create"; + private static final String UPDATE_SCOPE = "internal_api_resource_update"; + private static final String DELETE_SCOPE = "internal_api_resource_delete"; + + // Feature scopes (console:*) declared under . + private static final String FEATURE_SCOPE = "console:apiResources"; + private static final String VIEW_FEATURE_SCOPE = "console:apiResources_view"; + private static final String EDIT_FEATURE_SCOPE = "console:apiResources_edit"; + private static final String CREATE_FEATURE_SCOPE = "console:apiResources_create"; + private static final String UPDATE_FEATURE_SCOPE = "console:apiResources_update"; + private static final String DELETE_FEATURE_SCOPE = "console:apiResources_delete"; + + private APIResourceCollectionMgtConfigBuilder configBuilder; @BeforeMethod - public void setUp() throws IdentityEventException { + public void setUp() throws URISyntaxException { + + URL confResource = getClass().getResource( + "/repository/conf/" + APIResourceCollectionManagementConstants.API_RESOURCE_COLLECTION_FILE_NAME); + Assert.assertNotNull(confResource, "Test api-resource-collection.xml must be on the classpath."); + String confDir = new File(confResource.toURI()).getParent(); + System.setProperty("carbon.config.dir.path", confDir); + configBuilder = APIResourceCollectionMgtConfigBuilder.getInstance(); } - @Test - public void testGetApIResourceCollections() { + @Test(groups = "configLoading", description = "The config file is parsed into a non-empty collection map.") + public void testConfigurationsAreLoaded() { + + Map configurations = configBuilder.getApiResourceCollectionMgtConfigurations(); + Assert.assertNotNull(configurations); + Assert.assertFalse(configurations.isEmpty()); + } + + @Test(groups = "configLoading", + description = "Basic info (id, name, display name, type) is parsed for the merged collection.") + public void testApiResourcesCollectionBasicInfo() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertNotNull(collection.getId(), "Collection id should be set."); + Assert.assertEquals(collection.getName(), COLLECTION_NAME); + Assert.assertEquals(collection.getDisplayName(), COLLECTION_DISPLAY_NAME); + Assert.assertEquals(collection.getType(), COLLECTION_TYPE); + } + + @Test(groups = "readScopes", + description = "Read scopes contain the read action scope and the view feature scopes, " + + "without write-side scopes leaking in.") + public void testReadScopesContainReadAndPlainFeatureScopes() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertTrue(collection.getReadScopes().contains(READ_SCOPE)); + Assert.assertTrue(collection.getReadScopes().contains(FEATURE_SCOPE)); + Assert.assertTrue(collection.getReadScopes().contains(VIEW_FEATURE_SCOPE)); + + // Write-side scopes should not leak into the read bucket. + Assert.assertFalse(collection.getReadScopes().contains(CREATE_SCOPE)); + Assert.assertFalse(collection.getReadScopes().contains(EDIT_FEATURE_SCOPE)); + } + + @Test(groups = "writeScopes", + description = "Create/update/delete action scopes are aggregated into the combined write bucket.") + public void testWriteScopesAggregateCreateUpdateDelete() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertTrue(collection.getWriteScopes().contains(CREATE_SCOPE)); + Assert.assertTrue(collection.getWriteScopes().contains(UPDATE_SCOPE)); + Assert.assertTrue(collection.getWriteScopes().contains(DELETE_SCOPE)); + } + + @Test(groups = "writeScopes", + description = "The edit feature scope is aggregated into the write bucket only (not a granular bucket).") + public void testEditFeatureScopeAggregatedIntoWriteScopes() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertTrue(collection.getWriteScopes().contains(EDIT_FEATURE_SCOPE)); + + Assert.assertFalse(collection.getCreateScopes().contains(EDIT_FEATURE_SCOPE)); + Assert.assertFalse(collection.getUpdateScopes().contains(EDIT_FEATURE_SCOPE)); + Assert.assertFalse(collection.getDeleteScopes().contains(EDIT_FEATURE_SCOPE)); + } + + @Test(groups = "writeScopes", + description = "Write bucket holds the edit feature scope and the write normal (create/update/delete " + + "action) scopes.") + public void testWriteScopesContainEditFeatureScopes() { + + APIResourceCollection collection = getApiResourcesCollection(); + + // write feature + write normal scopes. + Assert.assertTrue(collection.getWriteScopes().contains(EDIT_FEATURE_SCOPE)); + Assert.assertTrue(collection.getWriteScopes().contains(CREATE_SCOPE)); + Assert.assertTrue(collection.getWriteScopes().contains(UPDATE_SCOPE)); + Assert.assertTrue(collection.getWriteScopes().contains(DELETE_SCOPE)); + + // The plain and view feature scopes are read-side; they are not stored in the write bucket itself. + Assert.assertFalse(collection.getWriteScopes().contains(FEATURE_SCOPE)); + Assert.assertFalse(collection.getWriteScopes().contains(VIEW_FEATURE_SCOPE)); + } + + @Test(groups = "granularActionScopes", + description = "Create/update/delete action scopes land in their own bucket and do not bleed across.") + public void testGranularActionScopesAreBucketed() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertNotNull(collection.getCreateScopes()); + Assert.assertNotNull(collection.getUpdateScopes()); + Assert.assertNotNull(collection.getDeleteScopes()); + Assert.assertTrue(collection.getCreateScopes().contains(CREATE_SCOPE), + "Create action scope should be bucketed into createScopes."); + Assert.assertTrue(collection.getUpdateScopes().contains(UPDATE_SCOPE), + "Update action scope should be bucketed into updateScopes."); + Assert.assertTrue(collection.getDeleteScopes().contains(DELETE_SCOPE), + "Delete action scope should be bucketed into deleteScopes."); + + // Granular buckets must not bleed into each other. + Assert.assertFalse(collection.getCreateScopes().contains(UPDATE_SCOPE)); + Assert.assertFalse(collection.getCreateScopes().contains(DELETE_SCOPE)); + Assert.assertFalse(collection.getUpdateScopes().contains(CREATE_SCOPE)); + Assert.assertFalse(collection.getUpdateScopes().contains(DELETE_SCOPE)); + Assert.assertFalse(collection.getDeleteScopes().contains(CREATE_SCOPE)); + Assert.assertFalse(collection.getDeleteScopes().contains(UPDATE_SCOPE)); + } + + @Test(groups = "granularActionScopes", + description = "Create bucket holds the create action scope and the create feature scope, without the " + + "plain feature scope (read-side) or the update/delete action scopes leaking in.") + public void testCreateScopesContainCreateActionAndFeatureScopes() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertTrue(collection.getCreateScopes().contains(CREATE_SCOPE)); + Assert.assertTrue(collection.getCreateScopes().contains(CREATE_FEATURE_SCOPE)); + + // Other write-side action scopes should not leak into the create bucket. + Assert.assertFalse(collection.getCreateScopes().contains(UPDATE_SCOPE)); + Assert.assertFalse(collection.getCreateScopes().contains(DELETE_SCOPE)); + } + + @Test(groups = "granularFeatureScopes", + description = "Granular console feature scopes resolve to their dedicated fields and matching buckets.") + public void testGranularFeatureScopesAreResolved() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertEquals(collection.getViewFeatureScope(), VIEW_FEATURE_SCOPE); + Assert.assertEquals(collection.getEditFeatureScope(), EDIT_FEATURE_SCOPE); + Assert.assertEquals(collection.getCreateFeatureScope(), CREATE_FEATURE_SCOPE); + Assert.assertEquals(collection.getUpdateFeatureScope(), UPDATE_FEATURE_SCOPE); + Assert.assertEquals(collection.getDeleteFeatureScope(), DELETE_FEATURE_SCOPE); + + // Each granular feature scope is also added to its respective action bucket. + Assert.assertTrue(collection.getCreateScopes().contains(CREATE_FEATURE_SCOPE)); + Assert.assertTrue(collection.getUpdateScopes().contains(UPDATE_FEATURE_SCOPE)); + Assert.assertTrue(collection.getDeleteScopes().contains(DELETE_FEATURE_SCOPE)); + } + + @Test(groups = "legacyScopes", + description = "Legacy read/write scopes are populated from the v0 collection.") + public void testLegacyScopesPopulatedFromV0Collection() { + + APIResourceCollection collection = getApiResourcesCollection(); + + Assert.assertNotNull(collection.getLegacyReadScopes()); + Assert.assertTrue(collection.getLegacyReadScopes().contains(READ_SCOPE)); + Assert.assertTrue(collection.getLegacyReadScopes().contains(FEATURE_SCOPE)); + + Assert.assertNotNull(collection.getLegacyWriteScopes()); + Assert.assertTrue(collection.getLegacyWriteScopes().contains(CREATE_SCOPE)); + Assert.assertTrue(collection.getLegacyWriteScopes().contains(UPDATE_SCOPE)); + Assert.assertTrue(collection.getLegacyWriteScopes().contains(DELETE_SCOPE)); + } + + /** + * Resolves the single merged {@code apiResources} collection from the parsed config. + */ + private APIResourceCollection getApiResourcesCollection() { - APIResourceCollectionMgtConfigBuilder instance = APIResourceCollectionMgtConfigBuilder.getInstance(); - Map apiResourceCollectionMap = instance - .getApiResourceCollectionMgtConfigurations(); - Assert.assertNotNull(apiResourceCollectionMap); - Assert.assertFalse(apiResourceCollectionMap.isEmpty()); + Map configurations = configBuilder.getApiResourceCollectionMgtConfigurations(); + APIResourceCollection collection = configurations.values().stream() + .filter(c -> COLLECTION_NAME.equals(c.getName())) + .findFirst() + .orElse(null); + Assert.assertNotNull(collection, "apiResources collection should be present in the parsed config."); + return collection; } } diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionTest.java b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionTest.java new file mode 100644 index 000000000000..008e630e1791 --- /dev/null +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/java/org/wso2/carbon/identity/api/resource/collection/mgt/APIResourceCollectionTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025, 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.api.resource.collection.mgt; + +import org.testng.Assert; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.api.resource.collection.mgt.model.APIResourceCollection; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Unit tests for the granular create/update/delete fields added to {@link APIResourceCollection}. + */ +public class APIResourceCollectionTest { + + @Test + public void testBuilderPopulatesGranularScopeFields() { + + List createScopes = Arrays.asList("create_scope_1", "create_scope_2"); + List updateScopes = Collections.singletonList("update_scope_1"); + List deleteScopes = Collections.singletonList("delete_scope_1"); + + APIResourceCollection collection = new APIResourceCollection.APIResourceCollectionBuilder() + .id("id") + .name("name") + .createScopes(createScopes) + .updateScopes(updateScopes) + .deleteScopes(deleteScopes) + .createFeatureScope("console:feature_create") + .updateFeatureScope("console:feature_update") + .deleteFeatureScope("console:feature_delete") + .build(); + + Assert.assertEquals(collection.getCreateScopes(), createScopes); + Assert.assertEquals(collection.getUpdateScopes(), updateScopes); + Assert.assertEquals(collection.getDeleteScopes(), deleteScopes); + Assert.assertEquals(collection.getCreateFeatureScope(), "console:feature_create"); + Assert.assertEquals(collection.getUpdateFeatureScope(), "console:feature_update"); + Assert.assertEquals(collection.getDeleteFeatureScope(), "console:feature_delete"); + } + + @Test + public void testGranularScopeSettersRoundTrip() { + + APIResourceCollection collection = new APIResourceCollection(); + + List createScopes = Collections.singletonList("create_scope"); + List updateScopes = Collections.singletonList("update_scope"); + List deleteScopes = Collections.singletonList("delete_scope"); + + collection.setCreateScopes(createScopes); + collection.setUpdateScopes(updateScopes); + collection.setDeleteScopes(deleteScopes); + collection.setCreateFeatureScope("console:feature_create"); + collection.setUpdateFeatureScope("console:feature_update"); + collection.setDeleteFeatureScope("console:feature_delete"); + + Assert.assertEquals(collection.getCreateScopes(), createScopes); + Assert.assertEquals(collection.getUpdateScopes(), updateScopes); + Assert.assertEquals(collection.getDeleteScopes(), deleteScopes); + Assert.assertEquals(collection.getCreateFeatureScope(), "console:feature_create"); + Assert.assertEquals(collection.getUpdateFeatureScope(), "console:feature_update"); + Assert.assertEquals(collection.getDeleteFeatureScope(), "console:feature_delete"); + } + + @Test + public void testGranularScopeFieldsDefaultToNull() { + + APIResourceCollection collection = new APIResourceCollection(); + + Assert.assertNull(collection.getCreateScopes()); + Assert.assertNull(collection.getUpdateScopes()); + Assert.assertNull(collection.getDeleteScopes()); + Assert.assertNull(collection.getCreateFeatureScope()); + Assert.assertNull(collection.getUpdateFeatureScope()); + Assert.assertNull(collection.getDeleteFeatureScope()); + } +} diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/repository/conf/api-resource-collection.xml b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/repository/conf/api-resource-collection.xml index 54eadef4dad3..0ff58517eb14 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/repository/conf/api-resource-collection.xml +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/repository/conf/api-resource-collection.xml @@ -45,6 +45,9 @@ + + + diff --git a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/testng.xml b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/testng.xml index 2e8c54328807..275c5bc90bc9 100644 --- a/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/testng.xml +++ b/components/api-resource-mgt/org.wso2.carbon.identity.api.resource.collection.mgt/src/test/resources/testng.xml @@ -22,7 +22,9 @@ + + diff --git a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml index 1c2d8d02b63c..0360f58cf709 100644 --- a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml +++ b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml @@ -772,6 +772,9 @@ + + + @@ -793,6 +796,9 @@ + + + @@ -810,6 +816,9 @@ + + + @@ -851,6 +860,9 @@ + + + @@ -871,6 +883,9 @@ + + + @@ -888,6 +903,9 @@ + + + @@ -909,6 +927,9 @@ + + + @@ -936,6 +957,9 @@ + + + @@ -954,6 +978,9 @@ + + + @@ -978,6 +1005,9 @@ + + + @@ -999,6 +1029,9 @@ + + + @@ -1031,6 +1064,9 @@ + + + @@ -1052,6 +1088,9 @@ + + + @@ -1073,6 +1112,9 @@ + + + @@ -1098,6 +1140,9 @@ + + + @@ -1120,6 +1165,9 @@ + + + @@ -1150,6 +1198,9 @@ + + + @@ -1171,6 +1222,9 @@ + + + @@ -1192,6 +1246,9 @@ + + + @@ -1213,6 +1270,9 @@ + + + @@ -1234,6 +1294,9 @@ + + + @@ -1267,6 +1330,9 @@ + + + @@ -1290,6 +1356,9 @@ + + + @@ -1314,6 +1383,9 @@ + + + @@ -1336,6 +1408,9 @@ + + + @@ -1364,6 +1439,9 @@ + + + @@ -1390,6 +1468,9 @@ + + + @@ -1421,6 +1502,9 @@ + + + @@ -1464,6 +1548,9 @@ + + + @@ -1495,6 +1582,9 @@ + + + @@ -1516,6 +1606,9 @@ + + + @@ -1540,6 +1633,9 @@ + + + @@ -1562,6 +1658,9 @@ + + + @@ -1583,6 +1682,9 @@ + + + @@ -1605,6 +1707,9 @@ + + + @@ -1627,6 +1732,9 @@ + + + @@ -1657,6 +1765,9 @@ + + + @@ -1687,6 +1798,9 @@ + + + @@ -1708,6 +1822,9 @@ + + + @@ -1739,6 +1856,9 @@ + + + @@ -1768,6 +1888,9 @@ + + + @@ -1799,6 +1922,9 @@ + + + @@ -1834,6 +1960,9 @@ + + + @@ -1858,6 +1987,9 @@ + + + @@ -1883,6 +2015,9 @@ + + + @@ -1910,6 +2045,9 @@ + + + @@ -1934,6 +2072,9 @@ + + + @@ -1951,6 +2092,9 @@ + + + @@ -1983,6 +2127,9 @@ + + + @@ -2000,6 +2147,9 @@ + + + @@ -2020,6 +2170,9 @@ + + + @@ -2041,6 +2194,9 @@ + + + @@ -2068,6 +2224,9 @@ + + + @@ -2086,6 +2245,9 @@ + + + @@ -2107,6 +2269,9 @@ + + + @@ -2128,6 +2293,9 @@ + + + @@ -2150,6 +2318,9 @@ + + + @@ -2162,6 +2333,9 @@ + + + @@ -2183,6 +2357,9 @@ + + + @@ -2203,6 +2380,9 @@ + + + @@ -2227,6 +2407,9 @@ + + + @@ -2250,6 +2433,9 @@ + + + @@ -2272,6 +2458,9 @@ + + + @@ -2292,6 +2481,9 @@ + + + diff --git a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml.j2 b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml.j2 index d6b70e512eff..52b02d5baa8a 100644 --- a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml.j2 +++ b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/api-resource-collection.xml.j2 @@ -521,6 +521,9 @@ + + + @@ -835,6 +838,9 @@ + + + @@ -856,6 +862,9 @@ + + + @@ -873,6 +882,9 @@ + + + @@ -915,6 +927,9 @@ + + + @@ -935,6 +950,9 @@ + + + @@ -952,6 +970,9 @@ + + + @@ -973,6 +994,9 @@ + + + @@ -1000,6 +1024,9 @@ + + + @@ -1018,6 +1045,9 @@ + + + @@ -1043,6 +1073,9 @@ + + + @@ -1064,6 +1097,9 @@ + + + @@ -1102,6 +1138,9 @@ + + + @@ -1123,6 +1162,9 @@ + + + @@ -1144,6 +1186,9 @@ + + + @@ -1169,6 +1214,9 @@ + + + @@ -1191,6 +1239,9 @@ + + + @@ -1224,6 +1275,9 @@ + + + @@ -1245,6 +1299,9 @@ + + + @@ -1267,6 +1324,9 @@ + + + @@ -1289,6 +1349,9 @@ + + + @@ -1310,6 +1373,9 @@ + + + @@ -1343,6 +1409,9 @@ + + + @@ -1366,6 +1435,9 @@ + + + @@ -1390,6 +1462,9 @@ + + + @@ -1412,6 +1487,9 @@ + + + {% if scim2.enable_scim2_roles_v3_api is sameas false %} @@ -1449,6 +1527,9 @@ + + + @@ -1476,6 +1557,9 @@ + + + @@ -1501,6 +1585,9 @@ + + + @@ -1527,6 +1614,9 @@ + + + @@ -1558,6 +1648,9 @@ + + + @@ -1602,6 +1695,9 @@ + + + @@ -1623,6 +1719,9 @@ + + + @@ -1647,6 +1746,9 @@ + + + @@ -1669,6 +1771,9 @@ + + + @@ -1691,6 +1796,9 @@ + + + @@ -1715,6 +1823,9 @@ + + + @@ -1738,6 +1849,9 @@ + + + @@ -1768,6 +1882,9 @@ + + + @@ -1799,6 +1916,9 @@ + + + @@ -1820,6 +1940,9 @@ + + + @@ -1857,6 +1980,9 @@ + + + @@ -1889,6 +2015,9 @@ + + + @@ -1920,6 +2049,9 @@ + + + @@ -1964,6 +2096,9 @@ + + + @@ -1988,6 +2123,9 @@ + + + @@ -2013,6 +2151,9 @@ + + + {% if scim2.enable_scim2_roles_v3_api is sameas false %} @@ -2049,6 +2190,9 @@ + + + @@ -2074,6 +2218,9 @@ + + + @@ -2097,6 +2244,9 @@ + + + @@ -2121,6 +2271,9 @@ + + + @@ -2138,6 +2291,9 @@ + + + @@ -2171,6 +2327,9 @@ + + + @@ -2188,6 +2347,9 @@ + + + @@ -2208,6 +2370,9 @@ + + + @@ -2229,6 +2394,9 @@ + + + @@ -2256,6 +2424,9 @@ + + + @@ -2274,6 +2445,9 @@ + + + @@ -2295,6 +2469,9 @@ + + + @@ -2316,6 +2493,9 @@ + + + @@ -2338,6 +2518,9 @@ + + + @@ -2350,6 +2533,9 @@ + + + @@ -2371,6 +2557,9 @@ + + + @@ -2392,6 +2581,9 @@ + + + @@ -2416,6 +2608,9 @@ + + + @@ -2441,6 +2636,9 @@ + + + @@ -2463,6 +2661,9 @@ + + + @@ -2483,6 +2684,9 @@ + + + diff --git a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml index 5a3e7dbfebd8..7c5e4f04ec7d 100644 --- a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml +++ b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml @@ -1368,6 +1368,12 @@ description="Manage views of applications from the Console"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml.j2 b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml.j2 index bb055b386676..b05879f0a2fa 100644 --- a/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml.j2 +++ b/features/api-resource-mgt/org.wso2.carbon.identity.api.resource.mgt.server.feature/resources/system-api-resource.xml.j2 @@ -1365,6 +1365,12 @@ description="Manage views of applications from the Console"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if consent_mgt.enable_v2_api %} @@ -2158,6 +2506,12 @@ description="Manage views of consent from the Console"/> + + + + + + {% endif %} @@ -2185,6 +2545,12 @@ description="Manage views of flows from the Console"/> + + + + + + + + + + + + + + + diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml index dfd05c92dde5..5b19650b21f1 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml @@ -2977,6 +2977,11 @@ + + + true + + + + {{console.console_settings.use_granular_console_permissions | default('true')}} + + {{myaccount.callback_url}} diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.default.json b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.default.json index d1d59ca5d539..14318a94c470 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.default.json +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.default.json @@ -2044,6 +2044,7 @@ "internal_validation_rule_mgt_update" ], "console.console_settings.enabled": true, + "console.console_settings.use_granular_console_permissions": true, "console.console_settings.disabled_features": [ "consoleSettings.invitedExternalAdmins", "consoleSettings.privilegedUsers" diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.infer.json b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.infer.json index 919703ef234b..1f4b0f7e8c59 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.infer.json +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/org.wso2.carbon.identity.core.server.feature.infer.json @@ -410,7 +410,13 @@ "actions.action_request.case_insensitive_header_filtering": false, "enhanced_organization_authentication.enabled_by_default_for_new_apps" : false, "saas.enable_app_creation": true, - "saas.enable_cross_tenant_operations": false + "saas.enable_cross_tenant_operations": false, + "console.console_settings.use_granular_console_permissions": false, + "console.console_settings.disabled_features": [ + "consoleSettings.invitedExternalAdmins", + "consoleSettings.privilegedUsers", + "consoleSettings.useGranularConsolePermissions" + ] }, "IS_7.2.0": { "authenticationendpoint.authenticator_validation.enabled": false, @@ -441,7 +447,13 @@ "actions.action_request.case_insensitive_header_filtering": false, "enhanced_organization_authentication.enabled_by_default_for_new_apps" : false, "saas.enable_app_creation": true, - "saas.enable_cross_tenant_operations": false + "saas.enable_cross_tenant_operations": false, + "console.console_settings.use_granular_console_permissions": false, + "console.console_settings.disabled_features": [ + "consoleSettings.invitedExternalAdmins", + "consoleSettings.privilegedUsers", + "consoleSettings.useGranularConsolePermissions" + ] } } }