AAI-common sonar fixes
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / auth / AAIAuthCore.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
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  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.aai.auth;
22
23 import com.att.eelf.configuration.EELFLogger;
24 import com.att.eelf.configuration.EELFManager;
25 import com.fasterxml.jackson.core.JsonProcessingException;
26 import com.google.gson.JsonArray;
27 import com.google.gson.JsonElement;
28 import com.google.gson.JsonObject;
29 import com.google.gson.JsonParser;
30
31 import java.io.File;
32 import java.io.FileNotFoundException;
33 import java.io.UnsupportedEncodingException;
34 import java.nio.file.Files;
35 import java.nio.file.Paths;
36 import java.util.*;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 import java.util.stream.Collectors;
40
41 import org.eclipse.jetty.util.security.Password;
42 import org.eclipse.persistence.internal.oxm.conversion.Base64;
43 import org.onap.aai.auth.exceptions.AAIUnrecognizedFunctionException;
44 import org.onap.aai.logging.ErrorLogHelper;
45 import org.onap.aai.logging.LoggingContext;
46 import org.onap.aai.logging.LoggingContext.StatusCode;
47 import org.onap.aai.util.AAIConfig;
48 import org.onap.aai.util.AAIConstants;
49 import org.onap.aai.util.FileWatcher;
50
51 /**
52  * The Class AAIAuthCore.
53  */
54 public final class AAIAuthCore {
55
56     private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(AAIAuthCore.class);
57
58     private static final String ERROR_CODE_AAI_4001 = "AAI_4001";
59
60     private String globalAuthFileName = AAIConstants.AAI_AUTH_CONFIG_FILENAME;
61
62     private final Pattern AUTH_POLICY_PATTERN;
63     private final Set<String> validFunctions = new HashSet<>();
64     private Map<String, AAIUser> users;
65     private boolean timerSet = false;
66     private Timer timer = null;
67
68     private String basePath;
69
70     /**
71      * Instantiates a new AAI auth core.
72      */
73     public AAIAuthCore(String basePath) {
74         this.basePath = basePath;
75         AUTH_POLICY_PATTERN = Pattern.compile("^" + this.basePath + "/v\\d+/([\\w\\-]*)");
76         init();
77     }
78
79     /**
80      * Inits the.
81      */
82     private synchronized void init() {
83
84         LOGGER.debug("Initializing Auth Policy Config");
85
86         reloadUsers();
87
88         /*
89          * this timer code is setting up a recurring task that checks if the
90          * auth config file has been updated and reloads the users if so to get
91          * the most up to date info (that update check logic is within
92          * FileWatcher)
93          * 
94          * the timing this method uses is coarser than the frequency of requests
95          * AI&I gets so we're looking at better ways of doing this (TODO)
96          */
97         TimerTask task = new FileWatcher(new File(globalAuthFileName)) {
98             @Override
99             protected void onChange(File file) {
100                 reloadUsers();
101             }
102         };
103
104         if (!timerSet) {
105             timerSet = true;
106             timer = new Timer();
107
108             // repeat the check every second
109             timer.schedule(task, new Date(), 10000);
110         }
111         LOGGER.debug("Static Initializiation complete");
112     }
113
114     /**
115      * Cleanup.
116      */
117     // just ends the auth config file update checking timer
118     public void cleanup() {
119         timer.cancel();
120     }
121
122     /**
123      * Reload users.
124      */
125     /*
126      * this essentially takes the data file, which is organized role-first with
127      * users under each role and converts it to data organized user-first with
128      * each user containing their role with its associated allowed functions
129      * this data stored in the class field users
130      */
131     private synchronized void reloadUsers() {
132
133         Map<String, AAIUser> tempUsers = new HashMap<>();
134
135         try {
136             LOGGER.debug("Reading from " + globalAuthFileName);
137             String authFile = new String(Files.readAllBytes(Paths.get(globalAuthFileName)));
138
139             JsonParser parser = new JsonParser();
140             JsonObject authObject = parser.parse(authFile).getAsJsonObject();
141             if (authObject.has("roles")) {
142                 JsonArray roles = authObject.getAsJsonArray("roles");
143                 for (JsonElement role : roles) {
144                     if (role.isJsonObject()) {
145                         JsonObject roleObject = role.getAsJsonObject();
146                         String roleName = roleObject.get("name").getAsString();
147                         Map<String, Boolean> usrs = this.getUsernamesFromRole(roleObject);
148                         List<String> aaiFunctions = this.getAAIFunctions(roleObject);
149
150                         usrs.forEach((key, value) -> {
151                             final AAIUser au = tempUsers.getOrDefault(key, new AAIUser(key, value));
152                             au.addRole(roleName);
153                             aaiFunctions.forEach(f -> {
154                                 List<String> httpMethods = this.getRoleHttpMethods(f, roleObject);
155                                 httpMethods.forEach(hm -> au.setUserAccess(f, hm));
156                                 this.validFunctions.add(f);
157                             });
158
159                             tempUsers.put(key, au);
160
161                         });
162                     }
163                 }
164                 if (!tempUsers.isEmpty()) {
165                     users = tempUsers;
166                 }
167             }
168         } catch (FileNotFoundException e) {
169             ErrorLogHelper.logError(ERROR_CODE_AAI_4001, globalAuthFileName + ". Exception: " + e);
170         } catch (JsonProcessingException e) {
171             ErrorLogHelper.logError(ERROR_CODE_AAI_4001, globalAuthFileName + ". Not valid JSON: " + e);
172         } catch (Exception e) {
173             ErrorLogHelper.logError(ERROR_CODE_AAI_4001, globalAuthFileName + ". Exception caught: " + e);
174         }
175     }
176
177     private List<String> getRoleHttpMethods(String aaiFunctionName, JsonObject roleObject) {
178         List<String> httpMethods = new ArrayList<>();
179
180         JsonArray ja = roleObject.getAsJsonArray("functions");
181         for (JsonElement je : ja) {
182             if (je.isJsonObject() && je.getAsJsonObject().has("name")
183                     && je.getAsJsonObject().get("name").getAsString().equals(aaiFunctionName)) {
184                 JsonArray jaMeth = je.getAsJsonObject().getAsJsonArray("methods");
185                 for (JsonElement jeMeth : jaMeth) {
186                     if (jeMeth.isJsonObject() && jeMeth.getAsJsonObject().has("name")) {
187                         httpMethods.add(jeMeth.getAsJsonObject().get("name").getAsString());
188                     }
189                 }
190             }
191         }
192
193         return httpMethods;
194     }
195
196     private List<String> getAAIFunctions(JsonObject roleObject) {
197         List<String> aaiFunctions = new ArrayList<>();
198
199         JsonArray ja = roleObject.getAsJsonArray("functions");
200         for (JsonElement je : ja) {
201             if (je.isJsonObject() && je.getAsJsonObject().has("name")) {
202                 aaiFunctions.add(je.getAsJsonObject().get("name").getAsString());
203             }
204         }
205
206         return aaiFunctions;
207     }
208
209     private Map<String, Boolean> getUsernamesFromRole(JsonObject roleObject) throws UnsupportedEncodingException {
210         Map<String, Boolean> usernames = new HashMap<>();
211
212         JsonArray uja = roleObject.getAsJsonArray("users");
213         for (JsonElement je : uja) {
214             if (je.isJsonObject()) {
215                 if (je.getAsJsonObject().has("username")) {
216                     if (je.getAsJsonObject().has("is-wildcard-id")) {
217                         usernames.put(je.getAsJsonObject().get("username").getAsString().toLowerCase(),
218                                 je.getAsJsonObject().get("is-wildcard-id").getAsBoolean());
219                     } else {
220                         usernames.put(je.getAsJsonObject().get("username").getAsString().toLowerCase(), false);
221                     }
222                 } else if (je.getAsJsonObject().has("user")) {
223                     String auth = je.getAsJsonObject().get("user").getAsString() + ":"
224                             + Password.deobfuscate(je.getAsJsonObject().get("pass").getAsString());
225                     String authorizationCode = new String(Base64.base64Encode(auth.getBytes("utf-8")));
226                     usernames.put(authorizationCode, false);
227                 }
228             }
229         }
230
231         return usernames;
232     }
233
234     public String getAuthPolicyFunctName(String uri) {
235         String authPolicyFunctionName = "";
236         if (uri.startsWith(basePath + "/search")) {
237             authPolicyFunctionName = "search";
238         } else if (uri.startsWith(basePath + "/recents")) {
239             authPolicyFunctionName = "recents";
240         } else if (uri.startsWith(basePath + "/cq2gremlin")) {
241             authPolicyFunctionName = "cq2gremlin";
242         } else if (uri.startsWith(basePath + "/cq2gremlintest")) {
243             authPolicyFunctionName = "cq2gremlintest";
244         } else if (uri.startsWith(basePath + "/util/echo")) {
245             authPolicyFunctionName = "util";
246         } else if (uri.startsWith(basePath + "/tools")) {
247             authPolicyFunctionName = "tools";
248         } else {
249             Matcher match = AUTH_POLICY_PATTERN.matcher(uri);
250             if (match.find()) {
251                 authPolicyFunctionName = match.group(1);
252             }
253         }
254         return authPolicyFunctionName;
255     }
256
257     /**
258      * for backwards compatibility
259      * 
260      * @param username
261      * @param uri
262      * @param httpMethod
263      * @param haProxyUser
264      * @return
265      * @throws AAIUnrecognizedFunctionException
266      */
267     public boolean authorize(String username, String uri, String httpMethod, String haProxyUser)
268             throws AAIUnrecognizedFunctionException {
269         return authorize(username, uri, httpMethod, haProxyUser, null);
270     }
271
272     /**
273      *
274      * @param username
275      * @param uri
276      * @param httpMethod
277      * @param haProxyUser
278      * @param issuer issuer of the cert
279      * @return
280      * @throws AAIUnrecognizedFunctionException
281      */
282     public boolean authorize(String username, String uri, String httpMethod, String haProxyUser, String issuer)
283             throws AAIUnrecognizedFunctionException {
284         String aaiMethod = this.getAuthPolicyFunctName(uri);
285         if (!this.validFunctions.contains(aaiMethod)) {
286             throw new AAIUnrecognizedFunctionException(aaiMethod);
287         }
288         boolean wildcardCheck = isWildcardIssuer(issuer);
289         boolean authorized;
290         LOGGER.debug(
291                 "Authorizing the user for the request cert {}, haproxy header {}, aai method {}, httpMethod {}, cert issuer {}",
292                 username, haProxyUser, aaiMethod, httpMethod, issuer);
293         Optional<AAIUser> oau = this.getUser(username, wildcardCheck);
294         if (oau.isPresent()) {
295             AAIUser au = oau.get();
296             if (au.hasRole("HAProxy")) {
297                 LOGGER.debug("User has HAProxy role");
298                 if ("GET".equalsIgnoreCase(httpMethod) && "util".equalsIgnoreCase(aaiMethod) && haProxyUser.isEmpty()) {
299                     LOGGER.debug("Authorized user has HAProxy role with echo request");
300                     authorized = this.authorize(au, aaiMethod, httpMethod);
301                 } else {
302                     authorized = this.authorize(haProxyUser, uri, httpMethod, "", issuer);
303                 }
304             } else {
305                 LOGGER.debug("User doesn't have HAProxy role so assuming its a regular client");
306                 authorized = this.authorize(au, aaiMethod, httpMethod);
307             }
308         } else {
309             LOGGER.debug("User not found: " + username + " on function " + aaiMethod + " request type " + httpMethod);
310             authorized = false;
311         }
312
313         return authorized;
314     }
315
316     private boolean isWildcardIssuer(String issuer) {
317         if (issuer != null && !issuer.isEmpty()) {
318             List<String> validIssuers = Arrays
319                     .asList(AAIConfig.get("aaf.valid.issuer.wildcard", UUID.randomUUID().toString()).split("\\|"));
320             for (String validIssuer : validIssuers) {
321                 if (issuer.contains(validIssuer)) {
322                     return true;
323                 }
324             }
325         }
326         return false;
327     }
328
329     /**
330      * returns aai user either matching the username or containing the wildcard.
331      * 
332      * @param username
333      * @return
334      */
335     public Optional<AAIUser> getUser(String username, boolean wildcardCheck) {
336         if (users.containsKey(username)) {
337             return Optional.of(users.get(username));
338         } else if (wildcardCheck) {
339             List<AAIUser> laus =
340                     users.entrySet().stream().filter(e -> e.getValue().isWildcard() && username.contains(e.getKey()))
341                             .map(Map.Entry::getValue).collect(Collectors.toList());
342             if (!laus.isEmpty()) {
343                 return Optional.of(laus.get(0));
344             }
345         }
346         return Optional.empty();
347     }
348
349     /**
350      *
351      * @param aaiUser
352      *        aai user with the username
353      * @param aaiMethod
354      *        aai function the authorization is required on
355      * @param httpMethod
356      *        http action user is attempting
357      * @return true, if successful
358      */
359     private boolean authorize(AAIUser aaiUser, String aaiMethod, String httpMethod) {
360         if (aaiUser.hasAccess(aaiMethod, httpMethod)) {
361             LoggingContext.statusCode(StatusCode.COMPLETE);
362             LOGGER.debug("AUTH ACCEPTED: " + aaiUser.getUsername() + " on function " + aaiMethod + " request type "
363                     + httpMethod);
364             return true;
365         } else {
366             LoggingContext.statusCode(StatusCode.ERROR);
367             LOGGER.debug("AUTH FAILED: " + aaiUser.getUsername() + " on function " + aaiMethod + " request type "
368                     + httpMethod);
369             return false;
370         }
371     }
372 }