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