mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-21 15:48:05 +00:00
feat(authorization): Adding AuthorizerContext + ResourceSpecResolver to context (#4982)
This commit is contained in:
parent
b77f381429
commit
ce061e3fb5
@ -15,12 +15,12 @@ public interface Authorizer {
|
||||
* @param authorizerConfig config provided to the authenticator derived from the Metadata Service YAML config. This
|
||||
* 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.
|
||||
*/
|
||||
AuthorizationResult authorize(AuthorizationRequest request);
|
||||
AuthorizationResult authorize(@Nonnull final AuthorizationRequest request);
|
||||
|
||||
/**
|
||||
* Retrieves the current list of actors authorized to for a particular privilege against
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.datahub.authorization.fieldresolverprovider;
|
||||
package com.datahub.authorization;
|
||||
|
||||
/**
|
||||
* List of resource field types to fetch for a given resource
|
@ -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);
|
||||
}
|
@ -30,7 +30,7 @@ public class AuthorizerChain implements Authorizer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(@Nonnull Map<String, Object> authorizerConfig) {
|
||||
public void init(@Nonnull Map<String, Object> authorizerConfig, @Nonnull AuthorizerContext ctx) {
|
||||
// pass.
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,6 @@ package com.datahub.authorization;
|
||||
|
||||
import com.datahub.authentication.Authentication;
|
||||
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.UrnUtils;
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
@ -13,18 +11,15 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nonnull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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
|
||||
@ -43,13 +38,12 @@ public class DataHubAuthorizer implements Authorizer {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
ALLOW_ALL
|
||||
}
|
||||
|
||||
|
||||
// Credentials used to make / authorize requests as the internal system actor.
|
||||
private final Authentication _systemAuthentication;
|
||||
|
||||
@ -59,9 +53,8 @@ public class DataHubAuthorizer implements Authorizer {
|
||||
|
||||
private final ScheduledExecutorService _refreshExecutorService = Executors.newScheduledThreadPool(1);
|
||||
private final PolicyRefreshRunnable _policyRefreshRunnable;
|
||||
|
||||
private final ResourceSpecResolver _resourceSpecResolver;
|
||||
private final PolicyEngine _policyEngine;
|
||||
private ResourceSpecResolver _resourceSpecResolver;
|
||||
private AuthorizationMode _mode;
|
||||
|
||||
public static final String ALL = "ALL";
|
||||
@ -72,20 +65,20 @@ public class DataHubAuthorizer implements Authorizer {
|
||||
final int delayIntervalSeconds,
|
||||
final int refreshIntervalSeconds,
|
||||
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);
|
||||
_refreshExecutorService.scheduleAtFixedRate(_policyRefreshRunnable, delayIntervalSeconds, refreshIntervalSeconds, TimeUnit.SECONDS);
|
||||
_mode = mode;
|
||||
_resourceSpecResolver = new ResourceSpecResolver(systemAuthentication, entityClient);
|
||||
_policyEngine = new PolicyEngine(systemAuthentication, entityClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(@Nonnull Map<String, Object> authorizerConfig) {
|
||||
public void init(@Nonnull Map<String, Object> authorizerConfig, @Nonnull AuthorizerContext ctx) {
|
||||
// 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.
|
||||
if (isSystemRequest(request, this._systemAuthentication)) {
|
||||
@ -265,20 +258,4 @@ public class DataHubAuthorizer implements Authorizer {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import com.datahub.authentication.Authentication;
|
||||
import com.datahub.authorization.fieldresolverprovider.DomainFieldResolverProvider;
|
||||
import com.datahub.authorization.fieldresolverprovider.EntityTypeFieldResolverProvider;
|
||||
import com.datahub.authorization.fieldresolverprovider.EntityUrnFieldResolverProvider;
|
||||
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
|
||||
import com.datahub.authorization.fieldresolverprovider.OwnerFieldResolverProvider;
|
||||
import com.datahub.authorization.fieldresolverprovider.ResourceFieldResolverProvider;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -14,21 +13,22 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class ResourceSpecResolver {
|
||||
public class DefaultResourceSpecResolver implements ResourceSpecResolver {
|
||||
private final List<ResourceFieldResolverProvider> _resourceFieldResolverProviders;
|
||||
|
||||
public ResourceSpecResolver(Authentication systemAuthentication, EntityClient entityClient) {
|
||||
public DefaultResourceSpecResolver(Authentication systemAuthentication, EntityClient entityClient) {
|
||||
_resourceFieldResolverProviders =
|
||||
ImmutableList.of(new EntityTypeFieldResolverProvider(), new EntityUrnFieldResolverProvider(),
|
||||
new DomainFieldResolverProvider(entityClient, systemAuthentication),
|
||||
new OwnerFieldResolverProvider(entityClient, systemAuthentication));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvedResourceSpec resolve(ResourceSpec resourceSpec) {
|
||||
return new ResolvedResourceSpec(resourceSpec, getFieldResolvers(resourceSpec));
|
||||
}
|
||||
|
||||
public Map<ResourceFieldType, FieldResolver> getFieldResolvers(ResourceSpec resourceSpec) {
|
||||
private Map<ResourceFieldType, FieldResolver> getFieldResolvers(ResourceSpec resourceSpec) {
|
||||
return _resourceFieldResolverProviders.stream()
|
||||
.collect(Collectors.toMap(ResourceFieldResolverProvider::getFieldType,
|
||||
hydrator -> hydrator.getFieldResolver(resourceSpec)));
|
@ -1,6 +1,5 @@
|
||||
package com.datahub.authorization;
|
||||
|
||||
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
|
||||
import com.linkedin.data.template.StringArray;
|
||||
import com.linkedin.policy.PolicyMatchCondition;
|
||||
import com.linkedin.policy.PolicyMatchCriterion;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.datahub.authorization;
|
||||
|
||||
import com.datahub.authentication.Authentication;
|
||||
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.data.template.StringArray;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.datahub.authorization.fieldresolverprovider;
|
||||
|
||||
import com.datahub.authentication.Authentication;
|
||||
import com.datahub.authorization.FieldResolver;
|
||||
import com.datahub.authorization.ResourceFieldType;
|
||||
import com.datahub.authorization.ResourceSpec;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.datahub.authorization.fieldresolverprovider;
|
||||
|
||||
import com.datahub.authorization.FieldResolver;
|
||||
import com.datahub.authorization.ResourceFieldType;
|
||||
import com.datahub.authorization.ResourceSpec;
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.datahub.authorization.fieldresolverprovider;
|
||||
|
||||
import com.datahub.authorization.FieldResolver;
|
||||
import com.datahub.authorization.ResourceFieldType;
|
||||
import com.datahub.authorization.ResourceSpec;
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.datahub.authorization.fieldresolverprovider;
|
||||
|
||||
import com.datahub.authentication.Authentication;
|
||||
import com.datahub.authorization.FieldResolver;
|
||||
import com.datahub.authorization.ResourceFieldType;
|
||||
import com.datahub.authorization.ResourceSpec;
|
||||
import com.linkedin.common.Ownership;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.datahub.authorization.fieldresolverprovider;
|
||||
|
||||
import com.datahub.authorization.FieldResolver;
|
||||
import com.datahub.authorization.ResourceFieldType;
|
||||
import com.datahub.authorization.ResourceSpec;
|
||||
|
||||
|
||||
|
@ -107,6 +107,7 @@ public class DataHubAuthorizerTest {
|
||||
10,
|
||||
DataHubAuthorizer.AuthorizationMode.DEFAULT
|
||||
);
|
||||
_dataHubAuthorizer.init(Collections.emptyMap(), createAuthorizerContext(systemAuthentication, _entityClient));
|
||||
_dataHubAuthorizer.invalidateCache();
|
||||
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")));
|
||||
return ownershipAspect;
|
||||
}
|
||||
|
||||
private AuthorizerContext createAuthorizerContext(final Authentication systemAuthentication, final EntityClient entityClient) {
|
||||
return new AuthorizerContext(new DefaultResourceSpecResolver(systemAuthentication, entityClient));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.datahub.authorization;
|
||||
|
||||
import com.datahub.authentication.Authentication;
|
||||
import com.datahub.authorization.fieldresolverprovider.ResourceFieldType;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -1,9 +1,14 @@
|
||||
package com.linkedin.gms.factory.auth;
|
||||
|
||||
import com.datahub.authentication.Authentication;
|
||||
import com.datahub.authorization.AuthorizerConfiguration;
|
||||
import com.datahub.authorization.AuthorizerContext;
|
||||
import com.datahub.authorization.DataHubAuthorizer;
|
||||
import com.datahub.authorization.Authorizer;
|
||||
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.spring.YamlPropertySourceFactory;
|
||||
import java.util.ArrayList;
|
||||
@ -31,20 +36,36 @@ public class AuthorizerChainFactory {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dataHubAuthorizer")
|
||||
private DataHubAuthorizer _dataHubAuthorizer;
|
||||
private DataHubAuthorizer dataHubAuthorizer;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("systemAuthentication")
|
||||
private Authentication systemAuthentication;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("javaEntityClient")
|
||||
private JavaEntityClient entityClient;
|
||||
|
||||
@Bean(name = "authorizerChain")
|
||||
@Scope("singleton")
|
||||
@Nonnull
|
||||
protected AuthorizerChain getInstance() {
|
||||
// Init authorizer context
|
||||
final AuthorizerContext ctx = initAuthorizerContext();
|
||||
// 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.
|
||||
authorizers.add(this._dataHubAuthorizer);
|
||||
this.dataHubAuthorizer.init(Collections.emptyMap(), ctx);
|
||||
authorizers.add(this.dataHubAuthorizer);
|
||||
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<>();
|
||||
|
||||
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.
|
||||
try {
|
||||
final Authorizer authorizerInstance = clazz.newInstance();
|
||||
authorizerInstance.init(configs);
|
||||
authorizerInstance.init(configs, ctx);
|
||||
customAuthorizers.add(authorizerInstance);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format("Failed to instantiate custom Authorizer with class name %s", clazz.getCanonicalName()), e);
|
||||
|
Loading…
x
Reference in New Issue
Block a user