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