Skip to content

Commit

Permalink
feat(metadata-service): Supporting a configurable Authorizer Chain (d…
Browse files Browse the repository at this point in the history
  • Loading branch information
jjoyce0510 authored and maggiehays committed Aug 1, 2022
1 parent e3ca561 commit 77bc648
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linkedin.datahub.graphql;

import com.datahub.authentication.token.TokenService;
import com.datahub.authorization.AuthorizationConfiguration;
import com.google.common.collect.ImmutableList;
import com.linkedin.datahub.graphql.analytics.resolver.AnalyticsChartTypeResolver;
import com.linkedin.datahub.graphql.analytics.resolver.GetChartsResolver;
Expand Down Expand Up @@ -207,6 +208,7 @@ public class GmsGraphQLEngine {
private final TimeseriesAspectService timeseriesAspectService;

private final IngestionConfiguration ingestionConfiguration;
private final AuthorizationConfiguration authorizationConfiguration;

private final DatasetType datasetType;
private final CorpUserType corpUserType;
Expand Down Expand Up @@ -271,6 +273,7 @@ public GmsGraphQLEngine() {
null,
null,
null,
null,
false);
}

Expand All @@ -286,6 +289,7 @@ public GmsGraphQLEngine(
final EntityRegistry entityRegistry,
final SecretService secretService,
final IngestionConfiguration ingestionConfiguration,
final AuthorizationConfiguration authorizationConfiguration,
final GitVersion gitVersion,
final boolean supportsImpactAnalysis
) {
Expand All @@ -305,6 +309,7 @@ public GmsGraphQLEngine(
this.timeseriesAspectService = timeseriesAspectService;

this.ingestionConfiguration = Objects.requireNonNull(ingestionConfiguration);
this.authorizationConfiguration = Objects.requireNonNull(authorizationConfiguration);

this.datasetType = new DatasetType(entityClient);
this.corpUserType = new CorpUserType(entityClient);
Expand Down Expand Up @@ -537,7 +542,9 @@ private void configureContainerResolvers(final RuntimeWiring.Builder builder) {
private void configureQueryResolvers(final RuntimeWiring.Builder builder) {
builder.type("Query", typeWiring -> typeWiring
.dataFetcher("appConfig",
new AppConfigResolver(gitVersion, analyticsService != null, this.ingestionConfiguration,
new AppConfigResolver(gitVersion, analyticsService != null,
this.ingestionConfiguration,
this.authorizationConfiguration,
supportsImpactAnalysis))
.dataFetcher("me", new AuthenticatedResolver<>(
new MeResolver(this.entityClient)))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.linkedin.datahub.graphql.resolvers.config;

import com.datahub.authorization.Authorizer;
import com.datahub.authorization.AuthorizationConfiguration;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.AnalyticsConfig;
import com.linkedin.datahub.graphql.generated.AppConfig;
Expand All @@ -27,16 +27,19 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
private final GitVersion _gitVersion;
private final boolean _isAnalyticsEnabled;
private final IngestionConfiguration _ingestionConfiguration;
private final AuthorizationConfiguration _authorizationConfiguration;
private final boolean _supportsImpactAnalysis;

public AppConfigResolver(
final GitVersion gitVersion,
final boolean isAnalyticsEnabled,
final IngestionConfiguration ingestionConfiguration,
final AuthorizationConfiguration authorizationConfiguration,
final boolean supportsImpactAnalysis) {
_gitVersion = gitVersion;
_isAnalyticsEnabled = isAnalyticsEnabled;
_ingestionConfiguration = ingestionConfiguration;
_authorizationConfiguration = authorizationConfiguration;
_supportsImpactAnalysis = supportsImpactAnalysis;
}

Expand All @@ -57,9 +60,7 @@ public CompletableFuture<AppConfig> get(final DataFetchingEnvironment environmen
analyticsConfig.setEnabled(_isAnalyticsEnabled);

final PoliciesConfig policiesConfig = new PoliciesConfig();

boolean policiesEnabled = Authorizer.AuthorizationMode.DEFAULT.equals(context.getAuthorizer().mode());
policiesConfig.setEnabled(policiesEnabled);
policiesConfig.setEnabled(_authorizationConfiguration.getDefaultAuthorizer().isEnabled());

policiesConfig.setPlatformPrivileges(com.linkedin.metadata.authorization.PoliciesConfig.PLATFORM_PRIVILEGES
.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.linkedin.datahub.graphql.resolvers.policy;

import com.datahub.authorization.AuthorizationManager;
import com.datahub.authorization.AuthorizerChain;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
Expand Down Expand Up @@ -30,8 +30,8 @@ public CompletableFuture<String> get(final DataFetchingEnvironment environment)
return CompletableFuture.supplyAsync(() -> {
try {
_entityClient.deleteEntity(urn, context.getAuthentication());
if (context.getAuthorizer() instanceof AuthorizationManager) {
((AuthorizationManager) context.getAuthorizer()).invalidateCache();
if (context.getAuthorizer() instanceof AuthorizerChain) {
((AuthorizerChain) context.getAuthorizer()).getDefaultAuthorizer().invalidateCache();
}
return policyUrn;
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linkedin.datahub.graphql.resolvers.policy;

import com.datahub.authorization.AuthorizationManager;
import com.datahub.authorization.AuthorizerChain;
import com.datahub.authorization.DataHubAuthorizer;
import com.datahub.authorization.ResourceSpec;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
Expand Down Expand Up @@ -35,9 +36,9 @@ public CompletableFuture<Privileges> get(final DataFetchingEnvironment environme
final Optional<ResourceSpec> resourceSpec = Optional.ofNullable(input.getResourceSpec())
.map(spec -> new ResourceSpec(EntityTypeMapper.getName(spec.getResourceType()), spec.getResourceUrn()));

if (context.getAuthorizer() instanceof AuthorizationManager) {
AuthorizationManager authorizationManager = (AuthorizationManager) context.getAuthorizer();
List<String> privileges = authorizationManager.getGrantedPrivileges(actor, resourceSpec);
if (context.getAuthorizer() instanceof AuthorizerChain) {
DataHubAuthorizer dataHubAuthorizer = ((AuthorizerChain) context.getAuthorizer()).getDefaultAuthorizer();
List<String> privileges = dataHubAuthorizer.getGrantedPrivileges(actor, resourceSpec);
return CompletableFuture.supplyAsync(() -> Privileges.builder()
.setPrivileges(privileges)
.build());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.linkedin.datahub.graphql.resolvers.policy;

import com.datahub.authorization.AuthorizationManager;
import com.datahub.authorization.AuthorizerChain;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
Expand Down Expand Up @@ -69,8 +69,8 @@ public CompletableFuture<String> get(final DataFetchingEnvironment environment)
try {
// TODO: We should also provide SystemMetadata.
String urn = _entityClient.ingestProposal(proposal, context.getAuthentication());
if (context.getAuthorizer() instanceof AuthorizationManager) {
((AuthorizationManager) context.getAuthorizer()).invalidateCache();
if (context.getAuthorizer() instanceof AuthorizerChain) {
((AuthorizerChain) context.getAuthorizer()).getDefaultAuthorizer().invalidateCache();
}
return urn;
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.datahub.authorization;

import java.util.List;
import lombok.Data;

/**
* POJO representing the "authentication" configuration block in application.yml.
*/
@Data
public class AuthorizationConfiguration {
/**
* Configuration for the default DataHub Policies-based authorizer.
*/
private DefaultAuthorizerConfiguration defaultAuthorizer;
/**
* List of configurations for {@link Authorizer}s to be registered
*/
private List<AuthorizerConfiguration> authorizers;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@
import lombok.Value;


/**
* A request to authorize a user for a specific privilege.
*/
@Value
public class AuthorizationRequest {
/**
* The urn of the actor (corpuser) making the request.
*/
String actorUrn;
/**
* The privilege that the user is requesting
*/
String privilege;
/**
* The resource that the user is requesting for, if applicable. If the privilege is a platform privilege
* this optional will be empty.
*/
Optional<ResourceSpec> resourceSpec;
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
package com.datahub.authorization;

import com.linkedin.policy.DataHubPolicyInfo;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Data;


/**
* A result returned after requesting authorization for a particular privilege.
*/
@Data
@AllArgsConstructor
public class AuthorizationResult {
/**
* The original authorization request
*/
AuthorizationRequest request;

Optional<DataHubPolicyInfo> policy;

Type type;

/**
* The result type. Allow or deny the authorization request for the actor.
*/
public enum Type {
/**
* Allow the request - the requested actor is privileged.
*/
ALLOW,
/**
* Deny the request - the requested actor is not privileged.
*/
DENY
}

/**
* The decision - whether to allow or deny the request.
*/
Type type;

/**
* Optional message associated with the decision. Useful for debugging.
*/
String message;
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
package com.datahub.authorization;

public interface Authorizer {
import java.util.Map;
import javax.annotation.Nonnull;

enum AuthorizationMode {
/**
* Default mode simply means that authorization is enforced, with a DENY result returned
*/
DEFAULT,
/**
* Allow all means that the AuthorizationManager will allow all actions. This is used as an override to disable the
* policies feature.
*/
ALLOW_ALL
}

/**
* An Authorizer is responsible for determining whether an actor should be granted a specific privilege.
*/
public interface Authorizer {
/**
* Authorizes an action based on the actor, the resource, & required privileges.
* Initialize the Authorizer. Invoked once at boot time.
*
* @param authorizerConfig config provided to the authenticator derived from the Metadata Service YAML config. This
* config comes from the "authorization.authorizers.config" configuration.
*/
AuthorizationResult authorize(AuthorizationRequest request);
void init(@Nonnull final Map<String, Object> authorizerConfig);

/**
* Returns the mode the Authorizer is operating in.
* Authorizes an action based on the actor, the resource, & required privileges.
*/
AuthorizationMode mode();
AuthorizationResult authorize(AuthorizationRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.datahub.authorization;

import java.util.Map;
import lombok.Data;


/**
* POJO representing {@link Authorizer} configurations provided in the application.yml.
*/
@Data
public class AuthorizerConfiguration {
/**
* A fully-qualified class name for the {@link Authorizer} implementation to be registered.
*/
private String type;
/**
* A set of authorizer-specific configurations passed through during "init" of the authorizer.
*/
private Map<String, Object> configs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.datahub.authorization;

import lombok.Data;

@Data
public class DefaultAuthorizerConfiguration {
/**
* Whether authorization via DataHub policies is enabled.
*/
private boolean enabled;
/**
* The duration between policies cache refreshes.
*/
private int cacheRefreshIntervalSecs;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
import lombok.Value;


/**
* Details about a specific resource being acted upon. Resource types currently supported
* can be found inside of {@link PoliciesConfig}.
*/
@Value
public class ResourceSpec {
/**
* The resource type. Most often, this corresponds to the entity type. (dataset, chart, dashboard, corpGroup, etc).
*/
@Nonnull
String type;

/**
* The resource identity. Most often, this corresponds to the raw entity urn. (urn:li:corpGroup:groupId)
*/
@Nonnull
String resource;
}
Loading

0 comments on commit 77bc648

Please sign in to comment.