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