From 7246eabfd23d6cadc9f658f666df62b93f30ed70 Mon Sep 17 00:00:00 2001 From: st782s Date: Tue, 20 Nov 2018 07:31:32 -0500 Subject: CADI Integration Issue-ID: PORTAL-474 System to system authorization using CADI Change-Id: I76487f8155a36fca8283669fe5e28ec7d5aec91d Signed-off-by: st782s --- .../core/onboarding/crossapi/CadiAuthFilter.java | 137 +++++++++++++++ .../crossapi/PortalRestAPICentralServiceImpl.java | 16 +- .../onboarding/crossapi/PortalRestAPIProxy.java | 58 ++++++- .../portalsdk/core/onboarding/util/AuthUtil.java | 190 +++++++++++++++++++++ .../core/onboarding/util/PortalApiConstants.java | 1 + .../core/restful/domain/EcompRoleFunction.java | 42 +++++ 6 files changed, 435 insertions(+), 9 deletions(-) create mode 100644 ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/CadiAuthFilter.java create mode 100644 ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/AuthUtil.java (limited to 'ecomp-sdk/epsdk-fw/src') diff --git a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/CadiAuthFilter.java b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/CadiAuthFilter.java new file mode 100644 index 00000000..8bddef85 --- /dev/null +++ b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/CadiAuthFilter.java @@ -0,0 +1,137 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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. + * + * ============LICENSE_END============================================ + * + * + */ +package org.onap.portalsdk.core.onboarding.crossapi; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.filter.CadiFilter; +import org.onap.portalsdk.core.onboarding.util.PortalApiConstants; +import org.onap.portalsdk.core.onboarding.util.PortalApiProperties; + +public class CadiAuthFilter extends CadiFilter { + + private static String inlclude_url_endpoints =""; + public static final String AUTHORIZATION = "Authorization"; + + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + inlclude_url_endpoints = filterConfig.getInitParameter("inlclude_url_endpoints"); + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + if (inlclude_url_endpoints.equals("") || inlclude_url_endpoints == null || inlclude_url_endpoints.isEmpty()) { + throw new NullPointerException("inlclude_url_endpoints is null"); + } else { + String includeUrlEndPointString = inlclude_url_endpoints; + ArrayList includeUrlEndPointList = new ArrayList( + Arrays.asList(includeUrlEndPointString.split(","))); + if (includeFilter(request, includeUrlEndPointList)) { + super.doFilter(request, response, chain); + } else + chain.doFilter(request, response); + } + } + + private boolean includeFilter(ServletRequest request, ArrayList includeapisList) { + boolean isauthenticated = false; + HttpServletRequest httpRequest = (HttpServletRequest) request; + + if(httpRequest.getHeader(AUTHORIZATION) == null) + return isauthenticated; + // TODO: refactor to have exclusion pattern + String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length() + 1); + if (path.contains("analytics")) { + return isauthenticated; + } + + for (String str : includeapisList) { + if (!isauthenticated) + isauthenticated = matchPattern(path, str); + } + if (isauthenticated && PortalApiProperties.getProperty(PortalApiConstants.ROLE_ACCESS_CENTRALIZED) + .equalsIgnoreCase("remote")) + isauthenticated = true; + else + isauthenticated = false; + return isauthenticated; + } + + private boolean matchPattern(String requestedPath, String includeUrl) { + includeUrl = includeUrl.substring(1); + String[] path = requestedPath.split("/"); + if (path.length > 1) { + String[] roleFunctionArray = includeUrl.split("/"); + boolean match = true; + for (int i = 0; i < roleFunctionArray.length; i++) { + if (match) { + if (!roleFunctionArray[i].equals("*")) { + Pattern p = Pattern.compile(Pattern.quote(path[i]), Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(roleFunctionArray[i]); + match = m.matches(); + } else if (roleFunctionArray[i].equals("*")) { + match = true; + } + + } + } + if (match) + return match; + } else { + if (requestedPath.matches(includeUrl)) + return true; + else if (includeUrl.equals("*")) + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPICentralServiceImpl.java b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPICentralServiceImpl.java index 208e8c3d..d53c0eb6 100644 --- a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPICentralServiceImpl.java +++ b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPICentralServiceImpl.java @@ -51,6 +51,7 @@ import javax.servlet.http.HttpServletRequest; import org.onap.portalsdk.core.onboarding.exception.CipherUtilException; import org.onap.portalsdk.core.onboarding.exception.PortalAPIException; import org.onap.portalsdk.core.onboarding.rest.RestWebServiceClient; +import org.onap.portalsdk.core.onboarding.util.AuthUtil; import org.onap.portalsdk.core.onboarding.util.CipherUtil; import org.onap.portalsdk.core.onboarding.util.PortalApiConstants; import org.onap.portalsdk.core.onboarding.util.PortalApiProperties; @@ -71,6 +72,8 @@ public class PortalRestAPICentralServiceImpl implements IPortalRestAPIService { IPortalRestCentralService portalRestCentralService; public static final String API_VERSION = "/v4"; private static String portalApiVersion = "/v3"; + private static final String nameSpace = PortalApiProperties + .getProperty(PortalApiConstants.AUTH_NAMESPACE); public PortalRestAPICentralServiceImpl() throws ServletException { String centralClassName = PortalApiProperties.getProperty(PortalApiConstants.PORTAL_API_IMPL_CLASS); @@ -186,16 +189,13 @@ public class PortalRestAPICentralServiceImpl implements IPortalRestAPIService { @Override public boolean isAppAuthenticated(HttpServletRequest request) throws PortalAPIException { - boolean response = false; + boolean accessAllowed = false; try { - String restUser = request.getHeader("username"); - String restPw = request.getHeader("password"); - response = restUser != null && restPw != null && restUser.equals(username) && restPw.equals(password); - logger.debug("isAppAuthenticated: " + response); - } catch (Exception ex) { - throw new PortalAPIException("isAppAuthenticated failed", ex); + accessAllowed = AuthUtil.isAccessAllowed(request, nameSpace); + } catch (Exception e) { + logger.error(e); } - return response; + return accessAllowed; } @Override diff --git a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPIProxy.java b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPIProxy.java index 1ce03146..71f66168 100644 --- a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPIProxy.java +++ b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/crossapi/PortalRestAPIProxy.java @@ -43,8 +43,13 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -60,6 +65,7 @@ import org.onap.portalsdk.core.onboarding.rest.RestWebServiceClient; import org.onap.portalsdk.core.onboarding.util.PortalApiConstants; import org.onap.portalsdk.core.onboarding.util.PortalApiProperties; import org.onap.portalsdk.core.restful.domain.EcompRole; +import org.onap.portalsdk.core.restful.domain.EcompRoleFunction; import org.onap.portalsdk.core.restful.domain.EcompUser; import org.owasp.esapi.ESAPI; @@ -146,6 +152,8 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer response.getWriter().write(buildJsonResponse(false, "Misconfigured - no instance of service class")); return; } + + String requestUri = request.getRequestURI(); String responseJson = ""; String storeAnalyticsContextPath = "/storeAnalytics"; @@ -217,6 +225,7 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer writeAndFlush(response, APPLICATION_JSON, buildJsonResponse(false, "Not authorized")); return; } + try { String requestBody = readRequestBody(request); @@ -264,6 +273,9 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer if (requestUri.endsWith(PortalApiConstants.API_PREFIX + "/user")) { try { EcompUser user = mapper.readValue(requestBody, EcompUser.class); + logger.debug("doPost: create user requestbody: "+ requestBody); + Set userEcompRoles = getEcompRolesOfUser(user); + user.setRoles(userEcompRoles); pushUser(user); if (logger.isDebugEnabled()) logger.debug("doPost: pushUser: success"); @@ -280,6 +292,9 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer String loginId = requestUri.substring(requestUri.lastIndexOf('/') + 1); try { EcompUser user = mapper.readValue(requestBody, EcompUser.class); + logger.debug("doPost: update user requestbody: "+ requestBody); + Set userEcompRoles = getEcompRolesOfUser(user); + user.setRoles(userEcompRoles); editUser(loginId, user); if (logger.isDebugEnabled()) logger.debug("doPost: editUser: success"); @@ -342,6 +357,7 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer buildJsonResponse(false, "Misconfigured - no instance of service class")); return; } + String requestUri = request.getRequestURI(); String contentType = APPLICATION_JSON; @@ -413,7 +429,6 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer writeAndFlush(response, APPLICATION_JSON, buildJsonResponse(false, "Not authorized")); return; } - String responseJson = null; try { // Ignore any request body in a GET. @@ -683,4 +698,45 @@ public class PortalRestAPIProxy extends HttpServlet implements IPortalRestAPISer return portalRestApiServiceImpl.getCredentials(); } + private Set getEcompRolesOfUser(EcompUser user) throws JsonProcessingException + { + + Set userEcompRoles = new TreeSet<>(); + Set ecompRoles = user.getRoles(); + for (EcompRole role : ecompRoles) { + Set roleFunctions = role.getRoleFunctions(); + Iterator roleIter = roleFunctions.iterator(); + ObjectMapper mapper = new ObjectMapper(); + Set EcompRoleFunctions = new TreeSet<>(); + while (roleIter.hasNext()) { + String str = mapper.writeValueAsString(roleIter.next()); + + String str1 = str.substring(1, str.length() - 1); + Map result = Arrays.stream(str1.split(",")).map(s -> s.split(":")) + .collect(Collectors.toMap(a -> a[0], // key + a -> a[1] // value + )); + + EcompRoleFunction roleFunction = new EcompRoleFunction(); + for (Map.Entry set : result.entrySet()) { + String key = set.getKey().replaceAll("\"", " ").trim(); + if (!key.isEmpty() && key.equalsIgnoreCase("action")) { + roleFunction.setAction(set.getValue().replaceAll("\"", " ").trim()); + } else if (!key.isEmpty() && key.equalsIgnoreCase("type")) { + roleFunction.setType(set.getValue().replaceAll("\"", " ").trim()); + + } else if (!key.isEmpty() && key.equalsIgnoreCase("code")) { + roleFunction.setCode(set.getValue().replaceAll("\"", " ").trim()); + + } else if (!key.isEmpty() && key.equalsIgnoreCase("name")) { + roleFunction.setName(set.getValue().replaceAll("\"", " ").trim()); + } + } + EcompRoleFunctions.add(roleFunction); + } + role.setRoleFunctions(EcompRoleFunctions); + userEcompRoles.add(role); + } + return userEcompRoles; + } } diff --git a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/AuthUtil.java b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/AuthUtil.java new file mode 100644 index 00000000..a7aa6765 --- /dev/null +++ b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/AuthUtil.java @@ -0,0 +1,190 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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. + * + * ============LICENSE_END============================================ + * + * + */ +package org.onap.portalsdk.core.onboarding.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.onap.aaf.cadi.CadiWrap; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.aaf.AAFPermission; +import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate; +import org.onap.portalsdk.core.onboarding.exception.PortalAPIException; + +public class AuthUtil { + + private static final String decodeValueOfForwardSlash = "2f"; + private static final String decodeValueOfHyphen = "2d"; + private static final String decodeValueOfAsterisk = "2a"; + private static final EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AuthUtil.class); + + /* + * This method compares the portalApiPath against the urlPattern; splits the + * portalApiPath by "/" and compares each part with that of the urlPattern. + * + * Example: "xyz/1/abc" matches with the pattern "xyz/* /abc" but not with + * "xyz/*" + * + */ + public static Boolean matchPattern(String portalApiPath, String urlPattern) { + String[] path = portalApiPath.split("/"); + if (path.length > 1) { + + String[] roleFunctionArray = urlPattern.split("/"); + boolean match = true; + if (roleFunctionArray.length == path.length) { + for (int i = 0; i < roleFunctionArray.length; i++) { + if (match) { + if (!roleFunctionArray[i].equals("*")) { + Pattern p = Pattern.compile(Pattern.quote(path[i]), Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(roleFunctionArray[i]); + match = m.matches(); + + } + } + } + if (match) + return match; + } + } else { + if (portalApiPath.matches(urlPattern)) + return true; + else if (urlPattern.equals("*")) + return true; + + } + return false; + } + /** + * + * @param request + * @return returns list of AAFPermission of the requested MechId for all the namespaces + */ + public static List getAAFPermissions(HttpServletRequest request) { + CadiWrap wrapReq = (CadiWrap) request; + List perms = wrapReq.getPermissions(wrapReq.getUserPrincipal()); + List aafPermsList = new ArrayList<>(); + for (Permission perm : perms) { + AAFPermission aafPerm = (AAFPermission) perm; + aafPermsList.add(aafPerm); + } + return aafPermsList; + } + + /** + * + * @param request + * @return returns list of AAFPermission for the specific namespace + */ + public static List getNameSpacesAAFPermissions(String nameSpace, + List allPermissionsList) { + String type = nameSpace + ".url"; + allPermissionsList.removeIf(perm -> (!perm.getType().equals(type))); + return allPermissionsList; + } + /** + * + * @param permsList + * @return returns the list of instaces of namespace + * @throws PortalAPIException + */ + public static List getAllInstances(List permsList) throws PortalAPIException { + List instanceList = permsList.stream().map(AAFPermission::getInstance).collect(Collectors.toList()); + + List finalInstanceList = new ArrayList<>(); + for (String instance : instanceList) { + String str = ""; + if (instance.equals("*")) + str = instance; + else + str = decodeFunctionCode(instance); + finalInstanceList.add(str); + } + return finalInstanceList; + } + + public static String decodeFunctionCode(String str) throws PortalAPIException { + String decodedString = str; + List decodingList = new ArrayList<>(); + decodingList.add(Pattern.compile(decodeValueOfForwardSlash)); + decodingList.add(Pattern.compile(decodeValueOfHyphen)); + decodingList.add(Pattern.compile(decodeValueOfAsterisk)); + for (Pattern xssInputPattern : decodingList) { + try { + decodedString = decodedString.replaceAll("%" + xssInputPattern, + new String(Hex.decodeHex(xssInputPattern.toString().toCharArray()))); + } catch (DecoderException e) { + logger.error(EELFLoggerDelegate.errorLogger, "Decode Failed! for instance: "+ str); + throw new PortalAPIException("decode failed", e); + } + } + + return decodedString; + } + + /** + * + * @param request + * @param nameSpace application namespace + * @return boolean value if the access is allowed + * @throws PortalAPIException + */ + public static boolean isAccessAllowed(HttpServletRequest request, String nameSpace) throws PortalAPIException { + List aafPermsList = getAAFPermissions(request); + logger.debug(EELFLoggerDelegate.debugLogger, "Application nameSpace: "+ nameSpace); + if (nameSpace.isEmpty()) { + throw new PortalAPIException("NameSpace not Declared!"); + } + List aafPermsFinalList = getNameSpacesAAFPermissions(nameSpace, aafPermsList); + List finalInstanceList = getAllInstances(aafPermsFinalList); + String requestUri = request.getRequestURI().substring(request.getContextPath().length() + 1); + boolean isauthorized = false; + for (String str : finalInstanceList) { + if (!isauthorized) + isauthorized = matchPattern(requestUri, str); + } + return isauthorized; + } +} \ No newline at end of file diff --git a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/PortalApiConstants.java b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/PortalApiConstants.java index 796f99d3..1b86af2e 100644 --- a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/PortalApiConstants.java +++ b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/onboarding/util/PortalApiConstants.java @@ -81,5 +81,6 @@ public interface PortalApiConstants { public static final String EXT_REQUEST_CONNECTION_TIMEOUT = "ext_req_connection_timeout"; public static final String EXT_REQUEST_READ_TIMEOUT = "ext_req_read_timeout"; public static final String ROLE_ACCESS_CENTRALIZED_FLAG = "role_access_centralized"; + public static final String AUTH_NAMESPACE= "auth_namespace"; } diff --git a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/restful/domain/EcompRoleFunction.java b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/restful/domain/EcompRoleFunction.java index d834a594..4eb96ae6 100644 --- a/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/restful/domain/EcompRoleFunction.java +++ b/ecomp-sdk/epsdk-fw/src/main/java/org/onap/portalsdk/core/restful/domain/EcompRoleFunction.java @@ -84,4 +84,46 @@ public class EcompRoleFunction implements Comparable{ } return result; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((action == null) ? 0 : action.hashCode()); + result = prime * result + ((code == null) ? 0 : code.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EcompRoleFunction other = (EcompRoleFunction) obj; + if (action == null) { + if (other.action != null) + return false; + } else if (!action.equals(other.action)) + return false; + if (code == null) { + if (other.code != null) + return false; + } else if (!code.equals(other.code)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + } -- cgit 1.2.3-korg