2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.aaf.auth;
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;
37 import java.io.FileNotFoundException;
38 import java.io.UnsupportedEncodingException;
39 import java.nio.file.Files;
40 import java.nio.file.Paths;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import java.util.stream.Collectors;
47 * The Class AAIAuthCore.
49 public final class AAIAuthCore {
51 private static final Logger LOGGER = LoggerFactory.getLogger(AAIAuthCore.class);
53 private static final String ERROR_CODE_AAI_4001 = "AAI_4001";
55 private String globalAuthFileName = AAIConstants.AAI_AUTH_CONFIG_FILENAME;
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;
63 private String basePath;
66 * Instantiates a new AAI auth core.
68 public AAIAuthCore(String basePath) {
69 this(basePath, AAIConstants.AAI_AUTH_CONFIG_FILENAME);
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\\-]*)");
79 public AAIAuthCore(String basePath, String filename, String pattern){
80 this.basePath = basePath;
81 this.globalAuthFileName = filename;
82 AUTH_POLICY_PATTERN = Pattern.compile(pattern);
89 private synchronized void init() {
91 LOGGER.debug("Initializing Auth Policy Config");
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
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)
104 TimerTask task = new FileWatcher(new File(globalAuthFileName)) {
106 protected void onChange(File file) {
115 // repeat the check every second
116 timer.schedule(task, new Date(), 10000);
118 LOGGER.debug("Static Initializiation complete");
124 // just ends the auth config file update checking timer
125 public void cleanup() {
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
138 private synchronized void reloadUsers() {
140 Map<String, AAIUser> tempUsers = new HashMap<>();
143 LOGGER.debug("Reading from " + globalAuthFileName);
144 String authFile = new String(Files.readAllBytes(Paths.get(globalAuthFileName)));
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);
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);
166 tempUsers.put(key, au);
171 if (!tempUsers.isEmpty()) {
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);
184 private List<String> getRoleHttpMethods(String aaiFunctionName, JsonObject roleObject) {
185 List<String> httpMethods = new ArrayList<>();
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());
203 private List<String> getAAIFunctions(JsonObject roleObject) {
204 List<String> aaiFunctions = new ArrayList<>();
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());
216 private Map<String, Boolean> getUsernamesFromRole(JsonObject roleObject) throws UnsupportedEncodingException {
217 Map<String, Boolean> usernames = new HashMap<>();
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());
227 usernames.put(je.getAsJsonObject().get("username").getAsString().toLowerCase(), false);
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);
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";
256 Matcher match = AUTH_POLICY_PATTERN.matcher(uri);
258 authPolicyFunctionName = match.group(1);
261 return authPolicyFunctionName;
265 * for backwards compatibility
272 * @throws AAIUnrecognizedFunctionException
274 public boolean authorize(String username, String uri, String httpMethod, String haProxyUser)
275 throws AAIUnrecognizedFunctionException {
276 return authorize(username, uri, httpMethod, haProxyUser, null);
285 * @param issuer issuer of the cert
287 * @throws AAIUnrecognizedFunctionException
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);
295 boolean wildcardCheck = isWildcardIssuer(issuer);
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);
309 authorized = this.authorize(haProxyUser, uri, httpMethod, "", issuer);
312 LOGGER.debug("User doesn't have HAProxy role so assuming its a regular client");
313 authorized = this.authorize(au, aaiMethod, httpMethod);
316 LOGGER.debug("User not found: " + username + " on function " + aaiMethod + " request type " + httpMethod);
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)) {
337 * returns aai user either matching the username or containing the wildcard.
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) {
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));
353 return Optional.empty();
359 * aai user with the username
361 * aai function the authorization is required on
363 * http action user is attempting
364 * @return true, if successful
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 "
372 LOGGER.debug("AUTH FAILED: " + aaiUser.getUsername() + " on function " + aaiMethod + " request type "