Skip to content

Commit 8e0f9ba

Browse files
author
Dexter Lee
committed
2 parents 489fe75 + 66c862f commit 8e0f9ba

File tree

77 files changed

+2332
-1341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+2332
-1341
lines changed
Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
package auth;
22

3+
import com.typesafe.config.Config;
4+
import java.util.Optional;
5+
6+
37
public class ConfigUtil {
48

5-
private ConfigUtil() { }
9+
private ConfigUtil() {
10+
}
611

7-
public static String getRequired(
8-
final com.typesafe.config.Config configs,
9-
final String path) {
12+
public static String getRequired(final Config configs, final String path) {
1013
if (!configs.hasPath(path)) {
11-
throw new IllegalArgumentException(
12-
String.format("Missing required config with path %s", path));
14+
throw new IllegalArgumentException(String.format("Missing required config with path %s", path));
1315
}
1416
return configs.getString(path);
1517
}
1618

17-
public static String getOptional
18-
(final com.typesafe.config.Config configs,
19-
final String path,
20-
final String defaultVal) {
19+
public static String getOptional(final Config configs, final String path, final String defaultVal) {
2120
if (!configs.hasPath(path)) {
2221
return defaultVal;
2322
}
2423
return configs.getString(path);
2524
}
2625

26+
public static Optional<String> getOptional(final Config configs, final String path) {
27+
if (!configs.hasPath(path)) {
28+
return Optional.empty();
29+
}
30+
return Optional.of(configs.getString(path));
31+
}
2732
}

datahub-frontend/app/auth/sso/oidc/OidcCallbackLogic.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ private List<CorpGroupSnapshot> extractGroups(CommonProfile profile) {
214214
final OidcConfigs configs = (OidcConfigs) _ssoManager.getSsoProvider().configs();
215215

216216
// First, attempt to extract a list of groups from the profile, using the group name attribute config.
217-
final String groupsClaimName = configs.groupsClaimName();
217+
final String groupsClaimName = configs.getGroupsClaimName();
218218
if (profile.containsAttribute(groupsClaimName)) {
219219
try {
220220
final List<CorpGroupSnapshot> groupSnapshots = new ArrayList<>();
Lines changed: 39 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package auth.sso.oidc;
22

33
import auth.sso.SsoConfigs;
4+
import java.util.Optional;
5+
import lombok.Getter;
46

57
import static auth.ConfigUtil.*;
68

79

810
/**
911
* Class responsible for extracting and validating OIDC related configurations.
1012
*/
13+
@Getter
1114
public class OidcConfigs extends SsoConfigs {
1215

1316
/**
@@ -29,6 +32,10 @@ public class OidcConfigs extends SsoConfigs {
2932
public static final String OIDC_PRE_PROVISIONING_REQUIRED_CONFIG_PATH = "auth.oidc.preProvisioningRequired";
3033
public static final String OIDC_EXTRACT_GROUPS_ENABLED = "auth.oidc.extractGroupsEnabled";
3134
public static final String OIDC_GROUPS_CLAIM_CONFIG_PATH_CONFIG_PATH = "auth.oidc.groupsClaim"; // Claim expected to be an array of group names.
35+
public static final String OIDC_RESPONSE_TYPE = "auth.oidc.responseType";
36+
public static final String OIDC_RESPONSE_MODE = "auth.oidc.responseMode";
37+
public static final String OIDC_USE_NONCE = "auth.oidc.useNonce";
38+
public static final String OIDC_CUSTOM_PARAM_RESOURCE = "auth.oidc.customParam.resource";
3239

3340
/**
3441
* Default values
@@ -43,85 +50,45 @@ public class OidcConfigs extends SsoConfigs {
4350
private static final String DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED = "true";
4451
private static final String DEFAULT_OIDC_GROUPS_CLAIM = "groups";
4552

46-
private String _clientId;
47-
private String _clientSecret;
48-
private String _discoveryUri;
49-
private String _userNameClaim;
50-
private String _userNameClaimRegex;
51-
private String _scope;
52-
private String _clientName;
53-
private String _clientAuthenticationMethod;
54-
private boolean _jitProvisioningEnabled;
55-
private boolean _preProvisioningRequired;
56-
private boolean _extractGroupsEnabled;
57-
private String _groupsClaimName;
53+
private String clientId;
54+
private String clientSecret;
55+
private String discoveryUri;
56+
private String userNameClaim;
57+
private String userNameClaimRegex;
58+
private String scope;
59+
private String clientName;
60+
private String clientAuthenticationMethod;
61+
private boolean jitProvisioningEnabled;
62+
private boolean preProvisioningRequired;
63+
private boolean extractGroupsEnabled;
64+
private String groupsClaimName;
65+
private Optional<String> responseType;
66+
private Optional<String> responseMode;
67+
private Optional<Boolean> useNonce;
68+
private Optional<String> customParamResource;
5869

5970
public OidcConfigs(final com.typesafe.config.Config configs) {
6071
super(configs);
61-
_clientId = getRequired(configs, OIDC_CLIENT_ID_CONFIG_PATH);
62-
_clientSecret = getRequired(configs, OIDC_CLIENT_SECRET_CONFIG_PATH);
63-
_discoveryUri = getRequired(configs, OIDC_DISCOVERY_URI_CONFIG_PATH);
64-
_userNameClaim = getOptional(configs, OIDC_USERNAME_CLAIM_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM);
65-
_userNameClaimRegex =
72+
clientId = getRequired(configs, OIDC_CLIENT_ID_CONFIG_PATH);
73+
clientSecret = getRequired(configs, OIDC_CLIENT_SECRET_CONFIG_PATH);
74+
discoveryUri = getRequired(configs, OIDC_DISCOVERY_URI_CONFIG_PATH);
75+
userNameClaim = getOptional(configs, OIDC_USERNAME_CLAIM_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM);
76+
userNameClaimRegex =
6677
getOptional(configs, OIDC_USERNAME_CLAIM_REGEX_CONFIG_PATH, DEFAULT_OIDC_USERNAME_CLAIM_REGEX);
67-
_scope = getOptional(configs, OIDC_SCOPE_CONFIG_PATH, DEFAULT_OIDC_SCOPE);
68-
_clientName = getOptional(configs, OIDC_CLIENT_NAME_CONFIG_PATH, DEFAULT_OIDC_CLIENT_NAME);
69-
_clientAuthenticationMethod = getOptional(configs, OIDC_CLIENT_AUTHENTICATION_METHOD_CONFIG_PATH,
78+
scope = getOptional(configs, OIDC_SCOPE_CONFIG_PATH, DEFAULT_OIDC_SCOPE);
79+
clientName = getOptional(configs, OIDC_CLIENT_NAME_CONFIG_PATH, DEFAULT_OIDC_CLIENT_NAME);
80+
clientAuthenticationMethod = getOptional(configs, OIDC_CLIENT_AUTHENTICATION_METHOD_CONFIG_PATH,
7081
DEFAULT_OIDC_CLIENT_AUTHENTICATION_METHOD);
71-
_jitProvisioningEnabled = Boolean.parseBoolean(
82+
jitProvisioningEnabled = Boolean.parseBoolean(
7283
getOptional(configs, OIDC_JIT_PROVISIONING_ENABLED_CONFIG_PATH, DEFAULT_OIDC_JIT_PROVISIONING_ENABLED));
73-
_preProvisioningRequired = Boolean.parseBoolean(
84+
preProvisioningRequired = Boolean.parseBoolean(
7485
getOptional(configs, OIDC_PRE_PROVISIONING_REQUIRED_CONFIG_PATH, DEFAULT_OIDC_PRE_PROVISIONING_REQUIRED));
75-
_extractGroupsEnabled = Boolean.parseBoolean(
86+
extractGroupsEnabled = Boolean.parseBoolean(
7687
getOptional(configs, OIDC_EXTRACT_GROUPS_ENABLED, DEFAULT_OIDC_EXTRACT_GROUPS_ENABLED));
77-
_groupsClaimName = getOptional(configs, OIDC_GROUPS_CLAIM_CONFIG_PATH_CONFIG_PATH, DEFAULT_OIDC_GROUPS_CLAIM);
78-
}
79-
80-
public boolean isJitProvisioningEnabled() {
81-
return _jitProvisioningEnabled;
82-
}
83-
84-
public boolean isPreProvisioningRequired() {
85-
return _preProvisioningRequired;
86-
}
87-
88-
public String getClientId() {
89-
return _clientId;
90-
}
91-
92-
public String getClientSecret() {
93-
return _clientSecret;
94-
}
95-
96-
public String getDiscoveryUri() {
97-
return _discoveryUri;
98-
}
99-
100-
public String getUserNameClaim() {
101-
return _userNameClaim;
102-
}
103-
104-
public String getUserNameClaimRegex() {
105-
return _userNameClaimRegex;
106-
}
107-
108-
public String getScope() {
109-
return _scope;
110-
}
111-
112-
public String getClientName() {
113-
return _clientName;
114-
}
115-
116-
public boolean isExtractGroupsEnabled() {
117-
return _extractGroupsEnabled;
118-
}
119-
120-
public String groupsClaimName() {
121-
return _groupsClaimName;
122-
}
123-
124-
public String getClientAuthenticationMethod() {
125-
return _clientAuthenticationMethod;
88+
groupsClaimName = getOptional(configs, OIDC_GROUPS_CLAIM_CONFIG_PATH_CONFIG_PATH, DEFAULT_OIDC_GROUPS_CLAIM);
89+
responseType = getOptional(configs, OIDC_RESPONSE_TYPE);
90+
responseMode = getOptional(configs, OIDC_RESPONSE_MODE);
91+
useNonce = getOptional(configs, OIDC_USE_NONCE).map(Boolean::parseBoolean);
92+
customParamResource = getOptional(configs, OIDC_CUSTOM_PARAM_RESOURCE);
12693
}
12794
}

datahub-frontend/app/auth/sso/oidc/OidcProvider.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package auth.sso.oidc;
22

3+
import auth.sso.SsoProvider;
4+
import com.google.common.collect.ImmutableMap;
35
import org.pac4j.core.client.Client;
46
import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver;
57
import org.pac4j.oidc.config.OidcConfiguration;
68
import org.pac4j.oidc.credentials.OidcCredentials;
79
import org.pac4j.oidc.profile.OidcProfile;
8-
import auth.sso.SsoProvider;
910

1011

1112
/**
@@ -51,8 +52,14 @@ private Client<OidcCredentials, OidcProfile> createPac4jClient() {
5152
oidcConfiguration.setDiscoveryURI(_oidcConfigs.getDiscoveryUri());
5253
oidcConfiguration.setClientAuthenticationMethodAsString(_oidcConfigs.getClientAuthenticationMethod());
5354
oidcConfiguration.setScope(_oidcConfigs.getScope());
55+
_oidcConfigs.getResponseType().ifPresent(oidcConfiguration::setResponseType);
56+
_oidcConfigs.getResponseMode().ifPresent(oidcConfiguration::setResponseType);
57+
_oidcConfigs.getUseNonce().ifPresent(oidcConfiguration::setUseNonce);
58+
_oidcConfigs.getCustomParamResource()
59+
.ifPresent(value -> oidcConfiguration.setCustomParams(ImmutableMap.of("resource", value)));
5460

55-
final org.pac4j.oidc.client.OidcClient<OidcProfile, OidcConfiguration> oidcClient = new org.pac4j.oidc.client.OidcClient<>(oidcConfiguration);
61+
final org.pac4j.oidc.client.OidcClient<OidcProfile, OidcConfiguration> oidcClient =
62+
new org.pac4j.oidc.client.OidcClient<>(oidcConfiguration);
5663
oidcClient.setName(OIDC_CLIENT_NAME);
5764
oidcClient.setCallbackUrl(_oidcConfigs.getAuthBaseUrl() + _oidcConfigs.getAuthBaseCallbackPath());
5865
oidcClient.setCallbackUrlResolver(new PathParameterCallbackUrlResolver());

datahub-frontend/conf/application.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ auth.oidc.jitProvisioningEnabled = ${?AUTH_OIDC_JIT_PROVISIONING_ENABLED} # Whet
121121
auth.oidc.preProvisioningRequired = ${?AUTH_OIDC_PRE_PROVISIONING_REQUIRED} # Whether the user should already exist in DataHub on login, failing login if they are not. Defaults to false.
122122
auth.oidc.extractGroupsEnabled = ${?AUTH_OIDC_EXTRACT_GROUPS_ENABLED} # Whether groups should be extracted from a claim in the OIDC profile. Only applies if JIT provisioning is enabled. Groups will be created if they do not exist. Defaults to true.
123123
auth.oidc.groupsClaim = ${?AUTH_OIDC_GROUPS_CLAIM} # The OIDC claim to extract groups information from. Defaults to 'groups'
124+
auth.oidc.responseType = ${?AUTH_OIDC_RESPONSE_TYPE}
125+
auth.oidc.responseMode = ${?AUTH_OIDC_RESPONSE_MODE}
126+
auth.oidc.useNonce = ${?AUTH_OIDC_USE_NONCE}
127+
auth.oidc.customParam.resource = ${?AUTH_OIDC_CUSTOM_PARAM_RESOURCE}
124128

125129
#
126130
# By default, the callback URL that should be registered with the identity provider is computed as {$baseUrl}/callback/oidc.

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@
4343
import com.linkedin.datahub.graphql.resolvers.load.EntityRelationshipsResultResolver;
4444
import com.linkedin.datahub.graphql.resolvers.load.TimeSeriesAspectResolver;
4545
import com.linkedin.datahub.graphql.resolvers.load.UsageTypeResolver;
46+
import com.linkedin.datahub.graphql.resolvers.mutate.AddTagResolver;
47+
import com.linkedin.datahub.graphql.resolvers.mutate.AddTermResolver;
4648
import com.linkedin.datahub.graphql.resolvers.mutate.MutableTypeResolver;
49+
import com.linkedin.datahub.graphql.resolvers.mutate.RemoveTagResolver;
50+
import com.linkedin.datahub.graphql.resolvers.mutate.RemoveTermResolver;
4751
import com.linkedin.datahub.graphql.resolvers.policy.DeletePolicyResolver;
4852
import com.linkedin.datahub.graphql.resolvers.policy.ListPoliciesResolver;
4953
import com.linkedin.datahub.graphql.resolvers.config.AppConfigResolver;
@@ -69,7 +73,7 @@
6973
import com.linkedin.datahub.graphql.resolvers.browse.BrowsePathsResolver;
7074
import com.linkedin.datahub.graphql.resolvers.browse.BrowseResolver;
7175
import com.linkedin.datahub.graphql.resolvers.search.AutoCompleteResolver;
72-
import com.linkedin.datahub.graphql.resolvers.search.AutoCompleteForAllResolver;
76+
import com.linkedin.datahub.graphql.resolvers.search.AutoCompleteForMultipleResolver;
7377
import com.linkedin.datahub.graphql.resolvers.search.SearchResolver;
7478
import com.linkedin.datahub.graphql.resolvers.type.EntityInterfaceTypeResolver;
7579
import com.linkedin.datahub.graphql.resolvers.type.PlatformSchemaUnionTypeResolver;
@@ -88,6 +92,7 @@
8892
import com.linkedin.datahub.graphql.types.glossary.GlossaryTermType;
8993

9094
import com.linkedin.datahub.graphql.types.usage.UsageType;
95+
import com.linkedin.metadata.entity.EntityService;
9196
import graphql.execution.DataFetcherResult;
9297
import graphql.schema.idl.RuntimeWiring;
9398
import java.util.ArrayList;
@@ -123,6 +128,7 @@ public class GmsGraphQLEngine {
123128
private static final Logger _logger = LoggerFactory.getLogger(GmsGraphQLEngine.class.getName());
124129

125130
private final AnalyticsService analyticsService;
131+
private final EntityService entityService;
126132

127133
private final DatasetType datasetType;
128134
private final CorpUserType corpUserType;
@@ -176,11 +182,13 @@ public class GmsGraphQLEngine {
176182
public final List<BrowsableEntityType<?>> browsableTypes;
177183

178184
public GmsGraphQLEngine() {
179-
this(null);
185+
this(null, null);
180186
}
181187

182-
public GmsGraphQLEngine(final AnalyticsService analyticsService) {
188+
public GmsGraphQLEngine(final AnalyticsService analyticsService, final EntityService entityService) {
183189
this.analyticsService = analyticsService;
190+
this.entityService = entityService;
191+
184192
this.datasetType = new DatasetType(GmsClientFactory.getEntitiesClient());
185193
this.corpUserType = new CorpUserType(GmsClientFactory.getEntitiesClient());
186194
this.corpGroupType = new CorpGroupType(GmsClientFactory.getEntitiesClient());
@@ -314,8 +322,8 @@ private void configureQueryResolvers(final RuntimeWiring.Builder builder) {
314322
new SearchResolver(searchableTypes)))
315323
.dataFetcher("autoComplete", new AuthenticatedResolver<>(
316324
new AutoCompleteResolver(searchableTypes)))
317-
.dataFetcher("autoCompleteForAll", new AuthenticatedResolver<>(
318-
new AutoCompleteForAllResolver(searchableTypes)))
325+
.dataFetcher("autoCompleteForMultiple", new AuthenticatedResolver<>(
326+
new AutoCompleteForMultipleResolver(searchableTypes)))
319327
.dataFetcher("browse", new AuthenticatedResolver<>(
320328
new BrowseResolver(browsableTypes)))
321329
.dataFetcher("browsePaths", new AuthenticatedResolver<>(
@@ -369,15 +377,19 @@ private void configureQueryResolvers(final RuntimeWiring.Builder builder) {
369377

370378
private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
371379
builder.type("Mutation", typeWiring -> typeWiring
372-
.dataFetcher("updateDataset", new AuthenticatedResolver<>(new MutableTypeResolver<>(datasetType)))
373-
.dataFetcher("updateTag", new AuthenticatedResolver<>(new MutableTypeResolver<>(tagType)))
374-
.dataFetcher("updateChart", new AuthenticatedResolver<>(new MutableTypeResolver<>(chartType)))
375-
.dataFetcher("updateDashboard", new AuthenticatedResolver<>(new MutableTypeResolver<>(dashboardType)))
376-
.dataFetcher("updateDataJob", new AuthenticatedResolver<>(new MutableTypeResolver<>(dataJobType)))
377-
.dataFetcher("updateDataFlow", new AuthenticatedResolver<>(new MutableTypeResolver<>(dataFlowType)))
378-
.dataFetcher("createPolicy", new UpsertPolicyResolver(GmsClientFactory.getAspectsClient()))
379-
.dataFetcher("updatePolicy", new UpsertPolicyResolver(GmsClientFactory.getAspectsClient()))
380-
.dataFetcher("deletePolicy", new DeletePolicyResolver(GmsClientFactory.getEntitiesClient()))
380+
.dataFetcher("updateDataset", new AuthenticatedResolver<>(new MutableTypeResolver<>(datasetType)))
381+
.dataFetcher("updateTag", new AuthenticatedResolver<>(new MutableTypeResolver<>(tagType)))
382+
.dataFetcher("updateChart", new AuthenticatedResolver<>(new MutableTypeResolver<>(chartType)))
383+
.dataFetcher("updateDashboard", new AuthenticatedResolver<>(new MutableTypeResolver<>(dashboardType)))
384+
.dataFetcher("updateDataJob", new AuthenticatedResolver<>(new MutableTypeResolver<>(dataJobType)))
385+
.dataFetcher("updateDataFlow", new AuthenticatedResolver<>(new MutableTypeResolver<>(dataFlowType)))
386+
.dataFetcher("addTag", new AuthenticatedResolver<>(new AddTagResolver(entityService)))
387+
.dataFetcher("removeTag", new AuthenticatedResolver<>(new RemoveTagResolver(entityService)))
388+
.dataFetcher("addTerm", new AuthenticatedResolver<>(new AddTermResolver(entityService)))
389+
.dataFetcher("removeTerm", new AuthenticatedResolver<>(new RemoveTermResolver(entityService)))
390+
.dataFetcher("createPolicy", new UpsertPolicyResolver(GmsClientFactory.getAspectsClient()))
391+
.dataFetcher("updatePolicy", new UpsertPolicyResolver(GmsClientFactory.getAspectsClient()))
392+
.dataFetcher("deletePolicy", new DeletePolicyResolver(GmsClientFactory.getEntitiesClient()))
381393
);
382394
}
383395

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.linkedin.datahub.graphql.resolvers.mutate;
2+
3+
import com.linkedin.common.urn.CorpuserUrn;
4+
5+
import com.linkedin.common.urn.Urn;
6+
import com.linkedin.datahub.graphql.QueryContext;
7+
import com.linkedin.datahub.graphql.exception.AuthorizationException;
8+
import com.linkedin.datahub.graphql.generated.TagUpdateInput;
9+
import com.linkedin.metadata.entity.EntityService;
10+
import graphql.schema.DataFetcher;
11+
import graphql.schema.DataFetchingEnvironment;
12+
import java.util.concurrent.CompletableFuture;
13+
import lombok.RequiredArgsConstructor;
14+
import lombok.extern.slf4j.Slf4j;
15+
16+
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
17+
18+
19+
@Slf4j
20+
@RequiredArgsConstructor
21+
public class AddTagResolver implements DataFetcher<CompletableFuture<Boolean>> {
22+
private final EntityService _entityService;
23+
24+
@Override
25+
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
26+
final TagUpdateInput input = bindArgument(environment.getArgument("input"), TagUpdateInput.class);
27+
Urn tagUrn = Urn.createFromString(input.getTagUrn());
28+
Urn targetUrn = Urn.createFromString(input.getTargetUrn());
29+
30+
if (!LabelUtils.isAuthorizedToUpdateTags(environment.getContext(), targetUrn, input.getSubResource())) {
31+
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
32+
}
33+
34+
return CompletableFuture.supplyAsync(() -> {
35+
try {
36+
37+
if (!tagUrn.getEntityType().equals("tag")) {
38+
log.error("Failed to add {}. It is not a tag urn.", tagUrn.toString());
39+
return false;
40+
}
41+
42+
log.info("Adding Tag. input: {}", input.toString());
43+
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActor());
44+
LabelUtils.addTagToTarget(
45+
tagUrn,
46+
targetUrn,
47+
input.getSubResource(),
48+
actor,
49+
_entityService
50+
);
51+
return true;
52+
} catch (Exception e) {
53+
log.error("Failed to perform update against input {}, {}", input.toString(), e.getMessage());
54+
throw new RuntimeException(String.format("Failed to perform update against input %s", input.toString()), e);
55+
}
56+
});
57+
}
58+
}

0 commit comments

Comments
 (0)