Merge branch 'master' into 'master'

Implement api keymgt extension

See merge request entgra/carbon-device-mgt!890
merge-requests/884/merge
Pahansith Gunathilake 3 years ago
commit b894d371fb

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>org.wso2.carbon.devicemgt</groupId>
<version>5.0.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.wso2.carbon.apimgt.keymgt.extension</artifactId>
<packaging>bundle</packaging>
<name>WSO2 Carbon - API Key Management</name>
<description>This module extends the API manager's key management.</description>
<url>http://wso2.org</url>
<dependencies>
<dependency>
<groupId>commons-codec.wso2</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.apimgt</groupId>
<artifactId>org.wso2.carbon.apimgt.keymgt</artifactId>
<version>${carbon.api.mgt.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.core</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>1.4.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${project.artifactId}</Bundle-Name>
<Bundle-Version>${carbon.device.mgt.version}</Bundle-Version>
<Bundle-Description>API Management Application Bundle</Bundle-Description>
<Import-Packages>
</Import-Packages>
<Export-Package>
org.wso2.carbon.apimgt.keymgt.extension
</Export-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile>
<outputDirectory>${basedir}/target/coverage-reports/site</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,369 @@
/*
* Copyright (c) 2022, 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.keymgt.extension;
import org.apache.commons.lang.StringUtils;
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.model.AccessTokenInfo;
import org.wso2.carbon.apimgt.api.model.KeyManager;
import org.wso2.carbon.apimgt.api.model.subscription.URLMapping;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.caching.CacheProvider;
import org.wso2.carbon.apimgt.impl.dto.APIKeyValidationInfoDTO;
import org.wso2.carbon.apimgt.impl.dto.KeyManagerDto;
import org.wso2.carbon.apimgt.impl.factory.KeyManagerHolder;
import org.wso2.carbon.apimgt.keymgt.APIKeyMgtException;
import org.wso2.carbon.apimgt.keymgt.SubscriptionDataHolder;
import org.wso2.carbon.apimgt.keymgt.handlers.DefaultKeyValidationHandler;
import org.wso2.carbon.apimgt.keymgt.model.SubscriptionDataStore;
import org.wso2.carbon.apimgt.keymgt.model.entity.API;
import org.wso2.carbon.apimgt.keymgt.service.TokenValidationContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.permission.mgt.Permission;
import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagementException;
import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService;
import org.wso2.carbon.device.mgt.core.permission.mgt.PermissionManagerServiceImpl;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
public class KeyValidationHandler extends DefaultKeyValidationHandler {
/* This key validation handler is written extending API Manager's
* AbstractKeyValidationHandler, which implements KeyValidationHandler
* where all the methods have been implemented. Since the logic is
* taken from KeyValidationHandler, the latest logical changes
* should be monitored and updated here accordingly */
private static final Log log = LogFactory.getLog(KeyValidationHandler.class);
public KeyValidationHandler() {
log.info(this.getClass().getName() + " Initialised");
}
@Override
public boolean validateScopes(TokenValidationContext validationContext) throws APIKeyMgtException {
if (validationContext.isCacheHit()) {
return true;
}
APIKeyValidationInfoDTO apiKeyValidationInfoDTO = validationContext.getValidationInfoDTO();
if (apiKeyValidationInfoDTO == null) {
throw new APIKeyMgtException("Key Validation information not set");
}
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
String httpVerb = validationContext.getHttpVerb();
String[] scopes;
Set<String> scopesSet = apiKeyValidationInfoDTO.getScopes();
StringBuilder scopeList = new StringBuilder();
if (scopesSet != null && !scopesSet.isEmpty()) {
scopes = scopesSet.toArray(new String[scopesSet.size()]);
if (log.isDebugEnabled() && scopes != null) {
for (String scope : scopes) {
scopeList.append(scope);
scopeList.append(",");
}
scopeList.deleteCharAt(scopeList.length() - 1);
log.debug("Scopes allowed for token : " + validationContext.getAccessToken() + " : "
+ scopeList.toString());
}
}
String resourceList = validationContext.getMatchingResource();
List<String> resourceArray;
if ((APIConstants.GRAPHQL_QUERY.equalsIgnoreCase(validationContext.getHttpVerb()))
|| (APIConstants.GRAPHQL_MUTATION.equalsIgnoreCase(validationContext.getHttpVerb()))
|| (APIConstants.GRAPHQL_SUBSCRIPTION.equalsIgnoreCase(validationContext.getHttpVerb()))) {
resourceArray = new ArrayList<>(Arrays.asList(resourceList.split(",")));
} else {
resourceArray = new ArrayList<>(Arrays.asList(resourceList));
}
String actualVersion = validationContext.getVersion();
//Check if the api version has been prefixed with _default_
if (actualVersion != null && actualVersion.startsWith(APIConstants.DEFAULT_VERSION_PREFIX)) {
//Remove the prefix from the version.
actualVersion = actualVersion.split(APIConstants.DEFAULT_VERSION_PREFIX)[1];
}
SubscriptionDataStore tenantSubscriptionStore =
SubscriptionDataHolder.getInstance().getTenantSubscriptionStore(tenantDomain);
API api = tenantSubscriptionStore.getApiByContextAndVersion(validationContext.getContext(),
actualVersion);
boolean scopesValidated = false;
if (api != null) {
for (String resource : resourceArray) {
List<URLMapping> resources = api.getResources();
URLMapping urlMapping = null;
for (URLMapping mapping : resources) {
if (Objects.equals(mapping.getHttpMethod(), httpVerb) || "WS".equalsIgnoreCase(api.getApiType())) {
if (isResourcePathMatching(resource, mapping)) {
urlMapping = mapping;
break;
}
}
}
if (urlMapping != null) {
if (urlMapping.getScopes().size() == 0) {
scopesValidated = true;
continue;
}
List<String> mappingScopes = urlMapping.getScopes();
boolean validate = false;
for (String scope : mappingScopes) {
if (scopesSet.contains(scope)) {
scopesValidated = true;
validate = true;
break;
}
try {
validate = scopesValidated = authorizePermissions(validationContext);
break;
} catch (UserStoreException e) {
String msg = "Error occurred while validating user permissions";
log.error(msg, e);
throw new APIKeyMgtException(msg);
}
}
if (!validate && urlMapping.getScopes().size() > 0) {
break;
}
}
}
}
if (!scopesValidated) {
apiKeyValidationInfoDTO.setAuthorized(false);
apiKeyValidationInfoDTO.setValidationStatus(APIConstants.KeyValidationStatus.INVALID_SCOPE);
}
return scopesValidated;
}
/**
* Authorizes the permissions of a user for a given context
*
* @param validationContext token validation context object
* @return returns whether a user is authorized
* @throws UserStoreException throws if an error occurs while getting the tenant user realm
* */
private boolean authorizePermissions(TokenValidationContext validationContext) throws UserStoreException {
PrivilegedCarbonContext context = PrivilegedCarbonContext.getThreadLocalCarbonContext();
String username;
RealmService realmService = (RealmService) context.getOSGiService(RealmService.class, null);
UserRealm userRealm = realmService.getTenantUserRealm(PrivilegedCarbonContext
.getThreadLocalCarbonContext().getTenantId());;
AccessTokenInfo accessTokenInfo;
try {
accessTokenInfo = getAccessTokenInfo(validationContext);
} catch (APIManagementException e) {
log.error("Error occurred while getting access token info");
return false;
}
username = accessTokenInfo.getEndUserName();
String tenantAwareUsername = MultitenantUtils.getTenantAwareUsername(username);
List<Permission> matchingPermissions;
StringBuilder ctx = new StringBuilder();
try {
PermissionManagerService permissionManagerService = PermissionManagerServiceImpl.getInstance();
String[] ctxArr = validationContext.getContext().split("/");
for (String c : ctxArr) {
if (c.matches("[v|V]\\d{1,3}\\.\\d{1,3}"))
ctx.append(c);
}
ctx = new StringBuilder(ctxArr[0] + "/" + ctxArr[1] + "/" + ctxArr[2] + "/" + ctxArr[3]);
matchingPermissions = permissionManagerService.getPermission(ctx.toString());
} catch (PermissionManagementException e) {
log.error("Error occurred while fetching permissions for context " + ctx, e);
return false;
}
String requestUri = validationContext.getContext();
String requestMethod = validationContext.getHttpVerb();
String contextPath = ctx.toString();
if (matchingPermissions == null) {
if (log.isDebugEnabled()) {
log.debug("Permission to request '" + requestUri + "' is not defined in the configuration");
}
return false;
}
String requiredPermission = null;
List<MatchingResource> matchingResources = new ArrayList<>();
for (Permission permission : matchingPermissions) {
if (requestMethod.equals(permission.getMethod()) && requestUri.matches(permission.getUrlPattern())) {
if (requestUri.equals(permission.getUrl())) { // is there a exact match
requiredPermission = permission.getPath();
break;
} else { // all templated urls add to a list
matchingResources.add(new MatchingResource(permission.getUrlPattern().replace(contextPath, ""), permission.getPath()));
}
}
}
if (requiredPermission == null) {
if (matchingResources.size() == 1) { // only 1 templated url found
requiredPermission = matchingResources.get(0).getPermission();
}
if (matchingResources.size() > 1) { // more than 1 templated urls found
String urlWithoutContext = requestUri.replace(contextPath, "");
StringTokenizer st = new StringTokenizer(urlWithoutContext, "/");
int tokenPosition = 1;
while (st.hasMoreTokens()) {
List<MatchingResource> tempList = new ArrayList<>();
String currentToken = st.nextToken();
for (MatchingResource matchingResource : matchingResources) {
StringTokenizer stmr = new StringTokenizer(matchingResource.getUrlPattern(), "/");
int internalTokenPosition = 1;
while (stmr.hasMoreTokens()) {
String internalToken = stmr.nextToken();
if ((tokenPosition == internalTokenPosition) && currentToken.equals(internalToken)) {
tempList.add(matchingResource);
}
internalTokenPosition++;
if (tokenPosition < internalTokenPosition) {
break;
}
}
}
if (tempList.size() == 1) {
requiredPermission = tempList.get(0).getPermission();
break;
}
tokenPosition++;
}
}
}
if (requiredPermission == null) {
if (log.isDebugEnabled()) {
log.debug("Matching permission not found for " + requestUri);
}
return false;
}
boolean isUserAuthorized;
try {
isUserAuthorized = userRealm.getAuthorizationManager().isUserAuthorized(
tenantAwareUsername,
requiredPermission,
"ui.execute" // check against null values
);
return isUserAuthorized;
} catch (Exception e) {
log.error("Error occurred while retrieving user store. " + e.getMessage());
return false;
}
}
private AccessTokenInfo getAccessTokenInfo(TokenValidationContext validationContext)
throws APIManagementException {
Object cachedAccessTokenInfo =
CacheProvider.createIntrospectionCache().get(validationContext.getAccessToken());
if (cachedAccessTokenInfo != null) {
return (AccessTokenInfo) cachedAccessTokenInfo;
}
String electedKeyManager = null;
// Obtaining details about the token.
if (StringUtils.isNotEmpty(validationContext.getTenantDomain())) {
Map<String, KeyManagerDto>
tenantKeyManagers = KeyManagerHolder.getTenantKeyManagers(validationContext.getTenantDomain());
KeyManager keyManagerInstance = null;
if (tenantKeyManagers.values().size() == 1) {
Map.Entry<String, KeyManagerDto> entry = tenantKeyManagers.entrySet().iterator().next();
if (entry != null) {
KeyManagerDto keyManagerDto = entry.getValue();
if (keyManagerDto != null && (validationContext.getKeyManagers()
.contains(APIConstants.KeyManager.API_LEVEL_ALL_KEY_MANAGERS) ||
validationContext.getKeyManagers().contains(keyManagerDto.getName()))) {
keyManagerInstance = keyManagerDto.getKeyManager();
electedKeyManager = entry.getKey();
}
}
} else if (tenantKeyManagers.values().size() > 1) {
if (validationContext.getKeyManagers()
.contains(APIConstants.KeyManager.API_LEVEL_ALL_KEY_MANAGERS)) {
for (Map.Entry<String, KeyManagerDto> keyManagerDtoEntry : tenantKeyManagers.entrySet()) {
if (keyManagerDtoEntry.getValue().getKeyManager() != null &&
keyManagerDtoEntry.getValue().getKeyManager()
.canHandleToken(validationContext.getAccessToken())) {
keyManagerInstance = keyManagerDtoEntry.getValue().getKeyManager();
electedKeyManager = keyManagerDtoEntry.getKey();
break;
}
}
} else {
for (String selectedKeyManager : validationContext.getKeyManagers()) {
KeyManagerDto keyManagerDto = tenantKeyManagers.get(selectedKeyManager);
if (keyManagerDto != null && keyManagerDto.getKeyManager() != null &&
keyManagerDto.getKeyManager().canHandleToken(validationContext.getAccessToken())) {
keyManagerInstance = keyManagerDto.getKeyManager();
electedKeyManager = selectedKeyManager;
break;
}
}
}
}
if (keyManagerInstance != null) {
AccessTokenInfo tokenInfo = keyManagerInstance.getTokenMetaData(validationContext.getAccessToken());
tokenInfo.setKeyManager(electedKeyManager);
CacheProvider.getGatewayIntrospectCache().put(validationContext.getAccessToken(), tokenInfo);
return tokenInfo;
} else {
log.debug("KeyManager not available to authorize token.");
}
}
return null;
}
private boolean isResourcePathMatching(String resourceString, URLMapping urlMapping) {
String resource = resourceString.trim();
String urlPattern = urlMapping.getUrlPattern().trim();
if (resource.equalsIgnoreCase(urlPattern)) {
return true;
}
// If the urlPattern is only one character longer than the resource and the urlPattern ends with a '/'
if (resource.length() + 1 == urlPattern.length() && urlPattern.endsWith("/")) {
// Check if resource is equal to urlPattern if the trailing '/' of the urlPattern is ignored
String urlPatternWithoutSlash = urlPattern.substring(0, urlPattern.length() - 1);
return resource.equalsIgnoreCase(urlPatternWithoutSlash);
}
return false;
}
}

@ -0,0 +1,27 @@
package org.wso2.carbon.apimgt.keymgt.extension;
public class MatchingResource {
private String urlPattern;
private String permission;
public MatchingResource(String urlPattern, String permission) {
this.urlPattern = urlPattern;
this.permission = permission;
}
public String getUrlPattern() {
return urlPattern;
}
public void setUrlPattern(String urlPattern) {
this.urlPattern = urlPattern;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
}

@ -37,6 +37,7 @@
<module>org.wso2.carbon.apimgt.application.extension</module> <module>org.wso2.carbon.apimgt.application.extension</module>
<module>org.wso2.carbon.apimgt.application.extension.api</module> <module>org.wso2.carbon.apimgt.application.extension.api</module>
<module>org.wso2.carbon.apimgt.annotations</module> <module>org.wso2.carbon.apimgt.annotations</module>
<module>org.wso2.carbon.apimgt.keymgt.extension</module>
</modules> </modules>
<build> <build>

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2022, Entgra (Pvt) Ltd. (http://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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>carbon-devicemgt</artifactId>
<groupId>org.wso2.carbon.devicemgt</groupId>
<version>5.0.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.wso2.carbon.apimgt.keymgt.extension.feature</artifactId>
<packaging>pom</packaging>
<name>WSO2 Carbon - Api Key Mgt Extensions Feature</name>
<url>http://wso2.org</url>
<description>This feature contains apimgt related key management extensions</description>
<dependencies>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.apimgt.keymgt.extension</artifactId>
<version>${carbon.device.mgt.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/resources</outputDirectory>
<resources>
<resource>
<directory>resources</directory>
<includes>
<include>build.properties</include>
<include>p2.inf</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.wso2.maven</groupId>
<artifactId>carbon-p2-plugin</artifactId>
<version>${carbon.p2.plugin.version}</version>
<executions>
<execution>
<id>p2-feature-generation</id>
<phase>package</phase>
<goals>
<goal>p2-feature-gen</goal>
</goals>
<configuration>
<id>org.wso2.carbon.apimgt.keymgt.extension</id>
<propertiesFile>../../../features/etc/feature.properties</propertiesFile>
<adviceFile>
<properties>
<propertyDef>org.wso2.carbon.p2.category.type:server</propertyDef>
<propertyDef>org.eclipse.equinox.p2.type.group:false</propertyDef>
</properties>
</adviceFile>
<bundles>
<bundleDef>
org.wso2.carbon.devicemgt:org.wso2.carbon.apimgt.keymgt.extension:${carbon.device.mgt.version}
</bundleDef>
</bundles>
<importBundles>
</importBundles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -36,6 +36,7 @@
<modules> <modules>
<module>org.wso2.carbon.apimgt.webapp.publisher.feature</module> <module>org.wso2.carbon.apimgt.webapp.publisher.feature</module>
<module>org.wso2.carbon.apimgt.application.extension.feature</module> <module>org.wso2.carbon.apimgt.application.extension.feature</module>
<module>org.wso2.carbon.apimgt.keymgt.extension.feature</module>
</modules> </modules>
</project> </project>

Loading…
Cancel
Save