diff --git a/metadata-service/auth-api/src/main/java/com/datahub/authorization/AuthorizedActors.java b/metadata-service/auth-api/src/main/java/com/datahub/authorization/AuthorizedActors.java new file mode 100644 index 0000000000..4da6296901 --- /dev/null +++ b/metadata-service/auth-api/src/main/java/com/datahub/authorization/AuthorizedActors.java @@ -0,0 +1,17 @@ +package com.datahub.authorization; + +import com.linkedin.common.urn.Urn; +import java.util.List; +import lombok.Builder; +import lombok.Value; + + +@Value +@Builder +public class AuthorizedActors { + String privilege; + List users; + List groups; + boolean allUsers; + boolean allGroups; +} diff --git a/metadata-service/auth-api/src/main/java/com/datahub/authorization/Authorizer.java b/metadata-service/auth-api/src/main/java/com/datahub/authorization/Authorizer.java index e2dda69c07..c92aad8b2b 100644 --- a/metadata-service/auth-api/src/main/java/com/datahub/authorization/Authorizer.java +++ b/metadata-service/auth-api/src/main/java/com/datahub/authorization/Authorizer.java @@ -1,6 +1,7 @@ package com.datahub.authorization; import java.util.Map; +import java.util.Optional; import javax.annotation.Nonnull; @@ -20,4 +21,12 @@ public interface Authorizer { * Authorizes an action based on the actor, the resource, & required privileges. */ AuthorizationResult authorize(AuthorizationRequest request); + + /** + * Retrieves the current list of actors authorized to for a particular privilege against + * an optional resource + */ + AuthorizedActors authorizedActors( + final String privilege, + final Optional resourceSpec); } diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/AuthorizerChain.java b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/AuthorizerChain.java index c5d7860f59..c06adf37bd 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/AuthorizerChain.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/AuthorizerChain.java @@ -1,12 +1,19 @@ package com.datahub.authorization; +import com.linkedin.common.urn.Urn; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; -import java.util.Objects; import lombok.extern.slf4j.Slf4j; + /** * A configurable chain of {@link Authorizer}s executed in series to attempt to authenticate an inbound request. * @@ -43,11 +50,11 @@ public class AuthorizerChain implements Authorizer { // Authorization was successful - Short circuit return result; } else { - log.debug("Received DENY result from Authorizer with class name {}. message: {}", authorizer.getClass().getCanonicalName(), result.getMessage()); + log.debug("Received DENY result from Authorizer with class name {}. message: {}", + authorizer.getClass().getCanonicalName(), result.getMessage()); } } catch (Exception e) { - log.error( - "Caught exception while attempting to authorize request using Authorizer {}. Skipping authorizer.", + log.error("Caught exception while attempting to authorize request using Authorizer {}. Skipping authorizer.", authorizer.getClass().getCanonicalName(), e); } } @@ -55,6 +62,59 @@ public class AuthorizerChain implements Authorizer { return new AuthorizationResult(request, AuthorizationResult.Type.DENY, null); } + @Override + public AuthorizedActors authorizedActors(String privilege, Optional resourceSpec) { + if (this.authorizers.isEmpty()) { + return null; + } + + AuthorizedActors finalAuthorizedActors = this.authorizers.get(0).authorizedActors(privilege, resourceSpec); + for (int i = 1; i < this.authorizers.size(); i++) { + finalAuthorizedActors = mergeAuthorizedActors(finalAuthorizedActors, + this.authorizers.get(i).authorizedActors(privilege, resourceSpec)); + } + return finalAuthorizedActors; + } + + private AuthorizedActors mergeAuthorizedActors(@Nullable AuthorizedActors original, + @Nullable AuthorizedActors other) { + if (original == null) { + return other; + } + if (other == null) { + return original; + } + + boolean isAllUsers = original.isAllUsers() || other.isAllUsers(); + List mergedUsers; + if (isAllUsers) { + // If enabled for all users, no need to check users + mergedUsers = Collections.emptyList(); + } else { + Set users = new HashSet<>(original.getUsers()); + users.addAll(other.getUsers()); + mergedUsers = new ArrayList<>(users); + } + + boolean isAllGroups = original.isAllGroups() || other.isAllGroups(); + List mergedGroups; + if (isAllGroups) { + // If enabled for all users, no need to check users + mergedGroups = Collections.emptyList(); + } else { + Set groups = new HashSet<>(original.getGroups()); + groups.addAll(other.getGroups()); + mergedGroups = new ArrayList<>(groups); + } + + return AuthorizedActors.builder() + .allUsers(original.isAllUsers() || other.isAllUsers()) + .allGroups(original.isAllGroups() || other.isAllGroups()) + .users(mergedUsers) + .groups(mergedGroups) + .build(); + } + /** * Returns an instance of {@link DataHubAuthorizer} if it is present in the Authentication chain, * or null if it cannot be found. diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/DataHubAuthorizer.java b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/DataHubAuthorizer.java index 92d3518b80..c8f4bac855 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/DataHubAuthorizer.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/DataHubAuthorizer.java @@ -131,7 +131,7 @@ public class DataHubAuthorizer implements Authorizer { */ public AuthorizedActors authorizedActors( final String privilege, - final Optional resourceSpec) throws RuntimeException { + final Optional resourceSpec) { // Step 1: Find policies granting the privilege. final List policiesToEvaluate = _policyCache.getOrDefault(privilege, new ArrayList<>()); @@ -309,44 +309,4 @@ public class DataHubAuthorizer implements Authorizer { .map(Owner::getOwner) .collect(Collectors.toList()); } - - /** - * Class used to represent all users authorized to perform a particular privilege. - */ - public static class AuthorizedActors { - final String _privilege; - final List _users; - final List _groups; - final Boolean _allUsers; - final Boolean _allGroups; - - public AuthorizedActors(final String privilege, final List users, final List groups, final Boolean allUsers, final Boolean allGroups) { - _privilege = privilege; - _users = users; - _groups = groups; - _allUsers = allUsers; - _allGroups = allGroups; - } - - public String getPrivilege() { - return _privilege; - } - - public List getUsers() { - return _users; - } - - public List getGroups() { - return _groups; - } - - public Boolean allUsers() { - return _allUsers; - } - - public Boolean allGroups() { - return _allGroups; - } - } - } diff --git a/metadata-service/auth-impl/src/test/java/com/datahub/authorization/DataHubAuthorizerTest.java b/metadata-service/auth-impl/src/test/java/com/datahub/authorization/DataHubAuthorizerTest.java index e33bab126e..ca0d7664c9 100644 --- a/metadata-service/auth-impl/src/test/java/com/datahub/authorization/DataHubAuthorizerTest.java +++ b/metadata-service/auth-impl/src/test/java/com/datahub/authorization/DataHubAuthorizerTest.java @@ -209,12 +209,12 @@ public class DataHubAuthorizerTest { @Test public void testAuthorizedActorsActivePolicy() throws Exception { - final DataHubAuthorizer.AuthorizedActors actors = + final AuthorizedActors actors = _dataHubAuthorizer.authorizedActors("EDIT_ENTITY_TAGS", // Should be inside the active policy. Optional.of(new ResourceSpec("dataset", "urn:li:dataset:1"))); - assertTrue(actors.allUsers()); - assertTrue(actors.allGroups()); + assertTrue(actors.isAllUsers()); + assertTrue(actors.isAllGroups()); assertEquals(new HashSet<>(actors.getUsers()), ImmutableSet.of( Urn.createFromString("urn:li:corpuser:user1"),