From 438b7b64afb7502d1b96b239b56c56bfff831500 Mon Sep 17 00:00:00 2001 From: Rajitha Kumara Date: Wed, 30 Oct 2024 00:47:34 +0530 Subject: [PATCH] Add scope updating improvements --- .../publisher/APIPublisherServiceImpl.java | 196 ++++++++++++++---- .../internal/APIPublisherDataHolder.java | 7 + .../publisher/util/SelfSyncingScopeTree.java | 116 +++++++++++ 3 files changed, 282 insertions(+), 37 deletions(-) create mode 100644 components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/util/SelfSyncingScopeTree.java diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherServiceImpl.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherServiceImpl.java index c3e5a6bc54..3bbfdd4ccb 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherServiceImpl.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherServiceImpl.java @@ -36,11 +36,16 @@ import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIService import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException; import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException; import io.entgra.device.mgt.core.apimgt.extension.rest.api.util.APIPublisherUtils; +import io.entgra.device.mgt.core.apimgt.webapp.publisher.util.SelfSyncingScopeTree; import io.entgra.device.mgt.core.apimgt.webapp.publisher.config.WebappPublisherConfig; import io.entgra.device.mgt.core.apimgt.webapp.publisher.dto.ApiScope; import io.entgra.device.mgt.core.apimgt.webapp.publisher.dto.ApiUriTemplate; import io.entgra.device.mgt.core.apimgt.webapp.publisher.exception.APIManagerPublisherException; import io.entgra.device.mgt.core.apimgt.webapp.publisher.internal.APIPublisherDataHolder; +import io.entgra.device.mgt.core.device.mgt.common.exceptions.MetadataManagementException; +import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.Metadata; +import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.MetadataManagementService; +import io.entgra.device.mgt.core.device.mgt.common.permission.mgt.PermissionManagementException; import io.entgra.device.mgt.core.device.mgt.core.config.DeviceConfigurationManager; import io.entgra.device.mgt.core.device.mgt.core.config.DeviceManagementConfig; import io.entgra.device.mgt.core.device.mgt.core.config.permission.DefaultPermission; @@ -68,7 +73,6 @@ import org.wso2.carbon.user.core.tenant.Tenant; import org.wso2.carbon.user.core.tenant.TenantSearchResult; import org.wso2.carbon.utils.CarbonUtils; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; -import io.entgra.device.mgt.core.device.mgt.common.permission.mgt.PermissionManagementException; import java.io.BufferedReader; import java.io.File; @@ -82,9 +86,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -570,8 +576,10 @@ public class APIPublisherServiceImpl implements APIPublisherService { scope.setName( scopeMapping[2] != null ? StringUtils.trim(scopeMapping[2]) : StringUtils.EMPTY); // scope.setPermissions( - // scopeMapping[3] != null ? StringUtils.trim(scopeMapping[3]) : StringUtils.EMPTY); - String permission = scopeMapping[3] != null ? StringUtils.trim(scopeMapping[3]) : StringUtils.EMPTY; + // scopeMapping[3] != null ? StringUtils.trim + // (scopeMapping[3]) : StringUtils.EMPTY); + String permission = scopeMapping[3] != null ? StringUtils.trim(scopeMapping[3]) : + StringUtils.EMPTY; List rolesList = new ArrayList<>(); for (int i = 4; i < scopeMapping.length; i++) { @@ -586,7 +594,7 @@ public class APIPublisherServiceImpl implements APIPublisherService { Scope[] scopes = publisherRESTAPIServices.getScopes(apiApplicationKey, accessTokenInfo); for (int i = 0; i < scopes.length; i++) { Scope relatedScope = scopes[i]; - if (relatedScope.getName().equals(scopeMapping[2].toString())) { + if (relatedScope.getName().equals(scopeMapping[2])) { scope.setId(relatedScope.getId()); scope.setUsageCount(relatedScope.getUsageCount()); //Including already existing roles @@ -595,7 +603,8 @@ public class APIPublisherServiceImpl implements APIPublisherService { } scope.setBindings(rolesList); - if (publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, scope.getName())) { + if (publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, + scope.getName())) { publisherRESTAPIServices.updateSharedScope(apiApplicationKey, accessTokenInfo, scope); // todo: permission changed in update path, is not handled yet. } else { @@ -642,11 +651,94 @@ public class APIPublisherServiceImpl implements APIPublisherService { } } + synchronized private SelfSyncingScopeTree getSyncTree(APIApplicationKey apiApplicationKey, + AccessTokenInfo accessTokenInfo) + throws APIManagerPublisherException { + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + SelfSyncingScopeTree selfSyncingScopeTree = + APIPublisherDataHolder.getInstance().getSelfSyncTrees().get(tenantDomain); + try { + if (selfSyncingScopeTree == null) { + Metadata metadata; + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(MultitenantConstants + .SUPER_TENANT_DOMAIN_NAME, true); + MetadataManagementService metadataManagementService = + APIPublisherDataHolder.getInstance().getMetadataManagementService(); + metadata = metadataManagementService.retrieveMetadata(Constants.PERM_SCOPE_MAPPING_META_KEY); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + + if (metadata == null) { + String msg = "Metadata registry service doesn't contains the required metadata for " + + Constants.PERM_SCOPE_MAPPING_META_KEY; + log.error(msg); + throw new APIManagerPublisherException(msg); + } + + // interchange the keys and values (this has to be done form the bringing + Map permScopeMap = gson.fromJson(metadata.getMetaValue(), HashMap.class); + + Map scopePermMap = new HashMap<>(); + for (String permission: permScopeMap.keySet()) { + scopePermMap.put(permScopeMap.get(permission), permission); + } + + PublisherRESTAPIServices publisherRESTAPIServices = + APIPublisherDataHolder.getInstance().getPublisherRESTAPIServices(); + Scope[] scopes = publisherRESTAPIServices.getScopes(apiApplicationKey, accessTokenInfo); + + // o(n) + for (Scope scope : scopes) { + if (selfSyncingScopeTree == null) { + selfSyncingScopeTree = new SelfSyncingScopeTree(scope, scopePermMap.get(scope.getName()), + apiApplicationKey, + accessTokenInfo); + } else { + selfSyncingScopeTree.add(scope, scopePermMap.get(scope.getName())); + } + } + + APIPublisherDataHolder.getInstance().getSelfSyncTrees().put(tenantDomain, selfSyncingScopeTree); + } + return selfSyncingScopeTree; + } catch (MetadataManagementException e) { + String msg = "Error encountered while retrieving metadata for " + Constants.PERM_SCOPE_MAPPING_META_KEY; + log.error(msg); + throw new APIManagerPublisherException(msg, e); + } catch (APIManagerPublisherException | BadRequestException + | UnexpectedResponseException | APIServicesException e) { + String msg = "Error encountered while invoking publisher rest services"; + log.error(msg); + throw new APIManagerPublisherException(msg, e); + } + } + + private Scope getUpdatedScope(Scope previousScope, String role, boolean remove) { + Scope scope = new Scope(); + scope.setId(previousScope.getId()); + scope.setName(previousScope.getName()); + scope.setDisplayName(previousScope.getDisplayName()); + scope.setUsageCount(previousScope.getUsageCount()); + Set bindings = new HashSet<>(previousScope.getBindings()); + if (remove) { + bindings.remove(role); + } else { + bindings.add(role); + } + scope.setBindings(new ArrayList<>(bindings)); + return scope; + } + @Override public void updateScopeRoleMapping(String roleName, String[] permissions, String[] removedPermissions) throws APIManagerPublisherException { String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); - APIApplicationServices apiApplicationServices = APIPublisherDataHolder.getInstance().getApiApplicationServices(); - PublisherRESTAPIServices publisherRESTAPIServices = APIPublisherDataHolder.getInstance().getPublisherRESTAPIServices(); + APIApplicationServices apiApplicationServices = + APIPublisherDataHolder.getInstance().getApiApplicationServices(); +// PublisherRESTAPIServices publisherRESTAPIServices = +// APIPublisherDataHolder.getInstance().getPublisherRESTAPIServices(); APIApplicationKey apiApplicationKey; AccessTokenInfo accessTokenInfo; try { @@ -663,40 +755,70 @@ public class APIPublisherServiceImpl implements APIPublisherService { throw new APIManagerPublisherException(e); } - try { - - Scope[] scopeList = publisherRESTAPIServices.getScopes(apiApplicationKey, accessTokenInfo); + SelfSyncingScopeTree selfSyncingScopeTree = getSyncTree(apiApplicationKey, accessTokenInfo); - Map permScopeMap = APIPublisherDataHolder.getInstance().getPermScopeMapping(); - if (permissions.length != 0) { - updateScopes(roleName, publisherRESTAPIServices, apiApplicationKey, accessTokenInfo, scopeList, permissions, permScopeMap, false); - } - if (removedPermissions.length != 0) { - updateScopes(roleName, publisherRESTAPIServices, apiApplicationKey, accessTokenInfo, scopeList, removedPermissions, permScopeMap, true); + for (String permission : permissions) { + Scope scope = selfSyncingScopeTree.findScope(permission); + if (scope != null) { + selfSyncingScopeTree.add(getUpdatedScope(scope, roleName, false), permission); + } else { + log.warn("Can not find a scope binding for permission [ " + permission + " ]"); } + } - try { - updatePermissions(roleName, Arrays.asList(permissions)); - } catch (UserStoreException e) { - String errorMsg = "Error occurred when adding permissions to role: " + roleName; - log.error(errorMsg, e); - throw new APIManagerPublisherException(errorMsg, e); + for (String permission : removedPermissions) { + Scope scope = selfSyncingScopeTree.findScope(permission); + if (scope != null) { + selfSyncingScopeTree.add(getUpdatedScope(scope, roleName, true), permission); + } else { + log.warn("Can not find a scope binding for permission [ " + permission + " ]"); } - } catch (APIServicesException e) { - String errorMsg = "Error while processing Publisher REST API response"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(errorMsg, e); - } catch (BadRequestException e) { - String errorMsg = "Error while calling Publisher REST APIs"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(errorMsg, e); - } catch (UnexpectedResponseException e) { - String errorMsg = "Unexpected response from the server"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(errorMsg, e); - } finally { - APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); } + +// try { +// updatePermissions(roleName, Arrays.asList(permissions)); +// } catch (UserStoreException e) { +// String errorMsg = "Error occurred when adding permissions to role: " + roleName; +// log.error(errorMsg, e); +// throw new APIManagerPublisherException(errorMsg, e); +// } finally { +// APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); +// } + +// try { +// +// Scope[] scopeList = publisherRESTAPIServices.getScopes(apiApplicationKey, accessTokenInfo); +// +// Map permScopeMap = APIPublisherDataHolder.getInstance().getPermScopeMapping(); +// if (permissions.length != 0) { +// updateScopes(roleName, publisherRESTAPIServices, apiApplicationKey, accessTokenInfo, scopeList, permissions, permScopeMap, false); +// } +// if (removedPermissions.length != 0) { +// updateScopes(roleName, publisherRESTAPIServices, apiApplicationKey, accessTokenInfo, scopeList, removedPermissions, permScopeMap, true); +// } +// +// try { +// updatePermissions(roleName, Arrays.asList(permissions)); +// } catch (UserStoreException e) { +// String errorMsg = "Error occurred when adding permissions to role: " + roleName; +// log.error(errorMsg, e); +// throw new APIManagerPublisherException(errorMsg, e); +// } +// } catch (APIServicesException e) { +// String errorMsg = "Error while processing Publisher REST API response"; +// log.error(errorMsg, e); +// throw new APIManagerPublisherException(errorMsg, e); +// } catch (BadRequestException e) { +// String errorMsg = "Error while calling Publisher REST APIs"; +// log.error(errorMsg, e); +// throw new APIManagerPublisherException(errorMsg, e); +// } catch (UnexpectedResponseException e) { +// String errorMsg = "Unexpected response from the server"; +// log.error(errorMsg, e); +// throw new APIManagerPublisherException(errorMsg, e); +// } finally { +// APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); +// } } /** @@ -851,7 +973,7 @@ public class APIPublisherServiceImpl implements APIPublisherService { scopeSet.add(scopeObject); List scopes = new ArrayList<>(); - scopes.addAll(Arrays.asList(apiUriTemplate.getScope().getKey())); + scopes.add(apiUriTemplate.getScope().getKey()); operation.setScopes(scopes); } } diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/internal/APIPublisherDataHolder.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/internal/APIPublisherDataHolder.java index 8e243ab263..f6d8fb263d 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/internal/APIPublisherDataHolder.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/internal/APIPublisherDataHolder.java @@ -21,6 +21,7 @@ import io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationService import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServices; import io.entgra.device.mgt.core.apimgt.webapp.publisher.APIConfig; import io.entgra.device.mgt.core.apimgt.webapp.publisher.APIPublisherService; +import io.entgra.device.mgt.core.apimgt.webapp.publisher.util.SelfSyncingScopeTree; import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.MetadataManagementService; import io.entgra.device.mgt.core.apimgt.webapp.publisher.PostApiPublishingObsever; import org.wso2.carbon.context.CarbonContext; @@ -38,6 +39,7 @@ import java.util.Map; import java.util.Stack; import java.util.List; import java.util.ArrayList; +import java.util.WeakHashMap; public class APIPublisherDataHolder { @@ -53,6 +55,11 @@ public class APIPublisherDataHolder { private APIApplicationServices apiApplicationServices; private PublisherRESTAPIServices publisherRESTAPIServices; private MetadataManagementService metadataManagementService; + private static final WeakHashMap selfSyncTrees = new WeakHashMap<>(); + + public WeakHashMap getSelfSyncTrees() { + return selfSyncTrees; + } private static APIPublisherDataHolder thisInstance = new APIPublisherDataHolder(); diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/util/SelfSyncingScopeTree.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/util/SelfSyncingScopeTree.java new file mode 100644 index 0000000000..657d852df9 --- /dev/null +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/util/SelfSyncingScopeTree.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.webapp.publisher.util; + +import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServices; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Scope; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.AccessTokenInfo; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIServicesException; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException; +import io.entgra.device.mgt.core.apimgt.webapp.publisher.exception.APIManagerPublisherException; +import io.entgra.device.mgt.core.apimgt.webapp.publisher.internal.APIPublisherDataHolder; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class SelfSyncingScopeTree { + private static final Log log = LogFactory.getLog(SelfSyncingScopeTree.class); + private static final PublisherRESTAPIServices publisherRESTAPIServices = + APIPublisherDataHolder.getInstance().getPublisherRESTAPIServices(); + private final APIApplicationKey apiApplicationKey; + private final AccessTokenInfo accessTokenInfo; + private final Map childTrees = new ConcurrentHashMap<>(); + private String pathKey; + private Scope scope; + + public SelfSyncingScopeTree(Scope scope, String permission, APIApplicationKey apiApplicationKey, + AccessTokenInfo accessTokenInfo) throws APIManagerPublisherException { + this.apiApplicationKey = apiApplicationKey; + this.accessTokenInfo = accessTokenInfo; + add(scope, permission); + } + + public void add(Scope scope, String permission) throws APIManagerPublisherException { + List pathKeys = Arrays.stream(StringUtils.split(permission, "/")).collect(Collectors.toList()); + + if (pathKeys.size() == 1) { + this.pathKey = pathKeys.get(0); + + if (this.scope == null) { + this.scope = scope; + } else { + this.update(scope); + } + + return; + } + + if (this.pathKey == null) { + this.pathKey = pathKeys.get(0); + } + + pathKeys.remove(0); + + if (childTrees.containsKey(pathKeys.get(0))) { + childTrees.get(pathKeys.get(0)).add(scope, String.join("/", pathKeys)); + } else { + childTrees.put(pathKeys.get(0), new SelfSyncingScopeTree(scope, String.join("/", pathKeys), + apiApplicationKey, accessTokenInfo)); + } + + } + + public Scope findScope(String permission) { + if (this.scope != null) { + return this.scope; + } + + List pathKeys = Arrays.stream(StringUtils.split(permission, "/")).collect(Collectors.toList()); + pathKeys.remove(0); + return childTrees.get(pathKeys.get(0)).findScope(String.join("/", pathKeys)); + } + + private void update(Scope scope) throws APIManagerPublisherException { + try { + if (publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, + this.scope.getName())) { + if (publisherRESTAPIServices.updateSharedScope(apiApplicationKey, accessTokenInfo, scope)) { + this.scope = scope; + } + } else { + log.warn("Found a scope which is not reflect in the APIM end. Scope [ " + scope.getName() + " ]"); + } + } catch (APIServicesException | BadRequestException | UnexpectedResponseException e) { + String msg = "Error encountered while updating the scope [ " + scope.getName() + "]"; + log.error(msg, e); + throw new APIManagerPublisherException(msg, e); + } + } +}