feat(authorization): Adding AuthorizerContext + ResourceSpecResolver to context (#4982)

This commit is contained in:
John Joyce 2022-05-24 11:06:49 -07:00 committed by GitHub
parent b77f381429
commit ce061e3fb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 147 additions and 81 deletions

View File

@ -15,12 +15,12 @@ public interface Authorizer {
* @param authorizerConfig config provided to the authenticator derived from the Metadata Service YAML config. This * @param authorizerConfig config provided to the authenticator derived from the Metadata Service YAML config. This
* config comes from the "authorization.authorizers.config" configuration. * config comes from the "authorization.authorizers.config" configuration.
*/ */
void init(@Nonnull final Map<String, Object> authorizerConfig); void init(@Nonnull final Map<String, Object> authorizerConfig, @Nonnull final AuthorizerContext ctx);
/** /**
* Authorizes an action based on the actor, the resource, & required privileges. * Authorizes an action based on the actor, the resource, & required privileges.
*/ */
AuthorizationResult authorize(AuthorizationRequest request); AuthorizationResult authorize(@Nonnull final AuthorizationRequest request);
/** /**
* Retrieves the current list of actors authorized to for a particular privilege against * Retrieves the current list of actors authorized to for a particular privilege against

View File

@ -0,0 +1,17 @@
package com.datahub.authorization;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Context provided to an Authorizer on initialization.
*/
@Data
@AllArgsConstructor
public class AuthorizerContext {
/**
* A utility for resolving a {@link ResourceSpec} to resolved resource field values.
*/
private ResourceSpecResolver resourceSpecResolver;
}

View File

@ -0,0 +1,66 @@
package com.datahub.authorization;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Wrapper around authorization request with field resolvers for lazily fetching the field values for each field type
*/
@RequiredArgsConstructor
public class ResolvedResourceSpec {
@Getter
private final ResourceSpec spec;
private final Map<ResourceFieldType, FieldResolver> fieldResolvers;
public Set<String> getFieldValues(ResourceFieldType resourceFieldType) {
if (!fieldResolvers.containsKey(resourceFieldType)) {
return Collections.emptySet();
}
return fieldResolvers.get(resourceFieldType).getFieldValuesFuture().join().getValues();
}
/**
* Fetch the entity-registry type for a resource. ('dataset', 'dashboard', 'chart').
* @return the entity type.
*/
public String getType() {
if (!fieldResolvers.containsKey(ResourceFieldType.RESOURCE_TYPE)) {
throw new UnsupportedOperationException("Failed to resolve resource type! No field resolver for RESOURCE_TYPE provided.");
}
Set<String> resourceTypes = fieldResolvers.get(ResourceFieldType.RESOURCE_TYPE).getFieldValuesFuture().join().getValues();
assert resourceTypes.size() == 1; // There should always be a single resource type.
return resourceTypes.stream().findFirst().get();
}
/**
* Fetch the owners for a resource.
* @return a set of owner urns, or empty set if none exist.
*/
public Set<String> getOwners() {
if (!fieldResolvers.containsKey(ResourceFieldType.OWNER)) {
return Collections.emptySet();
}
return fieldResolvers.get(ResourceFieldType.OWNER).getFieldValuesFuture().join().getValues();
}
/**
* Fetch the domain for a Resolved Resource Spec
* @return a Domain or null if one does not exist.
*/
@Nullable
public String getDomain() {
if (!fieldResolvers.containsKey(ResourceFieldType.DOMAIN)) {
return null;
}
Set<String> domains = fieldResolvers.get(ResourceFieldType.DOMAIN).getFieldValuesFuture().join().getValues();
if (domains.size() > 0) {
return domains.stream().findFirst().get();
}
return null;
}
}

View File

@ -1,4 +1,4 @@
package com.datahub.authorization.fieldresolverprovider; package com.datahub.authorization;
/** /**
* List of resource field types to fetch for a given resource * List of resource field types to fetch for a given resource

View File

@ -0,0 +1,11 @@
package com.datahub.authorization;
/**
* A Resource Spec Resolver is responsible for resolving a {@link ResourceSpec} to a {@link ResolvedResourceSpec}.
*/
public interface ResourceSpecResolver {
/**
Resolve a {@link ResourceSpec} to a resolved resource spec.
**/
ResolvedResourceSpec resolve(ResourceSpec resourceSpec);
}

View File

@ -30,7 +30,7 @@ public class AuthorizerChain implements Authorizer {
} }
@Override @Override
public void init(@Nonnull Map<String, Object> authorizerConfig) { public void init(@Nonnull Map<String, Object> authorizerConfig, @Nonnull AuthorizerContext ctx) {
// pass. // pass.
} }

View File

@ -2,8 +2,6 @@ package com.datahub.authorization;
import com.datahub.authentication.Authentication; import com.datahub.authentication.Authentication;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.linkedin.common.Owner;
import com.linkedin.common.Ownership;
import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils; import com.linkedin.common.urn.UrnUtils;
import com.linkedin.entity.client.EntityClient; import com.linkedin.entity.client.EntityClient;
@ -13,18 +11,15 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static com.linkedin.metadata.Constants.CORP_GROUP_ENTITY_NAME;
import static com.linkedin.metadata.Constants.CORP_USER_ENTITY_NAME;
/** /**
* The Authorizer is a singleton class responsible for authorizing * The Authorizer is a singleton class responsible for authorizing
@ -43,13 +38,12 @@ public class DataHubAuthorizer implements Authorizer {
*/ */
DEFAULT, DEFAULT,
/** /**
* Allow all means that the AuthorizationManager will allow all actions. This is used as an override to disable the * Allow all means that the DataHubAuthorizer will allow all actions. This is used as an override to disable the
* policies feature. * policies feature.
*/ */
ALLOW_ALL ALLOW_ALL
} }
// Credentials used to make / authorize requests as the internal system actor. // Credentials used to make / authorize requests as the internal system actor.
private final Authentication _systemAuthentication; private final Authentication _systemAuthentication;
@ -59,9 +53,8 @@ public class DataHubAuthorizer implements Authorizer {
private final ScheduledExecutorService _refreshExecutorService = Executors.newScheduledThreadPool(1); private final ScheduledExecutorService _refreshExecutorService = Executors.newScheduledThreadPool(1);
private final PolicyRefreshRunnable _policyRefreshRunnable; private final PolicyRefreshRunnable _policyRefreshRunnable;
private final ResourceSpecResolver _resourceSpecResolver;
private final PolicyEngine _policyEngine; private final PolicyEngine _policyEngine;
private ResourceSpecResolver _resourceSpecResolver;
private AuthorizationMode _mode; private AuthorizationMode _mode;
public static final String ALL = "ALL"; public static final String ALL = "ALL";
@ -72,20 +65,20 @@ public class DataHubAuthorizer implements Authorizer {
final int delayIntervalSeconds, final int delayIntervalSeconds,
final int refreshIntervalSeconds, final int refreshIntervalSeconds,
final AuthorizationMode mode) { final AuthorizationMode mode) {
_systemAuthentication = systemAuthentication; _systemAuthentication = Objects.requireNonNull(systemAuthentication);
_mode = Objects.requireNonNull(mode);
_policyEngine = new PolicyEngine(systemAuthentication, Objects.requireNonNull(entityClient));
_policyRefreshRunnable = new PolicyRefreshRunnable(systemAuthentication, new PolicyFetcher(entityClient), _policyCache); _policyRefreshRunnable = new PolicyRefreshRunnable(systemAuthentication, new PolicyFetcher(entityClient), _policyCache);
_refreshExecutorService.scheduleAtFixedRate(_policyRefreshRunnable, delayIntervalSeconds, refreshIntervalSeconds, TimeUnit.SECONDS); _refreshExecutorService.scheduleAtFixedRate(_policyRefreshRunnable, delayIntervalSeconds, refreshIntervalSeconds, TimeUnit.SECONDS);
_mode = mode;
_resourceSpecResolver = new ResourceSpecResolver(systemAuthentication, entityClient);
_policyEngine = new PolicyEngine(systemAuthentication, entityClient);
} }
@Override @Override
public void init(@Nonnull Map<String, Object> authorizerConfig) { public void init(@Nonnull Map<String, Object> authorizerConfig, @Nonnull AuthorizerContext ctx) {
// Pass. No static config. // Pass. No static config.
_resourceSpecResolver = Objects.requireNonNull(ctx.getResourceSpecResolver());
} }
public AuthorizationResult authorize(final AuthorizationRequest request) { public AuthorizationResult authorize(@Nonnull final AuthorizationRequest request) {
// 0. Short circuit: If the action is being performed by the system (root), always allow it. // 0. Short circuit: If the action is being performed by the system (root), always allow it.
if (isSystemRequest(request, this._systemAuthentication)) { if (isSystemRequest(request, this._systemAuthentication)) {
@ -265,20 +258,4 @@ public class DataHubAuthorizer implements Authorizer {
cache.put(ALL, existingPolicies); cache.put(ALL, existingPolicies);
} }
} }
private List<Urn> userOwners(final Ownership ownership) {
return ownership.getOwners()
.stream()
.filter(owner -> CORP_USER_ENTITY_NAME.equals(owner.getOwner().getEntityType()))
.map(Owner::getOwner)
.collect(Collectors.toList());
}
private List<Urn> groupOwners(final Ownership ownership) {
return ownership.getOwners()
.stream()
.filter(owner -> CORP_GROUP_ENTITY_NAME.equals(owner.getOwner().getEntityType()))
.map(Owner::getOwner)
.collect(Collectors.toList());
}
} }

View File

@ -4,7 +4,6 @@ import com.datahub.authentication.Authentication;
import com.datahub.authorization.fieldresolverprovider.DomainFieldResolverProvider; import com.datahub.authorization.fieldresolverprovider.DomainFieldResolverProvider;
import com.datahub.authorization.fieldresolverprovider.EntityTypeFieldResolverProvider; import com.datahub.authorization.fieldresolverprovider.EntityTypeFieldResolverProvider;
import com.datahub.authorization.fieldresolverprovider.EntityUrnFieldResolverProvider; import com.datahub.authorization.fieldresolverprovider.EntityUrnFieldResolverProvider;
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
import com.datahub.authorization.fieldresolverprovider.OwnerFieldResolverProvider; import com.datahub.authorization.fieldresolverprovider.OwnerFieldResolverProvider;
import com.datahub.authorization.fieldresolverprovider.ResourceFieldResolverProvider; import com.datahub.authorization.fieldresolverprovider.ResourceFieldResolverProvider;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -14,21 +13,22 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class ResourceSpecResolver { public class DefaultResourceSpecResolver implements ResourceSpecResolver {
private final List<ResourceFieldResolverProvider> _resourceFieldResolverProviders; private final List<ResourceFieldResolverProvider> _resourceFieldResolverProviders;
public ResourceSpecResolver(Authentication systemAuthentication, EntityClient entityClient) { public DefaultResourceSpecResolver(Authentication systemAuthentication, EntityClient entityClient) {
_resourceFieldResolverProviders = _resourceFieldResolverProviders =
ImmutableList.of(new EntityTypeFieldResolverProvider(), new EntityUrnFieldResolverProvider(), ImmutableList.of(new EntityTypeFieldResolverProvider(), new EntityUrnFieldResolverProvider(),
new DomainFieldResolverProvider(entityClient, systemAuthentication), new DomainFieldResolverProvider(entityClient, systemAuthentication),
new OwnerFieldResolverProvider(entityClient, systemAuthentication)); new OwnerFieldResolverProvider(entityClient, systemAuthentication));
} }
@Override
public ResolvedResourceSpec resolve(ResourceSpec resourceSpec) { public ResolvedResourceSpec resolve(ResourceSpec resourceSpec) {
return new ResolvedResourceSpec(resourceSpec, getFieldResolvers(resourceSpec)); return new ResolvedResourceSpec(resourceSpec, getFieldResolvers(resourceSpec));
} }
public Map<ResourceFieldType, FieldResolver> getFieldResolvers(ResourceSpec resourceSpec) { private Map<ResourceFieldType, FieldResolver> getFieldResolvers(ResourceSpec resourceSpec) {
return _resourceFieldResolverProviders.stream() return _resourceFieldResolverProviders.stream()
.collect(Collectors.toMap(ResourceFieldResolverProvider::getFieldType, .collect(Collectors.toMap(ResourceFieldResolverProvider::getFieldType,
hydrator -> hydrator.getFieldResolver(resourceSpec))); hydrator -> hydrator.getFieldResolver(resourceSpec)));

View File

@ -1,6 +1,5 @@
package com.datahub.authorization; package com.datahub.authorization;
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
import com.linkedin.data.template.StringArray; import com.linkedin.data.template.StringArray;
import com.linkedin.policy.PolicyMatchCondition; import com.linkedin.policy.PolicyMatchCondition;
import com.linkedin.policy.PolicyMatchCriterion; import com.linkedin.policy.PolicyMatchCriterion;

View File

@ -1,7 +1,6 @@
package com.datahub.authorization; package com.datahub.authorization;
import com.datahub.authentication.Authentication; import com.datahub.authentication.Authentication;
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils; import com.linkedin.common.urn.UrnUtils;
import com.linkedin.data.template.StringArray; import com.linkedin.data.template.StringArray;

View File

@ -1,33 +0,0 @@
package com.datahub.authorization;
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Wrapper around authorization request with field resolvers for lazily fetching the field values for each field type
*/
@RequiredArgsConstructor
public class ResolvedResourceSpec {
@Getter
private final ResourceSpec spec;
private final Map<ResourceFieldType, FieldResolver> fieldResolvers;
public Set<String> getFieldValues(ResourceFieldType resourceFieldType) {
if (!fieldResolvers.containsKey(resourceFieldType)) {
return Collections.emptySet();
}
return fieldResolvers.get(resourceFieldType).getFieldValuesFuture().join().getValues();
}
public Set<String> getOwners() {
if (!fieldResolvers.containsKey(ResourceFieldType.OWNER)) {
return Collections.emptySet();
}
return fieldResolvers.get(ResourceFieldType.OWNER).getFieldValuesFuture().join().getValues();
}
}

View File

@ -2,6 +2,7 @@ package com.datahub.authorization.fieldresolverprovider;
import com.datahub.authentication.Authentication; import com.datahub.authentication.Authentication;
import com.datahub.authorization.FieldResolver; import com.datahub.authorization.FieldResolver;
import com.datahub.authorization.ResourceFieldType;
import com.datahub.authorization.ResourceSpec; import com.datahub.authorization.ResourceSpec;
import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils; import com.linkedin.common.urn.UrnUtils;

View File

@ -1,6 +1,7 @@
package com.datahub.authorization.fieldresolverprovider; package com.datahub.authorization.fieldresolverprovider;
import com.datahub.authorization.FieldResolver; import com.datahub.authorization.FieldResolver;
import com.datahub.authorization.ResourceFieldType;
import com.datahub.authorization.ResourceSpec; import com.datahub.authorization.ResourceSpec;
import java.util.Collections; import java.util.Collections;

View File

@ -1,6 +1,7 @@
package com.datahub.authorization.fieldresolverprovider; package com.datahub.authorization.fieldresolverprovider;
import com.datahub.authorization.FieldResolver; import com.datahub.authorization.FieldResolver;
import com.datahub.authorization.ResourceFieldType;
import com.datahub.authorization.ResourceSpec; import com.datahub.authorization.ResourceSpec;
import java.util.Collections; import java.util.Collections;

View File

@ -2,6 +2,7 @@ package com.datahub.authorization.fieldresolverprovider;
import com.datahub.authentication.Authentication; import com.datahub.authentication.Authentication;
import com.datahub.authorization.FieldResolver; import com.datahub.authorization.FieldResolver;
import com.datahub.authorization.ResourceFieldType;
import com.datahub.authorization.ResourceSpec; import com.datahub.authorization.ResourceSpec;
import com.linkedin.common.Ownership; import com.linkedin.common.Ownership;
import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.Urn;

View File

@ -1,6 +1,7 @@
package com.datahub.authorization.fieldresolverprovider; package com.datahub.authorization.fieldresolverprovider;
import com.datahub.authorization.FieldResolver; import com.datahub.authorization.FieldResolver;
import com.datahub.authorization.ResourceFieldType;
import com.datahub.authorization.ResourceSpec; import com.datahub.authorization.ResourceSpec;

View File

@ -107,6 +107,7 @@ public class DataHubAuthorizerTest {
10, 10,
DataHubAuthorizer.AuthorizationMode.DEFAULT DataHubAuthorizer.AuthorizationMode.DEFAULT
); );
_dataHubAuthorizer.init(Collections.emptyMap(), createAuthorizerContext(systemAuthentication, _entityClient));
_dataHubAuthorizer.invalidateCache(); _dataHubAuthorizer.invalidateCache();
Thread.sleep(500); // Sleep so the runnable can execute. (not ideal) Thread.sleep(500); // Sleep so the runnable can execute. (not ideal)
} }
@ -282,4 +283,8 @@ public class DataHubAuthorizerTest {
ownershipAspect.setLastModified(new AuditStamp().setTime(0).setActor(Urn.createFromString("urn:li:corpuser:foo"))); ownershipAspect.setLastModified(new AuditStamp().setTime(0).setActor(Urn.createFromString("urn:li:corpuser:foo")));
return ownershipAspect; return ownershipAspect;
} }
private AuthorizerContext createAuthorizerContext(final Authentication systemAuthentication, final EntityClient entityClient) {
return new AuthorizerContext(new DefaultResourceSpecResolver(systemAuthentication, entityClient));
}
} }

View File

@ -1,7 +1,6 @@
package com.datahub.authorization; package com.datahub.authorization;
import com.datahub.authentication.Authentication; import com.datahub.authentication.Authentication;
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;

View File

@ -1,9 +1,14 @@
package com.linkedin.gms.factory.auth; package com.linkedin.gms.factory.auth;
import com.datahub.authentication.Authentication;
import com.datahub.authorization.AuthorizerConfiguration; import com.datahub.authorization.AuthorizerConfiguration;
import com.datahub.authorization.AuthorizerContext;
import com.datahub.authorization.DataHubAuthorizer; import com.datahub.authorization.DataHubAuthorizer;
import com.datahub.authorization.Authorizer; import com.datahub.authorization.Authorizer;
import com.datahub.authorization.AuthorizerChain; import com.datahub.authorization.AuthorizerChain;
import com.datahub.authorization.DefaultResourceSpecResolver;
import com.datahub.authorization.ResourceSpecResolver;
import com.linkedin.entity.client.JavaEntityClient;
import com.linkedin.gms.factory.config.ConfigurationProvider; import com.linkedin.gms.factory.config.ConfigurationProvider;
import com.linkedin.gms.factory.spring.YamlPropertySourceFactory; import com.linkedin.gms.factory.spring.YamlPropertySourceFactory;
import java.util.ArrayList; import java.util.ArrayList;
@ -31,20 +36,36 @@ public class AuthorizerChainFactory {
@Autowired @Autowired
@Qualifier("dataHubAuthorizer") @Qualifier("dataHubAuthorizer")
private DataHubAuthorizer _dataHubAuthorizer; private DataHubAuthorizer dataHubAuthorizer;
@Autowired
@Qualifier("systemAuthentication")
private Authentication systemAuthentication;
@Autowired
@Qualifier("javaEntityClient")
private JavaEntityClient entityClient;
@Bean(name = "authorizerChain") @Bean(name = "authorizerChain")
@Scope("singleton") @Scope("singleton")
@Nonnull @Nonnull
protected AuthorizerChain getInstance() { protected AuthorizerChain getInstance() {
// Init authorizer context
final AuthorizerContext ctx = initAuthorizerContext();
// Extract + initialize customer authorizers from application configs. // Extract + initialize customer authorizers from application configs.
final List<Authorizer> authorizers = new ArrayList<>(initCustomAuthorizers()); final List<Authorizer> authorizers = new ArrayList<>(initCustomAuthorizers(ctx));
// Add the DataHub core policies-based Authorizer - this one should always be enabled. // Add the DataHub core policies-based Authorizer - this one should always be enabled.
authorizers.add(this._dataHubAuthorizer); this.dataHubAuthorizer.init(Collections.emptyMap(), ctx);
authorizers.add(this.dataHubAuthorizer);
return new AuthorizerChain(authorizers); return new AuthorizerChain(authorizers);
} }
private List<Authorizer> initCustomAuthorizers() { private AuthorizerContext initAuthorizerContext() {
final ResourceSpecResolver resolver = new DefaultResourceSpecResolver(systemAuthentication, entityClient);
return new AuthorizerContext(resolver);
}
private List<Authorizer> initCustomAuthorizers(AuthorizerContext ctx) {
final List<Authorizer> customAuthorizers = new ArrayList<>(); final List<Authorizer> customAuthorizers = new ArrayList<>();
if (this.configurationProvider.getAuthorization().getAuthorizers() != null) { if (this.configurationProvider.getAuthorization().getAuthorizers() != null) {
@ -71,7 +92,7 @@ public class AuthorizerChainFactory {
// Else construct an instance of the class, each class should have an empty constructor. // Else construct an instance of the class, each class should have an empty constructor.
try { try {
final Authorizer authorizerInstance = clazz.newInstance(); final Authorizer authorizerInstance = clazz.newInstance();
authorizerInstance.init(configs); authorizerInstance.init(configs, ctx);
customAuthorizers.add(authorizerInstance); customAuthorizers.add(authorizerInstance);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(String.format("Failed to instantiate custom Authorizer with class name %s", clazz.getCanonicalName()), e); throw new RuntimeException(String.format("Failed to instantiate custom Authorizer with class name %s", clazz.getCanonicalName()), e);