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