Some bug fixes and Minor Chages.
[music.git] / src / main / java / org / onap / music / authentication / AuthUtil.java
1 /*
2  * ============LICENSE_START==========================================
3  * org.onap.music
4  * ===================================================================
5  *  Copyright (c) 2017 AT&T Intellectual Property
6  * ===================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  * 
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  * 
19  * ============LICENSE_END=============================================
20  * ====================================================================
21  */
22
23 package org.onap.music.authentication;
24
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.regex.Pattern;
30
31 import javax.servlet.ServletRequest;
32 import javax.servlet.http.HttpServletRequest;
33
34 import org.apache.commons.codec.DecoderException;
35 import org.apache.commons.codec.binary.Hex;
36 import org.onap.aaf.cadi.CadiWrap;
37 import org.onap.aaf.cadi.Permission;
38 import org.onap.aaf.cadi.aaf.AAFPermission;
39 import org.onap.music.eelf.logging.EELFLoggerDelegate;
40
41 public class AuthUtil {
42
43     private static final String decodeValueOfForwardSlash = "2f";
44     private static final String decodeValueOfHyphen = "2d";
45     private static final String decodeValueOfAsterisk = "2a";
46     private static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AuthUtil.class);
47
48     /**
49      * Get the list of permissions from the Request object.
50      * 
51      *  
52      * @param request servlet request object
53      * @return returns list of AAFPermission of the requested MechId for all the
54      *         namespaces
55      */
56     public static List<AAFPermission> getAAFPermissions(ServletRequest request) {
57         CadiWrap wrapReq = (CadiWrap) request;
58
59         List<Permission> perms = wrapReq.getPermissions(wrapReq.getUserPrincipal());
60         List<AAFPermission> aafPermsList = new ArrayList<>();
61         for (Permission perm : perms) {
62             AAFPermission aafPerm = (AAFPermission) perm;
63             aafPermsList.add(aafPerm);
64         }
65         return aafPermsList;
66     }
67
68     /**
69      * Here is a sample of a permission object in AAI. The key attribute will have 
70      * Type|Instance|Action.
71      * AAFPermission:
72      *   NS: null
73      *   Type: org.onap.music.cadi.keyspace ( Permission Type )
74      *   Instance: tomtest   ( Cassandra Keyspace )
75      *   Action: *|GET|ALL   ( Access Level [*|ALL] for full access and [GET] for Read only)
76      *   Key: org.onap.music.cadi.keyspace|tomtest|*
77      *   
78      * This method will filter all permissions whose key starts with the requested namespace. 
79      * The nsamespace here is the music namespace which is defined in music.property file.
80      * i;e is the type contains in key is org.onap.music.cadi.keyspace and the namespace 
81      * value is org.onap.music.cadi.keyspace, it will add to list
82      * otherwise reject.
83      * 
84      * @param nameSpace
85      * @param allPermissionsList
86      * @return
87      */
88     private static List<AAFPermission> filterNameSpacesAAFPermissions(String nameSpace,
89             List<AAFPermission> allPermissionsList) {
90         List<AAFPermission> list = new ArrayList<>();
91         for (Iterator<AAFPermission> iterator = allPermissionsList.iterator(); iterator.hasNext();) {
92             AAFPermission aafPermission = (AAFPermission) iterator.next();
93             if(aafPermission.getType().indexOf(nameSpace) == 0) {
94                 list.add(aafPermission);
95             }
96         }
97         return list;
98     }
99
100     /**
101      * Decode certian characters from url encoded to normal.
102      * 
103      * @param str - String being decoded.
104      * @return returns the decoded string.
105      * @throws Exception throws excpetion
106      */
107     public static String decodeFunctionCode(String str) throws Exception {
108         String decodedString = str;
109         List<Pattern> decodingList = new ArrayList<>();
110         decodingList.add(Pattern.compile(decodeValueOfForwardSlash));
111         decodingList.add(Pattern.compile(decodeValueOfHyphen));
112         decodingList.add(Pattern.compile(decodeValueOfAsterisk));
113         for (Pattern xssInputPattern : decodingList) {
114             try {
115                 decodedString = decodedString.replaceAll("%" + xssInputPattern,
116                         new String(Hex.decodeHex(xssInputPattern.toString().toCharArray())));
117             } catch (DecoderException e) {
118                 logger.error(EELFLoggerDelegate.applicationLogger, 
119                     "AuthUtil Decode Failed! for instance: " + str);
120                 throw new Exception("decode failed", e);
121             }
122         }
123
124         return decodedString;
125     }
126
127     /**
128      * 
129      * 
130      * @param request servlet request object
131      * @param nameSpace application namespace
132      * @return boolean value if the access is allowed
133      * @throws Exception throws exception
134      */
135     public static boolean isAccessAllowed(ServletRequest request, String nameSpace) throws Exception {
136
137         if (request==null) {
138             throw new Exception("Request cannot be null");
139         }
140         
141         if (nameSpace==null || nameSpace.isEmpty()) {
142             throw new Exception("NameSpace not Declared!");
143         }
144         
145         boolean isauthorized = false;
146         List<AAFPermission> aafPermsList = getAAFPermissions(request);
147         //logger.info(EELFLoggerDelegate.applicationLogger,
148         //        "AAFPermission  of the requested MechId for all the namespaces: " + aafPermsList);
149
150         logger.debug(EELFLoggerDelegate.applicationLogger, "Requested nameSpace: " + nameSpace);
151
152
153         List<AAFPermission> aafPermsFinalList = filterNameSpacesAAFPermissions(nameSpace, aafPermsList);
154
155         logger.debug(EELFLoggerDelegate.securityLogger,
156             "AuthUtil list of AAFPermission for the specific namespace :::"
157             + aafPermsFinalList);
158         
159         HttpServletRequest httpRequest = (HttpServletRequest) request;
160         String requestUri = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length() + 1);
161
162         logger.debug(EELFLoggerDelegate.securityLogger,
163                 "AuthUtil requestUri :::" + requestUri);
164
165         for (Iterator iterator = aafPermsFinalList.iterator(); iterator.hasNext();) {
166             AAFPermission aafPermission = (AAFPermission) iterator.next();
167             if(!isauthorized) {
168                 isauthorized = isMatchPatternWithInstanceAndAction(aafPermission, requestUri, httpRequest.getMethod());
169             }
170         }
171         
172         logger.debug(EELFLoggerDelegate.securityLogger,
173             "isAccessAllowed for the request uri: " + requestUri + "is :" + isauthorized);
174         return isauthorized;
175     }
176
177     /**
178      * 
179      * This method will check, if the requested URI matches any of the instance 
180      * found with the AAF permission list.
181      * i;e if the request URI is; /v2/keyspaces/tomtest/tables/emp15 and in the 
182      * AAF permission table, we have an instance 
183      * defined as "tomtest" mapped the logged in user, it will allow else error.
184      * 
185      * User trying to create or aquire a lock
186      * Here is the requested URI /v2/locks/create/tomtest.MyTable.Field1
187      * Here the keyspace name i;e tomtest will be test throught out the URL if it 
188      * matches, it will allow the user to create a lock.
189      * "tomtest" here is the key, which is mapped as an instance in permission object.
190      * Instance can be delimited with ":" i;e ":music-cassandra-1908-dev:admin". In this case, 
191      * each delimited
192      * token will be matched with that of request URI.
193      * 
194      * Example Permission:
195      * org.onap.music.api.user.access|tomtest|* or ALL
196      * org.onap.music.api.user.access|tomtest|GET
197      * In case of the action field is ALL and *, user will be allowed else it will 
198      * be matched with the requested http method type.
199      * 
200      * 
201      * 
202      * @param aafPermission - AAfpermission obtained by cadi.
203      * @param requestUri - Rest URL client is calling.
204      * @param method - REST Method being used (GET,POST,PUT,DELETE)
205      * @return returns a boolean
206      * @throws Exception - throws an exception
207      */
208     private static boolean isMatchPatternWithInstanceAndAction(
209         AAFPermission aafPermission, 
210         String requestUri, 
211         String method) throws Exception {
212         if (null == aafPermission || null == requestUri || null == method) {
213             return false;
214         }
215
216         String permKey = aafPermission.getKey();
217         
218         logger.debug(EELFLoggerDelegate.auditLogger, "isMatchPattern permKey: " 
219             + permKey + ", requestUri " + requestUri + " ," + method);
220         
221         String[] keyArray = permKey.split("\\|");
222         String[] subPath = null;
223         //String type = null;
224         //type = keyArray[0];
225         String instance = keyArray[1];
226         String action = keyArray[2];
227         
228         //if the instance & action both are * , then allow
229         if ("*".equalsIgnoreCase(instance) && "*".equalsIgnoreCase(action)) {
230             return true;
231         }
232         //Decode string like %2f, %2d and %2a
233         if (!"*".equals(instance)) {
234             instance = decodeFunctionCode(instance);
235         }
236         if (!"*".equals(action)) {
237             action = decodeFunctionCode(action);
238         }
239         //Instance: :music-cassandra-1908-dev:admin
240         List<String> instanceList = Arrays.asList(instance.split(":"));
241         
242         String[] path = requestUri.split("/");
243         
244         for (int i = 0; i < path.length; i++) {
245             // Sometimes the value will begin with "$", so we need to remove it
246             if (path[i].startsWith("$")) {
247                 path[i] = path[i].replace("$","");
248             }
249             // Each path element can again delemited by ".";i;e 
250             // tomtest.tables.emp. We have scenarios like lock aquire URL
251             subPath = path[i].split("\\.");
252             for (int j = 0; j < subPath.length; j++) {
253                 if (instanceList.contains(subPath[j])) {
254                     if ("*".equals(action) || "ALL".equalsIgnoreCase(action)) {
255                         return true;
256                     } else if (method.equalsIgnoreCase(action)) {
257                         return true;
258                     } else {
259                         return false;
260                     }
261                 } else {
262                     continue;
263                 }
264             }
265         }
266         return false;
267     }
268 }