diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.annotations/src/main/java/org/wso2/carbon/apimgt/annotations/api/Permission.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.annotations/src/main/java/org/wso2/carbon/apimgt/annotations/api/Permission.java new file mode 100644 index 00000000000..65ade5cc3aa --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.annotations/src/main/java/org/wso2/carbon/apimgt/annotations/api/Permission.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.apimgt.annotations.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This class is the representation of custom developed Permission annotation. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Permission { + + /** + * Represents the scope name. + * @return Returns scope name. + */ + String scope(); + + /** + * Represents the associated permissions. + * @return Returns list of permissions. + */ + String[] permissions(); + +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java index 3ce653c6c87..fcb27541dbe 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java @@ -18,16 +18,17 @@ */ package org.wso2.carbon.apimgt.webapp.publisher; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.apimgt.api.APIManagementException; import org.wso2.carbon.apimgt.api.APIProvider; import org.wso2.carbon.apimgt.api.FaultGatewaysException; -import org.wso2.carbon.apimgt.api.model.API; -import org.wso2.carbon.apimgt.api.model.APIIdentifier; -import org.wso2.carbon.apimgt.api.model.APIStatus; -import org.wso2.carbon.apimgt.api.model.URITemplate; +import org.wso2.carbon.apimgt.api.model.*; import org.wso2.carbon.apimgt.impl.APIManagerFactory; import org.wso2.carbon.apimgt.webapp.publisher.internal.APIPublisherDataHolder; import org.wso2.carbon.context.PrivilegedCarbonContext; @@ -38,10 +39,7 @@ import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import javax.xml.stream.XMLStreamException; import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; /** * This class represents the concrete implementation of the APIPublisherService that corresponds to providing all @@ -50,96 +48,123 @@ import java.util.Map; public class APIPublisherServiceImpl implements APIPublisherService { private static final Log log = LogFactory.getLog(APIPublisherServiceImpl.class); - private static final String PUBLISH_ACTION = "Publish"; - - @Override - public void publishAPI(final API api) throws APIManagementException, FaultGatewaysException { - String tenantDomain = MultitenantUtils.getTenantDomain(api.getApiOwner()); - PrivilegedCarbonContext.startTenantFlow(); - PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); - try { - int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); - // Below code snippet is added to load API Lifecycle in tenant mode. - RegistryService registryService = APIPublisherDataHolder.getInstance().getRegistryService(); - CommonUtil.addDefaultLifecyclesIfNotAvailable(registryService.getConfigSystemRegistry(tenantId), - CommonUtil.getRootSystemRegistry(tenantId)); - APIProvider provider = APIManagerFactory.getInstance().getAPIProvider(api.getApiOwner()); - MultitenantUtils.getTenantDomain(api.getApiOwner()); - if (provider != null) { - if (provider.isDuplicateContextTemplate(api.getContext())) { - throw new APIManagementException( - "Error occurred while adding the API. A duplicate API" + - " context already exists for " + api.getContext()); - } - if (!provider.isAPIAvailable(api.getId())) { - provider.addAPI(api); - provider.changeLifeCycleStatus(api.getId(), PUBLISH_ACTION); - if (log.isDebugEnabled()) { - log.debug("Successfully published API '" + api.getId().getApiName() + - "' with context '" + api.getContext() + "' and version '" - + api.getId().getVersion() + "'"); - } - } else { - api.setStatus(APIStatus.PUBLISHED); - provider.updateAPI(api); - if (log.isDebugEnabled()) { - log.debug("An API already exists with the name '" + api.getId().getApiName() + - "', context '" + api.getContext() + "' and version '" - + api.getId().getVersion() + "'. Thus, the API config is updated"); - } - } - provider.saveSwagger20Definition(api.getId(), createSwaggerDefinition(api)); - } else { - throw new APIManagementException("API provider configured for the given API configuration " + - "is null. Thus, the API is not published"); - } - } catch (FileNotFoundException e) { - throw new APIManagementException("Failed to retrieve life cycle file ", e); - } catch (RegistryException e) { - throw new APIManagementException("Failed to access the registry ", e); - } catch (XMLStreamException e) { - throw new APIManagementException("Failed parsing the lifecycle xml.", e); - } finally { - PrivilegedCarbonContext.endTenantFlow(); - } - } - - private String createSwaggerDefinition(API api) { - Map httpVerbsMap = new HashMap<>(); - - for (URITemplate uriTemplate : api.getUriTemplates()) { - JsonObject response = new JsonObject(); - response.addProperty("200", ""); - - JsonObject responses = new JsonObject(); - responses.add("responses", response); - JsonObject httpVerbs = httpVerbsMap.get(uriTemplate.getUriTemplate()); - if (httpVerbs == null) { - httpVerbs = new JsonObject(); - } - httpVerbs.add(uriTemplate.getHTTPVerb().toLowerCase(), responses); - httpVerbsMap.put(uriTemplate.getUriTemplate(), httpVerbs); - } - - Iterator it = httpVerbsMap.entrySet().iterator(); - JsonObject paths = new JsonObject(); - while (it.hasNext()) { - Map.Entry pair = (Map.Entry) it.next(); - paths.add(pair.getKey(), pair.getValue()); - it.remove(); - } - - JsonObject info = new JsonObject(); - info.addProperty("title", api.getId().getApiName()); - info.addProperty("version", api.getId().getVersion()); - - JsonObject swaggerDefinition = new JsonObject(); - swaggerDefinition.add("paths", paths); - swaggerDefinition.addProperty("swagger", "2.0"); - swaggerDefinition.add("info", info); - - return swaggerDefinition.toString(); - } + private static final String PUBLISH_ACTION = "Publish"; + + @Override + public void publishAPI(final API api) throws APIManagementException, FaultGatewaysException { + String tenantDomain = MultitenantUtils.getTenantDomain(api.getApiOwner()); + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); + try { + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + // Below code snippet is added to load API Lifecycle in tenant mode. + RegistryService registryService = APIPublisherDataHolder.getInstance().getRegistryService(); + CommonUtil.addDefaultLifecyclesIfNotAvailable(registryService.getConfigSystemRegistry(tenantId), + CommonUtil.getRootSystemRegistry(tenantId)); + APIProvider provider = APIManagerFactory.getInstance().getAPIProvider(api.getApiOwner()); + MultitenantUtils.getTenantDomain(api.getApiOwner()); + if (provider != null) { + if (provider.isDuplicateContextTemplate(api.getContext())) { + throw new APIManagementException( + "Error occurred while adding the API. A duplicate API" + + " context already exists for " + api.getContext()); + } + if (!provider.isAPIAvailable(api.getId())) { + provider.addAPI(api); + provider.changeLifeCycleStatus(api.getId(), PUBLISH_ACTION); + if (log.isDebugEnabled()) { + log.debug("Successfully published API '" + api.getId().getApiName() + + "' with context '" + api.getContext() + "' and version '" + + api.getId().getVersion() + "'"); + } + } else { + api.setStatus(provider.getAPI(api.getId()).getStatus()); + provider.updateAPI(api); + if (log.isDebugEnabled()) { + log.debug("An API already exists with the name '" + api.getId().getApiName() + + "', context '" + api.getContext() + "' and version '" + + api.getId().getVersion() + "'. Thus, the API config is updated"); + } + } + provider.saveSwagger20Definition(api.getId(), createSwaggerDefinition(api)); + } else { + throw new APIManagementException("API provider configured for the given API configuration " + + "is null. Thus, the API is not published"); + } + } catch (FileNotFoundException e) { + throw new APIManagementException("Failed to retrieve life cycle file ", e); + } catch (RegistryException e) { + throw new APIManagementException("Failed to access the registry ", e); + } catch (XMLStreamException e) { + throw new APIManagementException("Failed parsing the lifecycle xml.", e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + private String createSwaggerDefinition(API api) { + Map httpVerbsMap = new HashMap<>(); + List scopes = new ArrayList<>(); + + for (URITemplate uriTemplate : api.getUriTemplates()) { + JsonObject response = new JsonObject(); + response.addProperty("200", ""); + + JsonObject responses = new JsonObject(); + responses.add("responses", response); + JsonObject httpVerbs = httpVerbsMap.get(uriTemplate.getUriTemplate()); + if (httpVerbs == null) { + httpVerbs = new JsonObject(); + } + JsonObject httpVerb = new JsonObject(); + httpVerb.add("responses", response); + + httpVerb.addProperty("x-auth-type", "Application%20%26%20Application%20User"); + httpVerb.addProperty("x-throttling-tier", "Unlimited"); + if (uriTemplate.getScope() != null) { + httpVerb.addProperty("x-scope", uriTemplate.getScope().getName()); + scopes.add(uriTemplate.getScope()); + } + httpVerbs.add(uriTemplate.getHTTPVerb().toLowerCase(), httpVerb); + httpVerbsMap.put(uriTemplate.getUriTemplate(), httpVerbs); + } + + Iterator it = httpVerbsMap.entrySet().iterator(); + JsonObject paths = new JsonObject(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + paths.add(pair.getKey(), pair.getValue()); + it.remove(); + } + + JsonObject info = new JsonObject(); + info.addProperty("title", api.getId().getApiName()); + info.addProperty("version", api.getId().getVersion()); + + JsonObject swaggerDefinition = new JsonObject(); + swaggerDefinition.add("paths", paths); + swaggerDefinition.addProperty("swagger", "2.0"); + swaggerDefinition.add("info", info); + + // adding scopes to swagger definition + if (!api.getScopes().isEmpty()) { + Gson gson = new Gson(); + JsonElement element = gson.toJsonTree(api.getScopes(), new TypeToken>() { + }.getType()); + if (element != null) { + JsonArray apiScopes = element.getAsJsonArray(); + JsonObject apim = new JsonObject(); + apim.add("x-wso2-scopes", apiScopes); + JsonObject wso2Security = new JsonObject(); + wso2Security.add("apim", apim); + swaggerDefinition.add("x-wso2-security", wso2Security); + } + } + if (log.isDebugEnabled()) { + log.debug("API swagger definition: " + swaggerDefinition.toString()); + } + return swaggerDefinition.toString(); + } @Override public void removeAPI(APIIdentifier id) throws APIManagementException { @@ -169,4 +194,5 @@ public class APIPublisherServiceImpl implements APIPublisherService { log.debug("End of publishing the batch of APIs"); } } + } diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherUtil.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherUtil.java index 877f6bb7885..2e7c8caac7c 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherUtil.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherUtil.java @@ -38,38 +38,17 @@ import java.util.*; public class APIPublisherUtil { private static final Log log = LogFactory.getLog(APIPublisherUtil.class); - private static final String DEFAULT_API_VERSION = "1.0.0"; public static final String API_VERSION_PARAM = "{version}"; - public static final String API_PUBLISH_ENVIRONEMENT = "Production and Sandbox"; - + public static final String API_PUBLISH_ENVIRONMENT = "Production and Sandbox"; private static final String API_CONFIG_DEFAULT_VERSION = "1.0.0"; - - private static final String PARAM_MANAGED_API_NAME = "managed-api-name"; - private static final String PARAM_MANAGED_API_VERSION = "managed-api-version"; - private static final String PARAM_MANAGED_API_CONTEXT = "managed-api-context"; private static final String PARAM_MANAGED_API_ENDPOINT = "managed-api-endpoint"; private static final String PARAM_MANAGED_API_OWNER = "managed-api-owner"; private static final String PARAM_MANAGED_API_TRANSPORTS = "managed-api-transports"; private static final String PARAM_MANAGED_API_IS_SECURED = "managed-api-isSecured"; - private static final String PARAM_MANAGED_API_APPLICATION = "managed-api-application"; private static final String PARAM_SHARED_WITH_ALL_TENANTS = "isSharedWithAllTenants"; private static final String PARAM_PROVIDER_TENANT_DOMAIN = "providerTenantDomain"; - enum HTTPMethod { - GET, POST, DELETE, PUT, OPTIONS - } - - private static List httpMethods; - - static { - httpMethods = new ArrayList(5); - httpMethods.add(HTTPMethod.GET); - httpMethods.add(HTTPMethod.POST); - httpMethods.add(HTTPMethod.DELETE); - httpMethods.add(HTTPMethod.PUT); - httpMethods.add(HTTPMethod.OPTIONS); - } public static API getAPI(APIConfig config) throws APIManagementException { @@ -100,13 +79,11 @@ public class APIPublisherUtil { api.setStatus(APIStatus.CREATED); api.setTransports(config.getTransports()); api.setContextTemplate(config.getContextTemplate()); - api.setUriTemplates(config.getUriTemplates()); - Set environments = new HashSet<>(); - environments.add(API_PUBLISH_ENVIRONEMENT); + environments.add(API_PUBLISH_ENVIRONMENT); api.setEnvironments(environments); + Set tiers = new HashSet<>(); - Set tiers = new HashSet(); tiers.add(new Tier(APIConstants.UNLIMITED_TIER)); api.addAvailableTiers(tiers); @@ -132,35 +109,37 @@ public class APIPublisherUtil { Set tags = new HashSet<>(Arrays.asList(config.getTags())); api.addTags(tags); } - return api; - } - private static Set getURITemplates(String endpoint, String authType) { - Set uriTemplates = new LinkedHashSet(); - if (APIConstants.AUTH_NO_AUTHENTICATION.equals(authType)) { - for (HTTPMethod method : httpMethods) { - URITemplate template = new URITemplate(); - template.setAuthType(APIConstants.AUTH_NO_AUTHENTICATION); - template.setHTTPVerb(method.toString()); - template.setResourceURI(endpoint); - template.setUriTemplate("/*"); - uriTemplates.add(template); + // adding scopes to the api + Set uriTemplates = config.getUriTemplates(); + Map apiScopes = new HashMap<>(); + + if (uriTemplates != null) { + // this creates distinct scopes list + for (URITemplate template : uriTemplates) { + Scope scope = template.getScope(); + if (scope != null) { + if (apiScopes.get(scope.getKey()) == null) { + apiScopes.put(scope.getKey(), scope); + } + } } - } else { - for (HTTPMethod method : httpMethods) { - URITemplate template = new URITemplate(); - if (HTTPMethod.OPTIONS.equals(method)) { - template.setAuthType(APIConstants.AUTH_NO_AUTHENTICATION); - } else { - template.setAuthType(APIConstants.AUTH_APPLICATION_OR_USER_LEVEL_TOKEN); + Set scopes = new HashSet<>(apiScopes.values()); + api.setScopes(scopes); + + // this has to be done because of the use of pass by reference + // where same object reference of scope should be available for both + // api scope and uri template scope + for (Scope scope : scopes) { + for (URITemplate template : uriTemplates) { + if (scope.getKey().equals(template.getScope().getKey())) { + template.setScope(scope); + } } - template.setHTTPVerb(method.toString()); - template.setResourceURI(endpoint); - template.setUriTemplate("/*"); - uriTemplates.add(template); } + api.setUriTemplates(uriTemplates); } - return uriTemplates; + return api; } public static String getServerBaseUrl() { @@ -225,12 +204,13 @@ public class APIPublisherUtil { * Build the API Configuration to be passed to APIM, from a given list of URL templates * * @param servletContext + * @param apiDef * @return */ - public static APIConfig buildApiConfig(ServletContext servletContext, APIResourceConfiguration apidef) { + public static APIConfig buildApiConfig(ServletContext servletContext, APIResourceConfiguration apiDef) { APIConfig apiConfig = new APIConfig(); - String name = apidef.getName(); + String name = apiDef.getName(); if (name == null || name.isEmpty()) { if (log.isDebugEnabled()) { log.debug("API Name not set in @API Annotation"); @@ -239,7 +219,7 @@ public class APIPublisherUtil { } apiConfig.setName(name); - String version = apidef.getVersion(); + String version = apiDef.getVersion(); if (version == null || version.isEmpty()) { if (log.isDebugEnabled()) { log.debug("'API Version not set in @API Annotation'"); @@ -249,7 +229,7 @@ public class APIPublisherUtil { apiConfig.setVersion(version); - String context = apidef.getContext(); + String context = apiDef.getContext(); if (context == null || context.isEmpty()) { if (log.isDebugEnabled()) { log.debug("'API Context not set in @API Annotation'"); @@ -258,7 +238,7 @@ public class APIPublisherUtil { } apiConfig.setContext(context); - String[] tags = apidef.getTags(); + String[] tags = apiDef.getTags(); if (tags == null || tags.length == 0) { if (log.isDebugEnabled()) { log.debug("'API tag not set in @API Annotation'"); @@ -323,13 +303,14 @@ public class APIPublisherUtil { && Boolean.parseBoolean(sharingValueParam)); apiConfig.setSharedWithAllTenants(isSharedWithAllTenants); - Set uriTemplates = new LinkedHashSet(); - for (APIResource apiResource : apidef.getResources()) { + Set uriTemplates = new LinkedHashSet<>(); + for (APIResource apiResource : apiDef.getResources()) { URITemplate template = new URITemplate(); template.setAuthType(apiResource.getAuthType()); template.setHTTPVerb(apiResource.getHttpVerb()); template.setResourceURI(apiResource.getUri()); template.setUriTemplate(apiResource.getUriTemplate()); + template.setScope(apiResource.getScope()); uriTemplates.add(template); } apiConfig.setUriTemplates(uriTemplates); diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/APIResource.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/APIResource.java index 2f28fbe6a53..577f4efecb9 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/APIResource.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/APIResource.java @@ -18,70 +18,71 @@ package org.wso2.carbon.apimgt.webapp.publisher.config; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "Resource") -public class APIResource{ - - private String AuthType; - private String HttpVerb; - private String Uri; - private String UriTemplate; - private String consumes; - private String produces; - - public String getAuthType() { - return AuthType; - } - - @XmlElement(name = "AuthType", required = true) - public void setAuthType(String authType) { - AuthType = authType; - } - - public String getHttpVerb() { - return HttpVerb; - } - - @XmlElement(name = "HttpVerb", required = true) - public void setHttpVerb(String httpVerb) { - HttpVerb = httpVerb; - } - - public String getUri() { - return Uri; - } - - @XmlElement(name = "Uri", required = true) - public void setUri(String uri) { - Uri = uri; - } - - public String getUriTemplate() { - return UriTemplate; - } - - @XmlElement(name = "UriTemplate", required = true) - public void setUriTemplate(String uriTemplate) { - UriTemplate = uriTemplate; - } - - public String getConsumes() { - return consumes; - } - - @XmlElement(name = "Consumes", required = true) - public void setConsumes(String consumes) { - this.consumes = consumes; - } - - public String getProduces() { - return produces; - } - - @XmlElement(name = "Produces", required = true) - public void setProduces(String produces) { - this.produces = produces; - } +import org.wso2.carbon.apimgt.api.model.Scope; + +public class APIResource { + + private String AuthType; + private String HttpVerb; + private String Uri; + private String UriTemplate; + private String consumes; + private String produces; + private Scope scope; + + public String getAuthType() { + return AuthType; + } + + public void setAuthType(String authType) { + AuthType = authType; + } + + public String getHttpVerb() { + return HttpVerb; + } + + public void setHttpVerb(String httpVerb) { + HttpVerb = httpVerb; + } + + public String getUri() { + return Uri; + } + + public void setUri(String uri) { + Uri = uri; + } + + public String getUriTemplate() { + return UriTemplate; + } + + public void setUriTemplate(String uriTemplate) { + UriTemplate = uriTemplate; + } + + public String getConsumes() { + return consumes; + } + + public void setConsumes(String consumes) { + this.consumes = consumes; + } + + public String getProduces() { + return produces; + } + + public void setProduces(String produces) { + this.produces = produces; + } + + public Scope getScope() { + return scope; + } + + public void setScope(Scope scope) { + this.scope = scope; + } } diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/PermissionConfiguration.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/PermissionConfiguration.java new file mode 100644 index 00000000000..7d16d198b94 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/PermissionConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.apimgt.webapp.publisher.config; + +/** + * This class represents the information related to permissions. + */ +public class PermissionConfiguration { + + private String scopeName; + private String[] permissions; + + public String getScopeName() { + return scopeName; + } + + public void setScopeName(String scope) { + this.scopeName = scope; + } + + public String[] getPermissions() { + return permissions; + } + + public void setPermissions(String[] permissions) { + this.permissions = permissions; + } + +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/PermissionManagementException.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/PermissionManagementException.java new file mode 100644 index 00000000000..651c2857231 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/config/PermissionManagementException.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.apimgt.webapp.publisher.config; + +/** + * Custom exception class of Permission related operations. + */ +public class PermissionManagementException extends Exception { + + private static final long serialVersionUID = -3151279311929070298L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public PermissionManagementException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public PermissionManagementException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public PermissionManagementException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public PermissionManagementException() { + super(); + } + + public PermissionManagementException(Throwable cause) { + super(cause); + } + +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/listener/APIPublisherLifecycleListener.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/listener/APIPublisherLifecycleListener.java index 4c3d467155d..f3ecf346207 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/listener/APIPublisherLifecycleListener.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/listener/APIPublisherLifecycleListener.java @@ -24,8 +24,10 @@ import org.apache.catalina.LifecycleListener; import org.apache.catalina.core.StandardContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.apimgt.api.model.*; -import org.wso2.carbon.apimgt.webapp.publisher.*; +import org.wso2.carbon.apimgt.api.model.API; +import org.wso2.carbon.apimgt.webapp.publisher.APIConfig; +import org.wso2.carbon.apimgt.webapp.publisher.APIPublisherService; +import org.wso2.carbon.apimgt.webapp.publisher.APIPublisherUtil; import org.wso2.carbon.apimgt.webapp.publisher.config.APIResourceConfiguration; import org.wso2.carbon.apimgt.webapp.publisher.internal.APIPublisherDataHolder; import org.wso2.carbon.apimgt.webapp.publisher.lifecycle.util.AnnotationUtil; @@ -39,7 +41,6 @@ import java.util.Set; public class APIPublisherLifecycleListener implements LifecycleListener { private static final Log log = LogFactory.getLog(APIPublisherLifecycleListener.class); - private static final String PARAM_MANAGED_API_ENABLED = "managed-api-enabled"; @Override @@ -55,6 +56,7 @@ public class APIPublisherLifecycleListener implements LifecycleListener { AnnotationUtil annotationUtil = new AnnotationUtil(context); Set annotatedAPIClasses = annotationUtil. scanStandardContext(org.wso2.carbon.apimgt.annotations.api.API.class.getName()); + List apiDefinitions = annotationUtil.extractAPIInfo(servletContext, annotatedAPIClasses); diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/AnnotationUtil.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/AnnotationUtil.java index 8120a9d60e0..4974a54c0eb 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/AnnotationUtil.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/AnnotationUtil.java @@ -20,13 +20,18 @@ package org.wso2.carbon.apimgt.webapp.publisher.lifecycle.util; import org.apache.catalina.core.StandardContext; import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.scannotation.AnnotationDB; import org.scannotation.WarUrlFinder; import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.apimgt.annotations.api.Permission; +import org.wso2.carbon.apimgt.api.model.Scope; import org.wso2.carbon.apimgt.webapp.publisher.config.APIResource; import org.wso2.carbon.apimgt.webapp.publisher.config.APIResourceConfiguration; +import org.wso2.carbon.apimgt.webapp.publisher.config.PermissionConfiguration; +import org.wso2.carbon.apimgt.webapp.publisher.config.PermissionManagementException; import javax.servlet.ServletContext; import javax.ws.rs.*; @@ -38,7 +43,9 @@ import java.lang.reflect.Proxy; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; public class AnnotationUtil { @@ -118,7 +125,6 @@ public class AnnotationUtil { .class.getName()); Annotation apiAnno = clazz.getAnnotation(apiClazz); - List resourceList; if (apiAnno != null) { @@ -224,7 +230,6 @@ public class AnnotationUtil { Annotation[] annotations = method.getDeclaredAnnotations(); for (int i = 0; i < annotations.length; i++) { - processHTTPMethodAnnotation(resource, annotations[i]); if (annotations[i].annotationType().getName().equals(Consumes.class.getName())) { Class consumesClass = (Class) classLoader.loadClass( @@ -240,6 +245,18 @@ public class AnnotationUtil { Annotation producesAnno = method.getAnnotation(producesClass); resource.setProduces(invokeMethod(producesClassMethods[0], producesAnno, STRING_ARR)); } + if (annotations[i].annotationType().getName().equals(Permission.class.getName())) { + PermissionConfiguration permissionConf = this.getPermission(method); + if (permissionConf != null) { + Scope scope = new Scope(); + scope.setKey(permissionConf.getScopeName()); + scope.setDescription(permissionConf.getScopeName()); + scope.setName(permissionConf.getScopeName()); + String roles = StringUtils.join(permissionConf.getPermissions(), ","); + scope.setRoles(roles); + resource.setScope(scope); + } + } } resourceList.add(resource); } @@ -314,4 +331,34 @@ public class AnnotationUtil { InvocationHandler methodHandler = Proxy.getInvocationHandler(annotation); return ((String[]) methodHandler.invoke(annotation, method, null)); } + + private PermissionConfiguration getPermission(Method currentMethod) throws Throwable { + Class permissionClass = (Class) classLoader.loadClass(Permission.class.getName()); + Annotation permissionAnnotation = currentMethod.getAnnotation(permissionClass); + if (permissionClass != null) { + Method[] permissionClassMethods = permissionClass.getMethods(); + PermissionConfiguration permissionConf = new PermissionConfiguration(); + for (Method method : permissionClassMethods) { + switch (method.getName()) { + case "scope": + permissionConf.setScopeName(invokeMethod(method, permissionAnnotation, STRING)); + break; + case "permissions": + String permissions[] = invokeMethod(method, permissionAnnotation); + this.addPermission(permissions); + permissionConf.setPermissions(permissions); + break; + } + } + return permissionConf; + } + return null; + } + + private void addPermission(String[] permissions) throws PermissionManagementException { + for (String permission : permissions) { + PermissionUtils.addPermission(permission); + } + } + } diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/PermissionUtils.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/PermissionUtils.java new file mode 100644 index 00000000000..32c5bad8557 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/lifecycle/util/PermissionUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.apimgt.webapp.publisher.lifecycle.util; + +import org.wso2.carbon.apimgt.webapp.publisher.config.PermissionManagementException; +import org.wso2.carbon.apimgt.webapp.publisher.internal.APIPublisherDataHolder; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.registry.api.RegistryException; +import org.wso2.carbon.registry.api.Resource; +import org.wso2.carbon.registry.core.Registry; + +import java.util.StringTokenizer; + +/** + * Utility class which holds necessary utility methods required for persisting permissions in + * registry. + */ +public class PermissionUtils { + + public static final String ADMIN_PERMISSION_REGISTRY_PATH = "/permission/admin"; + public static final String PERMISSION_PROPERTY_NAME = "name"; + + public static Registry getGovernanceRegistry() throws PermissionManagementException { + try { + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + return APIPublisherDataHolder.getInstance().getRegistryService() + .getGovernanceSystemRegistry( + tenantId); + } catch (RegistryException e) { + throw new PermissionManagementException( + "Error in retrieving governance registry instance: " + + e.getMessage(), e); + } + } + + public static void addPermission(String permission) throws PermissionManagementException { + String resourcePermission = getAbsolutePermissionPath(permission); + try { + StringTokenizer tokenizer = new StringTokenizer(resourcePermission, "/"); + String lastToken = "", currentToken, tempPath; + while (tokenizer.hasMoreTokens()) { + currentToken = tokenizer.nextToken(); + tempPath = lastToken + "/" + currentToken; + if (!checkResourceExists(tempPath)) { + createRegistryCollection(tempPath, currentToken); + } + lastToken = tempPath; + } + } catch (RegistryException e) { + throw new PermissionManagementException("Error occurred while persisting permission : " + + resourcePermission, e); + } + } + + public static void createRegistryCollection(String path, String resourceName) + throws PermissionManagementException, + RegistryException { + Resource resource = PermissionUtils.getGovernanceRegistry().newCollection(); + resource.addProperty(PERMISSION_PROPERTY_NAME, resourceName); + PermissionUtils.getGovernanceRegistry().beginTransaction(); + PermissionUtils.getGovernanceRegistry().put(path, resource); + PermissionUtils.getGovernanceRegistry().commitTransaction(); + } + + public static boolean checkResourceExists(String path) + throws PermissionManagementException, + org.wso2.carbon.registry.core.exceptions.RegistryException { + return PermissionUtils.getGovernanceRegistry().resourceExists(path); + } + + private static String getAbsolutePermissionPath(String permissionPath) { + return PermissionUtils.ADMIN_PERMISSION_REGISTRY_PATH + permissionPath; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/config/permission/lifecycle/WebAppDeploymentLifecycleListener.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/config/permission/lifecycle/WebAppDeploymentLifecycleListener.java index eacc7a84c84..f274eee8459 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/config/permission/lifecycle/WebAppDeploymentLifecycleListener.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/config/permission/lifecycle/WebAppDeploymentLifecycleListener.java @@ -45,23 +45,23 @@ import java.util.List; @SuppressWarnings("unused") public class WebAppDeploymentLifecycleListener implements LifecycleListener { - private static final String PERMISSION_CONFIG_PATH = "META-INF" + File.separator + "permissions.xml"; - private static final Log log = LogFactory.getLog(WebAppDeploymentLifecycleListener.class); + private static final String PERMISSION_CONFIG_PATH = "META-INF" + File.separator + "permissions.xml"; + private static final Log log = LogFactory.getLog(WebAppDeploymentLifecycleListener.class); - @Override - public void lifecycleEvent(LifecycleEvent lifecycleEvent) { - if (Lifecycle.AFTER_START_EVENT.equals(lifecycleEvent.getType())) { - StandardContext context = (StandardContext) lifecycleEvent.getLifecycle(); - ServletContext servletContext = context.getServletContext(); - String contextPath = context.getServletContext().getContextPath(); - try { - InputStream permissionStream = servletContext.getResourceAsStream(PERMISSION_CONFIG_PATH); - if (permissionStream != null) { + @Override + public void lifecycleEvent(LifecycleEvent lifecycleEvent) { + if (Lifecycle.AFTER_START_EVENT.equals(lifecycleEvent.getType())) { + StandardContext context = (StandardContext) lifecycleEvent.getLifecycle(); + ServletContext servletContext = context.getServletContext(); + String contextPath = context.getServletContext().getContextPath(); + try { + InputStream permissionStream = servletContext.getResourceAsStream(PERMISSION_CONFIG_PATH); + if (permissionStream != null) { /* Un-marshaling Device Management configuration */ - JAXBContext cdmContext = JAXBContext.newInstance(PermissionConfiguration.class); - Unmarshaller unmarshaller = cdmContext.createUnmarshaller(); - PermissionConfiguration permissionConfiguration = (PermissionConfiguration) - unmarshaller.unmarshal(permissionStream); + JAXBContext cdmContext = JAXBContext.newInstance(PermissionConfiguration.class); + Unmarshaller unmarshaller = cdmContext.createUnmarshaller(); + PermissionConfiguration permissionConfiguration = (PermissionConfiguration) + unmarshaller.unmarshal(permissionStream); List permissions = permissionConfiguration.getPermissions(); String apiVersion = permissionConfiguration.getApiVersion(); if (permissionConfiguration != null && permissions != null) { @@ -69,22 +69,22 @@ public class WebAppDeploymentLifecycleListener implements LifecycleListener { // update the permission path to absolute permission path permission.setPath(PermissionUtils.getAbsolutePermissionPath(permission.getPath())); permission.setUrl(PermissionUtils.getAbsoluteContextPathOfAPI(contextPath, apiVersion, - permission.getUrl()).toLowerCase()); + permission.getUrl()).toLowerCase()); permission.setMethod(permission.getMethod().toUpperCase()); PermissionManagerServiceImpl.getInstance().addPermission(permission); } - } - } - } catch (JAXBException e) { + } + } + } catch (JAXBException e) { log.error( "Exception occurred while parsing the permission configuration of webapp : " - + context.getServletContext().getContextPath(), e); + + context.getServletContext().getContextPath(), e); } catch (PermissionManagementException e) { log.error("Exception occurred while adding the permissions from webapp : " - + servletContext.getContextPath(), e); + + servletContext.getContextPath(), e); } } - } + } } diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml index 5c343926c1d..a40e30b63ff 100644 --- a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml @@ -17,7 +17,8 @@ ~ under the License. --> - + org.wso2.carbon.devicemgt @@ -47,8 +48,12 @@ org.wso2.carbon.user.core - org.wso2.carbon - org.wso2.carbon.user.api + org.wso2.carbon.apimgt + org.wso2.carbon.apimgt.impl + + + com.googlecode.json-simple.wso2 + json-simple @@ -89,8 +94,25 @@ org.wso2.carbon.user.api, org.wso2.carbon.user.core.service, org.wso2.carbon.identity.application.common.model, - org.wso2.carbon.identity.application.authentication.framework.model, - org.wso2.carbon.user.core.tenant + org.wso2.carbon.identity.application.authentication.framework.model, + org.wso2.carbon.user.core.tenant, + org.json.simple, + javax.cache, + javax.xml.namespace, + org.apache.axiom.om, + org.wso2.carbon.apimgt.api, + org.wso2.carbon.apimgt.impl, + org.wso2.carbon.apimgt.impl.dao, + org.wso2.carbon.apimgt.impl.utils, + org.wso2.carbon.identity.application.common.cache, + org.wso2.carbon.identity.core.util, + org.wso2.carbon.identity.oauth2.dto, + org.wso2.carbon.identity.oauth2.token, + org.wso2.carbon.identity.oauth2.token.handlers.grant, + org.wso2.carbon.user.core, + org.wso2.carbon.user.core.config, + org.wso2.carbon.user.core.util, + org.wso2.carbon.utils diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/OAuthExtUtils.java b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/OAuthExtUtils.java index aa5a73b1401..32ad46ccd14 100644 --- a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/OAuthExtUtils.java +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/OAuthExtUtils.java @@ -20,9 +20,23 @@ package org.wso2.carbon.device.mgt.oauth.extensions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.impl.APIConstants; +import org.wso2.carbon.apimgt.impl.dao.ApiMgtDAO; +import org.wso2.carbon.apimgt.impl.utils.APIUtil; import org.wso2.carbon.device.mgt.oauth.extensions.internal.OAuthExtensionsDataHolder; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; import org.wso2.carbon.user.api.TenantManager; +import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.core.service.RealmService; + +import javax.cache.Caching; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; /** * This class holds util methods used by OAuth extension bundle. @@ -30,7 +44,16 @@ import org.wso2.carbon.user.api.UserStoreException; public class OAuthExtUtils { private static final Log log = LogFactory.getLog(OAuthExtUtils.class); + private static final String DEFAULT_SCOPE_NAME = "default"; + private static final String UI_EXECUTE = "ui.execute"; + private static final String REST_API_SCOPE_CACHE = "REST_API_SCOPE_CACHE"; + /** + * This method is used to get the tenant id when given tenant domain. + * + * @param tenantDomain Tenant domain name. + * @return Returns the tenant id. + */ public static int getTenantId(String tenantDomain) { int tenantId = 0; if (tenantDomain != null) { @@ -39,10 +62,202 @@ public class OAuthExtUtils { tenantId = tenantManager.getTenantId(tenantDomain); } catch (UserStoreException e) { String errorMsg = "Error when getting the tenant id from the tenant domain : " + - tenantDomain; + tenantDomain; log.error(errorMsg, e); } } return tenantId; } + + /** + * This method is used to set scopes that are authorized to the OAuth token request message context. + * + * @param tokReqMsgCtx OAuth token request message context + * @return Returns true if success. + */ + public static boolean setScopes(OAuthTokenReqMessageContext tokReqMsgCtx) { + String[] requestedScopes = tokReqMsgCtx.getScope(); + String[] defaultScope = new String[]{DEFAULT_SCOPE_NAME}; + + //If no scopes were requested. + if (requestedScopes == null || requestedScopes.length == 0) { + tokReqMsgCtx.setScope(defaultScope); + return true; + } + + String consumerKey = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(); + List reqScopeList = Arrays.asList(requestedScopes); + Map restAPIScopesOfCurrentTenant; + + try { + + Map appScopes; + ApiMgtDAO apiMgtDAO = new ApiMgtDAO(); + + //Get all the scopes and permissions against the scopes defined for the APIs subscribed to the application. + appScopes = apiMgtDAO.getScopeRolesOfApplication(consumerKey); + + //Add API Manager rest API scopes set. This list should be loaded at server start up and keep + //in memory and add it to each and every request coming. + String tenantDomain = tokReqMsgCtx.getAuthorizedUser().getTenantDomain(); + restAPIScopesOfCurrentTenant = (Map) Caching.getCacheManager(APIConstants.API_MANAGER_CACHE_MANAGER) + .getCache(REST_API_SCOPE_CACHE) + .get(tenantDomain); + if (restAPIScopesOfCurrentTenant != null) { + appScopes.putAll(restAPIScopesOfCurrentTenant); + } else { + restAPIScopesOfCurrentTenant = APIUtil. + getRESTAPIScopesFromConfig(APIUtil.getTenantRESTAPIScopesConfig(tenantDomain)); + + //call load tenant config for rest API. + //then put cache + appScopes.putAll(restAPIScopesOfCurrentTenant); + Caching.getCacheManager(APIConstants.API_MANAGER_CACHE_MANAGER) + .getCache(REST_API_SCOPE_CACHE) + .put(tenantDomain, restAPIScopesOfCurrentTenant); + } + //If no scopes can be found in the context of the application + if (appScopes.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("No scopes defined for the Application " + + tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId()); + } + + String[] allowedScopes = getAllowedScopes(reqScopeList); + tokReqMsgCtx.setScope(allowedScopes); + return true; + } + + // check for authorized scopes + List authorizedScopes = getAuthorizedScopes(tokReqMsgCtx, reqScopeList, appScopes); + + if (!authorizedScopes.isEmpty()) { + String[] authScopesArr = authorizedScopes.toArray(new String[authorizedScopes.size()]); + tokReqMsgCtx.setScope(authScopesArr); + } else { + tokReqMsgCtx.setScope(defaultScope); + } + } catch (APIManagementException e) { + log.error("Error while getting scopes of application " + e.getMessage()); + return false; + } + return true; + } + + /** + * Determines if the scope is specified in the white list. + * + * @param scope - The scope key to check + * @return - 'true' if the scope is white listed. 'false' if not. + */ + private static boolean isWhiteListedScope(String scope) { + // load white listed scopes + List scopeSkipList = OAuthExtensionsDataHolder.getInstance().getWhitelistedScopes(); + for (String scopeTobeSkipped : scopeSkipList) { + if (scope.matches(scopeTobeSkipped)) { + return true; + } + } + return false; + } + + /** + * Get the set of default scopes. If a requested scope is matches with the patterns specified in the white list, + * then such scopes will be issued without further validation. If the scope list is empty, + * token will be issued for default scope. + * + * @param requestedScopes - The set of requested scopes + * @return - The subset of scopes that are allowed + */ + private static String[] getAllowedScopes(List requestedScopes) { + List authorizedScopes = new ArrayList<>(); + + //Iterate the requested scopes list. + for (String scope : requestedScopes) { + if (isWhiteListedScope(scope)) { + authorizedScopes.add(scope); + } + } + if (authorizedScopes.isEmpty()) { + authorizedScopes.add(DEFAULT_SCOPE_NAME); + } + return authorizedScopes.toArray(new String[authorizedScopes.size()]); + } + + /** + * This method is used to get the authorized scopes out of requested scopes. It checks requested scopes with app + * scopes whether user has permissions to take actions for the requested scopes. + * + * @param tokReqMsgCtx OAuth token request message context. + * @param reqScopeList Requested scope list. + * @param appScopes App scopes. + * @return Returns a list of scopes. + */ + private static List getAuthorizedScopes(OAuthTokenReqMessageContext tokReqMsgCtx, List reqScopeList, + Map appScopes) { + + boolean status; + List authorizedScopes = new ArrayList<>(); + + int tenantId; + String username = tokReqMsgCtx.getAuthorizedUser().getUserName(); + String tenantDomain = tokReqMsgCtx.getAuthorizedUser().getTenantDomain(); + RealmService realmService = OAuthExtensionsDataHolder.getInstance().getRealmService(); + + try { + tenantId = realmService.getTenantManager().getTenantId(tenantDomain); + + // If tenant Id is not set in the tokenReqContext, deriving it from username. + if (tenantId == 0 || tenantId == -1) { + tenantId = IdentityTenantUtil.getTenantIdOfUser(username); + } + + UserRealm userRealm = OAuthExtensionsDataHolder.getInstance().getRealmService().getTenantUserRealm(tenantId); + + //Iterate the requested scopes list. + for (String scope : reqScopeList) { + status = false; + + //Get the set of roles associated with the requested scope. + String appPermissions = appScopes.get(scope); + + //If the scope has been defined in the context of the App and if permissions have been defined for the scope + if (appPermissions != null && appPermissions.length() != 0) { + List permissions = new ArrayList<>(Arrays.asList(appPermissions.replaceAll(" ", "").split(","))); + + //Check if user has at least one of the permission associated with the scope + if (!permissions.isEmpty()) { + for (String permission : permissions) { + if (userRealm != null && userRealm.getAuthorizationManager() != null) { + String userStore = tokReqMsgCtx.getAuthorizedUser().getUserStoreDomain(); + + if (userStore != null) { + status = userRealm.getAuthorizationManager() + .isUserAuthorized(userStore + "/" + username, permission, UI_EXECUTE); + } else { + status = userRealm.getAuthorizationManager() + .isUserAuthorized(username, permission, UI_EXECUTE); + } + if (status) { + break; + } + } + } + if (status) { + authorizedScopes.add(scope); + } + } + } + + //The scope string starts with 'device_'. + else if (appScopes.containsKey(scope) || isWhiteListedScope(scope)) { + authorizedScopes.add(scope); + } + } + } catch (UserStoreException e) { + log.error("Error occurred while initializing user store.", e); + } + return authorizedScopes; + } + } diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedPasswordGrantHandler.java b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedPasswordGrantHandler.java new file mode 100644 index 00000000000..d39ea69f0c2 --- /dev/null +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedPasswordGrantHandler.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.device.mgt.oauth.extensions.handlers.grant; + +import org.apache.axiom.om.OMElement; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.oauth.extensions.OAuthExtUtils; +import org.wso2.carbon.device.mgt.oauth.extensions.internal.OAuthExtensionsDataHolder; +import org.wso2.carbon.identity.application.common.cache.BaseCache; +import org.wso2.carbon.identity.core.util.IdentityConfigParser; +import org.wso2.carbon.identity.core.util.IdentityCoreConstants; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.ResponseHeader; +import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.token.handlers.grant.PasswordGrantHandler; +import org.wso2.carbon.user.api.Claim; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.user.core.UserRealm; +import org.wso2.carbon.user.core.config.RealmConfiguration; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.user.core.util.UserCoreUtil; + +import javax.xml.namespace.QName; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +@SuppressWarnings("unused") +public class ExtendedPasswordGrantHandler extends PasswordGrantHandler { + + private static Log log = LogFactory.getLog(ExtendedPasswordGrantHandler.class); + + private static final String CONFIG_ELEM_OAUTH = "OAuth"; + + // Claims that are set as response headers of access token response + private static final String REQUIRED_CLAIM_URIS = "RequiredRespHeaderClaimUris"; + private BaseCache userClaimsCache; + + // Primary/Secondary Login configuration + private static final String CLAIM_URI = "ClaimUri"; + private static final String LOGIN_CONFIG = "LoginConfig"; + private static final String USERID_LOGIN = "UserIdLogin"; + private static final String EMAIL_LOGIN = "EmailLogin"; + private static final String PRIMARY_LOGIN = "primary"; + + private Map> loginConfiguration = new ConcurrentHashMap<>(); + + private List requiredHeaderClaimUris = new ArrayList<>(); + + public void init() throws IdentityOAuth2Exception { + + super.init(); + + IdentityConfigParser configParser; + configParser = IdentityConfigParser.getInstance(); + OMElement oauthElem = configParser.getConfigElement(CONFIG_ELEM_OAUTH); + + // Get the required claim uris that needs to be included in the response. + parseRequiredHeaderClaimUris(oauthElem.getFirstChildWithName(getQNameWithIdentityNS(REQUIRED_CLAIM_URIS))); + + // read login config + parseLoginConfig(oauthElem); + + userClaimsCache = new BaseCache<>("UserClaimsCache"); + if (log.isDebugEnabled()) { + log.debug("Successfully created UserClaimsCache under " + OAuthConstants.OAUTH_CACHE_MANAGER); + } + } + + @Override + public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) + throws IdentityOAuth2Exception { + + OAuth2AccessTokenReqDTO oAuth2AccessTokenReqDTO = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); + String username = oAuth2AccessTokenReqDTO.getResourceOwnerUsername(); + String loginUserName = getLoginUserName(username); + tokReqMsgCtx.getOauth2AccessTokenReqDTO().setResourceOwnerUsername(loginUserName); + + boolean isValidated = super.validateGrant(tokReqMsgCtx); + + if (isValidated) { + + int tenantId; + tenantId = IdentityTenantUtil.getTenantIdOfUser(username); + + RealmService realmService = OAuthExtensionsDataHolder.getInstance().getRealmService(); + UserStoreManager userStoreManager; + try { + userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager(); + } catch (UserStoreException e) { + log.error("Error when getting the tenant's UserStoreManager", e); + return false; + } + + List respHeaders = new ArrayList<>(); + + if (oAuth2AccessTokenReqDTO.getResourceOwnerUsername() != null) { + try { + if (requiredHeaderClaimUris != null && !requiredHeaderClaimUris.isEmpty()) { + // Get user's claim values from the default profile. + String userStoreDomain = tokReqMsgCtx.getAuthorizedUser().getUserStoreDomain(); + + String endUsernameWithDomain = UserCoreUtil. + addDomainToName(oAuth2AccessTokenReqDTO.getResourceOwnerUsername(), userStoreDomain); + + Claim[] mapClaimValues = getUserClaimValues(endUsernameWithDomain, userStoreManager); + + if (mapClaimValues != null && mapClaimValues.length > 0) { + ResponseHeader header; + for (String claimUri : requiredHeaderClaimUris) { + for (Claim claim : mapClaimValues) { + if (claimUri.equals(claim.getClaimUri())) { + header = new ResponseHeader(); + header.setKey(claim.getDisplayTag()); + header.setValue(claim.getValue()); + respHeaders.add(header); + break; + } + } + } + } else if (log.isDebugEnabled()) { + log.debug("No claim values for user : " + endUsernameWithDomain); + } + } + } catch (Exception e) { + throw new IdentityOAuth2Exception("Error occurred while retrieving user claims", e); + } + } + tokReqMsgCtx.addProperty("RESPONSE_HEADERS", respHeaders.toArray(new ResponseHeader[respHeaders.size()])); + } + + return isValidated; + } + + @Override + public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) { + return OAuthExtUtils.setScopes(tokReqMsgCtx); + } + + private String getLoginUserName(String userID) { + String loginUserName = userID; + if (isSecondaryLogin(userID)) { + loginUserName = getPrimaryFromSecondary(userID); + } + return loginUserName; + } + + /** + * Identify whether the logged in user used his Primary Login name or + * Secondary login name + * + * @param userId - The username used to login. + * @return true if secondary login name is used, + * false if primary login name has been used + */ + private boolean isSecondaryLogin(String userId) { + + if (loginConfiguration.get(EMAIL_LOGIN) != null) { + Map emailConf = loginConfiguration.get(EMAIL_LOGIN); + if ("true".equalsIgnoreCase(emailConf.get(PRIMARY_LOGIN))) { + return !isUserLoggedInEmail(userId); + } else if ("false".equalsIgnoreCase(emailConf.get(PRIMARY_LOGIN))) { + return isUserLoggedInEmail(userId); + } + } else if (loginConfiguration.get(USERID_LOGIN) != null) { + Map userIdConf = loginConfiguration.get(USERID_LOGIN); + if ("true".equalsIgnoreCase(userIdConf.get(PRIMARY_LOGIN))) { + return isUserLoggedInEmail(userId); + } else if ("false".equalsIgnoreCase(userIdConf.get(PRIMARY_LOGIN))) { + return !isUserLoggedInEmail(userId); + } + } + return false; + } + + /** + * Identify whether the logged in user used his ordinal username or email + * + * @param userId - username used to login. + * @return - true if userId contains '@'. false otherwise + */ + private boolean isUserLoggedInEmail(String userId) { + return userId.contains("@"); + } + + /** + * Get the primaryLogin name using secondary login name. Primary secondary + * Configuration is provided in the identitiy.xml. In the userstore, it is + * users responsibility TO MAINTAIN THE SECONDARY LOGIN NAME AS UNIQUE for + * each and every users. If it is not unique, we will pick the very first + * entry from the userlist. + * + * @param login - username used to login. + * @return - + */ + private String getPrimaryFromSecondary(String login) { + + String claimURI, username = null; + if (isUserLoggedInEmail(login)) { + Map emailConf = loginConfiguration.get(EMAIL_LOGIN); + claimURI = emailConf.get(CLAIM_URI); + } else { + Map userIdConf = loginConfiguration.get(USERID_LOGIN); + claimURI = userIdConf.get(CLAIM_URI); + } + + try { + RealmService realmSvc = OAuthExtensionsDataHolder.getInstance().getRealmService(); + RealmConfiguration config = new RealmConfiguration(); + UserRealm realm = realmSvc.getUserRealm(config); + org.wso2.carbon.user.core.UserStoreManager storeManager = realm.getUserStoreManager(); + String[] user = storeManager.getUserList(claimURI, login, null); + if (user.length > 0) { + username = user[0]; + } + } catch (UserStoreException e) { + log.error("Error while retrieving the primaryLogin name using secondary login name : " + login, e); + } + return username; + } + + private Claim[] getUserClaimValues(String authorizedUser, UserStoreManager userStoreManager) + throws + UserStoreException { + Claim[] userClaims = userClaimsCache.getValueFromCache(authorizedUser); + if (userClaims != null) { + return userClaims; + } else { + if (log.isDebugEnabled()) { + log.debug("Cache miss for user claims. Username :" + authorizedUser); + } + userClaims = userStoreManager.getUserClaimValues( + authorizedUser, null); + userClaimsCache.addToCache(authorizedUser, userClaims); + return userClaims; + } + } + + /** + * Read the required claim configuration from identity.xml + */ + private void parseRequiredHeaderClaimUris(OMElement requiredClaimUrisElem) { + if (requiredClaimUrisElem == null) { + return; + } + + Iterator claimUris = requiredClaimUrisElem.getChildrenWithLocalName(CLAIM_URI); + if (claimUris != null) { + while (claimUris.hasNext()) { + OMElement claimUri = (OMElement) claimUris.next(); + if (claimUri != null) { + requiredHeaderClaimUris.add(claimUri.getText()); + } + } + } + } + + /** + * Read the primary/secondary login configuration + * + * .... + * + * + * + * + * + * http://wso2.org/claims/emailaddress + * + * + * ..... + * + * + * @param oauthConfigElem - The '' xml configuration element in the api-manager.xml + */ + private void parseLoginConfig(OMElement oauthConfigElem) { + OMElement loginConfigElem = oauthConfigElem.getFirstChildWithName(getQNameWithIdentityNS(LOGIN_CONFIG)); + if (loginConfigElem != null) { + if (log.isDebugEnabled()) { + log.debug("Login configuration is set "); + } + // Primary/Secondary supported login mechanisms + OMElement emailConfigElem = loginConfigElem.getFirstChildWithName(getQNameWithIdentityNS(EMAIL_LOGIN)); + + OMElement userIdConfigElem = loginConfigElem.getFirstChildWithName(getQNameWithIdentityNS(USERID_LOGIN)); + + Map emailConf = new HashMap(2); + emailConf.put(PRIMARY_LOGIN, + emailConfigElem.getAttributeValue(new QName(PRIMARY_LOGIN))); + emailConf.put(CLAIM_URI, + emailConfigElem.getFirstChildWithName(getQNameWithIdentityNS(CLAIM_URI)) + .getText()); + + Map userIdConf = new HashMap(2); + userIdConf.put(PRIMARY_LOGIN, + userIdConfigElem.getAttributeValue(new QName(PRIMARY_LOGIN))); + userIdConf.put(CLAIM_URI, + userIdConfigElem.getFirstChildWithName(getQNameWithIdentityNS(CLAIM_URI)) + .getText()); + + loginConfiguration.put(EMAIL_LOGIN, emailConf); + loginConfiguration.put(USERID_LOGIN, userIdConf); + } + } + + private QName getQNameWithIdentityNS(String localPart) { + return new QName(IdentityCoreConstants.IDENTITY_DEFAULT_NAMESPACE, localPart); + } +} diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionServiceComponent.java b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionServiceComponent.java index b8be1c467ff..3cdeeb5e8df 100644 --- a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionServiceComponent.java +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionServiceComponent.java @@ -21,9 +21,17 @@ package org.wso2.carbon.device.mgt.oauth.extensions.internal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.impl.APIConstants; +import org.wso2.carbon.apimgt.impl.APIManagerConfiguration; import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService; import org.wso2.carbon.identity.oauth2.OAuth2TokenValidationService; import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.utils.CarbonUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; /** * @scr.component name="org.wso2.carbon.device.mgt.oauth.extensions" immediate="true" @@ -49,17 +57,51 @@ import org.wso2.carbon.user.core.service.RealmService; public class OAuthExtensionServiceComponent { private static final Log log = LogFactory.getLog(OAuthExtensionServiceComponent.class); + private static final String REPOSITORY = "repository"; + private static final String CONFIGURATION = "conf"; + private static final String APIM_CONF_FILE = "api-manager.xml"; + @SuppressWarnings("unused") protected void activate(ComponentContext componentContext) { - if(log.isDebugEnabled()){ + if (log.isDebugEnabled()) { log.debug("Starting OAuthExtensionBundle"); } + try { + APIManagerConfiguration configuration = new APIManagerConfiguration(); + String filePath = new StringBuilder(). + append(CarbonUtils.getCarbonHome()). + append(File.separator). + append(REPOSITORY). + append(File.separator). + append(CONFIGURATION). + append(File.separator). + append(APIM_CONF_FILE).toString(); + + configuration.load(filePath); + // loading white listed scopes + List whiteList; + + // Read scope whitelist from Configuration. + whiteList = configuration.getProperty(APIConstants.API_KEY_MANGER_SCOPE_WHITELIST); + + // If whitelist is null, default scopes will be put. + if (whiteList == null) { + whiteList = new ArrayList(); + whiteList.add(APIConstants.OPEN_ID_SCOPE_NAME); + whiteList.add(APIConstants.DEVICE_SCOPE_PATTERN); + } + + OAuthExtensionsDataHolder.getInstance().setWhitelistedScopes(whiteList); + + } catch (APIManagementException e) { + log.error("Error occurred while loading APIM configurations", e); + } } @SuppressWarnings("unused") protected void deactivate(ComponentContext componentContext) { - if(log.isDebugEnabled()){ + if (log.isDebugEnabled()) { log.debug("Stopping OAuthExtensionBundle"); } } diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionsDataHolder.java b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionsDataHolder.java index f87ac765d38..f5916880016 100644 --- a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionsDataHolder.java +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/internal/OAuthExtensionsDataHolder.java @@ -22,6 +22,8 @@ import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService import org.wso2.carbon.identity.oauth2.OAuth2TokenValidationService; import org.wso2.carbon.user.core.service.RealmService; +import java.util.List; + /** * This holds the OSGi service references required for oauth extensions bundle. */ @@ -30,6 +32,7 @@ public class OAuthExtensionsDataHolder { private RealmService realmService; private OAuth2TokenValidationService oAuth2TokenValidationService; private PermissionManagerService permissionManagerService; + private List whitelistedScopes; private static OAuthExtensionsDataHolder thisInstance = new OAuthExtensionsDataHolder(); @@ -72,4 +75,12 @@ public class OAuthExtensionsDataHolder { } return permissionManagerService; } + + public List getWhitelistedScopes() { + return whitelistedScopes; + } + + public void setWhitelistedScopes(List whitelistedScopes) { + this.whitelistedScopes = whitelistedScopes; + } }