summaryrefslogtreecommitdiffstats
path: root/aai-aaf-auth
diff options
context:
space:
mode:
authorKajur, Harish (vk250x) <vk250x@att.com>2020-02-21 14:34:10 -0500
committerHarish Venkata Kajur <vk250x@att.com>2020-02-25 23:59:33 -0500
commit98749c47bbb5f5ddcc1c4f0690b79c7288f6bdd6 (patch)
treea472ce2edabd497b643917f44785b775fa16e15e /aai-aaf-auth
parente654645a50a0d028d8e67ea997f84efe8d28a6a0 (diff)
Enhancements for the aai-common library
Issue-ID: AAI-2806 Change-Id: I2dbb46b897b35136ac1bb802978d3f974af1b307 Signed-off-by: Kajur, Harish (vk250x) <vk250x@att.com>
Diffstat (limited to 'aai-aaf-auth')
-rw-r--r--aai-aaf-auth/pom.xml60
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIAuthCore.java377
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIUser.java81
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestFilter.java114
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestWrapper.java78
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/CertUtil.java157
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/FileWatcher.java60
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/ResponseFormatter.java57
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/exceptions/AAIUnrecognizedFunctionException.java46
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafAuthorizationFilter.java119
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertAuthorizationFilter.java107
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertFilter.java111
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafFilter.java108
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafProfiles.java33
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/CadiProps.java81
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/FilterPriority.java39
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/GremlinFilter.java99
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/PayloadBufferingRequestWrapper.java50
-rw-r--r--aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/TwoWaySslAuthorization.java186
-rw-r--r--aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIAuthCoreTest.java235
-rw-r--r--aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAISetup.java31
-rw-r--r--aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIUserTest.java53
-rw-r--r--aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/CertUtilTest.java87
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/cadi.properties14
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/org.onap.aai.props4
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/aaiconfig.properties58
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/error.properties160
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-cached.properties36
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-realtime.properties33
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/auth/aai_policy.json73
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/graphson/resource.graphson2
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource-format.json13
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource_and_url-format.json16
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/simple-format.json43
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/ambiguous-relationship.json10
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-failv10-successv9.json8
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-successv10-failv9.json8
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/nothing-to-parse.json4
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-related-link.json4
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-relationship-data.json7
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/too-many-items-relationship.json19
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/top-level-two-keys-relationship.json13
-rw-r--r--aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/two-top-level-relationship.json19
43 files changed, 2913 insertions, 0 deletions
diff --git a/aai-aaf-auth/pom.xml b/aai-aaf-auth/pom.xml
new file mode 100644
index 00000000..87eb9537
--- /dev/null
+++ b/aai-aaf-auth/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.onap.aai.aai-common</groupId>
+ <artifactId>aai-parent</artifactId>
+ <version>1.6.6-SNAPSHOT</version>
+ <relativePath>../aai-parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>aai-aaf-auth</artifactId>
+ <name>aai-aaf-auth</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.aai.aai-common</groupId>
+ <artifactId>aai-els-onap-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.aaf.authz</groupId>
+ <artifactId>aaf-cadi-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.aaf.authz</groupId>
+ <artifactId>aaf-cadi-aaf</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIAuthCore.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIAuthCore.java
new file mode 100644
index 00000000..c64251ad
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIAuthCore.java
@@ -0,0 +1,377 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.eclipse.jetty.util.security.Password;
+import org.onap.aai.aaf.auth.exceptions.AAIUnrecognizedFunctionException;
+import org.onap.aai.logging.ErrorLogHelper;
+import org.onap.aai.util.AAIConfig;
+import org.onap.aai.util.AAIConstants;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * The Class AAIAuthCore.
+ */
+public final class AAIAuthCore {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AAIAuthCore.class);
+
+ private static final String ERROR_CODE_AAI_4001 = "AAI_4001";
+
+ private String globalAuthFileName = AAIConstants.AAI_AUTH_CONFIG_FILENAME;
+
+ private final Pattern AUTH_POLICY_PATTERN;
+ private final Set<String> validFunctions = new HashSet<>();
+ private Map<String, AAIUser> users;
+ private boolean timerSet = false;
+ private Timer timer = null;
+
+ private String basePath;
+
+ /**
+ * Instantiates a new AAI auth core.
+ */
+ public AAIAuthCore(String basePath) {
+ this(basePath, AAIConstants.AAI_AUTH_CONFIG_FILENAME);
+ }
+
+ public AAIAuthCore(String basePath, String filename){
+ this.basePath = basePath;
+ this.globalAuthFileName = filename;
+ AUTH_POLICY_PATTERN = Pattern.compile("^" + this.basePath + "/v\\d+/([\\w\\-]*)");
+ init();
+ }
+
+ public AAIAuthCore(String basePath, String filename, String pattern){
+ this.basePath = basePath;
+ this.globalAuthFileName = filename;
+ AUTH_POLICY_PATTERN = Pattern.compile(pattern);
+ init();
+ }
+
+ /**
+ * Inits the.
+ */
+ private synchronized void init() {
+
+ LOGGER.debug("Initializing Auth Policy Config");
+
+ reloadUsers();
+
+ /*
+ * this timer code is setting up a recurring task that checks if the
+ * auth config file has been updated and reloads the users if so to get
+ * the most up to date info (that update check logic is within
+ * FileWatcher)
+ *
+ * the timing this method uses is coarser than the frequency of requests
+ * AI&I gets so we're looking at better ways of doing this (TODO)
+ */
+ TimerTask task = new FileWatcher(new File(globalAuthFileName)) {
+ @Override
+ protected void onChange(File file) {
+ reloadUsers();
+ }
+ };
+
+ if (!timerSet) {
+ timerSet = true;
+ timer = new Timer();
+
+ // repeat the check every second
+ timer.schedule(task, new Date(), 10000);
+ }
+ LOGGER.debug("Static Initializiation complete");
+ }
+
+ /**
+ * Cleanup.
+ */
+ // just ends the auth config file update checking timer
+ public void cleanup() {
+ timer.cancel();
+ }
+
+ /**
+ * Reload users.
+ */
+ /*
+ * this essentially takes the data file, which is organized role-first with
+ * users under each role and converts it to data organized user-first with
+ * each user containing their role with its associated allowed functions
+ * this data stored in the class field users
+ */
+ private synchronized void reloadUsers() {
+
+ Map<String, AAIUser> tempUsers = new HashMap<>();
+
+ try {
+ LOGGER.debug("Reading from " + globalAuthFileName);
+ String authFile = new String(Files.readAllBytes(Paths.get(globalAuthFileName)));
+
+ JsonParser parser = new JsonParser();
+ JsonObject authObject = parser.parse(authFile).getAsJsonObject();
+ if (authObject.has("roles")) {
+ JsonArray roles = authObject.getAsJsonArray("roles");
+ for (JsonElement role : roles) {
+ if (role.isJsonObject()) {
+ JsonObject roleObject = role.getAsJsonObject();
+ String roleName = roleObject.get("name").getAsString();
+ Map<String, Boolean> usrs = this.getUsernamesFromRole(roleObject);
+ List<String> aaiFunctions = this.getAAIFunctions(roleObject);
+
+ usrs.forEach((key, value) -> {
+ final AAIUser au = tempUsers.getOrDefault(key, new AAIUser(key, value));
+ au.addRole(roleName);
+ aaiFunctions.forEach(f -> {
+ List<String> httpMethods = this.getRoleHttpMethods(f, roleObject);
+ httpMethods.forEach(hm -> au.setUserAccess(f, hm));
+ this.validFunctions.add(f);
+ });
+
+ tempUsers.put(key, au);
+
+ });
+ }
+ }
+ if (!tempUsers.isEmpty()) {
+ users = tempUsers;
+ }
+ }
+ } catch (FileNotFoundException e) {
+ ErrorLogHelper.logError(ERROR_CODE_AAI_4001, globalAuthFileName + ". Exception: " + e);
+ } catch (JsonProcessingException e) {
+ ErrorLogHelper.logError(ERROR_CODE_AAI_4001, globalAuthFileName + ". Not valid JSON: " + e);
+ } catch (Exception e) {
+ ErrorLogHelper.logError(ERROR_CODE_AAI_4001, globalAuthFileName + ". Exception caught: " + e);
+ }
+ }
+
+ private List<String> getRoleHttpMethods(String aaiFunctionName, JsonObject roleObject) {
+ List<String> httpMethods = new ArrayList<>();
+
+ JsonArray ja = roleObject.getAsJsonArray("functions");
+ for (JsonElement je : ja) {
+ if (je.isJsonObject() && je.getAsJsonObject().has("name")
+ && je.getAsJsonObject().get("name").getAsString().equals(aaiFunctionName)) {
+ JsonArray jaMeth = je.getAsJsonObject().getAsJsonArray("methods");
+ for (JsonElement jeMeth : jaMeth) {
+ if (jeMeth.isJsonObject() && jeMeth.getAsJsonObject().has("name")) {
+ httpMethods.add(jeMeth.getAsJsonObject().get("name").getAsString());
+ }
+ }
+ }
+ }
+
+ return httpMethods;
+ }
+
+ private List<String> getAAIFunctions(JsonObject roleObject) {
+ List<String> aaiFunctions = new ArrayList<>();
+
+ JsonArray ja = roleObject.getAsJsonArray("functions");
+ for (JsonElement je : ja) {
+ if (je.isJsonObject() && je.getAsJsonObject().has("name")) {
+ aaiFunctions.add(je.getAsJsonObject().get("name").getAsString());
+ }
+ }
+
+ return aaiFunctions;
+ }
+
+ private Map<String, Boolean> getUsernamesFromRole(JsonObject roleObject) throws UnsupportedEncodingException {
+ Map<String, Boolean> usernames = new HashMap<>();
+
+ JsonArray uja = roleObject.getAsJsonArray("users");
+ for (JsonElement je : uja) {
+ if (je.isJsonObject()) {
+ if (je.getAsJsonObject().has("username")) {
+ if (je.getAsJsonObject().has("is-wildcard-id")) {
+ usernames.put(je.getAsJsonObject().get("username").getAsString().toLowerCase(),
+ je.getAsJsonObject().get("is-wildcard-id").getAsBoolean());
+ } else {
+ usernames.put(je.getAsJsonObject().get("username").getAsString().toLowerCase(), false);
+ }
+ } else if (je.getAsJsonObject().has("user")) {
+ String auth = je.getAsJsonObject().get("user").getAsString() + ":"
+ + Password.deobfuscate(je.getAsJsonObject().get("pass").getAsString());
+ String authorizationCode = new String(Base64.getEncoder().encode(auth.getBytes("utf-8")));
+ usernames.put(authorizationCode, false);
+ }
+ }
+ }
+
+ return usernames;
+ }
+
+ public String getAuthPolicyFunctName(String uri) {
+ String authPolicyFunctionName = "";
+ if (uri.startsWith(basePath + "/search")) {
+ authPolicyFunctionName = "search";
+ } else if (uri.startsWith(basePath + "/recents")) {
+ authPolicyFunctionName = "recents";
+ } else if (uri.startsWith(basePath + "/cq2gremlin")) {
+ authPolicyFunctionName = "cq2gremlin";
+ } else if (uri.startsWith(basePath + "/cq2gremlintest")) {
+ authPolicyFunctionName = "cq2gremlintest";
+ } else if (uri.startsWith(basePath + "/util/echo")) {
+ authPolicyFunctionName = "util";
+ } else if (uri.startsWith(basePath + "/tools")) {
+ authPolicyFunctionName = "tools";
+ } else {
+ Matcher match = AUTH_POLICY_PATTERN.matcher(uri);
+ if (match.find()) {
+ authPolicyFunctionName = match.group(1);
+ }
+ }
+ return authPolicyFunctionName;
+ }
+
+ /**
+ * for backwards compatibility
+ *
+ * @param username
+ * @param uri
+ * @param httpMethod
+ * @param haProxyUser
+ * @return
+ * @throws AAIUnrecognizedFunctionException
+ */
+ public boolean authorize(String username, String uri, String httpMethod, String haProxyUser)
+ throws AAIUnrecognizedFunctionException {
+ return authorize(username, uri, httpMethod, haProxyUser, null);
+ }
+
+ /**
+ *
+ * @param username
+ * @param uri
+ * @param httpMethod
+ * @param haProxyUser
+ * @param issuer issuer of the cert
+ * @return
+ * @throws AAIUnrecognizedFunctionException
+ */
+ public boolean authorize(String username, String uri, String httpMethod, String haProxyUser, String issuer)
+ throws AAIUnrecognizedFunctionException {
+ String aaiMethod = this.getAuthPolicyFunctName(uri);
+ if (!this.validFunctions.contains(aaiMethod) && !("info".equalsIgnoreCase(aaiMethod))) {
+ throw new AAIUnrecognizedFunctionException(aaiMethod);
+ }
+ boolean wildcardCheck = isWildcardIssuer(issuer);
+ boolean authorized;
+ LOGGER.debug(
+ "Authorizing the user for the request cert {}, haproxy header {}, aai method {}, httpMethod {}, cert issuer {}",
+ username, haProxyUser, aaiMethod, httpMethod, issuer);
+ Optional<AAIUser> oau = this.getUser(username, wildcardCheck);
+ if (oau.isPresent()) {
+ AAIUser au = oau.get();
+ if (au.hasRole("HAProxy")) {
+ LOGGER.debug("User has HAProxy role");
+ if ("GET".equalsIgnoreCase(httpMethod) && "util".equalsIgnoreCase(aaiMethod) && haProxyUser.isEmpty()) {
+ LOGGER.debug("Authorized user has HAProxy role with echo request");
+ authorized = this.authorize(au, aaiMethod, httpMethod);
+ } else {
+ authorized = this.authorize(haProxyUser, uri, httpMethod, "", issuer);
+ }
+ } else {
+ LOGGER.debug("User doesn't have HAProxy role so assuming its a regular client");
+ authorized = this.authorize(au, aaiMethod, httpMethod);
+ }
+ } else {
+ LOGGER.debug("User not found: " + username + " on function " + aaiMethod + " request type " + httpMethod);
+ authorized = false;
+ }
+
+ return authorized;
+ }
+
+ private boolean isWildcardIssuer(String issuer) {
+ if (issuer != null && !issuer.isEmpty()) {
+ List<String> validIssuers = Arrays
+ .asList(AAIConfig.get("aaf.valid.issuer.wildcard", UUID.randomUUID().toString()).split("\\|"));
+ for (String validIssuer : validIssuers) {
+ if (issuer.contains(validIssuer)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * returns aai user either matching the username or containing the wildcard.
+ *
+ * @param username
+ * @return
+ */
+ public Optional<AAIUser> getUser(String username, boolean wildcardCheck) {
+ if (users.containsKey(username)) {
+ return Optional.of(users.get(username));
+ } else if (wildcardCheck) {
+ List<AAIUser> laus =
+ users.entrySet().stream().filter(e -> e.getValue().isWildcard() && username.contains(e.getKey()))
+ .map(Map.Entry::getValue).collect(Collectors.toList());
+ if (!laus.isEmpty()) {
+ return Optional.of(laus.get(0));
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ *
+ * @param aaiUser
+ * aai user with the username
+ * @param aaiMethod
+ * aai function the authorization is required on
+ * @param httpMethod
+ * http action user is attempting
+ * @return true, if successful
+ */
+ private boolean authorize(AAIUser aaiUser, String aaiMethod, String httpMethod) {
+ if ("info".equalsIgnoreCase(aaiMethod)|| aaiUser.hasAccess(aaiMethod, httpMethod)) {
+ LOGGER.debug("AUTH ACCEPTED: " + aaiUser.getUsername() + " on function " + aaiMethod + " request type "
+ + httpMethod);
+ return true;
+ } else {
+ LOGGER.debug("AUTH FAILED: " + aaiUser.getUsername() + " on function " + aaiMethod + " request type "
+ + httpMethod);
+ return false;
+ }
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIUser.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIUser.java
new file mode 100644
index 00000000..4512adb0
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AAIUser.java
@@ -0,0 +1,81 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Modifications Copyright © 2018 IBM.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import java.util.*;
+
+public class AAIUser {
+
+ private String username;
+
+ private boolean isWildcard = false;
+ private Set<String> roles;
+ private Map<String, Set<String>> aaiFunctionToHttpMethod;
+
+ public AAIUser(String username) {
+ this(username, false);
+ }
+
+ public AAIUser(String username, boolean isWildcard) {
+ this.username = username;
+ this.roles = new HashSet<>();
+ this.aaiFunctionToHttpMethod = new HashMap<>();
+ this.isWildcard = isWildcard;
+ }
+
+ public boolean isWildcard() {
+ return isWildcard;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void addRole(String role) {
+ this.roles.add(role);
+ }
+
+ public boolean hasRole(String role) {
+ return this.roles.contains(role);
+ }
+
+ public void setUserAccess(String aaiMethod, String... httpMethods) {
+ for (String httpMethod : httpMethods) {
+ this.addUserAccess(aaiMethod, httpMethod);
+ }
+ }
+
+ private void addUserAccess(String aaiMethod, String httpMethod) {
+ Set<String> httpMethods = new HashSet<>();
+ if (this.aaiFunctionToHttpMethod.containsKey(aaiMethod)) {
+ httpMethods = this.aaiFunctionToHttpMethod.get(aaiMethod);
+ }
+ httpMethods.add(httpMethod);
+ this.aaiFunctionToHttpMethod.put(aaiMethod, httpMethods);
+ }
+
+ public boolean hasAccess(String aaiMethod, String httpMethod) {
+ return this.aaiFunctionToHttpMethod.getOrDefault(aaiMethod, Collections.emptySet()).contains(httpMethod);
+ }
+
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestFilter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestFilter.java
new file mode 100644
index 00000000..9a02fe2c
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestFilter.java
@@ -0,0 +1,114 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.onap.aaf.cadi.filter.CadiFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+import static org.onap.aai.aaf.auth.ResponseFormatter.errorResponse;
+
+/**
+ * The Class AafRequestFilter provides common auth filter methods
+ */
+public class AafRequestFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AafRequestFilter.class);
+
+ public static void authenticationFilter(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain, CadiFilter cadiFilter, Properties props, String userChainPattern)
+ throws IOException, ServletException {
+ if (!request.getRequestURI().matches("^.*/util/echo$")) {
+
+ List<String> cadiConfiguredIssuers = CertUtil.getCadiCertIssuers(props);
+ String issuer = CertUtil.getCertIssuer(request);
+ if (issuer == null || issuer.isEmpty()) {
+ errorResponse(request, response);
+ return;
+ }
+ issuer = issuer.replaceAll("\\s+", "").toUpperCase();
+
+ if (cadiConfiguredIssuers.contains(issuer)) {
+ LOGGER.debug("authenticationFilter CADI issuer " + issuer);
+ if (CertUtil.isHaProxy(request)) {
+ // get the end user/client mechid and use it in the user chain header value
+ String user = CertUtil.getMechId(request);
+ LOGGER.debug("authenticationFilter haProxy sent end user/mechid " + user);
+ if (user == null || user.isEmpty()) {
+ errorResponse(request, response);
+ return;
+ }
+ AafRequestWrapper reqWrapper = new AafRequestWrapper(request);
+ String userChainHdr = CertUtil.buildUserChainHeader(user, userChainPattern);
+ LOGGER.debug("User chain header value: " + userChainHdr);
+ reqWrapper.putHeader(CertUtil.AAF_USER_CHAIN_HDR, userChainHdr);
+ cadiFilter.doFilter(reqWrapper, response, filterChain);
+ } else {
+ cadiFilter.doFilter(request, response, filterChain);
+ }
+ if (response.getStatus() == 401 || response.getStatus() == 403) {
+ LOGGER.debug("authenticationFilter failed CADI authentication");
+ errorResponse(request, response);
+ return;
+ }
+ } else {
+ filterChain.doFilter(request, response);
+ }
+ } else {
+ filterChain.doFilter(request, response);
+ }
+ }
+
+ public static void authorizationFilter(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain, String permission, Properties props) throws IOException, ServletException {
+ if (request.getRequestURI().matches("^.*/util/echo$")) {
+ filterChain.doFilter(request, response);
+ }
+ List<String> cadiConfiguredIssuers = CertUtil.getCadiCertIssuers(props);
+ String issuer = CertUtil.getCertIssuer(request);
+ if (issuer == null || issuer.isEmpty()) {
+ errorResponse(request, response);
+ return;
+ }
+ issuer = issuer.replaceAll("\\s+", "").toUpperCase();
+ Enumeration hdrs = request.getHeaders(CertUtil.AAF_USER_CHAIN_HDR);
+ while (hdrs.hasMoreElements()) {
+ String headerValue = (String) hdrs.nextElement();
+ LOGGER.debug("authorizationFilter user chain headerValue=" + headerValue);
+ }
+ if ((cadiConfiguredIssuers.contains(issuer)) && (!request.isUserInRole(permission))) {
+ LOGGER.debug(
+ "authorizationFilter failed CADI authorization issuer=" + issuer + " permission=" + permission);
+ errorResponse(request, response);
+ } else {
+ filterChain.doFilter(request, response);
+ }
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestWrapper.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestWrapper.java
new file mode 100644
index 00000000..0ecca679
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/AafRequestWrapper.java
@@ -0,0 +1,78 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.util.*;
+
+/**
+ * The AafRequestWrapper sets the user in the principal name
+ */
+public class AafRequestWrapper extends HttpServletRequestWrapper {
+
+ private final Map<String, String> customHeaders;
+
+ public AafRequestWrapper(HttpServletRequest request) {
+ super(request);
+ this.customHeaders = new HashMap<String, String>();
+ }
+
+ public void putHeader(String name, String value) {
+ this.customHeaders.put(name, value);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ String headerValue = customHeaders.get(name);
+ if (headerValue != null) {
+ return headerValue;
+ }
+ return (((HttpServletRequest) getRequest()).getHeader(name));
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ Set<String> nameSet = new HashSet<String>(customHeaders.keySet());
+
+ Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
+ while (e.hasMoreElements()) {
+ String headerName = e.nextElement();
+ nameSet.add(headerName);
+ }
+ return Collections.enumeration(nameSet);
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ String myHeaderValue = customHeaders.get(name);
+ Set<String> headerValueSet = new HashSet<String>();
+ if (myHeaderValue != null) {
+ headerValueSet.add(myHeaderValue);
+ }
+ Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaders(name);
+ while (e.hasMoreElements()) {
+ String headerValue = e.nextElement();
+ headerValueSet.add(headerValue);
+ }
+ return Collections.enumeration(headerValueSet);
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/CertUtil.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/CertUtil.java
new file mode 100644
index 00000000..334a3060
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/CertUtil.java
@@ -0,0 +1,157 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * The Class CertUtil provides cert related utility methods.
+ */
+public class CertUtil {
+ public static final String DEFAULT_CADI_ISSUERS =
+ "CN=ATT AAF CADI Test Issuing CA 01, OU=CSO, O=ATT, C=US:CN=ATT AAF CADI Test Issuing CA 02, OU=CSO, O=ATT, C=US";
+ public static final String CADI_PROP_FILES = "cadi_prop_files";
+ public static final String CADI_ISSUERS_PROP_NAME = "cadi_x509_issuers";
+ public static final String CADI_ISSUERS_SEPARATOR = ":";
+ public static final String AAI_SSL_CLIENT_OU_HDR = "X-AAI-SSL-Client-OU";
+ public static final String AAI_SSL_ISSUER_HDR = "X-AAI-SSL-Issuer";
+ public static final String AAI_SSL_CLIENT_CN_HDR = "X-AAI-SSL-Client-CN";
+ public static final String AAI_SSL_CLIENT_O_HDR = "X-AAI-SSL-Client-O";
+ public static final String AAI_SSL_CLIENT_L_HDR = "X-AAI-SSL-Client-L";
+ public static final String AAI_SSL_CLIENT_ST_HDR = "X-AAI-SSL-Client-ST";
+ public static final String AAI_SSL_CLIENT_C_HDR = "X-AAI-SSL-Client-C";
+ public static final String AAF_USER_CHAIN_HDR = "USER_CHAIN";
+ public static final String AAF_ID = "<AAF-ID>";
+ private static final Logger LOGGER = LoggerFactory.getLogger(CertUtil.class);
+
+ public static String getAaiSslClientOuHeader(HttpServletRequest hsr) {
+ return (hsr.getHeader(AAI_SSL_CLIENT_OU_HDR));
+ }
+
+ public static boolean isHaProxy(HttpServletRequest hsr) {
+
+ String haProxyUser = "";
+ if (Objects.isNull(hsr.getHeader(AAI_SSL_CLIENT_CN_HDR)) || Objects.isNull(hsr.getHeader(AAI_SSL_CLIENT_OU_HDR))
+ || Objects.isNull(hsr.getHeader(AAI_SSL_CLIENT_O_HDR))
+ || Objects.isNull(hsr.getHeader(AAI_SSL_CLIENT_L_HDR))
+ || Objects.isNull(hsr.getHeader(AAI_SSL_CLIENT_ST_HDR))
+ || Objects.isNull(hsr.getHeader(AAI_SSL_CLIENT_C_HDR))) {
+ haProxyUser = "";
+ } else {
+ haProxyUser = String.format("CN=%s, OU=%s, O=\"%s\", L=%s, ST=%s, C=%s",
+ Objects.toString(hsr.getHeader(AAI_SSL_CLIENT_CN_HDR), ""),
+ Objects.toString(hsr.getHeader(AAI_SSL_CLIENT_OU_HDR), ""),
+ Objects.toString(hsr.getHeader(AAI_SSL_CLIENT_O_HDR), ""),
+ Objects.toString(hsr.getHeader(AAI_SSL_CLIENT_L_HDR), ""),
+ Objects.toString(hsr.getHeader(AAI_SSL_CLIENT_ST_HDR), ""),
+ Objects.toString(hsr.getHeader(AAI_SSL_CLIENT_C_HDR), "")).toLowerCase();
+ }
+ if (!haProxyUser.isEmpty()) {
+ LOGGER.debug("isHaProxy haProxyUser=" + haProxyUser);
+ return true;
+ }
+ LOGGER.debug("isHaProxy haProxyUser not found");
+ return false;
+ }
+
+ public static String getMechId(HttpServletRequest hsr) {
+ String mechId = null;
+ String ou = getAaiSslClientOuHeader(hsr);
+ if ((ou != null) && (!ou.isEmpty())) {
+ String[] parts = ou.split(CADI_ISSUERS_SEPARATOR);
+ if (parts != null && parts.length >= 1) {
+ mechId = parts[0];
+ }
+ }
+ LOGGER.debug("getMechId mechId=" + mechId);
+ return (mechId);
+ }
+
+ public static String getCertIssuer(HttpServletRequest hsr) {
+ String issuer = hsr.getHeader(AAI_SSL_ISSUER_HDR);
+ if (issuer != null && !issuer.isEmpty()) {
+ LOGGER.debug("getCertIssuer issuer from header " + AAI_SSL_ISSUER_HDR + " " + issuer);
+ // the haproxy header replaces the ', ' with '/' and reverses on the '/' need to undo that.
+ List<String> broken = Arrays.asList(issuer.split("/"));
+ broken = broken.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
+ Collections.reverse(broken);
+ issuer = String.join(", ", broken);
+ } else {
+ if (hsr.getAttribute("javax.servlet.request.cipher_suite") != null) {
+ X509Certificate[] certChain =
+ (X509Certificate[]) hsr.getAttribute("javax.servlet.request.X509Certificate");
+ if (certChain != null && certChain.length > 0) {
+ X509Certificate clientCert = certChain[0];
+ issuer = clientCert.getIssuerX500Principal().getName();
+ LOGGER.debug("getCertIssuer issuer from client cert " + issuer);
+ }
+ }
+ }
+ return issuer;
+ }
+
+ public static List<String> getCadiCertIssuers(Properties cadiProperties) {
+
+ List<String> defaultList = new ArrayList<String>();
+ List<String> resultList = new ArrayList<String>();
+
+ String[] cIssuers = DEFAULT_CADI_ISSUERS.split(CADI_ISSUERS_SEPARATOR);
+ for (String issuer : cIssuers) {
+ defaultList.add(issuer.replaceAll("\\s+", "").toUpperCase());
+ }
+ try {
+ String certPropFileName = cadiProperties.getProperty(CADI_PROP_FILES);
+ String configuredIssuers = DEFAULT_CADI_ISSUERS;
+ Properties certProperties = new Properties();
+ if (certPropFileName != null) {
+ certProperties.load(new FileInputStream(new File(certPropFileName)));
+ configuredIssuers = certProperties.getProperty(CADI_ISSUERS_PROP_NAME);
+ }
+ if ((configuredIssuers != null) && (!configuredIssuers.isEmpty())) {
+ cIssuers = configuredIssuers.split(CADI_ISSUERS_SEPARATOR);
+ for (String issuer : cIssuers) {
+ resultList.add(issuer.replaceAll("\\s+", "").toUpperCase());
+ }
+ }
+ } catch (IOException ioe) {
+ return (defaultList);
+ }
+ if (resultList.isEmpty()) {
+ return defaultList;
+ }
+ LOGGER.debug("getCadiCertIssuers " + resultList.toString());
+ return resultList;
+ }
+
+ public static String buildUserChainHeader(String user, String userChainPattern) {
+ // aaf.userchain.pattern=<AAF-ID>:${aaf.userchain.service.reference}:${aaf.userchain.auth.type}:AS
+ return (userChainPattern.replaceAll(AAF_ID, user));
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/FileWatcher.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/FileWatcher.java
new file mode 100644
index 00000000..45331ed9
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/FileWatcher.java
@@ -0,0 +1,60 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import java.io.File;
+import java.util.TimerTask;
+
+public abstract class FileWatcher extends TimerTask {
+ private long timeStamp;
+ private File file;
+
+ /**
+ * Instantiates a new file watcher.
+ *
+ * @param file the file
+ */
+ public FileWatcher(File file) {
+ this.file = file;
+ this.timeStamp = file.lastModified();
+ }
+
+ /**
+ * runs a timer task
+ *
+ * @see TimerTask#run
+ */
+ public final void run() {
+ long timeStamp = file.lastModified();
+
+ if ((timeStamp - this.timeStamp) > 500) {
+ this.timeStamp = timeStamp;
+ onChange(file);
+ }
+ }
+
+ /**
+ * On change.
+ *
+ * @param file the file
+ */
+ protected abstract void onChange(File file);
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/ResponseFormatter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/ResponseFormatter.java
new file mode 100644
index 00000000..d7c88e81
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/ResponseFormatter.java
@@ -0,0 +1,57 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.onap.aai.exceptions.AAIException;
+import org.onap.aai.logging.ErrorLogHelper;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class ResponseFormatter {
+
+ private static final String ACCEPT_HEADER = "accept";
+
+ public static void errorResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ errorResponse(new AAIException("AAI_3300"), request, response);
+ }
+
+ public static void errorResponse(AAIException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
+
+ if(response.isCommitted()){
+ return;
+ }
+
+ String accept = request.getHeader(ACCEPT_HEADER) == null ? MediaType.APPLICATION_XML : request.getHeader(ACCEPT_HEADER);
+
+ response.setStatus(exception.getErrorObject().getHTTPResponseCode().getStatusCode());
+ response.setHeader("Content-Type", accept);
+ response.resetBuffer();
+
+ String resp = ErrorLogHelper.getRESTAPIErrorResponse(Collections.singletonList(MediaType.valueOf(accept)), exception, new ArrayList<>());
+ response.getOutputStream().print(resp);
+ response.flushBuffer();
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/exceptions/AAIUnrecognizedFunctionException.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/exceptions/AAIUnrecognizedFunctionException.java
new file mode 100644
index 00000000..c6a97ac5
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/auth/exceptions/AAIUnrecognizedFunctionException.java
@@ -0,0 +1,46 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Modifications Copyright © 2018 IBM.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth.exceptions;
+
+import org.onap.aai.exceptions.AAIException;
+
+public class AAIUnrecognizedFunctionException extends AAIException {
+
+ private static final String AAI_3012 = "AAI_3012";
+ private static final long serialVersionUID = 3297064867724071290L;
+
+ public AAIUnrecognizedFunctionException() {
+ }
+
+ public AAIUnrecognizedFunctionException(String message) {
+ super(AAI_3012, message);
+ }
+
+ public AAIUnrecognizedFunctionException(Throwable cause) {
+ super(AAI_3012, cause);
+ }
+
+ public AAIUnrecognizedFunctionException(String message, Throwable cause) {
+ super(AAI_3012, cause, message);
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafAuthorizationFilter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafAuthorizationFilter.java
new file mode 100644
index 00000000..9f2782a4
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafAuthorizationFilter.java
@@ -0,0 +1,119 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.filters;
+
+import org.onap.aai.aaf.auth.ResponseFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.filter.OrderedRequestContextFilter;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * AAF authorization filter
+ */
+
+@Component
+@Profile(AafProfiles.AAF_AUTHENTICATION)
+@PropertySource(value = "file:${CONFIG_HOME}/aaf/permissions.properties", ignoreResourceNotFound = true)
+@PropertySource(value = "file:${server.local.startpath}/aaf/permissions.properties", ignoreResourceNotFound = true)
+public class AafAuthorizationFilter extends OrderedRequestContextFilter {
+
+ private static final String ADVANCED = "advanced";
+ private static final String BASIC = "basic";
+
+ private final String type;
+ private final String instance;
+
+ private GremlinFilter gremlinFilter;
+
+ private List<String> advancedKeywordsList;
+
+ @Autowired
+ public AafAuthorizationFilter(
+ GremlinFilter gremlinFilter,
+ @Value("${permission.type}") String type,
+ @Value("${permission.instance}") String instance,
+ @Value("${advanced.keywords.list:}") String advancedKeys
+ ) {
+ this.gremlinFilter = gremlinFilter;
+ this.type = type;
+ this.instance = instance;
+ if(advancedKeys == null || advancedKeys.isEmpty()){
+ this.advancedKeywordsList = new ArrayList<>();
+ } else {
+ this.advancedKeywordsList = Arrays.stream(advancedKeys.split(","))
+ .collect(Collectors.toList());
+ }
+ this.setOrder(FilterPriority.AAF_AUTHORIZATION.getPriority());
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ if(request.getRequestURI().endsWith("/query")){
+ gremlinFilter.doBasicAuthFilter(request, response, filterChain);
+ } else {
+
+ String permission = null;
+
+ if(advancedKeywordsList == null || advancedKeywordsList.size() == 0) {
+ permission = String.format("%s|%s|%s", type, instance, request.getMethod().toLowerCase());
+ } else {
+
+ boolean isAdvanced = this.containsAdvancedKeywords(request);
+
+ //if the URI contains advanced.keywords it's an advanced query
+ String queryType = isAdvanced ? ADVANCED : BASIC;
+ permission = String.format("%s|%s|%s", type, instance, queryType);
+ }
+
+ boolean isAuthorized = request.isUserInRole(permission);
+
+ if(!isAuthorized){
+ ResponseFormatter.errorResponse(request, response);
+ } else {
+ filterChain.doFilter(request,response);
+ }
+
+ }
+ }
+
+ private boolean containsAdvancedKeywords(HttpServletRequest request) {
+ String uri = request.getRequestURI();
+ for (String keyword: advancedKeywordsList) {
+ if (uri.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertAuthorizationFilter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertAuthorizationFilter.java
new file mode 100644
index 00000000..7ec6bb64
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertAuthorizationFilter.java
@@ -0,0 +1,107 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.filters;
+
+import org.onap.aai.aaf.auth.AafRequestFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.filter.OrderedRequestContextFilter;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+
+/**
+ * AAF with client cert authorization filter
+ */
+
+@Component
+@Profile(AafProfiles.AAF_CERT_AUTHENTICATION)
+@PropertySource(value = "file:${CONFIG_HOME}/aaf/permissions.properties", ignoreResourceNotFound = true)
+@PropertySource(value = "file:${server.local.startpath}/aaf/permissions.properties", ignoreResourceNotFound = true)
+public class AafCertAuthorizationFilter extends OrderedRequestContextFilter {
+
+ private static final String ADVANCED = "advanced";
+ private static final String BASIC = "basic";
+
+ String type;
+
+ String instance;
+
+ private CadiProps cadiProps;
+
+ private List<String> advancedKeywordsList;
+
+ @Autowired
+ public AafCertAuthorizationFilter(
+ @Value("${permission.type}") String type,
+ @Value("${permission.instance}") String instance,
+ @Value("${advanced.keywords.list:}") String advancedKeys,
+ CadiProps cadiProps
+ ) {
+ this.type = type;
+ this.instance = instance;
+ this.cadiProps = cadiProps;
+ if(advancedKeys == null || advancedKeys.isEmpty()){
+ this.advancedKeywordsList = new ArrayList<>();
+ } else {
+ this.advancedKeywordsList = Arrays.stream(advancedKeys.split(","))
+ .collect(Collectors.toList());
+ }
+ this.setOrder(FilterPriority.AAF_CERT_AUTHORIZATION.getPriority());
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ if(advancedKeywordsList == null || advancedKeywordsList.size() == 0){
+ String permission = String.format("%s|%s|%s", type, instance, request.getMethod().toLowerCase());
+ AafRequestFilter.authorizationFilter(request, response, filterChain, permission, cadiProps.getCadiProperties());
+ } else {
+ boolean isAdvanced = this.containsAdvancedKeywords(request);
+
+ //if the URI contains advanced.keywords it's an advanced query
+ String queryType = isAdvanced ? ADVANCED : BASIC;
+ String permission = String.format("%s|%s|%s", type, instance, queryType);
+ AafRequestFilter.authorizationFilter(request, response, filterChain, permission, cadiProps.getCadiProperties());
+ }
+ }
+
+ private boolean containsAdvancedKeywords(HttpServletRequest request) {
+ String uri = request.getRequestURI();
+ for (String keyword: advancedKeywordsList) {
+ if (uri.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertFilter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertFilter.java
new file mode 100644
index 00000000..71238cfc
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafCertFilter.java
@@ -0,0 +1,111 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.filters;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.onap.aaf.cadi.PropAccess;
+import org.onap.aaf.cadi.filter.CadiFilter;
+import org.onap.aai.aaf.auth.AafRequestFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.filter.OrderedRequestContextFilter;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * AAF with client cert authentication filter
+ */
+
+@Component
+@Profile(AafProfiles.AAF_CERT_AUTHENTICATION)
+@PropertySource(value = "file:${CONFIG_HOME}/aaf/permissions.properties", ignoreResourceNotFound = true)
+@PropertySource(value = "file:${server.local.startpath}/aaf/permissions.properties", ignoreResourceNotFound = true)
+public class AafCertFilter extends OrderedRequestContextFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AafCertFilter.class);
+
+ String aafUserChainPattern;
+
+ private final CadiFilter cadiFilter;
+
+ private final CadiProps cadiProps;
+
+ @Autowired
+ public AafCertFilter( @Value("${aaf.userchain.pattern}") String aafUserChainPattern,
+ CadiProps cadiProps) throws IOException, ServletException {
+
+ this.aafUserChainPattern = aafUserChainPattern;
+ this.cadiProps = cadiProps;
+ cadiFilter = new CadiFilter(new PropAccess((level,element)->{
+ switch (level) {
+ case DEBUG:
+ LOGGER.debug(buildMsg(element));
+ break;
+ case INFO:
+ case AUDIT:
+ LOGGER.info(buildMsg(element));
+ break;
+ case WARN:
+ LOGGER.warn(buildMsg(element));
+ break;
+ case ERROR:
+ LOGGER.error(buildMsg(element));
+ break;
+ case INIT:
+ LOGGER.info(buildMsg(element));
+ break;
+ case TRACE:
+ LOGGER.trace(buildMsg(element));
+ break;
+ case NONE:
+ break;
+ }
+ }, new String[]{"cadi_prop_files=" + cadiProps.getCadiFileName()} ));
+ this.setOrder(FilterPriority.AAF_CERT_AUTHENTICATION.getPriority());
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ AafRequestFilter.authenticationFilter(request, response, filterChain, cadiFilter, cadiProps.getCadiProperties(), aafUserChainPattern);
+ }
+ private String buildMsg(Object[] objects) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for ( Object o: objects ) {
+ if (first) {
+ first = false;
+ }
+ else {
+ sb.append(' ');
+ }
+ sb.append(o.toString());
+ }
+ return (sb.toString());
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafFilter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafFilter.java
new file mode 100644
index 00000000..16d9afd2
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafFilter.java
@@ -0,0 +1,108 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.filters;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.onap.aaf.cadi.PropAccess;
+import org.onap.aaf.cadi.filter.CadiFilter;
+import org.onap.aai.aaf.auth.ResponseFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.filter.OrderedRequestContextFilter;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+/**
+ * AAF authentication filter
+ */
+
+@Component
+@Profile(AafProfiles.AAF_AUTHENTICATION)
+public class AafFilter extends OrderedRequestContextFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AafCertFilter.class);
+
+ private final CadiFilter cadiFilter;
+
+ @Autowired
+ public AafFilter(CadiProps cadiProps) throws IOException, ServletException {
+ cadiFilter = new CadiFilter(new PropAccess((level,element)->{
+ switch (level) {
+ case DEBUG:
+ LOGGER.debug(buildMsg(element));
+ break;
+ case INFO:
+ case AUDIT:
+ LOGGER.info(buildMsg(element));
+ break;
+ case WARN:
+ LOGGER.warn(buildMsg(element));
+ break;
+ case ERROR:
+ LOGGER.error(buildMsg(element));
+ break;
+ case INIT:
+ LOGGER.info(buildMsg(element));
+ break;
+ case TRACE:
+ LOGGER.trace(buildMsg(element));
+ break;
+ case NONE:
+ break;
+ }
+ }, new String[]{"cadi_prop_files=" + cadiProps.getCadiFileName()} ));
+ this.setOrder(FilterPriority.AAF_AUTHENTICATION.getPriority());
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ if (!request.getRequestURI().matches("^.*/util/echo$")) {
+ cadiFilter.doFilter(request, response, filterChain);
+ if (response.getStatus() == 401 || response.getStatus() == 403) {
+ ResponseFormatter.errorResponse(request, response);
+ }
+ } else {
+ filterChain.doFilter(request, response);
+ }
+ }
+
+ private String buildMsg(Object[] objects) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for ( Object o: objects ) {
+ if (first) {
+ first = false;
+ }
+ else {
+ sb.append(' ');
+ }
+ sb.append(o.toString());
+ }
+ return (sb.toString());
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafProfiles.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafProfiles.java
new file mode 100644
index 00000000..b587716e
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/AafProfiles.java
@@ -0,0 +1,33 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.filters;
+
+public class AafProfiles {
+
+ // AAF Basic Auth
+ public static final String AAF_AUTHENTICATION = "aaf-auth";
+
+ // AAF Auth with Client Certs
+ public static final String AAF_CERT_AUTHENTICATION = "aaf-cert-auth";
+
+ public static final String TWO_WAY_SSL = "two-way-ssl";
+
+ private AafProfiles(){}
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/CadiProps.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/CadiProps.java
new file mode 100644
index 00000000..35e88f5f
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/CadiProps.java
@@ -0,0 +1,81 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.filters;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+// This component will be created if and only if any of the following profiles are active
+@Component
+@Profile({
+ AafProfiles.AAF_CERT_AUTHENTICATION,
+ AafProfiles.AAF_AUTHENTICATION,
+ AafProfiles.TWO_WAY_SSL
+})
+public class CadiProps {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CadiProps.class);
+
+ private String cadiFileName;
+
+ private Properties cadiProperties;
+
+ @Autowired
+ public CadiProps(@Value("${aaf.cadi.file:./resources/cadi.properties}") String filename){
+ cadiFileName = filename;
+ cadiProperties = new Properties();
+ }
+
+ @PostConstruct
+ public void init() throws IOException {
+
+ File cadiFile = new File(cadiFileName);
+
+ if(!cadiFile.exists()){
+ LOGGER.warn("Unable to find the cadi file in the given path {} so loading cadi.properties from classloader", cadiFileName);
+ InputStream is = this.getClass().getClassLoader().getResourceAsStream("cadi.properties");
+ cadiProperties.load(is);
+ } else {
+ LOGGER.info("Successfully found the file {} and started loading the properties from it", cadiFileName);
+ cadiFileName = cadiFile.getAbsolutePath();
+ try (InputStream inputStream = new FileInputStream(cadiFile)) {
+ cadiProperties.load(inputStream);
+ }
+
+ }
+ }
+ public String getCadiFileName() {
+ return cadiFileName;
+ }
+ public Properties getCadiProperties(){
+ return cadiProperties;
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/FilterPriority.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/FilterPriority.java
new file mode 100644
index 00000000..17b9f0e4
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/FilterPriority.java
@@ -0,0 +1,39 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.filters;
+
+import org.springframework.core.Ordered;
+
+public enum FilterPriority {
+
+ AAF_AUTHENTICATION(Ordered.HIGHEST_PRECEDENCE),
+ AAF_AUTHORIZATION(Ordered.HIGHEST_PRECEDENCE + 1), //higher number = lower priority
+ AAF_CERT_AUTHENTICATION(Ordered.HIGHEST_PRECEDENCE + 2 ),
+ AAF_CERT_AUTHORIZATION(Ordered.HIGHEST_PRECEDENCE + 3),
+ TWO_WAY_SSL_AUTH(Ordered.HIGHEST_PRECEDENCE + 4);
+
+ private final int priority;
+
+ FilterPriority(final int p) {
+ priority = p;
+ }
+
+ public int getPriority() { return priority; }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/GremlinFilter.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/GremlinFilter.java
new file mode 100644
index 00000000..23ff0c6d
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/GremlinFilter.java
@@ -0,0 +1,99 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.filters;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.io.IOUtils;
+import org.onap.aai.aaf.auth.ResponseFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+@Component
+@Profile({
+ AafProfiles.AAF_CERT_AUTHENTICATION,
+ AafProfiles.AAF_AUTHENTICATION
+})
+public class GremlinFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(GremlinFilter.class);
+
+ private static final String ADVANCED = "advanced";
+ private static final String BASIC = "basic";
+ private static final Pattern ECHO_ENDPOINT = Pattern.compile("^.*/util/echo$");
+
+ String type;
+
+ String instance;
+
+ private CadiProps cadiProps;
+
+ @Autowired
+ public GremlinFilter(
+ @Value("${permission.type}") String type,
+ @Value("${permission.instance}") String instance,
+ CadiProps cadiProps
+ ) {
+ this.type = type;
+ this.instance = instance;
+ this.cadiProps = cadiProps;
+ }
+
+ public void doBasicAuthFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ PayloadBufferingRequestWrapper requestBufferWrapper = new PayloadBufferingRequestWrapper(request);
+
+ if(ECHO_ENDPOINT.matcher(request.getRequestURI()).matches()){
+ filterChain.doFilter(requestBufferWrapper, response);
+ }
+
+ String payload = IOUtils.toString(requestBufferWrapper.getInputStream(), StandardCharsets.UTF_8.name());
+ boolean containsWordGremlin = payload.contains("\"gremlin\"");
+
+ //if the requestBufferWrapper contains the word "gremlin" it's an "advanced" query needing an "advanced" role
+ String permissionBasic = String.format("%s|%s|%s", type, instance, BASIC);
+ String permissionAdvanced = String.format("%s|%s|%s", type, instance, ADVANCED);
+
+ boolean isAuthorized;
+
+ if(containsWordGremlin){
+ isAuthorized = requestBufferWrapper.isUserInRole(permissionAdvanced);
+ }else{
+ isAuthorized = requestBufferWrapper.isUserInRole(permissionAdvanced) || requestBufferWrapper.isUserInRole(permissionBasic);
+ }
+
+ if(!isAuthorized){
+ String name = requestBufferWrapper.getUserPrincipal() != null ? requestBufferWrapper.getUserPrincipal().getName() : "unknown";
+ LOGGER.info("User " + name + " does not have a role for " + (containsWordGremlin ? "gremlin" : "non-gremlin") + " query" );
+ ResponseFormatter.errorResponse(request, response);
+ } else {
+ filterChain.doFilter(requestBufferWrapper,response);
+ }
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/PayloadBufferingRequestWrapper.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/PayloadBufferingRequestWrapper.java
new file mode 100644
index 00000000..eb8a61dd
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/PayloadBufferingRequestWrapper.java
@@ -0,0 +1,50 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.filters;
+
+import org.apache.commons.io.IOUtils;
+import org.onap.aaf.cadi.BufferedServletInputStream;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * This class buffers the payload of the servlet request. The reason is that we access the payload multiple times,
+ * which is not supported by the request per se.
+ */
+
+class PayloadBufferingRequestWrapper extends HttpServletRequestWrapper {
+
+ private byte[] buffer;
+
+ PayloadBufferingRequestWrapper(HttpServletRequest req) throws IOException {
+ super(req);
+ this.buffer = IOUtils.toByteArray(req.getInputStream());
+ }
+
+ @Override
+ public ServletInputStream getInputStream() {
+ ByteArrayInputStream bais = new ByteArrayInputStream(this.buffer);
+ return new BufferedServletInputStream(bais);
+ }
+}
diff --git a/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/TwoWaySslAuthorization.java b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/TwoWaySslAuthorization.java
new file mode 100644
index 00000000..0e206c50
--- /dev/null
+++ b/aai-aaf-auth/src/main/java/org/onap/aai/aaf/filters/TwoWaySslAuthorization.java
@@ -0,0 +1,186 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.filters;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.onap.aai.aaf.auth.AAIAuthCore;
+import org.onap.aai.aaf.auth.CertUtil;
+import org.onap.aai.aaf.auth.ResponseFormatter;
+import org.onap.aai.exceptions.AAIException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.filter.OrderedRequestContextFilter;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import javax.security.auth.x500.X500Principal;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+@Component
+@Profile("two-way-ssl")
+public class TwoWaySslAuthorization extends OrderedRequestContextFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TwoWaySslAuthorization.class);
+
+ public static final String HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override";
+
+ public static final String MERGE_PATCH = "MERGE_PATCH";
+
+ @Autowired
+ private Environment environment;
+
+ @Autowired
+ private AAIAuthCore aaiAuthCore;
+
+ @Autowired
+ private CadiProps cadiProps;
+
+ public TwoWaySslAuthorization(){
+ this.setOrder(FilterPriority.TWO_WAY_SSL_AUTH.getPriority());
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+
+ String uri = request.getRequestURI();
+ String httpMethod = getHttpMethod(request);
+
+ Optional<String> authUser = getUser(request);
+
+ if (authUser.isPresent()) {
+ Properties cadiProperties = cadiProps.getCadiProperties();
+
+ String issuer = CertUtil.getCertIssuer(request);
+ if (issuer == null || issuer.isEmpty()) {
+ AAIException aaie = new AAIException("AAI_9107");
+ ResponseFormatter.errorResponse(aaie, request, response);
+ return;
+ }
+ issuer = issuer.replaceAll("\\s+","").toUpperCase();
+
+ List<String> cadiConfiguredIssuers = CertUtil.getCadiCertIssuers(cadiProperties);
+ boolean isAafAuthProfileActive = this.isAafAuthProfileActive();
+ if ((!isAafAuthProfileActive) || (!cadiConfiguredIssuers.contains(issuer)) ) {
+ try {
+ this.authorize(uri, httpMethod, authUser.get(), this.getHaProxyUser(request), issuer);
+ } catch (AAIException e) {
+ ResponseFormatter.errorResponse(e, request, response);
+ return;
+ }
+ }
+ } else {
+ AAIException aaie = new AAIException("AAI_9107");
+ ResponseFormatter.errorResponse(aaie, request, response);
+ return;
+ }
+ filterChain.doFilter(request, response);
+ }
+
+
+ private String getHttpMethod(HttpServletRequest request) {
+ String httpMethod = request.getMethod();
+ if ("POST".equalsIgnoreCase(httpMethod)
+ && "PATCH".equals(request.getHeader(HTTP_METHOD_OVERRIDE))) {
+ httpMethod = MERGE_PATCH;
+ }
+ if (httpMethod.equalsIgnoreCase(MERGE_PATCH) || "patch".equalsIgnoreCase(httpMethod)) {
+ httpMethod = "PUT";
+ }
+ return httpMethod;
+ }
+
+ private Optional<String> getUser(HttpServletRequest hsr) {
+ String authUser = null;
+ if (hsr.getAttribute("javax.servlet.request.cipher_suite") != null) {
+ X509Certificate[] certChain = (X509Certificate[]) hsr.getAttribute("javax.servlet.request.X509Certificate");
+
+ /*
+ * If the certificate is null or the certificate chain length is zero Then
+ * retrieve the authorization in the request header Authorization Check that it
+ * is not null and that it starts with Basic and then strip the basic portion to
+ * get the base64 credentials Check if this is contained in the AAIBasicAuth
+ * Singleton class If it is, retrieve the username associated with that
+ * credentials and set to authUser Otherwise, get the principal from certificate
+ * and use that authUser
+ */
+
+ if (certChain == null || certChain.length == 0) {
+
+ String authorization = hsr.getHeader("Authorization");
+
+ if (authorization != null && authorization.startsWith("Basic ")) {
+ authUser = authorization.replace("Basic ", "");
+ }
+
+ } else {
+ X509Certificate clientCert = certChain[0];
+ X500Principal subjectDN = clientCert.getSubjectX500Principal();
+ authUser = subjectDN.toString().toLowerCase();
+ }
+ }
+
+ return Optional.ofNullable(authUser);
+ }
+
+ private String getHaProxyUser(HttpServletRequest hsr) {
+ String haProxyUser;
+ if (Objects.isNull(hsr.getHeader("X-AAI-SSL-Client-CN"))
+ || Objects.isNull(hsr.getHeader("X-AAI-SSL-Client-OU"))
+ || Objects.isNull(hsr.getHeader("X-AAI-SSL-Client-O"))
+ || Objects.isNull(hsr.getHeader("X-AAI-SSL-Client-L"))
+ || Objects.isNull(hsr.getHeader("X-AAI-SSL-Client-ST"))
+ || Objects.isNull(hsr.getHeader("X-AAI-SSL-Client-C"))) {
+ haProxyUser = "";
+ } else {
+ haProxyUser = String.format("CN=%s, OU=%s, O=\"%s\", L=%s, ST=%s, C=%s",
+ Objects.toString(hsr.getHeader("X-AAI-SSL-Client-CN"), ""),
+ Objects.toString(hsr.getHeader("X-AAI-SSL-Client-OU"), ""),
+ Objects.toString(hsr.getHeader("X-AAI-SSL-Client-O"), ""),
+ Objects.toString(hsr.getHeader("X-AAI-SSL-Client-L"), ""),
+ Objects.toString(hsr.getHeader("X-AAI-SSL-Client-ST"), ""),
+ Objects.toString(hsr.getHeader("X-AAI-SSL-Client-C"), "")).toLowerCase();
+ }
+ return haProxyUser;
+ }
+
+ private void authorize(String uri, String httpMethod, String authUser, String haProxyUser, String issuer) throws AAIException {
+ if (!aaiAuthCore.authorize(authUser, uri, httpMethod, haProxyUser, issuer)) {
+ throw new AAIException("AAI_9101", "Request on " + httpMethod + " " + uri + " status is not OK");
+ }
+ }
+
+ private boolean isAafAuthProfileActive() {
+ String[] profiles = environment.getActiveProfiles();
+ if (profiles != null) {
+ if (Arrays.stream(profiles).anyMatch(
+ env -> (env.equalsIgnoreCase(AafProfiles.AAF_CERT_AUTHENTICATION)))) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIAuthCoreTest.java b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIAuthCoreTest.java
new file mode 100644
index 00000000..6fca4fdb
--- /dev/null
+++ b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIAuthCoreTest.java
@@ -0,0 +1,235 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.aai.aaf.auth.exceptions.AAIUnrecognizedFunctionException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AAIAuthCoreTest extends AAISetup {
+
+ private AAIAuthCore authCore;
+
+ @Before
+ public void setup() {
+ authCore = new AAIAuthCore("/aai");
+ }
+
+ @Test
+ public void getAuthPolicyFunctionNameTest() {
+
+ String uri = "/aai/v3/search/edge-tag-query";
+ assertEquals("Get aai function name from " + uri, "search", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/v10/search/edge-tag-query";
+ assertEquals("Get aai function name from " + uri, "search", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/search/model";
+ assertEquals("Get aai function name from " + uri, "search", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/v9/cloud-infrastructure/cloud-regions/cloud-region/somecloudregion/some-cloud-owner";
+ assertEquals("Get aai function name from " + uri, "cloud-infrastructure", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/v8/network/pnfs/pnf/ff4ca01orc/p-interfaces";
+ assertEquals("Get aai function name from " + uri, "network", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/util/echo";
+ assertEquals("Get aai function name from " + uri, "util", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/tools";
+ assertEquals("Get aai function name from " + uri, "tools", authCore.getAuthPolicyFunctName(uri));
+
+ uri = "/aai/v12/bulk/single-transaction";
+ assertEquals("Get aai function name from " + uri, "bulk", authCore.getAuthPolicyFunctName(uri));
+
+ }
+
+ @Test
+ public void validUsernameAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("testUser".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT", ""));
+ }
+
+ @Test
+ public void validUsernameInvalidHttpMethodAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("testUser".toLowerCase(), "/aai/v0/testFunction/someUri", "POST", ""));
+ }
+
+ @Test(expected = AAIUnrecognizedFunctionException.class)
+ public void validUsernameInvalidFunctionInURIAuthTest() throws AAIUnrecognizedFunctionException {
+ authCore.authorize("testUser".toLowerCase(), "/aai/v0/badFunction/someUri", "PUT", "");
+ }
+
+ @Test
+ public void invalidUsernameAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("invlaidTestUser".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT", ""));
+ }
+
+ @Test
+ public void validUsernameIsTheExactWildcardIdAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("testWildcardId".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT", ""));
+ }
+
+ @Test
+ public void validUsernameContainsTheWildcardIdAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("cn=blah, testWildcardId, O=".toLowerCase(), "/aai/v0/testFunction/someUri",
+ "PUT", "", "aafWildCardIssuer"));
+ }
+
+ @Test
+ public void validUsernameContainsTheWildcardIdInvalidIssuerAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("cn=blah, testWildcardId, O=".toLowerCase(), "/aai/v0/testFunction/someUri",
+ "PUT", "", "invalidIssuer"));
+ }
+
+ @Test
+ public void invalidUsernameContainsRegularUsernameAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(
+ authCore.authorize("cn=blah, testUser, O=".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT", ""));
+ }
+
+ @Test
+ public void haProxyUsernameAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/util/echo", "GET", ""));
+ }
+
+ @Test
+ public void haProxyUsernameInvalidFunctionAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT", ""));
+ }
+
+ @Test
+ public void validUsernameViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT",
+ "testUser".toLowerCase()));
+ }
+
+ @Test
+ public void validUsernameInvalidHttpMethodViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "POST",
+ "testUser".toLowerCase()));
+ }
+
+ @Test(expected = AAIUnrecognizedFunctionException.class)
+ public void validUsernameInvalidFunctionInURIViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/badFunction/someUri", "PUT",
+ "testUser".toLowerCase());
+ }
+
+ @Test
+ public void invalidUsernameViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT",
+ "invlaidTestUser".toLowerCase()));
+ }
+
+ @Test
+ public void validUsernameIsTheExactWildcardIdViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT",
+ "testWildcardId".toLowerCase()));
+ }
+
+ @Test
+ public void validUsernameContainsTheWildcardIdViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT",
+ "cn=blah, testWildcardId, O=".toLowerCase(), "aafWildCardIssuer"));
+ }
+
+ @Test
+ public void invalidUsernameContainsRegularUsernameViaHaProxyAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT",
+ "cn=blah, testUser, O=".toLowerCase()));
+ }
+
+ @Test
+ public void haProxyUsernameTwiceAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("ha-proxy-user".toLowerCase(), "/aai/v0/testFunction/someUri", "PUT",
+ "ha-proxy-user".toLowerCase()));
+ }
+
+ @Test
+ public void haProxyWildcardIdAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(), "/aai/util/echo", "GET", "",
+ "aafWildCardIssuer"));
+ }
+
+ @Test
+ public void haProxyWildcardIdInvalidFunctionAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(),
+ "/aai/v0/testFunction/someUri", "PUT", ""));
+ }
+
+ @Test
+ public void validUsernameViaHaProxyWildcardIdAuthTest() throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(), "/aai/v0/testFunction/someUri",
+ "PUT", "testUser".toLowerCase(), "aafWildCardIssuer"));
+ }
+
+ @Test
+ public void validUsernameInvalidHttpMethodViaHaProxyWildcardIdAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(),
+ "/aai/v0/testFunction/someUri", "POST", "testUser".toLowerCase()));
+ }
+
+ @Test(expected = AAIUnrecognizedFunctionException.class)
+ public void validUsernameInvalidFunctionInURIViaHaProxyWildcardIdAuthTest()
+ throws AAIUnrecognizedFunctionException {
+ authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(), "/aai/v0/badFunction/someUri", "PUT",
+ "testUser".toLowerCase());
+ }
+
+ @Test
+ public void invalidUsernameViaHaProxyWildcardIdAuthTest() throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(),
+ "/aai/v0/testFunction/someUri", "PUT", "invlaidTestUser".toLowerCase()));
+ }
+
+ @Test
+ public void validUsernameIsTheExactWildcardIdViaHaProxyWildcardIdAuthTest()
+ throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(), "/aai/v0/testFunction/someUri",
+ "PUT", "testWildcardId".toLowerCase(), "aafWildCardIssuer"));
+ }
+
+ @Test
+ public void validUsernameContainsTheWildcardIdViaHaProxyWildcardIdAuthTest()
+ throws AAIUnrecognizedFunctionException {
+ assertTrue(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(), "/aai/v0/testFunction/someUri",
+ "PUT", "cn=blah, testWildcardId, O=".toLowerCase(), "aafWildCardIssuer"));
+ }
+
+ @Test
+ public void validUsernameContainsTheWildcardIdViaHaProxyWildcardIdInvalidIssuerAuthTest()
+ throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(),
+ "/aai/v0/testFunction/someUri", "PUT", "cn=blah, testWildcardId, O=".toLowerCase(), "invalidIssuer"));
+ }
+
+ @Test
+ public void invalidUsernameContainsRegularUsernameViaHaProxyWildcardIdAuthTest()
+ throws AAIUnrecognizedFunctionException {
+ assertFalse(authCore.authorize("cn=blah, ha-proxy-wildcard-id, O=".toLowerCase(),
+ "/aai/v0/testFunction/someUri", "PUT", "cn=blah, testUser, O=".toLowerCase()));
+ }
+
+}
diff --git a/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAISetup.java b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAISetup.java
new file mode 100644
index 00000000..0827782e
--- /dev/null
+++ b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAISetup.java
@@ -0,0 +1,31 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.aaf.auth;
+
+import org.junit.BeforeClass;
+
+public class AAISetup {
+
+ @BeforeClass
+ public static void preSetup(){
+ System.setProperty("AJSC_HOME", ".");
+ System.setProperty("BUNDLECONFIG_DIR", "src/test/resources/bundleconfig-local");
+ }
+}
diff --git a/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIUserTest.java b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIUserTest.java
new file mode 100644
index 00000000..e3b79cb7
--- /dev/null
+++ b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/AAIUserTest.java
@@ -0,0 +1,53 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class AAIUserTest extends AAISetup {
+
+ @Test
+ public void testIsAuth() {
+ AAIUser usr = new AAIUser("testUser");
+ usr.addRole("testRole");
+ usr.setUserAccess("auth", "GET");
+ usr.setUserAccess("auth", "PUT");
+ usr.setUserAccess("authentication", "PUT", "GET", "POST");
+
+ assertEquals(true, usr.hasAccess("auth", "GET"));
+ assertEquals(true, usr.hasAccess("auth", "PUT"));
+ assertEquals(true, usr.hasAccess("authentication", "POST"));
+ }
+
+ @Test
+ public void testIsNotAuth() {
+ AAIUser usr = new AAIUser("testUser");
+ usr.addRole("testRole");
+
+ assertEquals(false, usr.hasAccess("auth", "GET"));
+
+ usr.setUserAccess("auth", "GET");
+ assertEquals(false, usr.hasAccess("auth", "PUT"));
+ }
+
+}
diff --git a/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/CertUtilTest.java b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/CertUtilTest.java
new file mode 100644
index 00000000..9f307ac2
--- /dev/null
+++ b/aai-aaf-auth/src/test/java/org/onap/aai/aaf/auth/CertUtilTest.java
@@ -0,0 +1,87 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aai.aaf.auth;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * The Class CertUtilTest
+ */
+public class CertUtilTest extends AAISetup {
+
+ @Test
+ public void testCadiCertIssuers() throws IOException {
+ String propFile = System.getProperty("BUNDLECONFIG_DIR") + "/aaf/cadi.properties";
+ Properties cadiProperties = new Properties();
+ cadiProperties.load(new FileInputStream(new File(propFile)));
+
+ List<String> issuersList = CertUtil.getCadiCertIssuers(cadiProperties);
+ assertTrue("issuersList isn't populated", !issuersList.isEmpty());
+
+ int x = issuersList.get(0).indexOf(" ");
+ assertTrue("issuer contains spaces", x < 0);
+ }
+
+ @Test
+ public void testAaiSslClientOuHeader() {
+
+ HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+ expect(mockRequest.getHeader(CertUtil.AAI_SSL_CLIENT_OU_HDR)).andReturn("m55555@org.onap.com:TEST").times(1, 4);
+ expect(mockRequest.getHeader(CertUtil.AAI_SSL_CLIENT_CN_HDR)).andReturn("CN").times(1, 2);
+ expect(mockRequest.getHeader(CertUtil.AAI_SSL_CLIENT_O_HDR)).andReturn("O").times(1, 2);
+ expect(mockRequest.getHeader(CertUtil.AAI_SSL_CLIENT_L_HDR)).andReturn("L").times(1, 2);
+ expect(mockRequest.getHeader(CertUtil.AAI_SSL_CLIENT_ST_HDR)).andReturn("ST").times(1, 2);
+ expect(mockRequest.getHeader(CertUtil.AAI_SSL_CLIENT_C_HDR)).andReturn("C").times(1, 2);
+
+ replay(mockRequest);
+ String ou = CertUtil.getAaiSslClientOuHeader(mockRequest);
+ assertTrue("OU Header value is not as expected", ou.equals("m55555@org.onap.com:TEST"));
+
+ assertTrue("Unexpected isHaProxy() return value", CertUtil.isHaProxy(mockRequest));
+
+ String mechId = CertUtil.getMechId(mockRequest);
+ assertTrue("mechid value is not as expected", mechId.equals("m55555@org.onap.com"));
+
+ }
+
+ @Test
+ public void testBuildUserChain() {
+
+ // aaf.userchain.pattern=<AAF-ID>:${aaf.userchain.service.reference}:${aaf.userchain.auth.type}:AS
+ String aafUserChainPattern = "<AAF-ID>:org.onap.haproxy:X509:AS";
+ String mechid = "m11111@onap.org";
+ String result = CertUtil.buildUserChainHeader(mechid, aafUserChainPattern);
+
+ assertTrue("user chain value is not as expected", "m11111@onap.org:org.onap.haproxy:X509:AS".equals(result));
+
+ }
+}
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/cadi.properties b/aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/cadi.properties
new file mode 100644
index 00000000..8f7004ff
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/cadi.properties
@@ -0,0 +1,14 @@
+## Location properties
+##
+## Localized Machine Information
+##
+cadi_loglevel=DEBUG
+cadi_latitude=38.0
+cadi_longitude=-72.0
+
+# Locate URL (which AAF Env) - Use lower case
+aaf_locate_url=https://aafist.test.org:8095
+# AAF URL - Use upper case
+aaf_url=https://AAF_LOCATE_URL/service:2.0
+#
+cadi_prop_files=src/test/resources/bundleconfig-local/aaf/org.onap.aai.props
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/org.onap.aai.props b/aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/org.onap.aai.props
new file mode 100644
index 00000000..3056e5f9
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/aaf/org.onap.aai.props
@@ -0,0 +1,4 @@
+cm_url=cm_url
+hostname=hostname
+aaf_env=IST
+cadi_x509_issuers=CN=AAF CADI Test Issuing CA 01, OU=CSO, O=CO, C=US:CN=AAF CADI Test Issuing CA 02, OU=CSO, O=CO, C=US \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/aaiconfig.properties b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/aaiconfig.properties
new file mode 100644
index 00000000..0239e2ef
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/aaiconfig.properties
@@ -0,0 +1,58 @@
+#
+# ============LICENSE_START=======================================================
+# org.onap.aai
+# ================================================================================
+# Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+
+aai.config.checktime=1000
+
+# this could come from siteconfig.pl?
+aai.config.nodename=AutomaticallyOverwritten
+
+aai.transaction.logging=true
+aai.transaction.logging.get=true
+aai.transaction.logging.post=true
+
+aai.server.url.base=https://localhost:8443/aai/
+aai.server.url=https://localhost:8443/aai/v10/
+aai.oldserver.url.base=https://localhost:8443/aai/servers/
+aai.oldserver.url=https://localhost:8443/aai/servers/v2/
+aai.global.callback.url=https://localhost:8443/aai/
+
+aai.notification.current.version=v10
+aai.notificationEvent.default.status=UNPROCESSED
+aai.notificationEvent.default.eventType=AAI-EVENT
+aai.notificationEvent.default.domain=devINT1
+aai.notificationEvent.default.sourceName=aai
+aai.notificationEvent.default.sequenceNumber=0
+aai.notificationEvent.default.severity=NORMAL
+aai.notificationEvent.default.version=v10
+# This one lets us enable/disable resource-version checking on updates/deletes
+aai.resourceversion.enableflag=true
+aai.default.api.version=v10
+
+aai.example.passwd.x=OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0
+aai.example.string=hello
+aai.example.int=7748
+
+aai.realtime.clients=RO,SDNC,SO
+
+aai.jms.enable=false
+
+aai.rest.getall.depthparam=someuuid
+
+aaf.valid.issuer.wildcard=aaf wild card issuer|aafWildCardIssuer|OU=another
+
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/error.properties b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/error.properties
new file mode 100644
index 00000000..3a5671c2
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/error.properties
@@ -0,0 +1,160 @@
+# Adding comment trying to trigger a build
+#------------------------------------------------------------------------------- ----------
+#Key=Disposition:Category:Severity:Error Code:HTTP ResponseCode:RESTError Code:Error Message
+#------------------------------------------------------------------------------- ----------
+# testing code, please don't change unless error utility source code changes
+AAI_TESTING=5:2:WARN:0000:400:0001:Error code for testing
+
+# General success
+AAI_0000=0:0:INFO:0000:200:0000:Success
+
+# health check success
+AAI_0001=0:0:INFO:0001:200:0001:Success X-FromAppId=%1 X-TransactionId=%2
+AAI_0002=0:0:INFO:0002:200:0001:Successful health check
+
+# Success with additional info
+AAI_0003=0:3:INFO:0003:202:0003:Success with additional info performing %1 on %2. Added %3 with key %4
+AAI_0004=0:3:INFO:0004:202:0003:Added prerequisite object to db
+
+#--- aairest: 3000-3299
+# svc errors
+AAI_3000=5:2:INFO:3000:400:3000:Invalid input performing %1 on %2
+AAI_3001=5:6:INFO:3001:404:3001:Resource not found for %1 using id %2
+AAI_3002=5:1:WARN:3002:400:3002:Error writing output performing %1 on %2
+AAI_3003=5:1:WARN:3003:400:3003:Failed to make edge to missing target node of type %3 with keys %4 performing %1 on %2
+AAI_3005=5:6:WARN:3005:404:3001:Node cannot be directly accessed for read, must be accessed via ancestor(s)
+AAI_3006=5:6:WARN:3006:404:3001:Node cannot be directly accessed for write, must be accessed via ancestor(s)
+AAI_3007=5:6:INFO:3007:410:3007:This version (%1) of the API is retired, please migrate to %2
+AAI_3008=5:6:ERROR:3008:400:3008:URI is not encoded in UTF-8
+AAI_3009=5:6:ERROR:3009:400:3002:Malformed URL
+# pol errors
+AAI_3100=5:1:WARN:3100:400:3100:Unsupported operation %1
+AAI_3101=5:1:WARN:3101:403:3101:Attempt by client %1 to execute API %2
+AAI_3102=5:1:WARN:3102:400:3102:Error parsing input performing %1 on %2
+AAI_3300=5:1:WARN:3300:403:3300:Unauthorized
+AAI_3301=5:1:WARN:3301:401:3301:Stale credentials
+AAI_3302=5:1:WARN:3302:401:3301:Not authenticated
+AAI_3303=5:1:ERROR:3303:403:3300:Too many objects would be returned by this request, please refine your request and retry
+
+#--- aaigen: 4000-4099
+AAI_4000=5:4:ERROR:4000:500:3002:Internal Error
+AAI_4001=5:4:FATAL:4001:500:3002:Configuration file not found
+AAI_4002=5:4:FATAL:4002:500:3002:Error reading Configuration file
+AAI_4003=5:4:ERROR:4003:500:3002:Error writing to log file
+AAI_4004=5:4:FATAL:4004:500:3002:Error reading/parsing the error properties file
+AAI_4005=5:4:FATAL:4005:500:3002:Missing or invalid configuration parameter
+AAI_4006=5:4:FATAL:4006:500:3002:Unexpected error in service
+AAI_4007=5:4:ERROR:4007:500:3102:Input parsing error
+AAI_4008=5:4:ERROR:4008:500:3002:Output parsing error
+AAI_4009=4:0:ERROR:4009:400:3000:Invalid X-FromAppId in header
+AAI_4010=4:0:ERROR:4010:400:3000:Invalid X-TransactionId in header
+AAI_4011=5:4:ERROR:4011:500:3002:Missing data for REST error response
+AAI_4014=4:0:ERROR:4014:400:3000:Invalid Accept header
+AAI_4015=4:0:ERROR:4015:400:3000:You must provide at least one indexed property
+AAI_4016=4:0:ERROR:4016:400:3000:The depth parameter must be a number or the string "all"
+AAI_4017=5:2:INFO:4017:400:3000:Could not set property
+AAI_4018=5:2:ERROR:4018:400:3000:Unable to convert the string to integer
+#--- aaidbmap: 5102-5199
+AAI_5102=5:4:FATAL:5102:500:3002:Graph database is null after open
+AAI_5105=5:4:ERROR:5105:500:3002:Unexpected error reading/updating database
+AAI_5106=5:4:WARN:5106:404:3001:Node not found
+AAI_5107=5:2:WARN:5107:400:3000:Required information missing
+AAI_5108=5:2:WARN:5108:200:0:Unexpected information in request being ignored
+
+#--- aaidbgen: 6101-6199
+AAI_6101=5:4:ERROR:6101:500:3002:null JanusGraph object passed
+AAI_6102=5:4:WARN:6102:400:3000:Passed-in property is not valid for this nodeType
+AAI_6103=5:4:WARN:6103:400:3000:Required Node-property not found in input data
+AAI_6104=5:4:WARN:6104:400:3000:Required Node-property was passed with no data
+AAI_6105=5:4:WARN:6105:400:3000:Node-Key-Property not defined in DbMaps
+AAI_6106=5:4:WARN:6106:400:3000:Passed-in property is not valid for this edgeType
+AAI_6107=5:4:WARN:6107:400:3000:Required Edge-property not found in input data
+AAI_6108=5:4:WARN:6108:400:3000:Required Edge-property was passed with no data
+AAI_6109=5:4:WARN:6109:400:3000:Bad dependent Node value
+AAI_6110=5:4:ERROR:6110:400:3100:Node cannot be deleted
+AAI_6111=5:4:ERROR:6111:400:3000:JSON processing error
+AAI_6112=5:4:ERROR:6112:400:3000:More than one node found by getUniqueNode()
+AAI_6114=5:4:INFO:6114:404:3001:Node Not Found
+AAI_6115=5:4:ERROR:6115:400:3000:Unrecognized NodeType
+AAI_6116=5:4:ERROR:6116:400:3000:Unrecognized Property
+AAI_6117=5:4:ERROR:6117:400:3000:Uniqueness constraint violated
+AAI_6118=5:4:ERROR:6118:400:3000:Required Field not passed.
+AAI_6120=5:4:ERROR:6120:400:3000:Bad Parameter Passed
+AAI_6121=5:4:ERROR:6121:400:3000:Problem with internal AAI reference data
+AAI_6122=5:4:ERROR:6122:400:3000:Data Set not complete in DB for this request
+AAI_6123=5:4:ERROR:6123:500:3000:Bad Data found by DataGrooming Tool - Investigate
+AAI_6124=5:4:ERROR:6124:500:3000:File read/write error
+AAI_6125=5:4:WARN:6125:500:3000:Problem Pulling Data Set
+AAI_6126=5:4:ERROR:6126:400:3000:Edge cannot be deleted
+AAI_6127=5:4:INFO:6127:404:3001:Edge Not Found
+AAI_6128=5:4:INFO:6128:500:3000:Unexpected error
+AAI_6129=5:4:INFO:6129:404:3003:Error making edge to target node
+AAI_6130=5:4:WARN:6130:412:3000:Precondition Required
+AAI_6131=5:4:WARN:6131:412:3000:Precondition Failed
+AAI_6132=5:4:WARN:6132:400:3000:Bad Model Definition
+AAI_6133=5:4:WARN:6133:400:3000:Bad Named Query Definition
+AAI_6134=5:4:ERROR:6134:500:6134:Could not persist transaction to storage back end. Exhausted retry amount
+AAI_6135=5:4:WARN:6135:412:3000:Resource version specified on create
+AAI_6136=5:4:ERROR:6136:400:3000:Object cannot hold multiple items
+AAI_6137=5:4:ERROR:6137:400:3000:Cannot perform writes on multiple vertices
+AAI_6138=5:4:ERROR:6138:400:3000:Cannot delete multiple vertices
+AAI_6139=5:4:ERROR:6139:404:3000:Attempted to add edge to vertex that does not exist
+AAI_6140=5:4:ERROR:6140:400:3000:Edge multiplicity violated
+AAI_6141=5:4:WARN:6141:400:3000:Please Refine Query
+AAI_6142=5:4:INFO:6142:400:3000:Retrying transaction
+AAI_6143=5:4:INFO:6143:400:3000:Ghost vertex found
+AAI_6144=5:4:WARN:6144:400:3000:Cycle found in graph
+AAI_6145=5:4:ERROR:6145:400:3000:Cannot create a nested/containment edge via relationship
+AAI_6146=5:4:ERROR:6146:400:3000:Ambiguous identity map found, use a URI instead
+
+#--- aaicsvp: 7101-7199
+AAI_7101=5:4:ERROR:7101:500:3002:Unexpected error in CSV file processing
+AAI_7102=5:4:ERROR:7102:500:3002:Error in cleanup temporary directory
+#AAI_7103=4:2:ERROR:7103:500:3002:Unsupported user
+AAI_7104=5:4:ERROR:7104:500:3002:Failed to create directory
+AAI_7105=5:4:ERROR:7105:500:3002:Temporary directory exists
+AAI_7106=5:4:ERROR:7106:500:3002:Cannot delete
+AAI_7107=5:4:ERROR:7107:500:3002:Input file does not exist
+AAI_7108=5:4:ERROR:7108:500:3002:Output file does not exist
+AAI_7109=5:4:ERROR:7109:500:3002:Error closing file
+AAI_7110=5:4:ERROR:7110:500:3002:Error loading/reading properties file
+AAI_7111=5:4:ERROR:7111:500:3002:Error executing shell script
+AAI_7112=5:4:ERROR:7112:500:3002:Error creating output file
+AAI_7113=5:4:ERROR:7113:500:3002:Trailer record error
+AAI_7114=5:4:ERROR:7114:500:3002:Input file error
+AAI_7115=5:4:ERROR:7115:500:3002:Unexpected error
+AAI_7116=5:4:ERROR:7116:500:3002:Request error
+AAI_7117=5:4:ERROR:7117:500:3002:Error in get http client object
+AAI_7118=5:4:ERROR:7118:500:3002:Script Error
+AAI_7119=5:4:ERROR:7119:500:3002:Unknown host
+
+#--- aaisdnc: 7201-7299
+AAI_7202=5:4:ERROR:7202:500:3002:Error getting connection to odl
+AAI_7203=5:4:ERROR:7203:500:3002:Unexpected error calling DataChangeNotification API
+AAI_7204=5:4:ERROR:7204:500:3002:Error returned by DataChangeNotification API
+AAI_7205=5:4:ERROR:7205:500:3002:Unexpected error running notifySDNCOnUpdate
+AAI_7206=5:4:ERROR:7206:500:3002:Invalid data returned from ODL
+
+#--- NotificationEvent, using UEB space
+AAI_7350=5:4:ERROR:7305:500:3002:Notification event creation failed
+
+#--- aairestctlr: 7401-7499
+AAI_7401=5:4:ERROR:7401:500:3002:Error connecting to AAI REST API
+AAI_7402=5:4:ERROR:7402:500:3002:Unexpected error
+AAI_7403=5:4:WARN:7403:400:3001:Request error
+AAI_7404=5:4:INFO:7404:404:3001:Node not found
+AAI_7405=5:4:WARN:7405:200:0:UUID not formatted correctly, generating UUID
+
+#--- aaiauth: 9101-9199
+AAI_9101=5:0:WARN:9101:403:3300:User is not authorized to perform function
+AAI_9102=5:0:WARN:9102:401:3301:Refresh credentials from source
+AAI_9103=5:0:WARN:9103:403:3300:User not found
+AAI_9104=5:0:WARN:9104:401:3302:Authentication error
+AAI_9105=5:0:WARN:9105:403:3300:Authorization error
+AAI_9106=5:0:WARN:9106:403:3300:Invalid AppId
+#AAI_9107=5:0:WARN:9107:403:3300:No Username in Request
+AAI_9107=5:0:WARN:9107:403:3300:SSL is not provided in request, please contact admin
+
+#--- aaiinstar: 9201-9299
+AAI_9201=5:4:ERROR:9201:500:3002:Unable to send notification
+AAI_9202=5:4:ERROR:9202:500:3002:Unable to start a thread
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-cached.properties b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-cached.properties
new file mode 100644
index 00000000..aa3c0631
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-cached.properties
@@ -0,0 +1,36 @@
+#
+# ============LICENSE_START=======================================================
+# org.onap.aai
+# ================================================================================
+# Copyright © 2017-18 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+
+query.fast-property=true
+# the following parameters are not reloaded automatically and require a manual bounce
+storage.backend=inmemory
+storage.hostname=localhost
+
+#schema.default=none
+storage.lock.wait-time=300
+storage.hbase.table=aaigraph-dev1.dev
+storage.hbase.ext.zookeeper.znode.parent=/hbase-unsecure
+#caching on
+cache.db-cache = true
+cache.db-cache-clean-wait = 20
+cache.db-cache-time = 180000
+cache.db-cache-size = 0.3
+
+#load graphson file on startup
+load.snapshot.file=false
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-realtime.properties b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-realtime.properties
new file mode 100644
index 00000000..05394334
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/appprops/janusgraph-realtime.properties
@@ -0,0 +1,33 @@
+#
+# ============LICENSE_START=======================================================
+# org.onap.aai
+# ================================================================================
+# Copyright © 2017-18 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+
+query.fast-property=true
+# the following parameters are not reloaded automatically and require a manual bounce
+storage.backend=inmemory
+storage.hostname=localhost
+
+#schema.default=none
+storage.lock.wait-time=300
+storage.hbase.table=aaigraph-dev1.dev
+storage.hbase.ext.zookeeper.znode.parent=/hbase-unsecure
+# Setting db-cache to false ensure the fastest propagation of changes across servers
+cache.db-cache = false
+
+#load graphson file on startup
+load.snapshot.file=false
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/auth/aai_policy.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/auth/aai_policy.json
new file mode 100644
index 00000000..9335a7bb
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/auth/aai_policy.json
@@ -0,0 +1,73 @@
+{
+ "roles": [
+ {
+ "name": "testRole",
+ "functions": [
+ {
+ "name": "testFunction",
+ "methods": [
+ {
+ "name": "GET"
+ },
+ {
+ "name": "DELETE"
+ },
+ {
+ "name": "PUT"
+ }
+ ]
+ }
+ ],
+ "users": [
+ {
+ "username": "testUser"
+ },
+ {
+ "username": "testWildcardId",
+ "is-wildcard-id": true
+ }
+ ]
+ },
+ {
+ "name": "HAProxy",
+ "functions": [
+ {
+ "name": "util",
+ "methods": [
+ {
+ "name": "GET"
+ }
+ ]
+ }
+ ],
+ "users": [
+ {
+ "username": "ha-proxy-user"
+ },
+ {
+ "username": "ha-proxy-wildcard-id",
+ "is-wildcard-id": true
+ }
+ ]
+ },
+ {
+ "name": "testBasicAuth",
+ "functions": [
+ {
+ "name": "testBasicAuthFunction",
+ "methods": [
+ {
+ "name": "GET"
+ }
+ ]
+ }
+ ],
+ "users": [
+ {
+ "user": "testBasicAuthUser",
+ "pass": "OBF:1ytc1vu91v2p1rxf1mqh1v8s1z0d1msn1san1mqf1z0h1v9u1msl1rvf1v1p1vv11yta"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/graphson/resource.graphson b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/graphson/resource.graphson
new file mode 100644
index 00000000..04f48174
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/graphson/resource.graphson
@@ -0,0 +1,2 @@
+{"id": 386506928,"label": "vertex","properties": {"aai-last-mod-ts": [{"id": "ob632u-6e46nk-5j45","value": 1488308500413}],"aai-uri": [{"id": "ob6712-6e46nk-5lhh","value": "/cloud-infrastructure/cloud-regions/cloud-region/cloud-owner-987654321-9922-as988q/cloud-region-id-987654321-9922-as988q/tenants/tenant/tenant-987654321-9999-as988q/vservers/vserver/vserver-987654321-9999-as988q"}],"prov-status": [{"id": "ob651y-6e46nk-1kw5","value": "example-prov-status-val-7367"}],"aai-created-ts": [{"id": "ob62ae-6e46nk-5gqt","value": 1488308500413}],"source-of-truth": [{"id": "ob61w6-6e46nk-5jwl","value": "FitNesse-Test-as988q"}],"vserver-selflink": [{"id": "ob65g6-6e46nk-3xfp","value": "example-vserver-selflink-val-7367"}],"aai-node-type": [{"id": "ob61hy-6e46nk-5f5x","value": "vserver"}],"in-maint": [{"id": "ob65ue-6e46nk-20p1","value": false}],"resource-version": [{"id": "ob62om-6e46nk-23ut","value": "1488308500413"}],"vserver-name": [{"id": "ob649i-6e46nk-3u9x","value": "example-vserver-name-val-7367vserver-987654321-9999-as988q"}],"vserver-id": [{"id": "ob63va-6e46nk-3sp1","value": "vserver-987654321-9999-as988q"}],"last-mod-source-of-truth": [{"id": "ob63h2-6e46nk-5edh","value": "FitNesse-Test-as988q"}],"vserver-name2": [{"id": "ob64nq-6e46nk-3vut","value": "example-vserver-name2-val-7367"}],"is-closed-loop-disabled": [{"id": "ob668m-6e46nk-229x","value": false}]}}
+{"id": 2461872,"label": "vertex","properties": {"aai-last-mod-ts": [{"id": "21hqu-1grlc-5j45","value": 1467901600}],"in-maint": [{"id": "21i52-1grlc-20p1","value": false}],"resource-version": [{"id": "21ija-1grlc-23ut","value": "1467901600"}],"vserver-name": [{"id": "21ixi-1grlc-3u9x","value": "PerfTest_VServerFix0027TenantPez002701611467901587187Name"}],"aai-created-ts": [{"id": "21jbq-1grlc-5gqt","value": 1467901600}],"vserver-id": [{"id": "21jpy-1grlc-3sp1","value": "PerfTest_VServerFix0027TenantPez002701611467901587187"}],"last-mod-source-of-truth": [{"id": "21k46-1grlc-5edh","value": "MSO"}],"vserver-name2": [{"id": "21kie-1grlc-3vut","value": "PerfTest_VServerFix0027TenantPez002701611467901587187-VM Name2 optional"}],"source-of-truth": [{"id": "21kwm-1grlc-5jwl","value": "MSO"}],"vserver-selflink": [{"id": "21lau-1grlc-3xfp","value": "http://testvserverLink.com/.html?vserv=VserverLink"}],"is-closed-loop-disabled": [{"id": "21lp2-1grlc-229x","value": false}],"aai-node-type": [{"id": "21m3a-1grlc-5f5x","value": "vserver"}],"aai-uri": [{"id": "21m3a-1grlc-5a5x","value": "/vservers/vserver/test1"}]}} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource-format.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource-format.json
new file mode 100644
index 00000000..c7e42556
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource-format.json
@@ -0,0 +1,13 @@
+{
+ "results": [
+ {
+ "vserver": {
+
+ }
+ },
+ {
+ "vserver": {
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource_and_url-format.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource_and_url-format.json
new file mode 100644
index 00000000..d90a71bc
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/resource_and_url-format.json
@@ -0,0 +1,16 @@
+{
+ "results": [
+ {
+ "url" : "/cloud-infrastructure/cloud-regions/cloud-region/cloud-owner-987654321-9922-as988q/cloud-region-id-987654321-9922-as988q/tenants/tenant/tenant-987654321-9999-as988q/vservers/vserver/vserver-987654321-9999-as988q",
+ "vserver": {
+
+ }
+ },
+ {
+ "url" : "/vservers/vserver/test1",
+ "vserver": {
+
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/simple-format.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/simple-format.json
new file mode 100644
index 00000000..dd342615
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/queryformarts/simple-format.json
@@ -0,0 +1,43 @@
+{
+ "results" : [{
+ "id" : "0",
+ "node-type" : "generic-vnf",
+ "url" : "urimissing",
+ "properties" : {
+ "vnf-name" : "myVnf"
+ },
+ "related-to" : [{
+ "node-type" : "vserver",
+ "id" : "1",
+ "url" : "urimissing"
+ }]
+ } , {
+ "id" : "1",
+ "node-type" : "vserver",
+ "url" : "urimissing",
+ "properties" : {
+ "vserver-name" : "myVserver"
+ },
+ "related-to" : [{
+ "node-type" : "generic-vnf",
+ "id" : "0",
+ "url" : "urimissing"
+ },{
+ "node-type" : "pserver",
+ "id" : "2",
+ "url" : "/pservers/pserver/key1"
+ }]
+ },{
+ "id" : "2",
+ "node-type" : "pserver",
+ "url" : "/pservers/pserver/key1",
+ "properties" : {
+ "hostname" : "myPserver"
+ },
+ "related-to" : [{
+ "node-type" : "vserver",
+ "id" : "1",
+ "url" : "urimissing"
+ }]
+ }]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/ambiguous-relationship.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/ambiguous-relationship.json
new file mode 100644
index 00000000..c6407e2c
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/ambiguous-relationship.json
@@ -0,0 +1,10 @@
+{
+ "related-to": "generic-vnf",
+ "relationship-data" : [{
+ "relationship-key" : "generic-vnf.vnf-id",
+ "relationship-value":"key1"
+ },{
+ "relationship-key" : "generic-vnf.vnf-id",
+ "relationship-value":"key2"
+ }]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-failv10-successv9.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-failv10-successv9.json
new file mode 100644
index 00000000..5bafc9ff
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-failv10-successv9.json
@@ -0,0 +1,8 @@
+{
+ "related-to": "generic-vnf",
+ "related-link": "/aai/v10/network/generic-vnfs/test-objet/key1",
+ "relationship-data" : [{
+ "relationship-key" : "generic-vnf.vnf-id",
+ "relationship-value":"key2"
+ }]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-successv10-failv9.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-successv10-failv9.json
new file mode 100644
index 00000000..3afe6bb7
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/both-successv10-failv9.json
@@ -0,0 +1,8 @@
+{
+ "related-to": "generic-vnf",
+ "related-link": "http://localhost/aai/v10/network/generic-vnfs/generic-vnf/key1",
+ "relationship-data" : [{
+ "relationship-key" : "test-obect.vnf-id",
+ "relationship-value":"key2"
+ }]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/nothing-to-parse.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/nothing-to-parse.json
new file mode 100644
index 00000000..b24834af
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/nothing-to-parse.json
@@ -0,0 +1,4 @@
+{
+ "related-to": "generic-vnf",
+ "relationship-data" : []
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-related-link.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-related-link.json
new file mode 100644
index 00000000..4cc103e8
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-related-link.json
@@ -0,0 +1,4 @@
+{
+ "related-to": "generic-vnf",
+ "related-link": "http://localhost/aai/v10/network/generic-vnfs/generic-vnf/key1"
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-relationship-data.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-relationship-data.json
new file mode 100644
index 00000000..b9fccc9c
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/only-relationship-data.json
@@ -0,0 +1,7 @@
+{
+ "related-to": "generic-vnf",
+ "relationship-data" : [{
+ "relationship-key" : "generic-vnf.vnf-id",
+ "relationship-value":"key1"
+ }]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/too-many-items-relationship.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/too-many-items-relationship.json
new file mode 100644
index 00000000..97765cfb
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/too-many-items-relationship.json
@@ -0,0 +1,19 @@
+{
+ "related-to": "l-interface",
+ "relationship-data" : [{
+ "relationship-key" : "generic-vnf.vnf-id",
+ "relationship-value":"key1"
+ },{
+ "relationship-key" : "subnet.subnet-id",
+ "relationship-value":"key5"
+ },{
+ "relationship-key" : "vlan.vlan-interface",
+ "relationship-value":"key3"
+ },{
+ "relationship-key" : "l-interface.interface-name",
+ "relationship-value":"key2"
+ },{
+ "relationship-key" : "l3-interface-ipv4-address-list.l3-interface-ipv4-address",
+ "relationship-value":"key4"
+ }]
+}
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/top-level-two-keys-relationship.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/top-level-two-keys-relationship.json
new file mode 100644
index 00000000..c6dbf557
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/top-level-two-keys-relationship.json
@@ -0,0 +1,13 @@
+{
+ "related-to" : "availability-zone",
+ "relationship-data" : [ {
+ "relationship-key" : "cloud-region.cloud-owner",
+ "relationship-value" : "key1"
+ }, {
+ "relationship-key" : "cloud-region.cloud-region-id",
+ "relationship-value" : "key2"
+ }, {
+ "relationship-key" : "availability-zone.availability-zone-name",
+ "relationship-value" : "key3"
+ } ]
+} \ No newline at end of file
diff --git a/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/two-top-level-relationship.json b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/two-top-level-relationship.json
new file mode 100644
index 00000000..54cac2c8
--- /dev/null
+++ b/aai-aaf-auth/src/test/resources/bundleconfig-local/etc/relationship/two-top-level-relationship.json
@@ -0,0 +1,19 @@
+{
+ "related-to": "l-interface",
+ "relationship-data" : [{
+ "relationship-key" : "generic-vnf.vnf-id",
+ "relationship-value":"key1"
+ },{
+ "relationship-key" : "vlan.vlan-interface",
+ "relationship-value":"key3"
+ },{
+ "relationship-key" : "l-interface.interface-name",
+ "relationship-value":"key2"
+ },{
+ "relationship-key" : "zone.zone-id",
+ "relationship-value":"key5"
+ },{
+ "relationship-key" : "l3-interface-ipv4-address-list.l3-interface-ipv4-address",
+ "relationship-value":"key4"
+ }]
+}