/* * ============LICENSE_START========================================== * org.onap.music * =================================================================== * Copyright (c) 2017 AT&T Intellectual Property * * Modifications Copyright (C) 2019 IBM. * =================================================================== * Licensed 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. * * ============LICENSE_END============================================= * ==================================================================== */ package org.onap.music.authentication; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.servlet.ServletRequest; 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.music.eelf.logging.EELFLoggerDelegate; import org.onap.music.exceptions.MusicAuthenticationException; public class AuthUtil { private static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AuthUtil.class); private AuthUtil() { throw new IllegalStateException("Utility class"); } /** * Get the list of permissions from the Request object. * * * @param request servlet request object * @return returns list of AAFPermission of the requested MechId for all the * namespaces */ public static List getAAFPermissions(ServletRequest 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; } /** * Here is a sample of a permission object in AAI. The key attribute will have * Type|Instance|Action. * AAFPermission: * NS: null * Type: org.onap.music.cadi.keyspace ( Permission Type ) * Instance: tomtest ( Cassandra Keyspace ) * Action: *|GET|ALL ( Access Level [*|ALL] for full access and [GET] for Read only) * Key: org.onap.music.cadi.keyspace|tomtest|* * * This method will filter all permissions whose key starts with the requested namespace. * The nsamespace here is the music namespace which is defined in music.property file. * i;e is the type contains in key is org.onap.music.cadi.keyspace and the namespace * value is org.onap.music.cadi.keyspace, it will add to list * otherwise reject. * * @param nameSpace * @param allPermissionsList * @return */ private static List filterNameSpacesAAFPermissions(String nameSpace, List allPermissionsList) { List list = new ArrayList<>(); for (Iterator iterator = allPermissionsList.iterator(); iterator.hasNext();) { AAFPermission aafPermission = iterator.next(); if(aafPermission.getType().indexOf(nameSpace) == 0) { list.add(aafPermission); } } return list; } /** * Decode certian characters from url encoded to normal. * * @param str - String being decoded. * @return returns the decoded string. * @throws Exception throws excpetion */ public static String decodeFunctionCode(String str) throws MusicAuthenticationException { final String DECODEVALUE_FORWARDSLASH = "2f"; final String DECODEVALUE_HYPHEN = "2d"; final String DECODEVALUE_ASTERISK = "2a"; String decodedString = str; List decodingList = new ArrayList<>(); decodingList.add(Pattern.compile(DECODEVALUE_FORWARDSLASH)); decodingList.add(Pattern.compile(DECODEVALUE_HYPHEN)); decodingList.add(Pattern.compile(DECODEVALUE_ASTERISK)); for (Pattern xssInputPattern : decodingList) { try { decodedString = decodedString.replaceAll("%" + xssInputPattern, new String(Hex.decodeHex(xssInputPattern.toString().toCharArray()))); } catch (DecoderException e) { logger.error(EELFLoggerDelegate.securityLogger, "AuthUtil Decode Failed! for instance: " + str); throw new MusicAuthenticationException("Decode failed", e); } } return decodedString; } /** * * * @param request servlet request object * @param nameSpace application namespace * @return boolean value if the access is allowed * @throws Exception throws exception */ public static boolean isAccessAllowed(ServletRequest request, String nameSpace) throws MusicAuthenticationException { if (request==null) { throw new MusicAuthenticationException("Request cannot be null"); } if (nameSpace==null || nameSpace.isEmpty()) { throw new MusicAuthenticationException("NameSpace not Declared!"); } boolean isauthorized = false; List aafPermsList = getAAFPermissions(request); logger.info(EELFLoggerDelegate.securityLogger, "AAFPermission of the requested MechId for all the namespaces: " + aafPermsList); logger.debug(EELFLoggerDelegate.securityLogger, "Requested nameSpace: " + nameSpace); List aafPermsFinalList = filterNameSpacesAAFPermissions(nameSpace, aafPermsList); logger.debug(EELFLoggerDelegate.securityLogger, "AuthUtil list of AAFPermission for the specific namespace :::" + aafPermsFinalList); HttpServletRequest httpRequest = (HttpServletRequest) request; String requestUri = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length() + 1); logger.debug(EELFLoggerDelegate.securityLogger, "AuthUtil requestUri :::" + requestUri); for (Iterator iterator = aafPermsFinalList.iterator(); iterator.hasNext();) { AAFPermission aafPermission = iterator.next(); if(!isauthorized) { isauthorized = isMatchPattern(aafPermission, requestUri, httpRequest.getMethod()); } } logger.debug(EELFLoggerDelegate.securityLogger, "isAccessAllowed for the request uri: " + requestUri + "is :" + isauthorized); return isauthorized; } /** * * This method will check, if the requested URI matches any of the instance * found with the AAF permission list. * i;e if the request URI is; /v2/keyspaces/tomtest/tables/emp15 and in the * AAF permission table, we have an instance * defined as "tomtest" mapped the logged in user, it will allow else error. * * User trying to create or aquire a lock * Here is the requested URI /v2/locks/create/tomtest.MyTable.Field1 * Here the keyspace name i;e tomtest will be test throught out the URL if it * matches, it will allow the user to create a lock. * "tomtest" here is the key, which is mapped as an instance in permission object. * Instance can be delimited with ":" i;e ":music-cassandra-1908-dev:admin". In this case, * each delimited * token will be matched with that of request URI. * * Example Permission: * org.onap.music.api.user.access|tomtest|* or ALL * org.onap.music.api.user.access|tomtest|GET * In case of the action field is ALL and *, user will be allowed else it will * be matched with the requested http method type. * * * * @param aafPermission - AAfpermission obtained by cadi. * @param requestUri - Rest URL client is calling. * @param method - REST Method being used (GET,POST,PUT,DELETE) * @return returns a boolean * @throws Exception - throws an exception */ private static boolean isMatchPattern( AAFPermission aafPermission, String requestUri, String method) throws MusicAuthenticationException { if (null == aafPermission || null == requestUri || null == method) { return false; } String permKey = aafPermission.getKey(); logger.debug(EELFLoggerDelegate.securityLogger, "isMatchPattern permKey: " + permKey + ", requestUri " + requestUri + " ," + method); String[] keyArray = permKey.split("\\|"); String[] subPath = null; String instance = keyArray[1]; String action = keyArray[2]; //if the instance & action both are * , then allow if ("*".equalsIgnoreCase(instance) && "*".equalsIgnoreCase(action)) { return true; } //Decode string like %2f, %2d and %2a if (!"*".equals(instance)) { instance = decodeFunctionCode(instance); } if (!"*".equals(action)) { action = decodeFunctionCode(action); } //Instance: :music-cassandra-1908-dev:admin List instanceList = Arrays.asList(instance.split(":")); String[] path = requestUri.split("/"); for (int i = 0; i < path.length; i++) { // Sometimes the value will begin with "$", so we need to remove it if (path[i].startsWith("$")) { path[i] = path[i].replace("$",""); } // Each path element can again delemited by ".";i;e // tomtest.tables.emp. We have scenarios like lock aquire URL subPath = path[i].split("\\."); for (int j = 0; j < subPath.length; j++) { if (instanceList.contains(subPath[j])) { return checkAction(method,action); } else { continue; } } } return false; } private static boolean checkAction(String method, String action) { if ("*".equals(action) || "ALL".equalsIgnoreCase(action)) { return true; } else { return (method.equalsIgnoreCase(action)); } } }