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