From 9f64203a2109dd43cc0b423713ce388e7fa6c46d Mon Sep 17 00:00:00 2001 From: Whitney O'Meara Date: Mon, 20 May 2024 12:33:18 -0400 Subject: [PATCH] Feature/query microservices (#8) * Removed unnecessary jackson annotations to address enunciate issues * bumped release version * bumped versions for some modules * Updated with latest changes from main/integration * Implemented authorization and query federation for the query microservices --- pom.xml | 8 +- .../ConditionalRemoteUserOperations.java | 95 +++++++++++++++++++ .../authorization/UserOperations.java | 58 +++++++++++ 3 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 src/main/java/datawave/security/authorization/ConditionalRemoteUserOperations.java create mode 100644 src/main/java/datawave/security/authorization/UserOperations.java diff --git a/pom.xml b/pom.xml index bbbed71..20db1d1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,11 +4,11 @@ gov.nsa.datawave.microservice datawave-microservice-parent - 3.0.4 + 4.0.0-SNAPSHOT ../../microservices/microservice-parent/pom.xml base-rest-responses - 3.0.1-SNAPSHOT + 4.0.0-SNAPSHOT https://code.nsa.gov/datawave-base-rest-responses @@ -26,8 +26,8 @@ http://webservice.datawave.nsa/v1 3.12.0 2.3.3 - 3.0.1 - 2.0.0 + 4.0.0-SNAPSHOT + 3.0.0-SNAPSHOT 2.0.4 2.5.0 1.6.2 diff --git a/src/main/java/datawave/security/authorization/ConditionalRemoteUserOperations.java b/src/main/java/datawave/security/authorization/ConditionalRemoteUserOperations.java new file mode 100644 index 0000000..a05d316 --- /dev/null +++ b/src/main/java/datawave/security/authorization/ConditionalRemoteUserOperations.java @@ -0,0 +1,95 @@ +package datawave.security.authorization; + +import java.util.Collections; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import datawave.user.AuthorizationsListBase; +import datawave.webservice.result.GenericResponse; + +/** + * A conditional remote user operations will only invoke the delegate remote service base on a specified function of the specified principal. For example we may + * only need to invoke the remote user operations if we know the remote system will have additional auths that this user will need for the query logic being + * invoked. + * + * An example may be a composite query that call a local and a remote query logic. Perhaps we can already tell that the user will not be able to get any + * additional authorities from the remote system and hence the remote call will not be required. + */ +public class ConditionalRemoteUserOperations implements UserOperations { + private static final Logger log = LoggerFactory.getLogger(ConditionalRemoteUserOperations.class); + + private UserOperations delegate; + private Function condition; + private Supplier authorizationsListBaseSupplier; + + private static final GenericResponse EMPTY_RESPONSE = new GenericResponse<>(); + + public boolean isFiltered(ProxiedUserDetails principal) { + if (!condition.apply(principal)) { + if (log.isDebugEnabled()) { + log.debug("Filter " + condition + " blocking " + principal.getName() + " from " + delegate + " user operations"); + } + return true; + } else { + if (log.isDebugEnabled()) { + log.debug("Passing through filter " + condition + " for " + principal.getName() + " for " + delegate + " user operations"); + } + return false; + } + } + + @Override + public AuthorizationsListBase listEffectiveAuthorizations(ProxiedUserDetails callerObject) throws AuthorizationException { + assert (delegate != null); + assert (condition != null); + assert (authorizationsListBaseSupplier != null); + + if (!isFiltered(callerObject)) { + return delegate.listEffectiveAuthorizations(callerObject); + } else { + AuthorizationsListBase response = authorizationsListBaseSupplier.get(); + response.setUserAuths(callerObject.getPrimaryUser().getDn().subjectDN(), callerObject.getPrimaryUser().getDn().issuerDN(), Collections.EMPTY_LIST); + return response; + } + } + + @Override + public GenericResponse flushCachedCredentials(ProxiedUserDetails callerObject) throws AuthorizationException { + assert (delegate != null); + assert (condition != null); + assert (authorizationsListBaseSupplier != null); + + if (!isFiltered(callerObject)) { + return delegate.flushCachedCredentials(callerObject); + } else { + return EMPTY_RESPONSE; + } + } + + public UserOperations getDelegate() { + return delegate; + } + + public void setDelegate(UserOperations delegate) { + this.delegate = delegate; + } + + public Function getCondition() { + return condition; + } + + public void setCondition(Function condition) { + this.condition = condition; + } + + public Supplier getAuthorizationsListBaseSupplier() { + return authorizationsListBaseSupplier; + } + + public void setAuthorizationsListBaseSupplier(Supplier authorizationsListBaseSupplier) { + this.authorizationsListBaseSupplier = authorizationsListBaseSupplier; + } +} diff --git a/src/main/java/datawave/security/authorization/UserOperations.java b/src/main/java/datawave/security/authorization/UserOperations.java new file mode 100644 index 0000000..f720388 --- /dev/null +++ b/src/main/java/datawave/security/authorization/UserOperations.java @@ -0,0 +1,58 @@ +package datawave.security.authorization; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import datawave.user.AuthorizationsListBase; +import datawave.webservice.result.GenericResponse; + +/** + * A user operations service is one that can pass calls off to another external user operations endpoint + */ +public interface UserOperations { + + AuthorizationsListBase listEffectiveAuthorizations(ProxiedUserDetails callerObject) throws AuthorizationException; + + GenericResponse flushCachedCredentials(ProxiedUserDetails callerObject) throws AuthorizationException; + + default T getRemoteUser(T currentUser) throws AuthorizationException { + // get the effective authorizations for this user + AuthorizationsListBase auths = listEffectiveAuthorizations(currentUser); + // create a new set of proxied users + List mappedUsers = new ArrayList<>(); + Map localUsers = currentUser.getProxiedUsers().stream() + .collect(Collectors.toMap(DatawaveUser::getDn, Function.identity(), (v1, v2) -> v2)); + + // create a mapped user for the primary user with the auths returned by listEffectiveAuthorizations + SubjectIssuerDNPair primaryDn = SubjectIssuerDNPair.of(auths.getUserDn(), auths.getIssuerDn()); + DatawaveUser localUser = localUsers.get(primaryDn); + mappedUsers.add(new DatawaveUser(primaryDn, localUser.getUserType(), auths.getAllAuths(), auths.getAuthMapping().keySet(), + toMultimap(auths.getAuthMapping()), System.currentTimeMillis())); + // for each proxied user, create a new user with the auths returned by listEffectiveAuthorizations + Map> authMap = auths.getAuths(); + for (Map.Entry> entry : authMap.entrySet()) { + SubjectIssuerDNPair pair = SubjectIssuerDNPair.of(entry.getKey().subjectDN, entry.getKey().issuerDN); + if (!pair.equals(primaryDn)) { + mappedUsers.add(new DatawaveUser(pair, DatawaveUser.UserType.SERVER, entry.getValue(), null, null, System.currentTimeMillis())); + } + } + + // return a proxied user details with the mapped users + return currentUser.newInstance(mappedUsers); + } + + static Multimap toMultimap(Map> map) { + Multimap multimap = HashMultimap.create(); + map.forEach(multimap::putAll); + return multimap; + } + +}