diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/pom.xml b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/pom.xml index 00e8d5c08c..632150f8fe 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/pom.xml +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/pom.xml @@ -84,6 +84,10 @@ okhttp compile + + org.wso2.carbon + org.wso2.carbon.user.api + @@ -121,7 +125,10 @@ org.wso2.carbon.apimgt.impl;version="${carbon.api.mgt.version.range}", org.wso2.carbon.apimgt.impl.utils;version="${carbon.api.mgt.version.range}", org.wso2.carbon.apimgt.impl.internal;version="${carbon.api.mgt.version.range}", - org.json + org.json, + org.wso2.carbon.user.api, + org.wso2.carbon.context;version="4.6", + org.wso2.carbon.utils.* diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java index 05ecf1fd9e..c412d189c0 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java @@ -33,8 +33,11 @@ import okhttp3.RequestBody; import okhttp3.Credentials; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.impl.APIConstants; import org.wso2.carbon.apimgt.impl.APIManagerConfiguration; import org.wso2.carbon.apimgt.impl.internal.ServiceReferenceHolder; +import org.wso2.carbon.context.PrivilegedCarbonContext; + import java.io.IOException; public class APIApplicationServicesImpl implements APIApplicationServices { @@ -48,12 +51,12 @@ public class APIApplicationServicesImpl implements APIApplicationServices { getAPIManagerConfigurationService().getAPIManagerConfiguration(); @Override - public APIApplicationKey createAndRetrieveApplicationCredentials() - throws APIServicesException { + public APIApplicationKey createAndRetrieveApplicationCredentials() throws APIServicesException { + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String serverUser = getScopePublishUserName(tenantDomain); + String serverPassword = getScopePublishUserPassword(tenantDomain); String applicationEndpoint = config.getFirstProperty(Constants.DCR_END_POINT); - String serverUser = config.getFirstProperty(Constants.SERVER_USER); - String serverPassword = config.getFirstProperty(Constants.SERVER_PASSWORD); JSONObject jsonObject = new JSONObject(); jsonObject.put("callbackUrl", Constants.EMPTY_STRING); @@ -68,9 +71,11 @@ public class APIApplicationServicesImpl implements APIApplicationServices { .addHeader(Constants.AUTHORIZATION_HEADER_NAME, Credentials.basic(serverUser, serverPassword)) .post(requestBody) .build(); + try { - Response response = client.newCall(request).execute(); - return gson.fromJson(response.body().string(), APIApplicationKey.class); + try (Response response = client.newCall(request).execute()) { + return gson.fromJson(response.body().string(), APIApplicationKey.class); + } } catch (IOException e) { msg = "Error occurred while processing the response"; log.error(msg, e); @@ -82,8 +87,9 @@ public class APIApplicationServicesImpl implements APIApplicationServices { public AccessTokenInfo generateAccessTokenFromRegisteredApplication(String consumerKey, String consumerSecret) throws APIServicesException { - String userName = config.getFirstProperty(Constants.SERVER_USER); - String userPassword = config.getFirstProperty(Constants.SERVER_PASSWORD); + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String userName = getScopePublishUserName(tenantDomain); + String userPassword = getScopePublishUserPassword(tenantDomain); JSONObject params = new JSONObject(); params.put(Constants.GRANT_TYPE_PARAM_NAME, Constants.PASSWORD_GRANT_TYPE); @@ -94,9 +100,8 @@ public class APIApplicationServicesImpl implements APIApplicationServices { } @Override - public AccessTokenInfo generateAccessTokenFromRefreshToken(String refreshToken, String consumerKey, String consumerSecret) - throws APIServicesException { - + public AccessTokenInfo generateAccessTokenFromRefreshToken(String refreshToken, String consumerKey, + String consumerSecret) throws APIServicesException { JSONObject params = new JSONObject(); params.put(Constants.GRANT_TYPE_PARAM_NAME, Constants.REFRESH_TOKEN_GRANT_TYPE); params.put(Constants.REFRESH_TOKEN_GRANT_TYPE_PARAM_NAME, refreshToken); @@ -125,4 +130,20 @@ public class APIApplicationServicesImpl implements APIApplicationServices { throw new APIServicesException(e); } } + + private String getScopePublishUserName(String tenantDomain) { + if(APIConstants.SUPER_TENANT_DOMAIN.equals(tenantDomain)) { + return config.getFirstProperty(Constants.SERVER_USER); + } else { + return Constants.SCOPE_PUBLISH_RESERVED_USER_NAME + "@" + tenantDomain; + } + } + + private String getScopePublishUserPassword(String tenantDomain) { + if(APIConstants.SUPER_TENANT_DOMAIN.equals(tenantDomain)) { + return config.getFirstProperty(Constants.SERVER_PASSWORD); + } else { + return Constants.SCOPE_PUBLISH_RESERVED_USER_PASSWORD; + } + } } diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java index b86437b6f3..a263ad2d63 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java @@ -42,6 +42,9 @@ public interface PublisherRESTAPIServices { boolean updateSharedScope(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, Scope scope) throws APIServicesException, BadRequestException, UnexpectedResponseException; + boolean deleteSharedScope(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, Scope scope) + throws APIServicesException, BadRequestException, UnexpectedResponseException; + APIInfo getApi(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, String apiUuid) throws APIServicesException, BadRequestException, UnexpectedResponseException; diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServicesImpl.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServicesImpl.java index 8ba8bb12f7..8db730cc7b 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServicesImpl.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServicesImpl.java @@ -79,6 +79,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -123,6 +124,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { return false; } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -177,6 +179,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.message(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -231,6 +234,61 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); + throw new UnexpectedResponseException(msg); + } + } catch (IOException e) { + String msg = "Error occurred while processing the response"; + log.error(msg, e); + throw new APIServicesException(msg, e); + } + } + + @Override + public boolean deleteSharedScope(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, Scope scope) + throws APIServicesException, BadRequestException, UnexpectedResponseException { + String updateScopeUrl = endPointPrefix + Constants.SCOPE_API_ENDPOINT + scope.getId(); + + JSONArray bindings = new JSONArray(); + if (scope.getBindings() != null) { + for (String str : scope.getBindings()) { + bindings.put(str); + } + } + + JSONObject payload = new JSONObject(); + payload.put("name", (scope.getName() != null ? scope.getName() : "")); + payload.put("displayName", (scope.getDisplayName() != null ? scope.getDisplayName() : "")); + payload.put("description", (scope.getDescription() != null ? scope.getDescription() : "")); + payload.put("bindings", (bindings != null ? bindings : "")); + payload.put("usageCount", (scope.getUsageCount() != 0 ? scope.getUsageCount() : 0)); + + RequestBody requestBody = RequestBody.create(JSON, payload.toString()); + Request request = new Request.Builder() + .url(updateScopeUrl) + .addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER + + accessTokenInfo.getAccess_token()) + .delete(requestBody) + .build(); + + try { + Response response = client.newCall(request).execute(); + if (HttpStatus.SC_OK == response.code()) { + return true; + } else if (HttpStatus.SC_UNAUTHORIZED == response.code()) { + APIApplicationServices apiApplicationServices = new APIApplicationServicesImpl(); + AccessTokenInfo refreshedAccessToken = apiApplicationServices. + generateAccessTokenFromRefreshToken(accessTokenInfo.getRefresh_token(), + apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); + //TODO: max attempt count + return deleteSharedScope(apiApplicationKey, refreshedAccessToken, scope); + } else if (HttpStatus.SC_BAD_REQUEST == response.code()) { + String msg = "Bad Request, Invalid scope object"; + log.error(msg); + throw new BadRequestException(msg); + } else { + String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -269,6 +327,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -308,6 +367,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -447,6 +507,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response status : " + response.code() + " Response message : " + response.message(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -586,6 +647,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -632,6 +694,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -673,6 +736,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -720,6 +784,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -761,6 +826,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -804,6 +870,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -846,6 +913,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -889,6 +957,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -939,6 +1008,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -990,6 +1060,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -1031,6 +1102,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -1071,6 +1143,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -1111,6 +1184,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -1162,6 +1236,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { @@ -1208,6 +1283,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices { throw new BadRequestException(msg); } else { String msg = "Response : " + response.code() + response.body(); + log.error(msg); throw new UnexpectedResponseException(msg); } } catch (IOException e) { diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/constants/Constants.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/constants/Constants.java index 5a577e3eb1..90312d093d 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/constants/Constants.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/constants/Constants.java @@ -65,6 +65,10 @@ public final class Constants { public static final String SCOPE_API_ENDPOINT = "/api/am/publisher/v2/scopes/"; public static final String API_ENDPOINT = "/api/am/publisher/v2/apis/"; public static final String GET_ALL_APIS = "/api/am/publisher/v2/apis?limit=1000"; + public static final String SCOPE_PUBLISH_RESERVED_USER_NAME = "scope_publish_reserved_user"; + public static final String SCOPE_PUBLISH_RESERVED_USER_PASSWORD = "&gKfyE8E4rUY4Q"; + public static final String ADMIN_ROLE_KEY = "admin"; + public static final String PERM_SCOPE_MAPPING_META_KEY = "perm-scope-mapping"; } diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/internal/APIManagerServiceDataHolder.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/internal/APIManagerServiceDataHolder.java index 2779366b9d..deeeaa2a06 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/internal/APIManagerServiceDataHolder.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/internal/APIManagerServiceDataHolder.java @@ -21,12 +21,16 @@ package io.entgra.device.mgt.core.apimgt.extension.rest.api.internal; import io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationServices; import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServices; import org.wso2.carbon.apimgt.impl.APIManagerConfigurationService; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.user.core.tenant.TenantManager; public class APIManagerServiceDataHolder { private APIApplicationServices apiApplicationServices; private APIManagerConfigurationService apiManagerConfigurationService; private PublisherRESTAPIServices publisherRESTAPIServices; + private RealmService realmService; + private TenantManager tenantManager; private static APIManagerServiceDataHolder thisInstance = new APIManagerServiceDataHolder(); @@ -63,4 +67,27 @@ public class APIManagerServiceDataHolder { public void setPublisherRESTAPIServices(PublisherRESTAPIServices publisherRESTAPIServices) { this.publisherRESTAPIServices = publisherRESTAPIServices; } + + public RealmService getRealmService() { + if (realmService == null) { + throw new IllegalStateException("Realm service is not initialized properly"); + } + return realmService; + } + + public void setRealmService(RealmService realmService) { + this.realmService = realmService; + this.setTenantManager(realmService); + } + + public TenantManager getTenantManager() { + return tenantManager; + } + + private void setTenantManager(RealmService realmService) { + if (realmService == null) { + throw new IllegalStateException("Realm service is not initialized properly"); + } + this.tenantManager = realmService.getTenantManager(); + } } diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/APIPublisherUtils.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/APIPublisherUtils.java new file mode 100644 index 0000000000..6de3f50a32 --- /dev/null +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/APIPublisherUtils.java @@ -0,0 +1,91 @@ +/* + * 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.extension.rest.api.util; + +import io.entgra.device.mgt.core.apimgt.extension.rest.api.constants.Constants; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIServicesException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +/** + * This class contains utility methods needed for API publishing + */ +public class APIPublisherUtils { + private static final Log log = LogFactory.getLog(APIPublisherUtils.class); + + /** + * This method will create the temporary user created to publish scopes to the sub tenant space. + * @param tenantDomain sub tenant domain from which the user will be created + * @throws APIServicesException if the user was unable to be created + */ + public static void createScopePublishUserIfNotExists(String tenantDomain) throws APIServicesException { + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + try { + UserStoreManager userStoreManager = + PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getUserStoreManager(); + if (!userStoreManager.isExistingUser(MultitenantUtils.getTenantAwareUsername(Constants.SCOPE_PUBLISH_RESERVED_USER_NAME))) { + if (log.isDebugEnabled()) { + log.debug("Creating scope publish user '" + Constants.SCOPE_PUBLISH_RESERVED_USER_NAME + "' in '" + + tenantDomain + "' tenant domain."); + } + String[] roles = {Constants.ADMIN_ROLE_KEY}; + userStoreManager.addUser( + MultitenantUtils.getTenantAwareUsername(Constants.SCOPE_PUBLISH_RESERVED_USER_NAME), + Constants.SCOPE_PUBLISH_RESERVED_USER_PASSWORD, + roles, + null, + "" + ); + } + } catch (UserStoreException e) { + String msg = "Error occurred while creating scope publishing user in tenant: '" + tenantDomain + "'."; + log.error(msg); + throw new APIServicesException(msg, e); + } + } + } + + /** + * This method will delete the temporary user created to publish scopes to the sub tenant space. + * @param tenantDomain sub tenant domain from which the scope publish user will be removed from + */ + public static void removeScopePublishUserIfExists(String tenantDomain) { + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + try { + UserStoreManager userStoreManager = + PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getUserStoreManager(); + if (userStoreManager.isExistingUser(MultitenantUtils.getTenantAwareUsername(Constants.SCOPE_PUBLISH_RESERVED_USER_NAME))) { + if (log.isDebugEnabled()) { + log.debug("Deleting scope publish user '" + Constants.SCOPE_PUBLISH_RESERVED_USER_NAME + "' from '" + + tenantDomain + "' tenant domain."); + } + userStoreManager.deleteUser(MultitenantUtils.getTenantAwareUsername(Constants.SCOPE_PUBLISH_RESERVED_USER_NAME)); + } + } catch(UserStoreException e){ + String msg = "Error occurred while deleting scope publishing user from tenant: '" + tenantDomain + "'."; + log.error(msg); + } + } + } +} 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/APIPublisherService.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherService.java index eec6cfcab7..b66d723a1a 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherService.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.webapp.publisher/src/main/java/io/entgra/device/mgt/core/apimgt/webapp/publisher/APIPublisherService.java @@ -39,7 +39,7 @@ public interface APIPublisherService { /** * Add default scopes defined in the cdm-config.xml */ - void addDefaultScopesIfNotExist(); + void addDefaultScopesIfNotExist() throws APIManagerPublisherException; /** * If the permissions are in the permission list, identify the relevant scopes of the supplied permission list 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 8630a8ecb8..e9a58e556c 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 @@ -19,16 +19,23 @@ package io.entgra.device.mgt.core.apimgt.webapp.publisher; import com.google.gson.Gson; import io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationServices; -import io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationServicesImpl; import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServices; -import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServicesImpl; import io.entgra.device.mgt.core.apimgt.extension.rest.api.constants.Constants; import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey; -import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.*; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIInfo; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevision; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevisionDeployment; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.CORSConfiguration; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Documentation; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Mediation; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.MediationPolicy; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Operations; +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.extension.rest.api.util.APIPublisherUtils; 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; @@ -60,6 +67,7 @@ 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 org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.io.BufferedReader; import java.io.File; @@ -111,15 +119,6 @@ public class APIPublisherServiceImpl implements APIPublisherService { PublisherRESTAPIServices publisherRESTAPIServices = APIPublisherDataHolder.getInstance().getPublisherRESTAPIServices(); APIApplicationKey apiApplicationKey; AccessTokenInfo accessTokenInfo; - try { - apiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); - accessTokenInfo = apiApplicationServices.generateAccessTokenFromRegisteredApplication( - apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); - } catch (APIServicesException e) { - String errorMsg = "Error occurred while generating the API application"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(e); - } try { boolean tenantFound = false; @@ -154,6 +153,17 @@ public class APIPublisherServiceImpl implements APIPublisherService { PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(apiConfig.getOwner()); int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + try { + APIPublisherUtils.createScopePublishUserIfNotExists(tenantDomain); + apiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); + accessTokenInfo = apiApplicationServices.generateAccessTokenFromRegisteredApplication( + apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); + } catch (APIServicesException e) { + String errorMsg = "Error occurred while generating the API application"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + } + try { apiConfig.setOwner(APIUtil.getTenantAdminUserName(tenantDomain)); apiConfig.setTenantDomain(tenantDomain); @@ -428,6 +438,7 @@ public class APIPublisherServiceImpl implements APIPublisherService { log.error(msg, e); throw new APIManagerPublisherException(e); } finally { + APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); PrivilegedCarbonContext.endTenantFlow(); } } @@ -439,37 +450,53 @@ public class APIPublisherServiceImpl implements APIPublisherService { } } - public void addDefaultScopesIfNotExist() { + @Override + public void addDefaultScopesIfNotExist() throws APIManagerPublisherException { + WebappPublisherConfig config = WebappPublisherConfig.getInstance(); + List tenants = new ArrayList<>(Collections.singletonList(APIConstants.SUPER_TENANT_DOMAIN)); + tenants.addAll(config.getTenants().getTenant()); + DeviceManagementConfig deviceManagementConfig = DeviceConfigurationManager.getInstance().getDeviceManagementConfig(); DefaultPermissions defaultPermissions = deviceManagementConfig.getDefaultPermissions(); APIApplicationServices apiApplicationServices = APIPublisherDataHolder.getInstance().getApiApplicationServices(); PublisherRESTAPIServices publisherRESTAPIServices = APIPublisherDataHolder.getInstance().getPublisherRESTAPIServices(); - try { - APIApplicationKey apiApplicationKey = - apiApplicationServices.createAndRetrieveApplicationCredentials(); - AccessTokenInfo accessTokenInfo = - apiApplicationServices.generateAccessTokenFromRegisteredApplication( - apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); + for (String tenantDomain : tenants) { + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); - Scope scope = new Scope(); - for (DefaultPermission defaultPermission: defaultPermissions.getDefaultPermissions()) { - if (!publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, - defaultPermission.getScopeMapping().getKey())) { - ScopeMapping scopeMapping = defaultPermission.getScopeMapping(); - - List bindings = new ArrayList<>( - Arrays.asList(scopeMapping.getDefaultRoles().split(","))); - bindings.add(ADMIN_ROLE_KEY); - scope.setName(scopeMapping.getKey()); - scope.setDescription(scopeMapping.getName()); - scope.setDisplayName(scopeMapping.getName()); - scope.setBindings(bindings); - publisherRESTAPIServices.addNewSharedScope(apiApplicationKey, accessTokenInfo, scope); + APIPublisherUtils.createScopePublishUserIfNotExists(tenantDomain); + APIApplicationKey apiApplicationKey = + apiApplicationServices.createAndRetrieveApplicationCredentials(); + AccessTokenInfo accessTokenInfo = + apiApplicationServices.generateAccessTokenFromRegisteredApplication( + apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); + + Scope scope = new Scope(); + for (DefaultPermission defaultPermission : defaultPermissions.getDefaultPermissions()) { + if (!publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, + defaultPermission.getScopeMapping().getKey())) { + ScopeMapping scopeMapping = defaultPermission.getScopeMapping(); + + List bindings = new ArrayList<>( + Arrays.asList(scopeMapping.getDefaultRoles().split(","))); + bindings.add(ADMIN_ROLE_KEY); + scope.setName(scopeMapping.getKey()); + scope.setDescription(scopeMapping.getName()); + scope.setDisplayName(scopeMapping.getName()); + scope.setBindings(bindings); + publisherRESTAPIServices.addNewSharedScope(apiApplicationKey, accessTokenInfo, scope); + } } + } catch (BadRequestException | UnexpectedResponseException | APIServicesException e) { + String errorMsg = "Error occurred while adding default scopes"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + } finally { + APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); + PrivilegedCarbonContext.endTenantFlow(); } - } catch (BadRequestException | UnexpectedResponseException | APIServicesException e) { - log.error("Error occurred while adding default scopes"); } } @@ -487,24 +514,26 @@ public class APIPublisherServiceImpl implements APIPublisherService { APIApplicationKey apiApplicationKey; AccessTokenInfo accessTokenInfo; - try { - apiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); - accessTokenInfo = apiApplicationServices.generateAccessTokenFromRegisteredApplication( - apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); - } catch (APIServicesException e) { - String errorMsg = "Error occurred while generating the API application"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(e); - } UserStoreManager userStoreManager; + String fileName = null; - try { - for (String tenantDomain : tenants) { + for (String tenantDomain : tenants) { + try { PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); + try { + APIPublisherUtils.createScopePublishUserIfNotExists(tenantDomain); + apiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); + accessTokenInfo = apiApplicationServices.generateAccessTokenFromRegisteredApplication( + apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); + } catch (APIServicesException e) { + String errorMsg = "Error occurred while generating the API application"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + } try { - String fileName = + fileName = CarbonUtils.getCarbonConfigDirPath() + File.separator + "etc" + File.separator + tenantDomain + ".csv"; try { @@ -594,34 +623,39 @@ public class APIPublisherServiceImpl implements APIPublisherService { } } } - } catch (IOException | DirectoryIteratorException ex) { - log.error("failed to read scopes from file.", ex); + } catch (IOException | DirectoryIteratorException e) { + String errorMsg = "Failed to read scopes from file: '" + fileName + "'."; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); } + } catch (APIServicesException e) { + String errorMsg = "Error while processing Publisher REST API response"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + } catch (BadRequestException e) { + String errorMsg = "Error while calling Publisher REST APIs"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + } catch (UnexpectedResponseException e) { + String errorMsg = "Unexpected response from the server"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + } finally { + APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); + PrivilegedCarbonContext.endTenantFlow(); } - } catch (APIServicesException e) { - String errorMsg = "Error while processing Publisher REST API response"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(e); - } catch (BadRequestException e) { - String errorMsg = "Error while calling Publisher REST APIs"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(e); - } catch (UnexpectedResponseException e) { - String errorMsg = "Unexpected response from the server"; - log.error(errorMsg, e); - throw new APIManagerPublisherException(e); - } finally { - PrivilegedCarbonContext.endTenantFlow(); } } @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(); APIApplicationKey apiApplicationKey; AccessTokenInfo accessTokenInfo; try { + APIPublisherUtils.createScopePublishUserIfNotExists(tenantDomain); apiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); accessTokenInfo = apiApplicationServices.generateAccessTokenFromRegisteredApplication( apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); @@ -662,6 +696,8 @@ public class APIPublisherServiceImpl implements APIPublisherService { String errorMsg = "Unexpected response from the server"; log.error(errorMsg, e); throw new APIManagerPublisherException(errorMsg, e); + } finally { + APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); } } diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java index bc3a26b88d..34ea482e1b 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java @@ -419,7 +419,8 @@ public class UserManagementServiceImpl implements UserManagementService { userList = new ArrayList<>(users.size()); BasicUserInfo user; for (String username : users) { - if (Constants.APIM_RESERVED_USER.equals(username) || Constants.RESERVED_USER.equals(username)) { + if (Constants.APIM_RESERVED_USER.equals(username) || Constants.RESERVED_USER.equals(username) || + Constants.SCOPE_PUBLISH_RESERVED_USER.equals(username)) { continue; } user = getBasicUserInfo(username); @@ -485,6 +486,7 @@ public class UserManagementServiceImpl implements UserManagementService { if (commonUsers != null) { commonUsers.remove(Constants.APIM_RESERVED_USER); commonUsers.remove(Constants.RESERVED_USER); + commonUsers.remove(Constants.SCOPE_PUBLISH_RESERVED_USER); } if (!skipSearch(commonUsers) && StringUtils.isNotEmpty(firstName)) { @@ -660,7 +662,8 @@ public class UserManagementServiceImpl implements UserManagementService { userList = new ArrayList<>(); UserInfo user; for (String username : users) { - if (Constants.APIM_RESERVED_USER.equals(username) || Constants.RESERVED_USER.equals(username)) { + if (Constants.APIM_RESERVED_USER.equals(username) || Constants.RESERVED_USER.equals(username) || + Constants.SCOPE_PUBLISH_RESERVED_USER.equals(username)) { continue; } user = new UserInfo(); diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/util/Constants.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/util/Constants.java index 75ba2cc322..eb718ea320 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/util/Constants.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/util/Constants.java @@ -31,6 +31,7 @@ public class Constants { public static final String USER_CLAIM_DEVICES = "http://wso2.org/claims/devices"; public static final String PRIMARY_USER_STORE = "PRIMARY"; public static final String APIM_RESERVED_USER = "apim_reserved_user"; + public static final String SCOPE_PUBLISH_RESERVED_USER = "scope_publish_reserved_user"; public static final String RESERVED_USER = "reserved_user"; public static final String DEFAULT_STREAM_VERSION = "1.0.0"; public static final String SCOPE = "scope"; diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/pom.xml b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/pom.xml index a7ab146a4a..22a3e9c021 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/pom.xml +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/pom.xml @@ -374,6 +374,10 @@ 2.3.1.wso2v1 compile + + io.entgra.device.mgt.core + io.entgra.device.mgt.core.apimgt.extension.rest.api + diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/DeviceManagementDataHolder.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/DeviceManagementDataHolder.java index 5cdc29da25..6736874487 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/DeviceManagementDataHolder.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/DeviceManagementDataHolder.java @@ -18,6 +18,8 @@ package io.entgra.device.mgt.core.device.mgt.core.internal; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationServices; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServices; import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.DeviceStatusManagementService; import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.HeartBeatManagementService; import org.wso2.carbon.context.PrivilegedCarbonContext; @@ -93,8 +95,9 @@ public class DeviceManagementDataHolder { private MetadataManagementService metadataManagementService; private WhiteLabelManagementService whiteLabelManagementService; private TraccarManagementService traccarManagementService; - private DeviceStatusManagementService deviceStatusManagementService; + private APIApplicationServices apiApplicationServices; + private PublisherRESTAPIServices publisherRESTAPIServices; private final Map deviceStatusTaskPluginConfigs = Collections.synchronizedMap( new HashMap<>()); @@ -410,4 +413,38 @@ public class DeviceManagementDataHolder { public void setTraccarManagementService(TraccarManagementService traccarManagementService) { this.traccarManagementService = traccarManagementService; } + + /** + * Retrieves the Dynamic Client Registration REST API Service instance from OSGI service context. + * @return {@link APIApplicationServices} Dynamic Client Registration REST API Service + */ + public APIApplicationServices getApiApplicationServices() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + apiApplicationServices = (APIApplicationServices) ctx.getOSGiService(APIApplicationServices.class, null); + if (apiApplicationServices == null) { + throw new IllegalStateException("Dynamic Client Registration REST API Service was not initialized."); + } + return apiApplicationServices; + } + + public void setApiApplicationServices(APIApplicationServices apiApplicationServices) { + this.apiApplicationServices = apiApplicationServices; + } + + /** + * Retrieves the API Manager Publisher REST API Service instance from OSGI service context. + * @return {@link PublisherRESTAPIServices} API Manager Publisher REST API Service + */ + public PublisherRESTAPIServices getPublisherRESTAPIServices() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + publisherRESTAPIServices = (PublisherRESTAPIServices) ctx.getOSGiService(PublisherRESTAPIServices.class, null); + if (publisherRESTAPIServices == null) { + throw new IllegalStateException("API Manager Publisher REST API Service was not initialized."); + } + return publisherRESTAPIServices; + } + + public void setPublisherRESTAPIServices(PublisherRESTAPIServices publisherRESTAPIServices) { + this.publisherRESTAPIServices = publisherRESTAPIServices; + } } diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/TenantCreateObserver.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/TenantCreateObserver.java index 9360428b56..eb5fe919d2 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/TenantCreateObserver.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/internal/TenantCreateObserver.java @@ -17,12 +17,28 @@ */ package io.entgra.device.mgt.core.device.mgt.core.internal; +import com.google.gson.Gson; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationServices; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServices; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.constants.Constants; +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.extension.rest.api.util.APIPublisherUtils; +import io.entgra.device.mgt.core.device.mgt.common.exceptions.MetadataKeyAlreadyExistsException; +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 org.apache.axis2.context.ConfigurationContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.PrivilegedCarbonContext; import io.entgra.device.mgt.core.device.mgt.core.DeviceManagementConstants; import io.entgra.device.mgt.core.device.mgt.core.DeviceManagementConstants.User; +import org.wso2.carbon.tenant.mgt.exception.TenantManagementException; import org.wso2.carbon.user.api.AuthorizationManager; import org.wso2.carbon.user.api.Permission; import org.wso2.carbon.user.api.UserRealm; @@ -30,12 +46,20 @@ import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreManager; import org.wso2.carbon.utils.AbstractAxis2ConfigurationContextObserver; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Load configuration files to tenant's registry. */ public class TenantCreateObserver extends AbstractAxis2ConfigurationContextObserver { private static final Log log = LogFactory.getLog(TenantCreateObserver.class); + private String msg = null; /** * Create configuration context. @@ -82,6 +106,19 @@ public class TenantCreateObserver extends AbstractAxis2ConfigurationContextObser userStoreManager.updateRoleListOfUser(tenantAdminName, null, new String[] {DeviceManagementConstants.User.DEFAULT_DEVICE_ADMIN, DeviceManagementConstants.User.DEFAULT_DEVICE_USER}); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + publishScopesToTenant(tenantDomain); + } catch (TenantManagementException e) { + log.error("Error occurred while generating API application for the tenant: " + tenantDomain + "."); + } + } + }); + thread.start(); + if (log.isDebugEnabled()) { log.debug("Device management roles: " + User.DEFAULT_DEVICE_USER + ", " + User.DEFAULT_DEVICE_ADMIN + " created for the tenant:" + tenantDomain + "." @@ -94,4 +131,308 @@ public class TenantCreateObserver extends AbstractAxis2ConfigurationContextObser log.error("Error occurred while creating roles for the tenant: " + tenantDomain + "."); } } + + /** + * This method will create OAuth application under the given tenant domain and generate an access token against the + * client credentials. Once this access token is generated it will then be used to retrieve all the scopes that are already + * published to that tenant space. The scopes of the super tenant will also be retrieved in order to compare which scopes were added + * or removed. (A temporary admin user will be created in the sub tenant space to publish the scopes and will be deleted once + * the scope publishing task is done) + * @param tenantDomain tenant domain that the scopes will be published to. + * @throws TenantManagementException if there are any errors when publishing scopes to a tenant + */ + private void publishScopesToTenant(String tenantDomain) throws TenantManagementException { + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + + MetadataManagementService metadataManagementService = DeviceManagementDataHolder.getInstance().getMetadataManagementService(); + + Map superTenantPermScopeMapping = getPermScopeMapping(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + Map subTenantPermScopeMapping = getPermScopeMapping(tenantDomain); + + if (superTenantPermScopeMapping == null) { + msg = "Error occurred while retrieving meta key '" + Constants.PERM_SCOPE_MAPPING_META_KEY + "' for tenant '" + + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME + "'. Hence aborting publishing scopes to tenant: '" + + tenantDomain + "'."; + log.error(msg); + throw new TenantManagementException(msg); + } + if (superTenantPermScopeMapping.equals(subTenantPermScopeMapping)) { + if (log.isDebugEnabled()) { + log.debug( "Scopes in '" + tenantDomain + "' are up to date with super tenant scopes."); + } + return; + } + + APIApplicationServices apiApplicationServices = DeviceManagementDataHolder.getInstance().getApiApplicationServices(); + APIApplicationKey apiApplicationKey; + AccessTokenInfo accessTokenInfo; + + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); + + APIPublisherUtils.createScopePublishUserIfNotExists(tenantDomain); + apiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); + accessTokenInfo = apiApplicationServices.generateAccessTokenFromRegisteredApplication( + apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret()); + } catch (APIServicesException e) { + msg = "Error occurred while generating the API application for tenant: '" + tenantDomain + "'."; + log.error(msg, e); + throw new TenantManagementException(msg, e); + } + + try { + PublisherRESTAPIServices publisherRESTAPIServices = DeviceManagementDataHolder.getInstance().getPublisherRESTAPIServices(); + Scope[] superTenantScopes = getAllScopesFromSuperTenant(apiApplicationServices, publisherRESTAPIServices); + + if (superTenantScopes != null) { + if (log.isDebugEnabled()) { + log.debug("Number of super tenant scopes already published - " + superTenantScopes.length); + } + + Scope[] subTenantScopes = publisherRESTAPIServices.getScopes(apiApplicationKey, accessTokenInfo); + + if (subTenantScopes.length > 0) { + // If there is already existing scopes on the sub tenant space then do a comparison with the + // super tenant scopes to add those new scopes to sub tenant space or to delete them from + // sub tenant space if it is not existing on the super tenant scope list. + + if (log.isDebugEnabled()) { + log.debug("Number of sub tenant scopes already published - " + subTenantScopes.length); + } + + List missingScopes = new ArrayList<>(); + List deletedScopes = new ArrayList<>(); + + for (Scope superTenantScope : superTenantScopes) { + boolean isMatchingScope = false; + for (Scope subTenantScope : subTenantScopes) { + if (superTenantScope.getName().equals(subTenantScope.getName())) { + isMatchingScope = true; + break; + } + } + if (!isMatchingScope) { + if (log.isDebugEnabled()) { + log.debug("Missing scope found in sub tenant space - " + + superTenantScope.getName()); + } + missingScopes.add(superTenantScope); + } + } + + if (log.isDebugEnabled()) { + log.debug("Total number of missing scopes found in sub tenant space - " + + missingScopes.size()); + } + + if (missingScopes.size() > 0) { + if (log.isDebugEnabled()) { + log.debug("Starting to add new/updated shared scopes to the tenant: '" + tenantDomain + "'."); + } + publishSharedScopes(missingScopes, publisherRESTAPIServices, apiApplicationKey, + accessTokenInfo); + } + + for (Scope subTenantScope : subTenantScopes) { + boolean isMatchingScope = false; + for (Scope superTenantScope : superTenantScopes) { + if (superTenantScope.getName().equals(subTenantScope.getName())) { + isMatchingScope = true; + break; + } + } + if (!isMatchingScope) { + if (log.isDebugEnabled()) { + log.debug("Deleted scope found in sub tenant space - " + + subTenantScope.getName()); + } + deletedScopes.add(subTenantScope); + } + } + + if (log.isDebugEnabled()) { + log.debug("Total number of deleted scopes found in sub tenant space - " + + deletedScopes.size()); + } + + if (deletedScopes.size() > 0) { + if (log.isDebugEnabled()) { + log.debug("Starting to delete shared scopes from the tenant: '" + tenantDomain + "'."); + } + for (Scope deletedScope : deletedScopes) { + if (publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, + deletedScope.getName())) { + Scope scope = createScopeObject(deletedScope); + publisherRESTAPIServices.deleteSharedScope(apiApplicationKey, accessTokenInfo, scope); + } + } + } + + if (missingScopes.size() > 0 || deletedScopes.size() > 0) { + updatePermScopeMetaData(superTenantPermScopeMapping, metadataManagementService); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Starting to publish shared scopes to newly created tenant: '" + tenantDomain + "'."); + } + + publishSharedScopes(Arrays.asList(superTenantScopes), publisherRESTAPIServices, + apiApplicationKey, accessTokenInfo); + updatePermScopeMetaData(superTenantPermScopeMapping, metadataManagementService); + } + } else { + msg = "Unable to publish scopes to sub tenants due to super tenant scopes list being empty."; + log.error(msg); + throw new TenantManagementException(msg); + } + } catch (BadRequestException e) { + msg = "Invalid request sent when publishing scopes to '" + tenantDomain + "' tenant space."; + log.error(msg, e); + throw new TenantManagementException(msg, e); + } catch (UnexpectedResponseException e) { + msg = "Unexpected response received when publishing scopes to '" + tenantDomain + "' tenant space."; + log.error(msg, e); + throw new TenantManagementException(msg, e); + } catch (APIServicesException e) { + msg = "Error occurred while publishing scopes to '" + tenantDomain + "' tenant space."; + log.error(msg, e); + throw new TenantManagementException(msg, e); + } catch (MetadataManagementException e) { + msg = "Error occurred trying to create metadata entry '" + Constants.PERM_SCOPE_MAPPING_META_KEY + "'."; + log.error(msg); + throw new TenantManagementException(msg); + } catch (MetadataKeyAlreadyExistsException e) { + msg = "Error occurred trying to create metadata entry '" + Constants.PERM_SCOPE_MAPPING_META_KEY + "'. The meta key " + + "already exists."; + log.error(msg); + throw new TenantManagementException(msg); + } finally { + APIPublisherUtils.removeScopePublishUserIfExists(tenantDomain); + PrivilegedCarbonContext.endTenantFlow(); + } + } + } + + /** + * This method will retrieve the value of the permission scope mapping meta key stored in each tenant's metadata + * @param tenantDomain the tenant domain that the permission scope mapping meta value retrieved from + * @return {@link Map} containing the permission key and the scope value + * @throws TenantManagementException if there is an error while retrieving permission scope metadata + */ + private Map getPermScopeMapping(String tenantDomain) throws TenantManagementException { + if (log.isDebugEnabled()) { + log.debug("Retrieving permission scope mapping from metadata from the tenant: '" + tenantDomain + "'."); + } + Map permScopeMapping = null; + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); + MetadataManagementService metadataManagementService = DeviceManagementDataHolder.getInstance().getMetadataManagementService(); + Metadata metadata = metadataManagementService.retrieveMetadata(Constants.PERM_SCOPE_MAPPING_META_KEY); + if (metadata != null) { + permScopeMapping = new Gson().fromJson(metadata.getMetaValue().toString(), HashMap.class); + } + } catch (MetadataManagementException e) { + msg = "Error occurred while retrieving permission scope mapping from metadata for tenant: '" + tenantDomain + "'."; + log.error(msg, e); + throw new TenantManagementException(msg, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + return permScopeMapping; + } + + /** + * This method will create a new metadata entry or update the existing metadata entry in the sub tenant metadata repository which is + * taken from the super tenant metadata + * @param superTenantPermScopeMapping {@link Map} containing the permission key and the scope value of the super tenant + * @param metadataManagementService {@link MetadataManagementService} instance + * @throws MetadataManagementException if there is an error while creating or updating the metadata entry + * @throws MetadataKeyAlreadyExistsException if the metadata key already exists while trying to create a new metadata entry + */ + private void updatePermScopeMetaData(Map superTenantPermScopeMapping, + MetadataManagementService metadataManagementService) throws MetadataManagementException, + MetadataKeyAlreadyExistsException { + + Metadata newMetaData = new Metadata(); + newMetaData.setMetaKey(Constants.PERM_SCOPE_MAPPING_META_KEY); + newMetaData.setMetaValue(new Gson().toJson(superTenantPermScopeMapping)); + if (metadataManagementService.retrieveMetadata(Constants.PERM_SCOPE_MAPPING_META_KEY) == null) { + metadataManagementService.createMetadata(newMetaData); + } else { + metadataManagementService.updateMetadata(newMetaData); + } + } + + /** + * Get all the scopes from the super tenant space + * @param apiApplicationServices {@link APIApplicationServices} is used to create an OAuth application and retrieve client ID and secret + * @param publisherRESTAPIServices {@link PublisherRESTAPIServices} is used to get all scopes under a given tenant using client credentials + * @return array of {@link Scope} + * @throws BadRequestException if an invalid request is sent to the API Manager Publisher REST API Service + * @throws UnexpectedResponseException if an unexpected response is received from the API Manager Publisher REST API Service + * @throws TenantManagementException if an error occurred while processing the request sent to API Manager Publisher REST API Service + */ + private Scope[] getAllScopesFromSuperTenant(APIApplicationServices apiApplicationServices, + PublisherRESTAPIServices publisherRESTAPIServices) throws BadRequestException, + UnexpectedResponseException, TenantManagementException { + + try { + // Get all scopes of super tenant to compare later with the sub tenant scopes. This is done + // in order to see if any new scopes were added or deleted + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true); + APIApplicationKey superTenantApiApplicationKey = apiApplicationServices.createAndRetrieveApplicationCredentials(); + AccessTokenInfo superTenantAccessToken = apiApplicationServices.generateAccessTokenFromRegisteredApplication( + superTenantApiApplicationKey.getClientId(), superTenantApiApplicationKey.getClientSecret()); + return publisherRESTAPIServices.getScopes(superTenantApiApplicationKey, superTenantAccessToken); + } catch (APIServicesException e) { + msg = "Error occurred while retrieving access token from super tenant"; + log.error(msg, e); + throw new TenantManagementException(msg, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Add shared scopes to the tenant space. + * @param scopeList {@link List} of {@link Scope} + * @param publisherRESTAPIServices {@link PublisherRESTAPIServices} is used to add shared scopes to a given tenant using client credentials + * @param apiApplicationKey {@link APIApplicationKey} contains client credentials of the OAuth application + * @param accessTokenInfo {@link AccessTokenInfo} contains token information generated from the client credentials + * @throws BadRequestException if an invalid request is sent to the API Manager Publisher REST API Service + * @throws UnexpectedResponseException if an unexpected response is received from the API Manager Publisher REST API Service + * @throws APIServicesException if an error occurred while processing the request sent to API Manager Publisher REST API Service + */ + private void publishSharedScopes (List scopeList, PublisherRESTAPIServices publisherRESTAPIServices, + APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo) + throws BadRequestException, UnexpectedResponseException, APIServicesException { + + for (Scope tenantScope : scopeList) { + if (!publisherRESTAPIServices.isSharedScopeNameExists(apiApplicationKey, accessTokenInfo, + tenantScope.getName())) { + Scope scope = createScopeObject(tenantScope); + publisherRESTAPIServices.addNewSharedScope(apiApplicationKey, accessTokenInfo, scope); + } + } + } + + /** + * Creates a new scope object from the passed scope which includes the id, display name, description, name and bindings. + * @param tenantScope existing {@link Scope} from a tenant + * @return {@link Scope} + */ + private Scope createScopeObject (Scope tenantScope) { + Scope scope = new Scope(); + scope.setId(tenantScope.getId()); + scope.setDisplayName(tenantScope.getDisplayName()); + scope.setDescription(tenantScope.getDescription()); + scope.setName(tenantScope.getName()); + List bindings = new ArrayList<>(); + bindings.add(Constants.ADMIN_ROLE_KEY); + scope.setBindings(bindings); + return scope; + } } \ No newline at end of file