Sonar Changes for Authentication Classes
[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 import org.onap.music.exceptions.MusicAuthenticationException;
41
42 public class AuthUtil {
43
44     private static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AuthUtil.class);
45
46     private AuthUtil() {
47         throw new IllegalStateException("Utility class");
48     }
49     
50     /**
51      * Get the list of permissions from the Request object.
52      * 
53      *  
54      * @param request servlet request object
55      * @return returns list of AAFPermission of the requested MechId for all the
56      *         namespaces
57      */
58     public static List<AAFPermission> getAAFPermissions(ServletRequest request) {
59         CadiWrap wrapReq = (CadiWrap) request;
60
61         List<Permission> perms = wrapReq.getPermissions(wrapReq.getUserPrincipal());
62         List<AAFPermission> aafPermsList = new ArrayList<>();
63         for (Permission perm : perms) {
64             AAFPermission aafPerm = (AAFPermission) perm;
65             aafPermsList.add(aafPerm);
66         }
67         return aafPermsList;
68     }
69
70     /**
71      * Here is a sample of a permission object in AAI. The key attribute will have 
72      * Type|Instance|Action.
73      * AAFPermission:
74      *   NS: null
75      *   Type: org.onap.music.cadi.keyspace ( Permission Type )
76      *   Instance: tomtest   ( Cassandra Keyspace )
77      *   Action: *|GET|ALL   ( Access Level [*|ALL] for full access and [GET] for Read only)
78      *   Key: org.onap.music.cadi.keyspace|tomtest|*
79      *   
80      * This method will filter all permissions whose key starts with the requested namespace. 
81      * The nsamespace here is the music namespace which is defined in music.property file.
82      * i;e is the type contains in key is org.onap.music.cadi.keyspace and the namespace 
83      * value is org.onap.music.cadi.keyspace, it will add to list
84      * otherwise reject.
85      * 
86      * @param nameSpace
87      * @param allPermissionsList
88      * @return
89      */
90     private static List<AAFPermission> filterNameSpacesAAFPermissions(String nameSpace,
91             List<AAFPermission> allPermissionsList) {
92         List<AAFPermission> list = new ArrayList<>();
93         for (Iterator<AAFPermission> iterator = allPermissionsList.iterator(); iterator.hasNext();) {
94             AAFPermission aafPermission = (AAFPermission) iterator.next();
95             if(aafPermission.getType().indexOf(nameSpace) == 0) {
96                 list.add(aafPermission);
97             }
98         }
99         return list;
100     }
101
102     /**
103      * Decode certian characters from url encoded to normal.
104      * 
105      * @param str - String being decoded.
106      * @return returns the decoded string.
107      * @throws Exception throws excpetion
108      */
109     public static String decodeFunctionCode(String str) throws MusicAuthenticationException {
110         final String DECODEVALUE_FORWARDSLASH = "2f";
111         final String DECODEVALUE_HYPHEN = "2d";
112         final String DECODEVALUE_ASTERISK = "2a";
113         String decodedString = str;
114         List<Pattern> decodingList = new ArrayList<>();
115         decodingList.add(Pattern.compile(DECODEVALUE_FORWARDSLASH));
116         decodingList.add(Pattern.compile(DECODEVALUE_HYPHEN));
117         decodingList.add(Pattern.compile(DECODEVALUE_ASTERISK));
118         for (Pattern xssInputPattern : decodingList) {
119             try {
120                 decodedString = decodedString.replaceAll("%" + xssInputPattern,
121                         new String(Hex.decodeHex(xssInputPattern.toString().toCharArray())));
122             } catch (DecoderException e) {
123                 logger.error(EELFLoggerDelegate.securityLogger, 
124                     "AuthUtil Decode Failed! for instance: " + str);
125                 throw new MusicAuthenticationException("Decode failed", e);
126             }
127         }
128
129         return decodedString;
130     }
131
132     /**
133      * 
134      * 
135      * @param request servlet request object
136      * @param nameSpace application namespace
137      * @return boolean value if the access is allowed
138      * @throws Exception throws exception
139      */
140     public static boolean isAccessAllowed(ServletRequest request, String nameSpace) throws MusicAuthenticationException {
141
142         if (request==null) {
143             throw new MusicAuthenticationException("Request cannot be null");
144         }
145         
146         if (nameSpace==null || nameSpace.isEmpty()) {
147             throw new MusicAuthenticationException("NameSpace not Declared!");
148         }
149         
150         boolean isauthorized = false;
151         List<AAFPermission> aafPermsList = getAAFPermissions(request);
152         logger.info(EELFLoggerDelegate.securityLogger,
153             "AAFPermission  of the requested MechId for all the namespaces: " + aafPermsList);
154         logger.debug(EELFLoggerDelegate.securityLogger, "Requested nameSpace: " + nameSpace);
155
156         List<AAFPermission> aafPermsFinalList = filterNameSpacesAAFPermissions(nameSpace, aafPermsList);
157
158         logger.debug(EELFLoggerDelegate.securityLogger,
159             "AuthUtil list of AAFPermission for the specific namespace :::"
160             + aafPermsFinalList);
161         
162         HttpServletRequest httpRequest = (HttpServletRequest) request;
163         String requestUri = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length() + 1);
164
165         logger.debug(EELFLoggerDelegate.securityLogger,
166                 "AuthUtil requestUri :::" + requestUri);
167
168         for (Iterator<AAFPermission> iterator = aafPermsFinalList.iterator(); iterator.hasNext();) {
169             AAFPermission aafPermission = (AAFPermission) iterator.next();
170             if(!isauthorized) {
171                 isauthorized = isMatchPattern(aafPermission, requestUri, httpRequest.getMethod());
172             }
173         }
174         
175         logger.debug(EELFLoggerDelegate.securityLogger,
176             "isAccessAllowed for the request uri: " + requestUri + "is :" + isauthorized);
177         return isauthorized;
178     }
179
180     /**
181      * 
182      * This method will check, if the requested URI matches any of the instance 
183      * found with the AAF permission list.
184      * i;e if the request URI is; /v2/keyspaces/tomtest/tables/emp15 and in the 
185      * AAF permission table, we have an instance 
186      * defined as "tomtest" mapped the logged in user, it will allow else error.
187      * 
188      * User trying to create or aquire a lock
189      * Here is the requested URI /v2/locks/create/tomtest.MyTable.Field1
190      * Here the keyspace name i;e tomtest will be test throught out the URL if it 
191      * matches, it will allow the user to create a lock.
192      * "tomtest" here is the key, which is mapped as an instance in permission object.
193      * Instance can be delimited with ":" i;e ":music-cassandra-1908-dev:admin". In this case, 
194      * each delimited
195      * token will be matched with that of request URI.
196      * 
197      * Example Permission:
198      * org.onap.music.api.user.access|tomtest|* or ALL
199      * org.onap.music.api.user.access|tomtest|GET
200      * In case of the action field is ALL and *, user will be allowed else it will 
201      * be matched with the requested http method type.
202      * 
203      * 
204      * 
205      * @param aafPermission - AAfpermission obtained by cadi.
206      * @param requestUri - Rest URL client is calling.
207      * @param method - REST Method being used (GET,POST,PUT,DELETE)
208      * @return returns a boolean
209      * @throws Exception - throws an exception
210      */
211     private static boolean isMatchPattern(
212         AAFPermission aafPermission, 
213         String requestUri, 
214         String method) throws MusicAuthenticationException {
215         if (null == aafPermission || null == requestUri || null == method) {
216             return false;
217         }
218
219         String permKey = aafPermission.getKey();
220         
221         logger.debug(EELFLoggerDelegate.securityLogger, "isMatchPattern permKey: " 
222             + permKey + ", requestUri " + requestUri + " ," + method);
223         
224         String[] keyArray = permKey.split("\\|");
225         String[] subPath = null;
226         String instance = keyArray[1];
227         String action = keyArray[2];
228         
229         //if the instance & action both are * , then allow
230         if ("*".equalsIgnoreCase(instance) && "*".equalsIgnoreCase(action)) {
231             return true;
232         }
233         //Decode string like %2f, %2d and %2a
234         if (!"*".equals(instance)) {
235             instance = decodeFunctionCode(instance);
236         }
237         if (!"*".equals(action)) {
238             action = decodeFunctionCode(action);
239         }
240         //Instance: :music-cassandra-1908-dev:admin
241         List<String> instanceList = Arrays.asList(instance.split(":"));
242         
243         String[] path = requestUri.split("/");
244         
245         for (int i = 0; i < path.length; i++) {
246             // Sometimes the value will begin with "$", so we need to remove it
247             if (path[i].startsWith("$")) {
248                 path[i] = path[i].replace("$","");
249             }
250             // Each path element can again delemited by ".";i;e 
251             // tomtest.tables.emp. We have scenarios like lock aquire URL
252             subPath = path[i].split("\\.");
253             for (int j = 0; j < subPath.length; j++) {
254                 if (instanceList.contains(subPath[j])) {
255                     return checkAction(method,action);
256                 } else {
257                     continue;
258                 }
259             }
260         }
261         return false;
262     }
263
264     private static boolean checkAction(String method, String action) {
265         if ("*".equals(action) || "ALL".equalsIgnoreCase(action)) {
266             return true;
267         } else {
268             return (method.equalsIgnoreCase(action));
269         }
270     }
271
272
273
274 }