Split music src into music-core and music-rest
[music.git] / music-rest / src / main / java / org / onap / music / authentication / AuthUtil.java
diff --git a/music-rest/src/main/java/org/onap/music/authentication/AuthUtil.java b/music-rest/src/main/java/org/onap/music/authentication/AuthUtil.java
new file mode 100644 (file)
index 0000000..ee3b77a
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * ============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<AAFPermission> getAAFPermissions(ServletRequest request) {
+        CadiWrap wrapReq = (CadiWrap) request;
+
+        List<Permission> perms = wrapReq.getPermissions(wrapReq.getUserPrincipal());
+        List<AAFPermission> 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<AAFPermission> filterNameSpacesAAFPermissions(String nameSpace,
+            List<AAFPermission> allPermissionsList) {
+        List<AAFPermission> list = new ArrayList<>();
+        for (Iterator<AAFPermission> 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<Pattern> 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<AAFPermission> 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<AAFPermission> 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<AAFPermission> 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<String> 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));
+        }
+    }
+
+
+
+}
\ No newline at end of file