From 6ec2a39e51999d1a1f2e6f9288926aa362d32851 Mon Sep 17 00:00:00 2001 From: hasinitg Date: Thu, 30 Jul 2015 16:57:18 +0530 Subject: [PATCH 1/9] Added identity context to store user identity info in thread local. --- .../api/server/security/IdentityContext.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/IdentityContext.java diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/IdentityContext.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/IdentityContext.java new file mode 100644 index 0000000000..24cc225237 --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/IdentityContext.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security; + +import org.apache.airavata.model.security.AuthzToken; + +/** + * This provides a thread local container for AuthzToken through out the execution of a particular thread. + */ +public class IdentityContext { + private static ThreadLocal authzTokenContainer = new ThreadLocal(); + + public static void set(AuthzToken authzToken){ + authzTokenContainer.set(authzToken); + } + + public static void unset(){ + authzTokenContainer.remove(); + } + + public static AuthzToken get(){ + return (AuthzToken) authzTokenContainer.get(); + } + +} From 7ef83689624cf135234976b4abb2d3fd7b43499b Mon Sep 17 00:00:00 2001 From: hasinitg Date: Fri, 31 Jul 2015 17:13:46 +0530 Subject: [PATCH 2/9] adding some missing files from previous commit. --- .../security/AiravataSecurityManager.java | 2 +- .../DefaultAiravataSecurityManager.java | 2 +- .../server/security/SecurityInterceptor.java | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java index 5937d3e7b4..348675f246 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java @@ -24,5 +24,5 @@ import org.apache.airavata.security.AiravataSecurityException; public interface AiravataSecurityManager { - public boolean isUserAuthenticatedAndAuthorized(AuthzToken authzToken) throws AiravataSecurityException; + public boolean isUserAuthorized(AuthzToken authzToken) throws AiravataSecurityException; } diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java index 739a1ecd60..9d7c959f47 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java @@ -37,7 +37,7 @@ public class DefaultAiravataSecurityManager implements AiravataSecurityManager { private final static Logger logger = LoggerFactory.getLogger(DefaultAiravataSecurityManager.class); - public boolean isUserAuthenticatedAndAuthorized(AuthzToken authzToken) throws AiravataSecurityException { + public boolean isUserAuthorized(AuthzToken authzToken) throws AiravataSecurityException { try { ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java index ac89092483..cf8f7e2797 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java @@ -28,7 +28,6 @@ import org.apache.airavata.security.AiravataSecurityException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; /** * Interceptor of Airavata API calls for the purpose of applying security. @@ -37,17 +36,26 @@ public class SecurityInterceptor implements MethodInterceptor{ private final static Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class); @Override public Object invoke(MethodInvocation invocation) throws Throwable { - authenticateNAuthorize((AuthzToken) invocation.getArguments()[0]); - return invocation.proceed(); + //obtain the authz token from the input parameters + AuthzToken authzToken = (AuthzToken) invocation.getArguments()[0]; + //authorize the API call + authorize(authzToken); + //set the user identity info in a thread local to be used in downstream execution. + IdentityContext.set(authzToken); + //let the method call procees upon successful authorization + Object returnObj = invocation.proceed(); + //clean the identity context before the method call returns + IdentityContext.unset(); + return returnObj; } - private void authenticateNAuthorize(AuthzToken authzToken) throws AuthorizationException { + private void authorize(AuthzToken authzToken) throws AuthorizationException { try { boolean isAPISecured = ServerSettings.isAPISecured(); if (isAPISecured) { AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); - boolean isAuthz = securityManager.isUserAuthenticatedAndAuthorized(authzToken); + boolean isAuthz = securityManager.isUserAuthorized(authzToken); if (!isAuthz) { throw new AuthorizationException("User is not authenticated or authorized."); } From 9c02f24d99c139b7dcc38b6fcddd17dd935c8e73 Mon Sep 17 00:00:00 2001 From: hasinitg Date: Sat, 1 Aug 2015 01:19:34 +0530 Subject: [PATCH 3/9] adding XACML based authorization for API calls. --- airavata-api/airavata-api-server/pom.xml | 8 +- .../security/AiravataSecurityManager.java | 4 +- .../DefaultAiravataSecurityManager.java | 21 ++++- .../server/security/DefaultOAuthClient.java | 55 ++--------- .../api/server/security/DefaultXACMLPEP.java | 92 +++++++++++++++++++ .../server/security/SecurityInterceptor.java | 17 +++- .../airavata/common/utils/Constants.java | 6 ++ .../airavata-default-xacml-policy.xml | 62 +++++++++++++ 8 files changed, 211 insertions(+), 54 deletions(-) create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java create mode 100644 modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml diff --git a/airavata-api/airavata-api-server/pom.xml b/airavata-api/airavata-api-server/pom.xml index 7cd0f3bd6e..543bbaa787 100644 --- a/airavata-api/airavata-api-server/pom.xml +++ b/airavata-api/airavata-api-server/pom.xml @@ -8,7 +8,8 @@ ANY ~ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 @@ -106,6 +107,11 @@ org.wso2.carbon.utils 4.2.0 + + org.wso2.carbon + org.wso2.carbon.identity.entitlement.stub + 4.2.1 + com.google.inject guice diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java index 348675f246..37c348ce54 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java @@ -23,6 +23,8 @@ import org.apache.airavata.model.security.AuthzToken; import org.apache.airavata.security.AiravataSecurityException; +import java.util.Map; + public interface AiravataSecurityManager { - public boolean isUserAuthorized(AuthzToken authzToken) throws AiravataSecurityException; + public boolean isUserAuthorized(AuthzToken authzToken, Map metaData) throws AiravataSecurityException; } diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java index 9d7c959f47..623031089f 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java @@ -24,6 +24,7 @@ import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.security.AuthzToken; import org.apache.airavata.security.AiravataSecurityException; +import org.apache.airavata.security.util.TrustStoreManager; import org.apache.axis2.AxisFault; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; @@ -31,22 +32,36 @@ import org.slf4j.LoggerFactory; import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationResponseDTO; +import java.util.Map; + /** * This enforces authentication and authorization on Airavata API calls. */ public class DefaultAiravataSecurityManager implements AiravataSecurityManager { private final static Logger logger = LoggerFactory.getLogger(DefaultAiravataSecurityManager.class); - public boolean isUserAuthorized(AuthzToken authzToken) throws AiravataSecurityException { + public boolean isUserAuthorized(AuthzToken authzToken, Map metaData) throws AiravataSecurityException { try { ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); - //TODO:read following properties from server-settings.properties file. + + //initialize SSL context with the trust store that contains the public cert of WSO2 Identity Server. + TrustStoreManager trustStoreManager = new TrustStoreManager(); + trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), + ServerSettings.getTrustStorePassword()); + DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteOauthServerUrl(), ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); OAuth2TokenValidationResponseDTO validationResponse = oauthClient.validateAccessToken( authzToken.getAccessToken()); - return validationResponse.getValid(); + boolean isOAuthTokenValid = validationResponse.getValid(); + //if XACML based authorization is enabled, check for role based authorization for the API invocation + DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteOauthServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + boolean authorizationDecision = entitlementClient.getAuthorizationDecision(authzToken, metaData); + + return (isOAuthTokenValid && authorizationDecision); + } catch (AxisFault axisFault) { logger.error(axisFault.getMessage(), axisFault); throw new AiravataSecurityException("Error in initializing the configuration context for creating the OAuth validation client."); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java index 7996474f3c..e1afacd8fa 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java @@ -58,47 +58,14 @@ public class DefaultOAuthClient { */ public DefaultOAuthClient(String auhorizationServerURL, String username, String password, ConfigurationContext configCtx) throws AiravataSecurityException { - String serviceURL = auhorizationServerURL + "OAuth2TokenValidationService"; try { + String serviceURL = auhorizationServerURL + "OAuth2TokenValidationService"; stub = new OAuth2TokenValidationServiceStub(configCtx, serviceURL); CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, stub._getServiceClient()); } catch (AxisFault e) { logger.error(e.getMessage(), e); throw new AiravataSecurityException("Error initializing OAuth client."); } - /*//TODO:Import the WSO2 IS cert into Airavata trust store. - try { - // Get SSL context - SSLContext sc = SSLContext.getInstance("SSL"); - - // Create empty HostnameVerifier - HostnameVerifier hv = new HostnameVerifier() { - public boolean verify(String urlHostName, SSLSession session) { - return true; - } - }; - HttpsURLConnection.setDefaultHostnameVerifier(hv); - - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, - String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, - String authType) { - } - }}; - - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - SSLContext.setDefault(sc); - } catch (Exception e) { - e.printStackTrace(); - }*/ } /** @@ -110,24 +77,22 @@ public void checkServerTrusted(java.security.cert.X509Certificate[] certs, */ public OAuth2TokenValidationResponseDTO validateAccessToken(String accessToken) throws AiravataSecurityException { - OAuth2TokenValidationRequestDTO oauthReq = new OAuth2TokenValidationRequestDTO(); - OAuth2TokenValidationRequestDTO_OAuth2AccessToken token = - new OAuth2TokenValidationRequestDTO_OAuth2AccessToken(); - token.setIdentifier(accessToken); - token.setTokenType(BEARER_TOKEN_TYPE); - oauthReq.setAccessToken(token); + try { - //initialize SSL context with the trust store. - TrustStoreManager trustStoreManager = new TrustStoreManager(); - trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), ServerSettings.getTrustStorePassword()); + OAuth2TokenValidationRequestDTO oauthReq = new OAuth2TokenValidationRequestDTO(); + OAuth2TokenValidationRequestDTO_OAuth2AccessToken token = + new OAuth2TokenValidationRequestDTO_OAuth2AccessToken(); + token.setIdentifier(accessToken); + token.setTokenType(BEARER_TOKEN_TYPE); + oauthReq.setAccessToken(token); return stub.validate(oauthReq); } catch (RemoteException e) { logger.error(e.getMessage(), e); throw new AiravataSecurityException("Error in validating the OAuth access token."); - } catch (ApplicationSettingsException e) { + } /*catch (ApplicationSettingsException e) { logger.error(e.getMessage(), e); throw new AiravataSecurityException("Error in reading OAuth configuration."); - } + }*/ } } diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java new file mode 100644 index 0000000000..371b35de93 --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java @@ -0,0 +1,92 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security; + +import org.apache.airavata.common.utils.Constants; +import org.apache.airavata.model.security.AuthzToken; +import org.apache.airavata.security.AiravataSecurityException; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.ConfigurationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceStub; +import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceException; +import org.wso2.carbon.utils.CarbonUtils; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.Map; + +/** + * This enforces XACML based fine grained authorization on the API calls. + */ +public class DefaultXACMLPEP { + + private final static Logger logger = LoggerFactory.getLogger(DefaultXACMLPEP.class); + private EntitlementServiceStub entitlementServiceStub; + + public DefaultXACMLPEP(String auhorizationServerURL, String username, String password, + ConfigurationContext configCtx) throws AiravataSecurityException { + try { + + String PDPURL = auhorizationServerURL + "EntitlementService"; + entitlementServiceStub = new EntitlementServiceStub(configCtx, PDPURL); + CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, entitlementServiceStub._getServiceClient()); + } catch (AxisFault e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error initializing XACML PEP client."); + } + + } + + /** + * Send the XACML authorization request to XAML PDP and return the authorization decision. + * + * @param authzToken + * @param metaData + * @return + */ + public boolean getAuthorizationDecision(AuthzToken authzToken, Map metaData) throws AiravataSecurityException { + String decision; + try { + String subject = authzToken.getClaimsMap().get(Constants.USER_NAME); + String action = "/airavata/" + metaData.get(Constants.API_METHOD_NAME); + String decisionString = entitlementServiceStub.getDecisionByAttributes(subject, null, action, null); + //parse the XML decision string and obtain the decision + + if ("NotApplicable".equals(decision) || "Indeterminate".equals(decision) || decision == null) { + logger.error("Authorization Decision is: " + decision); + throw new AiravataSecurityException("Error in authorizing the user."); + } + } catch (RemoteException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in authorizing the user."); + } catch (EntitlementServiceException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in authorizing the user."); + } + return Boolean.valueOf(decision); + } + + private String parseDecisionString(String decisionString) { + + } +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java index cf8f7e2797..ff47e5ae30 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java @@ -19,9 +19,11 @@ * */ package org.apache.airavata.api.server.security; + import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.common.utils.Constants; import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.error.AuthorizationException; import org.apache.airavata.model.security.AuthzToken; @@ -29,17 +31,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Map; + /** * Interceptor of Airavata API calls for the purpose of applying security. */ -public class SecurityInterceptor implements MethodInterceptor{ +public class SecurityInterceptor implements MethodInterceptor { private final static Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class); + @Override public Object invoke(MethodInvocation invocation) throws Throwable { //obtain the authz token from the input parameters AuthzToken authzToken = (AuthzToken) invocation.getArguments()[0]; //authorize the API call - authorize(authzToken); + System.out.println("METHOD NAME: " + invocation.getMethod().getName()); + HashMap metaDataMap = new HashMap(); + metaDataMap.put(Constants.API_METHOD_NAME, invocation.getMethod().getName()); + authorize(authzToken, metaDataMap); //set the user identity info in a thread local to be used in downstream execution. IdentityContext.set(authzToken); //let the method call procees upon successful authorization @@ -49,13 +58,13 @@ public Object invoke(MethodInvocation invocation) throws Throwable { return returnObj; } - private void authorize(AuthzToken authzToken) throws AuthorizationException { + private void authorize(AuthzToken authzToken, Map metaData) throws AuthorizationException { try { boolean isAPISecured = ServerSettings.isAPISecured(); if (isAPISecured) { AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); - boolean isAuthz = securityManager.isUserAuthorized(authzToken); + boolean isAuthz = securityManager.isUserAuthorized(authzToken, metaData); if (!isAuthz) { throw new AuthorizationException("User is not authenticated or authorized."); } diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java index a2d032fc92..e373316517 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java @@ -42,4 +42,10 @@ public final class Constants { public static final String KEYSTORE_PATH = "keystore.path"; public static final String KEYSTORE_PASSWORD = "keystore.password"; public static final String TLS_CLIENT_TIMEOUT = "TLS.client.timeout"; + public static final String API_METHOD_NAME = "api.method.name"; + + //Names of the attributes that could be passed in the AuthzToken's claims map. + public static final String USER_NAME = "userName"; + public static final String EMAIL = "email"; + public static final String ROLE = "role"; } diff --git a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml new file mode 100644 index 0000000000..7aa42fe9c9 --- /dev/null +++ b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml @@ -0,0 +1,62 @@ + + + + + + + + /airavata/* + + + + + + + + admin + + + + + + + + + + ^(?:(?! + /airavata/addGateway| + /airavata/deleteteway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/getExperimentStatistics).)*$\r?\n? + + + + + + + + + Internal/everyone + + + + + + + From d3ac7ceb611b3ed853e828c8492927020aacc72a Mon Sep 17 00:00:00 2001 From: hasinitg Date: Sat, 1 Aug 2015 16:01:13 +0530 Subject: [PATCH 4/9] adding XACML based fine grained authorization on API calls. --- .../api/server/security/DefaultXACMLPEP.java | 54 ++++++++++++++++--- .../server/security/SecurityInterceptor.java | 1 - .../src/main/assembly/bin-assembly.xml | 1 + .../airavata/common/utils/Constants.java | 6 +++ .../airavata-default-xacml-policy.xml | 33 +++++++----- .../airavata/secure/sample/SecureClient.java | 5 +- 6 files changed, 79 insertions(+), 21 deletions(-) diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java index 371b35de93..e61904cb3b 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java @@ -27,10 +27,21 @@ import org.apache.axis2.context.ConfigurationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceStub; import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceException; import org.wso2.carbon.utils.CarbonUtils; +import org.xml.sax.SAXException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.rmi.Remote; import java.rmi.RemoteException; import java.util.Map; @@ -71,10 +82,15 @@ public boolean getAuthorizationDecision(AuthzToken authzToken, Map metaDataMap = new HashMap(); metaDataMap.put(Constants.API_METHOD_NAME, invocation.getMethod().getName()); authorize(authzToken, metaDataMap); diff --git a/distribution/src/main/assembly/bin-assembly.xml b/distribution/src/main/assembly/bin-assembly.xml index c00c2e20cd..e399291690 100644 --- a/distribution/src/main/assembly/bin-assembly.xml +++ b/distribution/src/main/assembly/bin-assembly.xml @@ -105,6 +105,7 @@ gsissh.properties airavata.jks client_truststore.jks + airavata-default-xacml-policy.xml diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java index e373316517..215a31324b 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java @@ -44,6 +44,12 @@ public final class Constants { public static final String TLS_CLIENT_TIMEOUT = "TLS.client.timeout"; public static final String API_METHOD_NAME = "api.method.name"; + //constants in XACML authorization response. + public static final String NOT_APPLICABLE = "NotApplicable"; + public static final String INDETERMINATE = "Indeterminate"; + public static final String DENY = "Deny"; + public static final String PERMIT = "Permit"; + //Names of the attributes that could be passed in the AuthzToken's claims map. public static final String USER_NAME = "userName"; public static final String EMAIL = "email"; diff --git a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml index 7aa42fe9c9..ab3208d385 100644 --- a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml +++ b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml @@ -1,4 +1,4 @@ - @@ -29,18 +29,25 @@ ^(?:(?! - /airavata/addGateway| - /airavata/deleteteway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/updateGateway| - /airavata/getExperimentStatistics).)*$\r?\n? - +/airavata/addGateway| +/airavata/deleteteway| +/airavata/updateGateway| +/airavata/registerApplicationModule| +/airavata/deleteApplicationModule| +/airavata/getAllApplicationInterfaces| +/airavata/updateApplicationInterface| +/airavata/deleteApplicationInterface| +/airavata/getAllComputeResourceNames| +/airavata/getAllApplicationDeployments| +/airavata/updateApplicationDeployment| +/airavata/registerApplicationDeployment| +/airavata/deleteApplicationDeployment| +/airavata/getAllAppModules| +/airavata/getApplicationInterface| +/airavata/getApplicationInputs| +/airavata/getApplicationOutputs| +/airavata/getExperimentStatistics).)*$\r?\n? + diff --git a/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java b/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java index 36b1783423..ac34c18b67 100644 --- a/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java +++ b/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java @@ -151,6 +151,9 @@ public static void main(String[] args) throws Exception { } } else if (grantType == 2) { System.out.println("Obtaining OAuth access token via 'Client Credential' grant type...' grant type...."); + System.out.println("Please enter the user name to be passed: "); + String userNameInput = scanner.next(); + userName = userNameInput.trim(); } /***************************** Finish obtaining input from user*******************************************/ @@ -176,7 +179,7 @@ public static void main(String[] args) throws Exception { AuthzToken authzToken = new AuthzToken(); authzToken.setAccessToken(acTk); Map claimsMap = new HashMap<>(); - claimsMap.put("userName", "hasinitg"); + claimsMap.put("userName", userName); claimsMap.put("email", "hasini@gmail.com"); authzToken.setClaimsMap(claimsMap); String version = client.getAPIVersion(authzToken); From 4226a2db00aec8ba0abb84e722bcb9767f0c96fa Mon Sep 17 00:00:00 2001 From: hasinitg Date: Sat, 1 Aug 2015 20:56:51 +0530 Subject: [PATCH 5/9] Updated the secure-client sample to showcase the XACML based authorization on API calls and fixed some issues found when running the sample. --- .../api/server/security/DefaultXACMLPEP.java | 7 +-- .../server/security/SecurityInterceptor.java | 3 ++ .../airavata/secure/sample/SecureClient.java | 43 ++++++++++++++++--- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java index e61904cb3b..b60069cf84 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java @@ -83,13 +83,10 @@ public boolean getAuthorizationDecision(AuthzToken authzToken, Map metaData) thro try { boolean isAPISecured = ServerSettings.isAPISecured(); if (isAPISecured) { + //check in the cache + //if not in the cache, perform authorization with the authorization server AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); boolean isAuthz = securityManager.isUserAuthorized(authzToken, metaData); if (!isAuthz) { throw new AuthorizationException("User is not authenticated or authorized."); } + //put the successful authorization decision in the cache } } catch (AiravataSecurityException e) { logger.error(e.getMessage(), e); diff --git a/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java b/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java index ac34c18b67..890aa99a4c 100644 --- a/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java +++ b/samples/java-client/secure-client/src/main/java/org/apache/airavata/secure/sample/SecureClient.java @@ -24,6 +24,7 @@ import org.apache.airavata.model.error.*; import org.apache.airavata.api.Airavata; import org.apache.airavata.model.security.AuthzToken; +import org.apache.airavata.model.workspace.Gateway; import org.apache.airavata.security.AiravataSecurityException; import org.apache.axis2.AxisFault; import org.apache.axis2.context.ConfigurationContext; @@ -109,6 +110,7 @@ public static void main(String[] args) throws Exception { throw e; } } else if (option == 2) { + System.out.println(""); System.out.println("Enter Consumer Id: "); consumerId = scanner.next().trim(); System.out.println("Enter Consumer Secret: "); @@ -117,6 +119,7 @@ public static void main(String[] args) throws Exception { //obtain OAuth access token /************************Start obtaining input from user*****************************/ + System.out.println(""); System.out.println("Please select the preferred grant type: (or press d to use the default option" + Properties.grantType + ")"); System.out.println("1. Resource Owner Password Credential."); System.out.println("2. Client Credential."); @@ -150,10 +153,12 @@ public static void main(String[] args) throws Exception { password = passwordInput.trim(); } } else if (grantType == 2) { - System.out.println("Obtaining OAuth access token via 'Client Credential' grant type...' grant type...."); + System.out.println(""); System.out.println("Please enter the user name to be passed: "); String userNameInput = scanner.next(); userName = userNameInput.trim(); + System.out.println(""); + System.out.println("Obtaining OAuth access token via 'Client Credential' grant type...' grant type...."); } /***************************** Finish obtaining input from user*******************************************/ @@ -161,10 +166,11 @@ public static void main(String[] args) throws Exception { //obtain the OAuth token for the specified end user. String accessToken = new OAuthTokenRetrievalClient().retrieveAccessToken(consumerId, consumerSecret, userName, password, grantType); - System.out.println("OAuth access token is: " + accessToken); System.out.println(""); + System.out.println("OAuth access token is: " + accessToken); //invoke Airavata API by the SecureClient, on behalf of the user. + System.out.println(""); System.out.println("Invoking Airavata API..."); System.out.println("Enter the access token to be used: (default:" + accessToken + ", press 'd' to use default value.)"); String accessTokenInput = scanner.next(); @@ -175,6 +181,14 @@ public static void main(String[] args) throws Exception { acTk = accessTokenInput.trim(); } + //obtain as input, the method to be invoked + System.out.println(""); + System.out.println("Enter the number corresponding to the method to be invoked: "); + System.out.println("1. getAPIVersion"); + System.out.println("2. addGateway"); + String methodNumberString = scanner.next(); + int methodNumber = Integer.valueOf(methodNumberString.trim()); + Airavata.Client client = createAiravataClient(Properties.SERVER_HOST, Properties.SERVER_PORT); AuthzToken authzToken = new AuthzToken(); authzToken.setAccessToken(acTk); @@ -182,9 +196,28 @@ public static void main(String[] args) throws Exception { claimsMap.put("userName", userName); claimsMap.put("email", "hasini@gmail.com"); authzToken.setClaimsMap(claimsMap); - String version = client.getAPIVersion(authzToken); - System.out.println("Airavata API version: " + version); - System.out.println(""); + if (methodNumber == 1) { + + String version = client.getAPIVersion(authzToken); + System.out.println(""); + System.out.println("Airavata API version: " + version); + System.out.println(""); + + } else if (methodNumber == 2) { + System.out.println(""); + System.out.println("Enter the gateway id: "); + String gatewayId = scanner.next().trim(); + + Gateway gateway = new Gateway(gatewayId); + gateway.setDomain("airavata.org"); + gateway.setEmailAddress("airavata@apache.org"); + gateway.setGatewayName("airavataGW"); + String output = client.addGateway(authzToken, gateway); + System.out.println(""); + System.out.println("Output of addGateway: " + output); + System.out.println(""); + + } } catch (InvalidRequestException e) { e.printStackTrace(); } catch (TException e) { From c3652607aff77da6dc4dd6ab039ada78aa836c79 Mon Sep 17 00:00:00 2001 From: hasinitg Date: Wed, 5 Aug 2015 14:04:41 +0530 Subject: [PATCH 6/9] updated the default-xacml-policy with a new rule for admin-read-only role, finished identifying including all admin methods in the policy and updated the sample client to demonstrate the latest updates to the authorization policy. --- .../airavata-default-xacml-policy.xml | 98 ++++++++++++++++++- .../airavata/secure/sample/SecureClient.java | 18 +++- 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml index ab3208d385..b0ca91e1b9 100644 --- a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml +++ b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml @@ -23,6 +23,64 @@ + + + + + + ^(?:(?! +/airavata/addGateway| +/airavata/deleteteway| +/airavata/updateGateway| +/airavata/registerApplicationModule| +/airavata/deleteApplicationModule| +/airavata/updateApplicationInterface| +/airavata/deleteApplicationInterface| +/airavata/updateApplicationDeployment| +/airavata/registerApplicationDeployment| +/airavata/deleteApplicationDeployment| +/airavata/updateComputeResource| +/airavata/registerComputeResource| +/airavata/deleteBatchQueue| +/airavata/updateResourceJobManager| +/airavata/addLocalSubmissionDetails| +/airavata/updateResourceJobManager| +/airavaa/updateSSHJobSubmissionDetails| +/airavata/addSSHJobSubmissionDetails| +/airavata/updateUnicoreJobSubmissionDetails| +/airavata/addUNICOREJobSubmissionDetails| +/airavata/addLocalDataMovementDetails| +/airavata/updateSCPDataMovementDetails| +/airavata/addSCPDataMovementDetails| +/airavata/updateGridFTPDataMovementDetails| +/airavata/addGridFTPDataMovementDetails| +/airavata/updateUnicoreDataMovementDetails| +/airavata/addUnicoreDataMovementDetails| +/airavata/deleteJobSubmissionInterface| +/airavata/deleteDataMovementInterface| +/airavata/deleteComputeResource| +/airavata/updateGatewayResourceProfile| +/airavata/registerGatewayResourceProfile| +/airavata/addGatewayComputeResourcePreference| +/airavata/deleteGatewayResourceProfile| +/airavata/deleteGatewayComputeResourcePreference).)*$\r?\n? + + + + + + + + + admin_read_only + + + + @@ -30,6 +88,7 @@ ^(?:(?! /airavata/addGateway| +/airavata/getExperimentStatistics| /airavata/deleteteway| /airavata/updateGateway| /airavata/registerApplicationModule| @@ -46,7 +105,44 @@ /airavata/getApplicationInterface| /airavata/getApplicationInputs| /airavata/getApplicationOutputs| -/airavata/getExperimentStatistics).)*$\r?\n? +/airavata/updateComputeResource| +/airavata/getComputeResource| +/airavata/registerComputeResource| +/airavata/deleteBatchQueue| +/airavata/getLocalJobSubmission| +/airavata/updateResourceJobManager| +/airavata/addLocalSubmissionDetails| +/airavata/getSSHJobSubmission| +/airavata/updateResourceJobManager| +/airavata/getresourceJobManager| +/airavaa/updateSSHJobSubmissionDetails| +/airavata/addSSHJobSubmissionDetails| +/airavata/getUnicoreJobSubmission| +/airavata/updateUnicoreJobSubmissionDetails| +/airavata/addUNICOREJobSubmissionDetails| +/airavata/addLocalDataMovementDetails| +/airavata/updateSCPDataMovementDetails| +/airavata/addSCPDataMovementDetails| +/airavata/updateGridFTPDataMovementDetails| +/airavata/addGridFTPDataMovementDetails| +/airavata/updateUnicoreDataMovementDetails| +/airavata/addUnicoreDataMovementDetails| +/airavata/getCloudJobSubmission| +/airavata/getSCPDataMovement| +/airavata/getGridFTPDataMovement| +/airavata/getUnicoreDataMovement| +/airavata/deleteJobSubmissionInterface| +/airavata/deleteDataMovementInterface| +/airavata/deleteComputeResource| +/airavata/updateGatewayResourceProfile| +/airavata/registerGatewayResourceProfile| +/airavata/getAllGateways| +/airavata/getGateway| +/airavata/getAllGatewayComputeResources| +/airavata/addGatewayComputeResourcePreference| +/airavata/deleteGatewayResourceProfile| +/airavata/deleteGatewayComputeResourcePreference| +/airavata/getAvailableAppInterfaceComputeResources).)*$\r?\n? appModules= client.getAllAppModules(authzToken, gatewayId); + System.out.println("Output of getAllAppModuels: "); + for (ApplicationModule appModule : appModules) { + System.out.println(appModule.getAppModuleName()); + } + System.out.println(""); + System.out.println(""); + } else if (methodNumber == 3) { + System.out.println(""); + System.out.println("Enter the gateway id: "); + String gatewayId = scanner.next().trim(); + Gateway gateway = new Gateway(gatewayId); gateway.setDomain("airavata.org"); gateway.setEmailAddress("airavata@apache.org"); From 59f4acda5c600cb7c11a645fba1bacb4bad27e16 Mon Sep 17 00:00:00 2001 From: hasinitg Date: Sat, 8 Aug 2015 01:21:08 +0530 Subject: [PATCH 7/9] added PAP client in Airavata Server, which publishes and enables the default XACML authorization policy in the XACML authorization server, at the airavata server startup - if the security is enabled. --- airavata-api/airavata-api-server/pom.xml | 5 + .../api/server/AiravataAPIServer.java | 10 ++ .../security/AiravataSecurityManager.java | 13 ++ .../DefaultAiravataSecurityManager.java | 56 +++++++- .../api/server/security/DefaultPAPClient.java | 126 ++++++++++++++++++ .../api/server/security/DefaultXACMLPEP.java | 3 +- .../airavata/common/utils/Constants.java | 2 + .../airavata/common/utils/ServerSettings.java | 6 +- .../airavata-default-xacml-policy.xml | 2 +- .../main/resources/airavata-server.properties | 1 + 10 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java diff --git a/airavata-api/airavata-api-server/pom.xml b/airavata-api/airavata-api-server/pom.xml index 543bbaa787..e78ff9d688 100644 --- a/airavata-api/airavata-api-server/pom.xml +++ b/airavata-api/airavata-api-server/pom.xml @@ -112,6 +112,11 @@ org.wso2.carbon.identity.entitlement.stub 4.2.1 + + org.wso2.carbon + org.wso2.carbon.identity.entitlement.common + 4.2.1 + com.google.inject guice diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java index 1b336e1ab8..c06cd39972 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java @@ -27,6 +27,8 @@ import org.apache.airavata.api.Airavata; import org.apache.airavata.api.server.handler.AiravataServerHandler; +import org.apache.airavata.api.server.security.AiravataSecurityManager; +import org.apache.airavata.api.server.security.SecurityManagerFactory; import org.apache.airavata.api.server.security.SecurityModule; import org.apache.airavata.api.server.util.AppCatalogInitUtil; import org.apache.airavata.api.server.util.Constants; @@ -38,6 +40,7 @@ import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.error.AiravataErrorType; import org.apache.airavata.model.error.AiravataSystemException; +import org.apache.airavata.security.AiravataSecurityException; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TThreadPoolServer; import org.apache.thrift.transport.TServerSocket; @@ -145,6 +148,10 @@ public void run() { }.start(); logger.info("Airavata API server starter over TLS on Port: " + ServerSettings.getTLSServerPort()); } + //perform any security related initialization at the server startup, according to the security manager being used. + AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); + securityManager.initializeSecurityInfra(); + } catch (TTransportException e) { logger.error(e.getMessage()); setStatus(ServerStatus.FAILED); @@ -156,6 +163,9 @@ public void run() { } catch (UnknownHostException e) { logger.error(e.getMessage(), e); throw new AiravataSystemException(AiravataErrorType.INTERNAL_ERROR); + } catch (AiravataSecurityException e) { + logger.error(e.getMessage(), e); + throw new AiravataSystemException(AiravataErrorType.INTERNAL_ERROR); } } public static void main(String[] args) { diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java index 37c348ce54..924557683a 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java @@ -26,5 +26,18 @@ import java.util.Map; public interface AiravataSecurityManager { + /** + * Implement this method in your SecurityManager to perform necessary initializations at the server startup. + * @throws AiravataSecurityException + */ + public void initializeSecurityInfra() throws AiravataSecurityException; + + /** + * Implement this method with the user authentication/authorization logic in your SecurityManager. + * @param authzToken : this includes OAuth token and user's claims + * @param metaData : this includes other meta data needed for security enforcements. + * @return + * @throws AiravataSecurityException + */ public boolean isUserAuthorized(AuthzToken authzToken, Map metaData) throws AiravataSecurityException; } diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java index 623031089f..532f9f608f 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java @@ -21,6 +21,7 @@ package org.apache.airavata.api.server.security; import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.common.utils.Constants; import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.security.AuthzToken; import org.apache.airavata.security.AiravataSecurityException; @@ -32,6 +33,7 @@ import org.slf4j.LoggerFactory; import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationResponseDTO; +import java.io.*; import java.util.Map; /** @@ -40,6 +42,56 @@ public class DefaultAiravataSecurityManager implements AiravataSecurityManager { private final static Logger logger = LoggerFactory.getLogger(DefaultAiravataSecurityManager.class); + @Override + public void initializeSecurityInfra() throws AiravataSecurityException { + /* in the default security manager, this method checks if the xacml authorization policy is published, + * and if not, publish the policy to the PDP (of WSO2 Identity Server) + */ + try { + if (ServerSettings.isAPISecured()) { + + ConfigurationContext configContext = + ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); + //initialize SSL context with the trust store that contains the public cert of WSO2 Identity Server. + TrustStoreManager trustStoreManager = new TrustStoreManager(); + trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), + ServerSettings.getTrustStorePassword()); + DefaultPAPClient PAPClient = new DefaultPAPClient(ServerSettings.getRemoteAuthzServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + boolean policyAdded = PAPClient.isPolicyAdded(ServerSettings.getAuthorizationPoliyName()); + if (policyAdded) { + logger.info("Authorization policy is already added in the authorization server."); + } else { + //read the policy as a string + BufferedReader bufferedReader = new BufferedReader(new FileReader(new File( + ServerSettings.getAuthorizationPoliyName() + ".xml"))); + String line; + StringBuilder stringBuilder = new StringBuilder(); + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + //publish the policy and enable it in a separate thread + PAPClient.addPolicy(stringBuilder.toString()); + } + } + + } catch (AxisFault axisFault) { + logger.error(axisFault.getMessage(), axisFault); + throw new AiravataSecurityException("Error in initializing the configuration context for creating the " + + "PAP client."); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in reading configuration when creating the PAP client."); + } catch (FileNotFoundException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in reading authorization policy."); + } catch (IOException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in reading the authorization policy."); + } + + } + public boolean isUserAuthorized(AuthzToken authzToken, Map metaData) throws AiravataSecurityException { try { ConfigurationContext configContext = @@ -50,13 +102,13 @@ public boolean isUserAuthorized(AuthzToken authzToken, Map metaD trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), ServerSettings.getTrustStorePassword()); - DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteOauthServerUrl(), + DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteAuthzServerUrl(), ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); OAuth2TokenValidationResponseDTO validationResponse = oauthClient.validateAccessToken( authzToken.getAccessToken()); boolean isOAuthTokenValid = validationResponse.getValid(); //if XACML based authorization is enabled, check for role based authorization for the API invocation - DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteOauthServerUrl(), + DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteAuthzServerUrl(), ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); boolean authorizationDecision = entitlementClient.getAuthorizationDecision(authzToken, metaData); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java new file mode 100644 index 0000000000..b75129c2ed --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java @@ -0,0 +1,126 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security; + +import com.sun.corba.se.spi.activation.Server; +import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.common.utils.ServerSettings; +import org.apache.airavata.security.AiravataSecurityException; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.ConfigurationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.wso2.carbon.identity.entitlement.stub.EntitlementPolicyAdminServiceStub; +import org.wso2.carbon.identity.entitlement.stub.dto.PaginatedStatusHolder; +import org.wso2.carbon.identity.entitlement.stub.dto.PolicyDTO; +import org.wso2.carbon.identity.entitlement.stub.dto.StatusHolder; +import org.wso2.carbon.identity.entitlement.common.EntitlementConstants; +import org.wso2.carbon.identity.entitlement.stub.EntitlementPolicyAdminServiceEntitlementException; +import org.wso2.carbon.utils.CarbonUtils; + +import java.rmi.RemoteException; + +/** + * This publishes the airavata-default-xacml-policy.xml to the PDP via PAP API (of WSO2 Identity Server) + */ +public class DefaultPAPClient { + + private final static Logger logger = LoggerFactory.getLogger(DefaultPAPClient.class); + private EntitlementPolicyAdminServiceStub entitlementPolicyAdminServiceStub; + + public DefaultPAPClient(String auhorizationServerURL, String username, String password, + ConfigurationContext configCtx) throws AiravataSecurityException { + try { + + String PDPURL = auhorizationServerURL + "EntitlementPolicyAdminService"; + entitlementPolicyAdminServiceStub = new EntitlementPolicyAdminServiceStub(configCtx, PDPURL); + CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, + entitlementPolicyAdminServiceStub._getServiceClient()); + } catch (AxisFault e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error initializing XACML PEP client."); + } + + } + + public boolean isPolicyAdded(String policyName) { + try { + PolicyDTO policyDTO = entitlementPolicyAdminServiceStub.getPolicy(policyName, false); + } catch (RemoteException e) { + logger.debug("Error in retrieving the policy.", e); + return false; + } catch (EntitlementPolicyAdminServiceEntitlementException e) { + logger.debug("Error in retrieving the policy.", e); + return false; + } + return true; + } + + public void addPolicy(String policy) throws AiravataSecurityException { + new Thread() { + public void run() { + try { + PolicyDTO policyDTO = new PolicyDTO(); + policyDTO.setPolicy(policy); + entitlementPolicyAdminServiceStub.addPolicy(policyDTO); + entitlementPolicyAdminServiceStub.publishToPDP(new String[]{ServerSettings.getAuthorizationPoliyName()}, + EntitlementConstants.PolicyPublish.ACTION_CREATE, null, false, 0); + + //Since policy publishing happens asynchronously, we need to retrieve the status and verify. + Thread.sleep(2000); + PaginatedStatusHolder paginatedStatusHolder = entitlementPolicyAdminServiceStub. + getStatusData(EntitlementConstants.Status.ABOUT_POLICY, ServerSettings.getAuthorizationPoliyName(), + EntitlementConstants.StatusTypes.PUBLISH_POLICY, "*", 1); + StatusHolder statusHolder = paginatedStatusHolder.getStatusHolders()[0]; + if (statusHolder.getSuccess() && EntitlementConstants.PolicyPublish.ACTION_CREATE.equals(statusHolder.getTargetAction())) { + logger.info("Authorization policy is published successfully."); + } else { + throw new AiravataSecurityException("Failed to publish the authorization policy."); + } + + //enable the published policy + entitlementPolicyAdminServiceStub.enableDisablePolicy(ServerSettings.getAuthorizationPoliyName(), true); + //Since policy enabling happens asynchronously, we need to retrieve the status and verify. + Thread.sleep(2000); + paginatedStatusHolder = entitlementPolicyAdminServiceStub. + getStatusData(EntitlementConstants.Status.ABOUT_POLICY, ServerSettings.getAuthorizationPoliyName(), + EntitlementConstants.StatusTypes.PUBLISH_POLICY, "*", 1); + statusHolder = paginatedStatusHolder.getStatusHolders()[0]; + if (statusHolder.getSuccess() && EntitlementConstants.PolicyPublish.ACTION_ENABLE.equals(statusHolder.getTargetAction())) { + logger.info("Authorization policy is enabled successfully."); + } else { + throw new AiravataSecurityException("Failed to enable the authorization policy."); + } + } catch (RemoteException e) { + logger.error(e.getMessage(), e); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + } catch (AiravataSecurityException e) { + logger.error(e.getMessage(), e); + } catch (EntitlementPolicyAdminServiceEntitlementException e) { + logger.error(e.getMessage(), e); + } + } + }.start(); + } +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java index b60069cf84..71ced3aeb5 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java @@ -47,7 +47,8 @@ import java.util.Map; /** - * This enforces XACML based fine grained authorization on the API calls. + * This enforces XACML based fine grained authorization on the API calls, by authorizing the API calls + * through default PDP which is WSO2 Identity Server. */ public class DefaultXACMLPEP { diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java index 215a31324b..af8ca9670d 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java @@ -50,6 +50,8 @@ public final class Constants { public static final String DENY = "Deny"; public static final String PERMIT = "Permit"; + public static final String AUTHORIZATION_POLICY_NAME = "authorization.policy"; + //Names of the attributes that could be passed in the AuthzToken's claims map. public static final String USER_NAME = "userName"; public static final String EMAIL = "email"; diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java index b898d962a7..d87da70138 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java @@ -269,7 +269,7 @@ public static boolean isAPISecured() throws ApplicationSettingsException { return Boolean.valueOf(getSetting(Constants.IS_API_SECURED)); } - public static String getRemoteOauthServerUrl() throws ApplicationSettingsException { + public static String getRemoteAuthzServerUrl() throws ApplicationSettingsException { return getSetting(Constants.REMOTE_OAUTH_SERVER_URL); } @@ -281,6 +281,10 @@ public static String getAdminPassword() throws ApplicationSettingsException { return getSetting(Constants.ADMIN_PASSWORD); } + public static String getAuthorizationPoliyName() throws ApplicationSettingsException{ + return getSetting(Constants.AUTHORIZATION_POLICY_NAME); + } + public static String getZookeeperConnection() throws ApplicationSettingsException { return getSetting(ZOOKEEPER_SERVER_CONNECTION, "localhost:2181"); } diff --git a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml index b0ca91e1b9..a8fbf4cb44 100644 --- a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml +++ b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml @@ -1,4 +1,4 @@ - diff --git a/modules/configuration/server/src/main/resources/airavata-server.properties b/modules/configuration/server/src/main/resources/airavata-server.properties index 004593599d..58a42a3ae8 100644 --- a/modules/configuration/server/src/main/resources/airavata-server.properties +++ b/modules/configuration/server/src/main/resources/airavata-server.properties @@ -237,5 +237,6 @@ keystore.password=airavata trust.store=client_truststore.jks trust.store.password=airavata remote.oauth.authorization.server=https://localhost:9443/services/ +authorization.policy=airavata-default-xacml-policy admin.user.name=admin admin.password=admin \ No newline at end of file From 27774766f502f2e62c288c5bce0f8980926a7741 Mon Sep 17 00:00:00 2001 From: hasinitg Date: Sun, 16 Aug 2015 09:39:56 -0400 Subject: [PATCH 8/9] Adding the Authorization caching implementation. This completes the security solution implementation in the Airavata source code. --- .../api/server/AiravataAPIServer.java | 7 +- .../server/handler/AiravataServerHandler.java | 5 +- .../DefaultAiravataSecurityManager.java | 102 +++++++++++--- .../api/server/security/DefaultXACMLPEP.java | 132 ------------------ .../security/SecurityManagerFactory.java | 7 +- .../security/authzcache/AuthzCache.java | 61 ++++++++ .../authzcache/AuthzCachedStatus.java | 34 +++++ .../{ => interceptor}/SecurityCheck.java | 2 +- .../SecurityInterceptor.java | 9 +- .../{ => interceptor}/SecurityModule.java | 4 +- .../{ => oauth}/DefaultOAuthClient.java | 6 +- .../{ => xacml}/DefaultPAPClient.java | 3 +- .../airavata/common/utils/Constants.java | 7 + .../airavata/common/utils/ServerSettings.java | 82 ++++++----- .../main/resources/airavata-server.properties | 10 ++ 15 files changed, 260 insertions(+), 211 deletions(-) delete mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCachedStatus.java rename airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/{ => interceptor}/SecurityCheck.java (95%) rename airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/{ => interceptor}/SecurityInterceptor.java (92%) rename airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/{ => interceptor}/SecurityModule.java (86%) rename airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/{ => oauth}/DefaultOAuthClient.java (93%) rename airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/{ => xacml}/DefaultPAPClient.java (98%) diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java index c06cd39972..ca4e345924 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/AiravataAPIServer.java @@ -29,13 +29,11 @@ import org.apache.airavata.api.server.handler.AiravataServerHandler; import org.apache.airavata.api.server.security.AiravataSecurityManager; import org.apache.airavata.api.server.security.SecurityManagerFactory; -import org.apache.airavata.api.server.security.SecurityModule; +import org.apache.airavata.api.server.security.interceptor.SecurityModule; import org.apache.airavata.api.server.util.AppCatalogInitUtil; import org.apache.airavata.api.server.util.Constants; import org.apache.airavata.api.server.util.RegistryInitUtil; import org.apache.airavata.common.exception.ApplicationSettingsException; -import org.apache.airavata.common.utils.AiravataUtils; -import org.apache.airavata.common.utils.AiravataZKUtils; import org.apache.airavata.common.utils.IServer; import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.error.AiravataErrorType; @@ -148,7 +146,8 @@ public void run() { }.start(); logger.info("Airavata API server starter over TLS on Port: " + ServerSettings.getTLSServerPort()); } - //perform any security related initialization at the server startup, according to the security manager being used. + /*perform any security related initialization at the server startup, according to the underlying security + manager implementation being used.*/ AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); securityManager.initializeSecurityInfra(); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java index 600c6947fa..7531fae57b 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java @@ -23,9 +23,7 @@ import org.apache.airavata.api.Airavata; import org.apache.airavata.api.airavataAPIConstants; -import org.apache.airavata.api.server.security.AiravataSecurityManager; -import org.apache.airavata.api.server.security.SecurityCheck; -import org.apache.airavata.api.server.security.SecurityManagerFactory; +import org.apache.airavata.api.server.security.interceptor.SecurityCheck; import org.apache.airavata.common.exception.AiravataException; import org.apache.airavata.common.exception.ApplicationSettingsException; import org.apache.airavata.common.utils.AiravataUtils; @@ -105,7 +103,6 @@ import org.apache.airavata.registry.cpi.ResultOrderType; import org.apache.airavata.registry.cpi.WorkflowCatalog; import org.apache.airavata.registry.cpi.utils.Constants; -import org.apache.airavata.security.AiravataSecurityException; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java index 532f9f608f..f42d98dd1e 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java @@ -20,6 +20,10 @@ */ package org.apache.airavata.api.server.security; +import org.apache.airavata.api.server.security.authzcache.*; +import org.apache.airavata.api.server.security.oauth.DefaultOAuthClient; +import org.apache.airavata.api.server.security.xacml.DefaultPAPClient; +import org.apache.airavata.api.server.security.xacml.DefaultXACMLPEP; import org.apache.airavata.common.exception.ApplicationSettingsException; import org.apache.airavata.common.utils.Constants; import org.apache.airavata.common.utils.ServerSettings; @@ -72,6 +76,7 @@ public void initializeSecurityInfra() throws AiravataSecurityException { } //publish the policy and enable it in a separate thread PAPClient.addPolicy(stringBuilder.toString()); + logger.info("Authorization policy is published in the authorization server."); } } @@ -94,25 +99,84 @@ public void initializeSecurityInfra() throws AiravataSecurityException { public boolean isUserAuthorized(AuthzToken authzToken, Map metaData) throws AiravataSecurityException { try { - ConfigurationContext configContext = - ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); - - //initialize SSL context with the trust store that contains the public cert of WSO2 Identity Server. - TrustStoreManager trustStoreManager = new TrustStoreManager(); - trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), - ServerSettings.getTrustStorePassword()); - - DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteAuthzServerUrl(), - ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); - OAuth2TokenValidationResponseDTO validationResponse = oauthClient.validateAccessToken( - authzToken.getAccessToken()); - boolean isOAuthTokenValid = validationResponse.getValid(); - //if XACML based authorization is enabled, check for role based authorization for the API invocation - DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteAuthzServerUrl(), - ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); - boolean authorizationDecision = entitlementClient.getAuthorizationDecision(authzToken, metaData); - - return (isOAuthTokenValid && authorizationDecision); + //if the authz cache is enabled, check in the cache if the authz decision is cached and if so, what the status is + if (ServerSettings.isAuthzCacheEnabled()) { + //obtain an instance of AuthzCacheManager implementation. + AuthzCacheManager authzCacheManager = AuthzCacheManagerFactory.getAuthzCacheManager(); + //collect the necessary info for contructing the authz cache index + String subject = authzToken.getClaimsMap().get(Constants.USER_NAME); + String accessToken = authzToken.getAccessToken(); + String action = metaData.get(Constants.API_METHOD_NAME); + //check in the cache + AuthzCachedStatus authzCachedStatus = authzCacheManager.getAuthzCachedStatus( + new AuthzCacheIndex(subject, accessToken, action)); + + if (AuthzCachedStatus.AUTHORIZED.equals(authzCachedStatus)) { + //TODO: following info log is for demonstration purpose. change it to debug log. + logger.info("Authz decision for: (" + subject + ", " + accessToken + ", " + action + ") is retrieved from cache."); + return true; + } else if (AuthzCachedStatus.NOT_AUTHORIZED.equals(authzCachedStatus)) { + //TODO: following info log is for demonstration purpose. change it to debug log. + logger.info("Authz decision for: (" + subject + ", " + accessToken + ", " + action + ") is retrieved from cache."); + return false; + } else if (AuthzCachedStatus.NOT_CACHED.equals(authzCachedStatus)) { + //TODO: following info log is for demonstration purpose. change it to debug log. + logger.info("Authz decision for: (" + subject + ", " + accessToken + ", " + action + ") is not in the cache. " + + "Obtaining it from the authorization server."); + //talk to Authorization Server, obtain the decision, cache it and return the result. + ConfigurationContext configContext = + ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); + + //initialize SSL context with the trust store that contains the public cert of WSO2 Identity Server. + TrustStoreManager trustStoreManager = new TrustStoreManager(); + trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), + ServerSettings.getTrustStorePassword()); + + DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteAuthzServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + OAuth2TokenValidationResponseDTO validationResponse = oauthClient.validateAccessToken( + authzToken.getAccessToken()); + boolean isOAuthTokenValid = validationResponse.getValid(); + long expiryTimestamp = validationResponse.getExpiryTime(); + + //check for fine grained authorization for the API invocation, based on XACML. + DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteAuthzServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + boolean authorizationDecision = entitlementClient.getAuthorizationDecision(authzToken, metaData); + + boolean decision = isOAuthTokenValid && authorizationDecision; + + //cache the authorization decision + authzCacheManager.addToAuthzCache(new AuthzCacheIndex(subject, accessToken, action), + new AuthzCacheEntry(decision, expiryTimestamp)); + + return decision; + } else { + //undefined status returned from the authz cache manager + throw new AiravataSecurityException("Error in reading from the authorization cache."); + } + } else { + //talk to Authorization Server, obtain the decision and return the result (authz cache is not enabled). + ConfigurationContext configContext = + ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); + + //initialize SSL context with the trust store that contains the public cert of WSO2 Identity Server. + TrustStoreManager trustStoreManager = new TrustStoreManager(); + trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), + ServerSettings.getTrustStorePassword()); + + DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteAuthzServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + OAuth2TokenValidationResponseDTO validationResponse = oauthClient.validateAccessToken( + authzToken.getAccessToken()); + boolean isOAuthTokenValid = validationResponse.getValid(); + //if XACML based authorization is enabled, check for role based authorization for the API invocation + DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteAuthzServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + boolean authorizationDecision = entitlementClient.getAuthorizationDecision(authzToken, metaData); + + return (isOAuthTokenValid && authorizationDecision); + } } catch (AxisFault axisFault) { logger.error(axisFault.getMessage(), axisFault); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java deleted file mode 100644 index 71ced3aeb5..0000000000 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security; - -import org.apache.airavata.common.utils.Constants; -import org.apache.airavata.model.security.AuthzToken; -import org.apache.airavata.security.AiravataSecurityException; -import org.apache.axis2.AxisFault; -import org.apache.axis2.context.ConfigurationContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceStub; -import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceException; -import org.wso2.carbon.utils.CarbonUtils; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.util.Map; - -/** - * This enforces XACML based fine grained authorization on the API calls, by authorizing the API calls - * through default PDP which is WSO2 Identity Server. - */ -public class DefaultXACMLPEP { - - private final static Logger logger = LoggerFactory.getLogger(DefaultXACMLPEP.class); - private EntitlementServiceStub entitlementServiceStub; - - public DefaultXACMLPEP(String auhorizationServerURL, String username, String password, - ConfigurationContext configCtx) throws AiravataSecurityException { - try { - - String PDPURL = auhorizationServerURL + "EntitlementService"; - entitlementServiceStub = new EntitlementServiceStub(configCtx, PDPURL); - CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, entitlementServiceStub._getServiceClient()); - } catch (AxisFault e) { - logger.error(e.getMessage(), e); - throw new AiravataSecurityException("Error initializing XACML PEP client."); - } - - } - - /** - * Send the XACML authorization request to XAML PDP and return the authorization decision. - * - * @param authzToken - * @param metaData - * @return - */ - public boolean getAuthorizationDecision(AuthzToken authzToken, Map metaData) throws AiravataSecurityException { - String decision; - try { - String subject = authzToken.getClaimsMap().get(Constants.USER_NAME); - String action = "/airavata/" + metaData.get(Constants.API_METHOD_NAME); - String decisionString = entitlementServiceStub.getDecisionByAttributes(subject, null, action, null); - //parse the XML decision string and obtain the decision - decision = parseDecisionString(decisionString); - if (Constants.PERMIT.equals(decision)) { - return true; - } else { - logger.error("Authorization decision is: " + decision); - return false; - } - } catch (RemoteException e) { - logger.error(e.getMessage(), e); - throw new AiravataSecurityException("Error in authorizing the user."); - } catch (EntitlementServiceException e) { - logger.error(e.getMessage(), e); - throw new AiravataSecurityException("Error in authorizing the user."); - } - } - - /** - * This parses the XML based authorization response by the PDP and returns the decision string. - * - * @param decisionString - * @return - * @throws AiravataSecurityException - */ - private String parseDecisionString(String decisionString) throws AiravataSecurityException { - try { - DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); - InputStream inputStream = new ByteArrayInputStream(decisionString.getBytes("UTF-8")); - Document doc = docBuilderFactory.newDocumentBuilder().parse(inputStream); - Node resultNode = doc.getDocumentElement().getFirstChild(); - Node decisionNode = resultNode.getFirstChild(); - String decision = decisionNode.getTextContent(); - return decision; - } catch (ParserConfigurationException e) { - logger.error(e.getMessage(), e); - throw new AiravataSecurityException("Error in parsing XACML authorization response."); - } catch (UnsupportedEncodingException e) { - logger.error(e.getMessage(), e); - throw new AiravataSecurityException("Error in parsing XACML authorization response."); - } catch (SAXException e) { - logger.error(e.getMessage(), e); - throw new AiravataSecurityException("Error in parsing XACML authorization response."); - } catch (IOException e) { - logger.error("Error in parsing XACML authorization response."); - throw new AiravataSecurityException("Error in parsing XACML authorization response."); - } - } -} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityManagerFactory.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityManagerFactory.java index 0b376a7fff..dc03b638bb 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityManagerFactory.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityManagerFactory.java @@ -32,15 +32,12 @@ */ public class SecurityManagerFactory { private final static Logger logger = LoggerFactory.getLogger(SecurityManagerFactory.class); - private static Class secManagerImpl = null; public static AiravataSecurityManager getSecurityManager() throws AiravataSecurityException { try { - if(secManagerImpl == null){ - secManagerImpl = Class.forName(ServerSettings.getSecurityManagerClassName()); - } + Class secManagerImpl = Class.forName(ServerSettings.getSecurityManagerClassName()); AiravataSecurityManager securityManager = (AiravataSecurityManager) secManagerImpl.newInstance(); - return securityManager; + return securityManager; } catch (ClassNotFoundException e) { String error = "Security Manager class could not be found."; logger.error(e.getMessage(), e); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java new file mode 100644 index 0000000000..a563caa779 --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +import javax.management.MXBean; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.common.utils.ServerSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthzCache extends LinkedHashMap { + + private static int MAX_SIZE; + private final static Logger logger = LoggerFactory.getLogger(AuthzCache.class); + + private static AuthzCache authzCache = null; + + public static AuthzCache getInstance() throws ApplicationSettingsException { + if (authzCache == null) { + synchronized (AuthzCache.class) { + if (authzCache == null) { + authzCache = new AuthzCache(ServerSettings.getCacheSize()); + } + } + } + return authzCache; + } + + private AuthzCache(int initialCapacity) { + super(initialCapacity); + MAX_SIZE = initialCapacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + //TODO: following info log is for demonstration purposes. Remove it. + logger.info("Authz cache max size exceeded. Removing the old entries."); + return size() > MAX_SIZE; + } +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCachedStatus.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCachedStatus.java new file mode 100644 index 0000000000..e16626505f --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCachedStatus.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +/** + * This enum defines the status of the authorization cache returned by the authorization cache manager + * when an authorization status is checked against an authorization request. + */ +public enum AuthzCachedStatus { + /*Authorization decision is cached for the given authrization request and the decision authorizes the request.*/ + AUTHORIZED, + /*Authorization decision is cached for the given authorization request and the decision denies authorization.*/ + NOT_AUTHORIZED, + /*Authorization decision is not either cached or the cached entry is invalid such that re-authorization is needed.*/ + NOT_CACHED +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityCheck.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityCheck.java similarity index 95% rename from airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityCheck.java rename to airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityCheck.java index dc362113f0..d4b495265b 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityCheck.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityCheck.java @@ -18,7 +18,7 @@ * under the License. * */ -package org.apache.airavata.api.server.security; +package org.apache.airavata.api.server.security.interceptor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityInterceptor.java similarity index 92% rename from airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java rename to airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityInterceptor.java index 2d35b1b1f9..1b4f0ad4fc 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityInterceptor.java @@ -18,10 +18,13 @@ * under the License. * */ -package org.apache.airavata.api.server.security; +package org.apache.airavata.api.server.security.interceptor; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.apache.airavata.api.server.security.AiravataSecurityManager; +import org.apache.airavata.api.server.security.IdentityContext; +import org.apache.airavata.api.server.security.SecurityManagerFactory; import org.apache.airavata.common.exception.ApplicationSettingsException; import org.apache.airavata.common.utils.Constants; import org.apache.airavata.common.utils.ServerSettings; @@ -61,15 +64,11 @@ private void authorize(AuthzToken authzToken, Map metaData) thro try { boolean isAPISecured = ServerSettings.isAPISecured(); if (isAPISecured) { - //check in the cache - - //if not in the cache, perform authorization with the authorization server AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); boolean isAuthz = securityManager.isUserAuthorized(authzToken, metaData); if (!isAuthz) { throw new AuthorizationException("User is not authenticated or authorized."); } - //put the successful authorization decision in the cache } } catch (AiravataSecurityException e) { logger.error(e.getMessage(), e); diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityModule.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityModule.java similarity index 86% rename from airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityModule.java rename to airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityModule.java index 0b56221259..f30dc9b5a5 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityModule.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/interceptor/SecurityModule.java @@ -18,10 +18,12 @@ * under the License. * */ -package org.apache.airavata.api.server.security; +package org.apache.airavata.api.server.security.interceptor; import com.google.inject.matcher.Matchers; import com.google.inject.AbstractModule; +import org.apache.airavata.api.server.security.interceptor.SecurityCheck; +import org.apache.airavata.api.server.security.interceptor.SecurityInterceptor; /** * This does the plumbing work of integrating the interceptor with Guice framework for the methods to be diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/oauth/DefaultOAuthClient.java similarity index 93% rename from airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java rename to airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/oauth/DefaultOAuthClient.java index e1afacd8fa..74b36cffb2 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/oauth/DefaultOAuthClient.java @@ -18,12 +18,9 @@ * under the License. * */ -package org.apache.airavata.api.server.security; +package org.apache.airavata.api.server.security.oauth; -import org.apache.airavata.common.exception.ApplicationSettingsException; -import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.security.AiravataSecurityException; -import org.apache.airavata.security.util.TrustStoreManager; import org.apache.axis2.AxisFault; import org.apache.axis2.context.ConfigurationContext; import org.slf4j.Logger; @@ -34,7 +31,6 @@ import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationResponseDTO; import org.wso2.carbon.utils.CarbonUtils; -import javax.net.ssl.*; import java.rmi.RemoteException; /** diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultPAPClient.java similarity index 98% rename from airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java rename to airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultPAPClient.java index b75129c2ed..110d4d35f5 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultPAPClient.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultPAPClient.java @@ -18,9 +18,8 @@ * under the License. * */ -package org.apache.airavata.api.server.security; +package org.apache.airavata.api.server.security.xacml; -import com.sun.corba.se.spi.activation.Server; import org.apache.airavata.common.exception.ApplicationSettingsException; import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.security.AiravataSecurityException; diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java index af8ca9670d..dba05258b4 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java @@ -42,6 +42,7 @@ public final class Constants { public static final String KEYSTORE_PATH = "keystore.path"; public static final String KEYSTORE_PASSWORD = "keystore.password"; public static final String TLS_CLIENT_TIMEOUT = "TLS.client.timeout"; + public static final String API_METHOD_NAME = "api.method.name"; //constants in XACML authorization response. @@ -52,6 +53,12 @@ public final class Constants { public static final String AUTHORIZATION_POLICY_NAME = "authorization.policy"; + public static final String AUTHZ_CACHE_MANAGER_CLASS = "authz.cache.manager.class"; + + public static final String AUTHZ_CACHE_ENABLED = "authz.cache.enabled"; + + public static final String IN_MEMORY_CACHE_SIZE = "in.memory.cache.size"; + //Names of the attributes that could be passed in the AuthzToken's claims map. public static final String USER_NAME = "userName"; public static final String EMAIL = "email"; diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java index d87da70138..b47a939e93 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/ServerSettings.java @@ -30,25 +30,25 @@ public class ServerSettings extends ApplicationSettings { - private static final Logger log = LoggerFactory.getLogger(ServerSettings.class); + private static final Logger log = LoggerFactory.getLogger(ServerSettings.class); private static final String DEFAULT_USER = "default.registry.user"; private static final String DEFAULT_USER_PASSWORD = "default.registry.password"; - private static final String DEFAULT_USER_GATEWAY = "default.registry.gateway"; - private static final String OUTPUT_LOCATION = "out.location"; + private static final String DEFAULT_USER_GATEWAY = "default.registry.gateway"; + private static final String OUTPUT_LOCATION = "out.location"; private static final String SERVER_CONTEXT_ROOT = "server.context-root"; public static final String IP = "ip"; // Orchestrator Constants public static final String ORCHESTRATOR_SERVER_HOST = "orchestrator.server.host"; public static final String ORCHESTRATOR_SERVER_PORT = "orchestrator.server.port"; - public static final String ORCHESTRATOR_SERVER_NAME = "orchestrator.server.name"; - // Gfac constants + public static final String ORCHESTRATOR_SERVER_NAME = "orchestrator.server.name"; + // Gfac constants public static final String GFAC_SERVER_HOST = "gfac.server.host"; public static final String GFAC_SERVER_PORT = "gfac.server.port"; public static final String GFAC_SERVER_NAME = "gfac.server.name"; public static final String GFAC_THREAD_POOL_SIZE = "gfac.thread.pool.size"; - public static final int DEFAULT_GFAC_THREAD_POOL_SIZE = 50; + public static final int DEFAULT_GFAC_THREAD_POOL_SIZE = 50; public static final String GFAC_CONFIG_XML = "gfac-config.xml"; // Credential Store constants public static final String CREDENTIAL_SERVER_HOST = "credential.store.server.host"; @@ -99,9 +99,9 @@ public class ServerSettings extends ApplicationSettings { private static boolean stopAllThreads = false; private static boolean emailBaseNotificationEnable; - private static String outputLocation; + private static String outputLocation; - public static String getDefaultUser() throws ApplicationSettingsException { + public static String getDefaultUser() throws ApplicationSettingsException { return getSetting(DEFAULT_USER); } @@ -281,7 +281,7 @@ public static String getAdminPassword() throws ApplicationSettingsException { return getSetting(Constants.ADMIN_PASSWORD); } - public static String getAuthorizationPoliyName() throws ApplicationSettingsException{ + public static String getAuthorizationPoliyName() throws ApplicationSettingsException { return getSetting(Constants.AUTHORIZATION_POLICY_NAME); } @@ -289,9 +289,9 @@ public static String getZookeeperConnection() throws ApplicationSettingsExceptio return getSetting(ZOOKEEPER_SERVER_CONNECTION, "localhost:2181"); } - public static int getZookeeperTimeout() { - return Integer.valueOf(getSetting(ZOOKEEPER_TIMEOUT, "3000")); - } + public static int getZookeeperTimeout() { + return Integer.valueOf(getSetting(ZOOKEEPER_TIMEOUT, "3000")); + } public static String getGFacServerName() throws ApplicationSettingsException { return getSetting(GFAC_SERVER_NAME); @@ -308,43 +308,47 @@ public static String getGFacServerPort() throws ApplicationSettingsException { public static int getGFacThreadPoolSize() { try { String threadPoolSize = getSetting(GFAC_THREAD_POOL_SIZE); - if (threadPoolSize != null && !threadPoolSize.isEmpty()) { - return Integer.valueOf(threadPoolSize); - } else { - log.warn("Thread pool size is not configured, use default gfac thread pool size " + - DEFAULT_GFAC_THREAD_POOL_SIZE); - } + if (threadPoolSize != null && !threadPoolSize.isEmpty()) { + return Integer.valueOf(threadPoolSize); + } else { + log.warn("Thread pool size is not configured, use default gfac thread pool size " + + DEFAULT_GFAC_THREAD_POOL_SIZE); + } } catch (ApplicationSettingsException e) { - log.warn("Couldn't read thread pool size from configuration on exception, use default gfac thread pool " + - "size " + DEFAULT_GFAC_THREAD_POOL_SIZE); + log.warn("Couldn't read thread pool size from configuration on exception, use default gfac thread pool " + + "size " + DEFAULT_GFAC_THREAD_POOL_SIZE); } - return DEFAULT_GFAC_THREAD_POOL_SIZE; + return DEFAULT_GFAC_THREAD_POOL_SIZE; } - public static String getOrchestratorServerName() throws ApplicationSettingsException { - return getSetting(ORCHESTRATOR_SERVER_NAME); - } + public static String getOrchestratorServerName() throws ApplicationSettingsException { + return getSetting(ORCHESTRATOR_SERVER_NAME); + } - public static String getOrchestratorServerHost() throws ApplicationSettingsException { - return getSetting(ORCHESTRATOR_SERVER_HOST); - } + public static String getOrchestratorServerHost() throws ApplicationSettingsException { + return getSetting(ORCHESTRATOR_SERVER_HOST); + } - public static int getOrchestratorServerPort() throws ApplicationSettingsException { - return Integer.valueOf(getSetting(ORCHESTRATOR_SERVER_PORT)); - } + public static int getOrchestratorServerPort() throws ApplicationSettingsException { + return Integer.valueOf(getSetting(ORCHESTRATOR_SERVER_PORT)); + } public static boolean isTLSEnabled() throws ApplicationSettingsException { return Boolean.valueOf(getSetting(Constants.IS_TLS_ENABLED)); } + public static int getTLSServerPort() throws ApplicationSettingsException { return Integer.valueOf(getSetting(Constants.TLS_SERVER_PORT)); } + public static String getKeyStorePath() throws ApplicationSettingsException { return getSetting(Constants.KEYSTORE_PATH); } + public static String getKeyStorePassword() throws ApplicationSettingsException { return getSetting(Constants.KEYSTORE_PASSWORD); } + public static int getTLSClientTimeout() throws ApplicationSettingsException { return Integer.valueOf(getSetting(Constants.TLS_CLIENT_TIMEOUT)); } @@ -353,7 +357,19 @@ public static String getSecurityManagerClassName() throws ApplicationSettingsExc return getSetting(Constants.SECURITY_MANAGER_CLASS); } - public static String getOutputLocation() { - return getSetting(OUTPUT_LOCATION, System.getProperty("java.io.tmpdir")); - } + public static String getAuthzCacheManagerClassName() throws ApplicationSettingsException { + return getSetting(Constants.AUTHZ_CACHE_MANAGER_CLASS); + } + + public static boolean isAuthzCacheEnabled() throws ApplicationSettingsException { + return Boolean.valueOf(getSetting(Constants.AUTHZ_CACHE_ENABLED)); + } + + public static int getCacheSize() throws ApplicationSettingsException { + return Integer.valueOf(getSetting(Constants.IN_MEMORY_CACHE_SIZE)); + } + + public static String getOutputLocation() { + return getSetting(OUTPUT_LOCATION, System.getProperty("java.io.tmpdir")); + } } diff --git a/modules/configuration/server/src/main/resources/airavata-server.properties b/modules/configuration/server/src/main/resources/airavata-server.properties index 58a42a3ae8..fb573821e7 100644 --- a/modules/configuration/server/src/main/resources/airavata-server.properties +++ b/modules/configuration/server/src/main/resources/airavata-server.properties @@ -229,14 +229,24 @@ zookeeper.timeout=30000 ######################################################################## api.secured=true security.manager.class=org.apache.airavata.api.server.security.DefaultAiravataSecurityManager +### TLS related configuration #### TLS.enabled=true TLS.api.server.port=9930 TLS.client.timeout=10000 +#### keystore configuration #### keystore.path=airavata.jks keystore.password=airavata +#### trust store configuration #### trust.store=client_truststore.jks trust.store.password=airavata +#### remote authorization server url #### remote.oauth.authorization.server=https://localhost:9443/services/ +#### xacml based authorization policy #### authorization.policy=airavata-default-xacml-policy +#### authorization cache related configuration #### +authz.cache.enabled=true +authz.cache.manager.class=org.apache.airavata.api.server.security.cache.DefaultAuthzCacheManager +in.memory.cache.size=1000 +#### admin user credentials of authorization server #### admin.user.name=admin admin.password=admin \ No newline at end of file From f080ac263c6cd1c1df98dcfbd5f3366f0734fb44 Mon Sep 17 00:00:00 2001 From: hasinitg Date: Sun, 16 Aug 2015 23:44:51 -0400 Subject: [PATCH 9/9] Completion of the security solution in Airavata - adding some missing files from the previous commit and fixing issues found while testing. --- .../DefaultAiravataSecurityManager.java | 2 +- .../security/authzcache/AuthzCache.java | 4 +- .../security/authzcache/AuthzCacheEntry.java | 63 +++++++++ .../security/authzcache/AuthzCacheIndex.java | 78 +++++++++++ .../authzcache/AuthzCacheManager.java | 80 +++++++++++ .../authzcache/AuthzCacheManagerFactory.java | 60 ++++++++ .../authzcache/DefaultAuthzCacheManager.java | 108 +++++++++++++++ .../security/xacml/DefaultXACMLPEP.java | 129 ++++++++++++++++++ .../main/resources/airavata-server.properties | 2 +- 9 files changed, 523 insertions(+), 3 deletions(-) create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheEntry.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheIndex.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManager.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManagerFactory.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/DefaultAuthzCacheManager.java create mode 100644 airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultXACMLPEP.java diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java index f42d98dd1e..707865973c 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java @@ -148,7 +148,7 @@ public boolean isUserAuthorized(AuthzToken authzToken, Map metaD //cache the authorization decision authzCacheManager.addToAuthzCache(new AuthzCacheIndex(subject, accessToken, action), - new AuthzCacheEntry(decision, expiryTimestamp)); + new AuthzCacheEntry(decision, expiryTimestamp, System.currentTimeMillis())); return decision; } else { diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java index a563caa779..8b1455667e 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCache.java @@ -55,7 +55,9 @@ private AuthzCache(int initialCapacity) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { //TODO: following info log is for demonstration purposes. Remove it. - logger.info("Authz cache max size exceeded. Removing the old entries."); + if (size() > MAX_SIZE) { + logger.info("Authz cache max size exceeded. Removing the old entries."); + } return size() > MAX_SIZE; } } diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheEntry.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheEntry.java new file mode 100644 index 0000000000..03ca229f4b --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheEntry.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +/** + * Cache entry in the default authorization cache. + */ +public class AuthzCacheEntry { + //authorization decision for the authorization request associated with this cache entry. + private boolean decision; + //time to live value for the access token in seconds. + private long expiryTime; + //time stamp in milli seconds at the time this entry is put into the cache + private long entryTimestamp; + + public AuthzCacheEntry(boolean decision, long expiryTime, long entryTimestamp) { + this.decision = decision; + this.expiryTime = expiryTime; + this.entryTimestamp = entryTimestamp; + } + + public long getEntryTimestamp() { + return entryTimestamp; + } + + public void setEntryTimestamp(long entryTimestamp) { + this.entryTimestamp = entryTimestamp; + } + + public long getExpiryTime() { + return expiryTime; + } + + public void setExpiryTime(long timestamp) { + this.expiryTime = timestamp; + } + + public boolean getDecision() { + return decision; + } + + public void setDecision(boolean decision) { + this.decision = decision; + } +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheIndex.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheIndex.java new file mode 100644 index 0000000000..59667d86ba --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheIndex.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +/** + * Cache index of the default authorization cache. + */ +public class AuthzCacheIndex { + + private String subject; + private String oauthAccessToken; + private String action; + + public AuthzCacheIndex(String userName, String accessToken, String actionString) { + this.subject = userName; + this.oauthAccessToken = accessToken; + this.action = actionString; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getOauthAccessToken() { + return oauthAccessToken; + } + + public void setOauthAccessToken(String oauthAccessToken) { + this.oauthAccessToken = oauthAccessToken; + } + + /*Equals and hash code methods are overriden since this is being used as an index of a map and that containsKey method + * should return true if the values of two index objects are equal.*/ + @Override + public boolean equals(Object other) { + if (other == null || other.getClass() != getClass()) { + return false; + } + return ((this.getSubject().equals(((AuthzCacheIndex) other).getSubject())) + && (this.getOauthAccessToken().equals(((AuthzCacheIndex) other).getOauthAccessToken())) + && (this.getAction().equals(((AuthzCacheIndex) other).getAction()))); + } + + @Override + public int hashCode() { + return this.getSubject().hashCode() + this.getOauthAccessToken().hashCode() + this.getAction().hashCode(); + } +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManager.java new file mode 100644 index 0000000000..48cfb03dd6 --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManager.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +import org.apache.airavata.security.AiravataSecurityException; + +/** + * This is the interface through which security manager accesses the underlying caching implementation + * See the DefaultAuthzCacheManager.java for an example implementation of this interface. + */ +public interface AuthzCacheManager { + /** + * Returns the status of the cache w.r.t the given authorization request which is encapsulated in + * the AuthzCacheIndex. + * + * @param authzCacheIndex + * @return + */ + public AuthzCachedStatus getAuthzCachedStatus(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException; + + /** + * Add to cache the authorization decision pertaining to a given authorization request. + * + * @param authzCacheIndex + * @param authzCacheEntry + * @throws AiravataSecurityException + */ + public void addToAuthzCache(AuthzCacheIndex authzCacheIndex, AuthzCacheEntry authzCacheEntry) throws AiravataSecurityException; + + /** + * Check if a valid decision is cached for a given authorization request. + * + * @param authzCacheIndex + * @return + */ + public boolean isAuthzDecisionCached(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException; + + /** + * Returns the AuthzCacheEntry for a given authorization request. + * + * @param authzCacheIndex + * @return + * @throws AiravataSecurityException + */ + public AuthzCacheEntry getAuthzCacheEntry(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException; + + /** + * Removes the authorization cache entry for a given authorization request. + * + * @param authzCacheIndex + * @throws AiravataSecurityException + */ + public void removeAuthzCacheEntry(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException; + + /** + * Clear the authorization cache. + * + * @return + */ + public void clearCache() throws AiravataSecurityException; + +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManagerFactory.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManagerFactory.java new file mode 100644 index 0000000000..b555122ac9 --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/AuthzCacheManagerFactory.java @@ -0,0 +1,60 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +import org.apache.airavata.api.server.security.AiravataSecurityManager; +import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.common.utils.ServerSettings; +import org.apache.airavata.security.AiravataSecurityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * This initializes the AuthzCacheManager implementation to be used as defined by the configuration. + */ +public class AuthzCacheManagerFactory { + private final static Logger logger = LoggerFactory.getLogger(AuthzCacheManagerFactory.class); + + public static AuthzCacheManager getAuthzCacheManager() throws AiravataSecurityException { + try { + Class authzCacheManagerImpl = Class.forName(ServerSettings.getAuthzCacheManagerClassName()); + AuthzCacheManager authzCacheManager = (AuthzCacheManager) authzCacheManagerImpl.newInstance(); + return authzCacheManager; + } catch (ClassNotFoundException e) { + String error = "Authorization Cache Manager class could not be found."; + logger.error(e.getMessage(), e); + throw new AiravataSecurityException(error); + } catch (ApplicationSettingsException e) { + String error = "Error in reading the configuration related to Authorization Cache Manager class."; + logger.error(e.getMessage(), e); + throw new AiravataSecurityException(error); + } catch (InstantiationException e) { + String error = "Error in instantiating the Authorization Cache Manager class."; + logger.error(e.getMessage(), e); + throw new AiravataSecurityException(error); + } catch (IllegalAccessException e) { + String error = "Error in instantiating the Authorization Cache Manager class."; + logger.error(e.getMessage(), e); + throw new AiravataSecurityException(error); + + } + } + +} diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/DefaultAuthzCacheManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/DefaultAuthzCacheManager.java new file mode 100644 index 0000000000..232908d9b0 --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/authzcache/DefaultAuthzCacheManager.java @@ -0,0 +1,108 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.authzcache; + +import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.security.AiravataSecurityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +public class DefaultAuthzCacheManager implements AuthzCacheManager { + + private final static Logger logger = LoggerFactory.getLogger(DefaultAuthzCacheManager.class); + + @Override + public AuthzCachedStatus getAuthzCachedStatus(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException { + if (isAuthzDecisionCached(authzCacheIndex)) { + AuthzCacheEntry cacheEntry = getAuthzCacheEntry(authzCacheIndex); + long expiryTime = cacheEntry.getExpiryTime(); + long currentTime = System.currentTimeMillis(); + long timePassed = (currentTime - cacheEntry.getEntryTimestamp()) / 1000; + if (expiryTime > timePassed) { + //access token is still valid. Hence, return the cached decision + if (cacheEntry.getDecision()) { + return AuthzCachedStatus.AUTHORIZED; + } else { + return AuthzCachedStatus.NOT_AUTHORIZED; + } + } else { + //access token has been expired. Hence, remove the entry and return. + removeAuthzCacheEntry(authzCacheIndex); + return AuthzCachedStatus.NOT_CACHED; + } + } else { + return AuthzCachedStatus.NOT_CACHED; + } + } + + @Override + public void addToAuthzCache(AuthzCacheIndex authzCacheIndex, AuthzCacheEntry authzCacheEntry) throws AiravataSecurityException { + try { + AuthzCache.getInstance().put(authzCacheIndex, authzCacheEntry); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in obtaining the authorization cache instance."); + } + } + + @Override + public boolean isAuthzDecisionCached(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException { + try { + return AuthzCache.getInstance().containsKey(authzCacheIndex); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in obtaining the authorization cache instance."); + } + } + + @Override + public AuthzCacheEntry getAuthzCacheEntry(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException { + try { + return AuthzCache.getInstance().get(authzCacheIndex); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in obtaining the authorization cache instance."); + } + } + + @Override + public void removeAuthzCacheEntry(AuthzCacheIndex authzCacheIndex) throws AiravataSecurityException { + try { + AuthzCache.getInstance().remove(authzCacheIndex); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in obtaining the authorization cache instance."); + } + } + + @Override + public void clearCache() throws AiravataSecurityException { + try { + AuthzCache.getInstance().clear(); + } catch (ApplicationSettingsException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in obtaining the authorization cache instance."); + + } + } +} \ No newline at end of file diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultXACMLPEP.java new file mode 100644 index 0000000000..42328d18ec --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/xacml/DefaultXACMLPEP.java @@ -0,0 +1,129 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.airavata.api.server.security.xacml; + +import org.apache.airavata.common.utils.Constants; +import org.apache.airavata.model.security.AuthzToken; +import org.apache.airavata.security.AiravataSecurityException; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.ConfigurationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceStub; +import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceException; +import org.wso2.carbon.utils.CarbonUtils; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.rmi.RemoteException; +import java.util.Map; + +/** + * This enforces XACML based fine grained authorization on the API calls, by authorizing the API calls + * through default PDP which is WSO2 Identity Server. + */ +public class DefaultXACMLPEP { + + private final static Logger logger = LoggerFactory.getLogger(DefaultXACMLPEP.class); + private EntitlementServiceStub entitlementServiceStub; + + public DefaultXACMLPEP(String auhorizationServerURL, String username, String password, + ConfigurationContext configCtx) throws AiravataSecurityException { + try { + + String PDPURL = auhorizationServerURL + "EntitlementService"; + entitlementServiceStub = new EntitlementServiceStub(configCtx, PDPURL); + CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, entitlementServiceStub._getServiceClient()); + } catch (AxisFault e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error initializing XACML PEP client."); + } + + } + + /** + * Send the XACML authorization request to XAML PDP and return the authorization decision. + * + * @param authzToken + * @param metaData + * @return + */ + public boolean getAuthorizationDecision(AuthzToken authzToken, Map metaData) throws AiravataSecurityException { + String decision; + try { + String subject = authzToken.getClaimsMap().get(Constants.USER_NAME); + String action = "/airavata/" + metaData.get(Constants.API_METHOD_NAME); + String decisionString = entitlementServiceStub.getDecisionByAttributes(subject, null, action, null); + //parse the XML decision string and obtain the decision + decision = parseDecisionString(decisionString); + if (Constants.PERMIT.equals(decision)) { + return true; + } else { + logger.error("Authorization decision is: " + decision); + return false; + } + } catch (RemoteException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in authorizing the user."); + } catch (EntitlementServiceException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in authorizing the user."); + } + } + + /** + * This parses the XML based authorization response by the PDP and returns the decision string. + * + * @param decisionString + * @return + * @throws AiravataSecurityException + */ + private String parseDecisionString(String decisionString) throws AiravataSecurityException { + try { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + InputStream inputStream = new ByteArrayInputStream(decisionString.getBytes("UTF-8")); + Document doc = docBuilderFactory.newDocumentBuilder().parse(inputStream); + Node resultNode = doc.getDocumentElement().getFirstChild(); + Node decisionNode = resultNode.getFirstChild(); + String decision = decisionNode.getTextContent(); + return decision; + } catch (ParserConfigurationException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in parsing XACML authorization response."); + } catch (UnsupportedEncodingException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in parsing XACML authorization response."); + } catch (SAXException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in parsing XACML authorization response."); + } catch (IOException e) { + logger.error("Error in parsing XACML authorization response."); + throw new AiravataSecurityException("Error in parsing XACML authorization response."); + } + } +} diff --git a/modules/configuration/server/src/main/resources/airavata-server.properties b/modules/configuration/server/src/main/resources/airavata-server.properties index fb573821e7..d45e3d93a2 100644 --- a/modules/configuration/server/src/main/resources/airavata-server.properties +++ b/modules/configuration/server/src/main/resources/airavata-server.properties @@ -245,7 +245,7 @@ remote.oauth.authorization.server=https://localhost:9443/services/ authorization.policy=airavata-default-xacml-policy #### authorization cache related configuration #### authz.cache.enabled=true -authz.cache.manager.class=org.apache.airavata.api.server.security.cache.DefaultAuthzCacheManager +authz.cache.manager.class=org.apache.airavata.api.server.security.authzcache.DefaultAuthzCacheManager in.memory.cache.size=1000 #### admin user credentials of authorization server #### admin.user.name=admin