mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-24 08:28:12 +00:00
feat(frontend) Adds multiple group claim support (#4450)
This commit is contained in:
parent
c5f1d2c9bd
commit
db35aca869
@ -36,6 +36,7 @@ import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -77,11 +78,8 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
private final Authentication _systemAuthentication;
|
||||
private final AuthServiceClient _authClient;
|
||||
|
||||
public OidcCallbackLogic(
|
||||
final SsoManager ssoManager,
|
||||
final Authentication systemAuthentication,
|
||||
final EntityClient entityClient,
|
||||
final AuthServiceClient authClient) {
|
||||
public OidcCallbackLogic(final SsoManager ssoManager, final Authentication systemAuthentication,
|
||||
final EntityClient entityClient, final AuthServiceClient authClient) {
|
||||
_ssoManager = ssoManager;
|
||||
_systemAuthentication = systemAuthentication;
|
||||
_entityClient = entityClient;
|
||||
@ -92,7 +90,9 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
public Result perform(PlayWebContext context, Config config,
|
||||
HttpActionAdapter<Result, PlayWebContext> httpActionAdapter, String defaultUrl, Boolean saveInSession,
|
||||
Boolean multiProfile, Boolean renewSession, String defaultClient) {
|
||||
final Result result = super.perform(context, config, httpActionAdapter, defaultUrl, saveInSession, multiProfile, renewSession, defaultClient);
|
||||
final Result result =
|
||||
super.perform(context, config, httpActionAdapter, defaultUrl, saveInSession, multiProfile, renewSession,
|
||||
defaultClient);
|
||||
|
||||
// Handle OIDC authentication errors.
|
||||
if (OidcResponseErrorHandler.isError(context)) {
|
||||
@ -104,10 +104,7 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
return handleOidcCallback(oidcConfigs, result, context, getProfileManager(context, config));
|
||||
}
|
||||
|
||||
private Result handleOidcCallback(
|
||||
final OidcConfigs oidcConfigs,
|
||||
final Result result,
|
||||
final PlayWebContext context,
|
||||
private Result handleOidcCallback(final OidcConfigs oidcConfigs, final Result result, final PlayWebContext context,
|
||||
final ProfileManager<CommonProfile> profileManager) {
|
||||
|
||||
log.debug("Beginning OIDC Callback Handling...");
|
||||
@ -141,49 +138,40 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
}
|
||||
// Update user status to active on login.
|
||||
// If we want to prevent certain users from logging in, here's where we'll want to do it.
|
||||
setUserStatus(corpUserUrn, new CorpUserStatus()
|
||||
.setStatus(Constants.CORP_USER_STATUS_ACTIVE)
|
||||
.setLastModified(new AuditStamp()
|
||||
.setActor(Urn.createFromString(Constants.SYSTEM_ACTOR))
|
||||
.setTime(System.currentTimeMillis()))
|
||||
);
|
||||
setUserStatus(corpUserUrn, new CorpUserStatus().setStatus(Constants.CORP_USER_STATUS_ACTIVE)
|
||||
.setLastModified(new AuditStamp().setActor(Urn.createFromString(Constants.SYSTEM_ACTOR))
|
||||
.setTime(System.currentTimeMillis())));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to perform post authentication steps. Redirecting to error page.", e);
|
||||
return internalServerError(String.format("Failed to perform post authentication steps. Error message: %s", e.getMessage()));
|
||||
return internalServerError(
|
||||
String.format("Failed to perform post authentication steps. Error message: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
// Successfully logged in - Generate GMS login token
|
||||
final String accessToken = _authClient.generateSessionTokenForUser(corpUserUrn.getId());
|
||||
context.getJavaSession().put(ACCESS_TOKEN, accessToken);
|
||||
context.getJavaSession().put(ACCESS_TOKEN, accessToken);
|
||||
context.getJavaSession().put(ACTOR, corpUserUrn.toString());
|
||||
return result.withCookies(createActorCookie(corpUserUrn.toString(), oidcConfigs.getSessionTtlInHours()));
|
||||
}
|
||||
return internalServerError("Failed to authenticate current user. Cannot find valid identity provider profile in session.");
|
||||
return internalServerError(
|
||||
"Failed to authenticate current user. Cannot find valid identity provider profile in session.");
|
||||
}
|
||||
|
||||
private String extractUserNameOrThrow(final OidcConfigs oidcConfigs, final CommonProfile profile) {
|
||||
// Ensure that the attribute exists (was returned by IdP)
|
||||
if (!profile.containsAttribute(oidcConfigs.getUserNameClaim())) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"Failed to resolve user name claim from profile provided by Identity Provider. Missing attribute. Attribute: '%s', Regex: '%s', Profile: %s",
|
||||
oidcConfigs.getUserNameClaim(),
|
||||
oidcConfigs.getUserNameClaimRegex(),
|
||||
profile.getAttributes().toString()
|
||||
));
|
||||
throw new RuntimeException(String.format(
|
||||
"Failed to resolve user name claim from profile provided by Identity Provider. Missing attribute. Attribute: '%s', Regex: '%s', Profile: %s",
|
||||
oidcConfigs.getUserNameClaim(), oidcConfigs.getUserNameClaimRegex(), profile.getAttributes().toString()));
|
||||
}
|
||||
|
||||
final String userNameClaim = (String) profile.getAttribute(oidcConfigs.getUserNameClaim());
|
||||
|
||||
final Optional<String> mappedUserName = extractRegexGroup(
|
||||
oidcConfigs.getUserNameClaimRegex(),
|
||||
userNameClaim);
|
||||
final Optional<String> mappedUserName = extractRegexGroup(oidcConfigs.getUserNameClaimRegex(), userNameClaim);
|
||||
|
||||
return mappedUserName.orElseThrow(() ->
|
||||
new RuntimeException(String.format("Failed to extract DataHub username from username claim %s using regex %s. Profile: %s",
|
||||
userNameClaim,
|
||||
oidcConfigs.getUserNameClaimRegex(),
|
||||
profile.getAttributes().toString())));
|
||||
return mappedUserName.orElseThrow(() -> new RuntimeException(
|
||||
String.format("Failed to extract DataHub username from username claim %s using regex %s. Profile: %s",
|
||||
userNameClaim, oidcConfigs.getUserNameClaimRegex(), profile.getAttributes().toString())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,66 +229,76 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
final OidcConfigs configs = (OidcConfigs) _ssoManager.getSsoProvider().configs();
|
||||
|
||||
// First, attempt to extract a list of groups from the profile, using the group name attribute config.
|
||||
final String groupsClaimName = configs.getGroupsClaimName();
|
||||
if (profile.containsAttribute(groupsClaimName)) {
|
||||
try {
|
||||
final List<CorpGroupSnapshot> groupSnapshots = new ArrayList<>();
|
||||
final Collection<String> groupNames;
|
||||
final Object groupAttribute = profile.getAttribute(groupsClaimName);
|
||||
if (groupAttribute instanceof Collection) {
|
||||
// List of group names
|
||||
groupNames = (Collection<String>) profile.getAttribute(groupsClaimName, Collection.class);
|
||||
} else if (groupAttribute instanceof String) {
|
||||
// Single group name
|
||||
groupNames = Collections.singleton(profile.getAttribute(groupsClaimName, String.class));
|
||||
} else {
|
||||
log.error(String.format("Failed to parse OIDC group claim with name %s. Unknown type %s provided.",
|
||||
groupsClaimName,
|
||||
groupAttribute.getClass()));
|
||||
// Return empty list. Do not throw.
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<CorpGroupSnapshot> extractedGroups = new ArrayList<>();
|
||||
final List<String> groupsClaimNames =
|
||||
new ArrayList<String>(Arrays.asList(configs.getGroupsClaimName().split(","))).stream()
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (String groupName : groupNames) {
|
||||
// Create a basic CorpGroupSnapshot from the information.
|
||||
try {
|
||||
for (final String groupsClaimName : groupsClaimNames) {
|
||||
|
||||
final CorpGroupInfo corpGroupInfo = new CorpGroupInfo();
|
||||
corpGroupInfo.setAdmins(new CorpuserUrnArray());
|
||||
corpGroupInfo.setGroups(new CorpGroupUrnArray());
|
||||
corpGroupInfo.setMembers(new CorpuserUrnArray());
|
||||
corpGroupInfo.setEmail("");
|
||||
corpGroupInfo.setDisplayName(groupName);
|
||||
|
||||
// To deal with the possibility of spaces, we url encode the URN group name.
|
||||
final String urlEncodedGroupName = URLEncoder.encode(groupName, StandardCharsets.UTF_8.toString());
|
||||
final CorpGroupUrn groupUrn = new CorpGroupUrn(urlEncodedGroupName);
|
||||
final CorpGroupSnapshot corpGroupSnapshot = new CorpGroupSnapshot();
|
||||
corpGroupSnapshot.setUrn(groupUrn);
|
||||
final CorpGroupAspectArray aspects = new CorpGroupAspectArray();
|
||||
aspects.add(CorpGroupAspect.create(corpGroupInfo));
|
||||
corpGroupSnapshot.setAspects(aspects);
|
||||
groupSnapshots.add(corpGroupSnapshot);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
log.error(String.format("Failed to URL encoded extracted group name %s. Skipping", groupName));
|
||||
if (profile.containsAttribute(groupsClaimName)) {
|
||||
try {
|
||||
final List<CorpGroupSnapshot> groupSnapshots = new ArrayList<>();
|
||||
final Collection<String> groupNames;
|
||||
final Object groupAttribute = profile.getAttribute(groupsClaimName);
|
||||
if (groupAttribute instanceof Collection) {
|
||||
// List of group names
|
||||
groupNames = (Collection<String>) profile.getAttribute(groupsClaimName, Collection.class);
|
||||
} else if (groupAttribute instanceof String) {
|
||||
// Single group name
|
||||
groupNames = Collections.singleton(profile.getAttribute(groupsClaimName, String.class));
|
||||
} else {
|
||||
log.error(
|
||||
String.format("Fail to parse OIDC group claim with name %s. Unknown type %s provided.", groupsClaimName,
|
||||
groupAttribute.getClass()));
|
||||
// Skip over group attribute. Do not throw.
|
||||
groupNames = Collections.emptyList();
|
||||
}
|
||||
|
||||
for (String groupName : groupNames) {
|
||||
// Create a basic CorpGroupSnapshot from the information.
|
||||
try {
|
||||
|
||||
final CorpGroupInfo corpGroupInfo = new CorpGroupInfo();
|
||||
corpGroupInfo.setAdmins(new CorpuserUrnArray());
|
||||
corpGroupInfo.setGroups(new CorpGroupUrnArray());
|
||||
corpGroupInfo.setMembers(new CorpuserUrnArray());
|
||||
corpGroupInfo.setEmail("");
|
||||
corpGroupInfo.setDisplayName(groupName);
|
||||
|
||||
// To deal with the possibility of spaces, we url encode the URN group name.
|
||||
final String urlEncodedGroupName = URLEncoder.encode(groupName, StandardCharsets.UTF_8.toString());
|
||||
final CorpGroupUrn groupUrn = new CorpGroupUrn(urlEncodedGroupName);
|
||||
final CorpGroupSnapshot corpGroupSnapshot = new CorpGroupSnapshot();
|
||||
corpGroupSnapshot.setUrn(groupUrn);
|
||||
final CorpGroupAspectArray aspects = new CorpGroupAspectArray();
|
||||
aspects.add(CorpGroupAspect.create(corpGroupInfo));
|
||||
corpGroupSnapshot.setAspects(aspects);
|
||||
groupSnapshots.add(corpGroupSnapshot);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
log.error(String.format("Failed to URL encoded extracted group name %s. Skipping", groupName));
|
||||
}
|
||||
}
|
||||
if (groupSnapshots.isEmpty()) {
|
||||
log.warn(String.format("Failed to extract groups: No OIDC claim with name %s found", groupsClaimName));
|
||||
} else {
|
||||
extractedGroups.addAll(groupSnapshots);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(String.format(
|
||||
"Failed to extract groups: Expected to find a list of strings for attribute with name %s, found %s",
|
||||
groupsClaimName, profile.getAttribute(groupsClaimName).getClass()));
|
||||
}
|
||||
return groupSnapshots;
|
||||
} catch (Exception e) {
|
||||
log.error(String.format(
|
||||
"Failed to extract groups: Expected to find a list of strings for attribute with name %s, found %s",
|
||||
groupsClaimName,
|
||||
profile.getAttribute(groupsClaimName).getClass()));
|
||||
}
|
||||
}
|
||||
log.warn(String.format("Failed to extract groups: No OIDC claim with name %s found", groupsClaimName));
|
||||
return Collections.emptyList();
|
||||
return extractedGroups;
|
||||
}
|
||||
|
||||
private GroupMembership createGroupMembership(final List<CorpGroupSnapshot> extractedGroups) {
|
||||
final GroupMembership groupMembershipAspect = new GroupMembership();
|
||||
groupMembershipAspect.setGroups(new UrnArray(extractedGroups.stream().map(CorpGroupSnapshot::getUrn).collect(
|
||||
Collectors.toList())));
|
||||
groupMembershipAspect.setGroups(
|
||||
new UrnArray(extractedGroups.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList())));
|
||||
return groupMembershipAspect;
|
||||
}
|
||||
|
||||
@ -313,11 +311,12 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
final Entity corpUser = _entityClient.get(corpUserSnapshot.getUrn(), _systemAuthentication);
|
||||
final CorpUserSnapshot existingCorpUserSnapshot = corpUser.getValue().getCorpUserSnapshot();
|
||||
|
||||
log.debug(String.format("Fetched GMS user with urn %s",corpUserSnapshot.getUrn()));
|
||||
log.debug(String.format("Fetched GMS user with urn %s", corpUserSnapshot.getUrn()));
|
||||
|
||||
// If we find more than the key aspect, then the entity "exists".
|
||||
if (existingCorpUserSnapshot.getAspects().size() <= 1) {
|
||||
log.debug(String.format("Extracted user that does not yet exist %s. Provisioning...", corpUserSnapshot.getUrn()));
|
||||
log.debug(
|
||||
String.format("Extracted user that does not yet exist %s. Provisioning...", corpUserSnapshot.getUrn()));
|
||||
// 2. The user does not exist. Provision them.
|
||||
final Entity newEntity = new Entity();
|
||||
newEntity.setValue(Snapshot.create(corpUserSnapshot));
|
||||
@ -334,9 +333,8 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
|
||||
private void tryProvisionGroups(List<CorpGroupSnapshot> corpGroups) {
|
||||
|
||||
log.debug(String.format("Attempting to provision groups with urns %s", corpGroups
|
||||
.stream()
|
||||
.map(CorpGroupSnapshot::getUrn).collect(Collectors.toList())));
|
||||
log.debug(String.format("Attempting to provision groups with urns %s",
|
||||
corpGroups.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList())));
|
||||
|
||||
// 1. Check if this user already exists.
|
||||
try {
|
||||
@ -354,30 +352,30 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
|
||||
// If more than the key aspect exists, then the group already "exists".
|
||||
if (corpGroupSnapshot.getAspects().size() <= 1) {
|
||||
log.debug(String.format("Extracted group that does not yet exist %s. Provisioning...", corpGroupSnapshot.getUrn()));
|
||||
log.debug(String.format("Extracted group that does not yet exist %s. Provisioning...",
|
||||
corpGroupSnapshot.getUrn()));
|
||||
groupsToCreate.add(extractedGroup);
|
||||
}
|
||||
log.debug(String.format("Group %s already exists. Skipping provisioning", corpGroupSnapshot.getUrn()));
|
||||
} else {
|
||||
// Should not occur until we stop returning default Key aspects for unrecognized entities.
|
||||
log.debug(String.format("Extracted group that does not yet exist %s. Provisioning...", extractedGroup.getUrn()));
|
||||
log.debug(
|
||||
String.format("Extracted group that does not yet exist %s. Provisioning...", extractedGroup.getUrn()));
|
||||
groupsToCreate.add(extractedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
List<Urn> groupsToCreateUrns = groupsToCreate
|
||||
.stream()
|
||||
.map(CorpGroupSnapshot::getUrn).collect(Collectors.toList());
|
||||
List<Urn> groupsToCreateUrns =
|
||||
groupsToCreate.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList());
|
||||
|
||||
log.debug(String.format("Provisioning groups with urns %s", groupsToCreateUrns));
|
||||
|
||||
// Now batch create all entities identified to create.
|
||||
_entityClient.batchUpdate(groupsToCreate.stream().map(groupSnapshot ->
|
||||
new Entity().setValue(Snapshot.create(groupSnapshot))
|
||||
).collect(Collectors.toSet()), _systemAuthentication);
|
||||
_entityClient.batchUpdate(groupsToCreate.stream()
|
||||
.map(groupSnapshot -> new Entity().setValue(Snapshot.create(groupSnapshot)))
|
||||
.collect(Collectors.toSet()), _systemAuthentication);
|
||||
|
||||
log.debug(String.format("Successfully provisioned groups with urns %s", groupsToCreateUrns));
|
||||
|
||||
} catch (RemoteInvocationException e) {
|
||||
// Failing provisioning is something worth throwing about.
|
||||
throw new RuntimeException(String.format("Failed to provision groups with urns %s.",
|
||||
@ -410,9 +408,8 @@ public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebConte
|
||||
// If we find more than the key aspect, then the entity "exists".
|
||||
if (corpUser.getValue().getCorpUserSnapshot().getAspects().size() <= 1) {
|
||||
log.debug(String.format("Found user that does not yet exist %s. Invalid login attempt. Throwing...", urn));
|
||||
throw new RuntimeException(
|
||||
String.format("User with urn %s has not yet been provisioned in DataHub. "
|
||||
+ "Please contact your DataHub admin to provision an account.", urn));
|
||||
throw new RuntimeException(String.format("User with urn %s has not yet been provisioned in DataHub. "
|
||||
+ "Please contact your DataHub admin to provision an account.", urn));
|
||||
}
|
||||
// Otherwise, the user exists.
|
||||
} catch (RemoteInvocationException e) {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
# OIDC Authentication
|
||||
|
||||
The DataHub React application supports OIDC authentication built on top of the [Pac4j Play](https://github.com/pac4j/play-pac4j) library.
|
||||
The DataHub React application supports OIDC authentication built on top of the [Pac4j Play](https://github.com/pac4j/play-pac4j) library.
|
||||
This enables operators of DataHub to integrate with 3rd party identity providers like Okta, Google, Keycloak, & more to authenticate their users.
|
||||
|
||||
When configured, OIDC auth will be enabled between clients of the DataHub UI & `datahub-frontend` server. Beyond this point is considered
|
||||
When configured, OIDC auth will be enabled between clients of the DataHub UI & `datahub-frontend` server. Beyond this point is considered
|
||||
to be a secure environment and as such authentication is validated & enforced only at the "front door" inside datahub-frontend.
|
||||
|
||||
## Provider-Specific Guides
|
||||
@ -12,7 +12,7 @@ to be a secure environment and as such authentication is validated & enforced on
|
||||
2. [Configuring OIDC using Okta](configure-oidc-react-okta.md)
|
||||
3. [Configuring OIDC using Azure](configure-oidc-react-azure.md)
|
||||
|
||||
## Configuring OIDC in React
|
||||
## Configuring OIDC in React
|
||||
|
||||
### 1. Register an app with your Identity Provider
|
||||
|
||||
@ -25,9 +25,9 @@ have their own instructions. Provided below are links to examples for Okta, Goog
|
||||
- [Keycloak - Securing Applications and Services Guide](https://www.keycloak.org/docs/latest/securing_apps/)
|
||||
|
||||
During the registration process, you'll need to provide a login redirect URI to the identity provider. This tells the identity provider
|
||||
where to redirect to once they've authenticated the end user.
|
||||
where to redirect to once they've authenticated the end user.
|
||||
|
||||
By default, the URL will be constructed as follows:
|
||||
By default, the URL will be constructed as follows:
|
||||
|
||||
> "http://your-datahub-domain.com/callback/oidc"
|
||||
|
||||
@ -38,11 +38,11 @@ directly: `http://localhost:9002/callback/oidc`
|
||||
The goal of this step should be to obtain the following values, which will need to be configured before deploying DataHub:
|
||||
|
||||
1. **Client ID** - A unique identifier for your application with the identity provider
|
||||
2. **Client Secret** - A shared secret to use for exchange between you and your identity provider
|
||||
2. **Client Secret** - A shared secret to use for exchange between you and your identity provider
|
||||
3. **Discovery URL** - A URL where the OIDC API of your identity provider can be discovered. This should suffixed by
|
||||
`.well-known/openid-configuration`. Sometimes, identity providers will not explicitly include this URL in their setup guides, though
|
||||
this endpoint *will* exist as per the OIDC specification. For more info see http://openid.net/specs/openid-connect-discovery-1_0.html.
|
||||
|
||||
`.well-known/openid-configuration`. Sometimes, identity providers will not explicitly include this URL in their setup guides, though
|
||||
this endpoint *will* exist as per the OIDC specification. For more info see http://openid.net/specs/openid-connect-discovery-1_0.html.
|
||||
|
||||
|
||||
### 2. Configure DataHub Frontend Server
|
||||
|
||||
@ -67,13 +67,13 @@ AUTH_OIDC_BASE_URL=your-datahub-url
|
||||
- `AUTH_OIDC_BASE_URL`: The base URL of your DataHub deployment, e.g. https://yourorgdatahub.com (prod) or http://localhost:9002 (testing)
|
||||
|
||||
Providing these configs will cause DataHub to delegate authentication to your identity
|
||||
provider, requesting the "oidc email profile" scopes and parsing the "preferred_username" claim from
|
||||
provider, requesting the "oidc email profile" scopes and parsing the "preferred_username" claim from
|
||||
the authenticated profile as the DataHub CorpUser identity.
|
||||
|
||||
|
||||
> By default, the login callback endpoint exposed by DataHub will be located at `${AUTH_OIDC_BASE_URL}/callback/oidc`. This must **exactly** match the login redirect URL you've registered with your identity provider in step 1.
|
||||
|
||||
In kubernetes, you can add the above env variables in the values.yaml as follows.
|
||||
In kubernetes, you can add the above env variables in the values.yaml as follows.
|
||||
|
||||
```
|
||||
datahub-frontend:
|
||||
@ -93,9 +93,9 @@ datahub-frontend:
|
||||
|
||||
You can also package OIDC client secrets into a k8s secret by running
|
||||
|
||||
```kubectl create secret generic datahub-oidc-secret --from-literal=secret=<<OIDC SECRET>>```
|
||||
```kubectl create secret generic datahub-oidc-secret --from-literal=secret=<<OIDC SECRET>>```
|
||||
|
||||
Then set the secret env as follows.
|
||||
Then set the secret env as follows.
|
||||
|
||||
```
|
||||
- name: AUTH_OIDC_CLIENT_SECRET
|
||||
@ -108,8 +108,8 @@ Then set the secret env as follows.
|
||||
|
||||
#### Advanced
|
||||
|
||||
You can optionally customize the flow further using advanced configurations. These allow
|
||||
you to specify the OIDC scopes requested, how the DataHub username is parsed from the claims returned by the identity provider, and how users and groups are extracted and provisioned from the OIDC claim set.
|
||||
You can optionally customize the flow further using advanced configurations. These allow
|
||||
you to specify the OIDC scopes requested, how the DataHub username is parsed from the claims returned by the identity provider, and how users and groups are extracted and provisioned from the OIDC claim set.
|
||||
|
||||
```
|
||||
# Optional Configuration Values:
|
||||
@ -120,15 +120,15 @@ AUTH_OIDC_CLIENT_AUTHENTICATION_METHOD=authentication-method
|
||||
```
|
||||
|
||||
- `AUTH_OIDC_USER_NAME_CLAIM`: The attribute that will contain the username used on the DataHub platform. By default, this is "preferred_username" provided
|
||||
as part of the standard `profile` scope.
|
||||
- `AUTH_OIDC_USER_NAME_CLAIM_REGEX`: A regex string used for extracting the username from the userNameClaim attribute. For example, if
|
||||
the userNameClaim field will contain an email address, and we want to omit the domain name suffix of the email, we can specify a custom
|
||||
regex to do so. (e.g. `([^@]+)`)
|
||||
as part of the standard `profile` scope.
|
||||
- `AUTH_OIDC_USER_NAME_CLAIM_REGEX`: A regex string used for extracting the username from the userNameClaim attribute. For example, if
|
||||
the userNameClaim field will contain an email address, and we want to omit the domain name suffix of the email, we can specify a custom
|
||||
regex to do so. (e.g. `([^@]+)`)
|
||||
- `AUTH_OIDC_SCOPE`: a string representing the scopes to be requested from the identity provider, granted by the end user. For more info,
|
||||
see [OpenID Connect Scopes](https://auth0.com/docs/scopes/openid-connect-scopes).
|
||||
- `AUTH_OIDC_CLIENT_AUTHENTICATION_METHOD`: a string representing the token authentication method to use with the identity provider. Default value
|
||||
is `client_secret_basic`, which uses HTTP Basic authentication. Another option is `client_secret_post`, which includes the client_id and secret_id
|
||||
as form parameters in the HTTP POST request. For more info, see [OAuth 2.0 Client Authentication](https://darutk.medium.com/oauth-2-0-client-authentication-4b5f929305d4)
|
||||
is `client_secret_basic`, which uses HTTP Basic authentication. Another option is `client_secret_post`, which includes the client_id and secret_id
|
||||
as form parameters in the HTTP POST request. For more info, see [OAuth 2.0 Client Authentication](https://darutk.medium.com/oauth-2-0-client-authentication-4b5f929305d4)
|
||||
|
||||
|
||||
##### User & Group Provisioning (JIT Provisioning)
|
||||
@ -137,7 +137,7 @@ By default, DataHub will optimistically attempt to provision users and groups th
|
||||
For users, we extract information like first name, last name, display name, & email to construct a basic user profile. If a groups claim is present,
|
||||
we simply extract their names.
|
||||
|
||||
The default provisioning behavior can be customized using the following configs.
|
||||
The default provisioning behavior can be customized using the following configs.
|
||||
|
||||
```
|
||||
# User and groups provisioning
|
||||
@ -150,7 +150,7 @@ AUTH_OIDC_GROUPS_CLAIM=<your-groups-claim-name>
|
||||
- `AUTH_OIDC_JIT_PROVISIONING_ENABLED`: Whether DataHub users & groups should be provisioned on login if they do not exist. Defaults to true.
|
||||
- `AUTH_OIDC_PRE_PROVISIONING_REQUIRED`: Whether the user should already exist in DataHub when they login, failing login if they are not. This is appropriate for situations in which users and groups are batch ingested and tightly controlled inside your environment. Defaults to false.
|
||||
- `AUTH_OIDC_EXTRACT_GROUPS_ENABLED`: Only applies if `AUTH_OIDC_JIT_PROVISIONING_ENABLED` is set to true. This determines whether we should attempt to extract a list of group names from a particular claim in the OIDC attributes. Note that if this is enabled, each login will re-sync group membership with the groups in your Identity Provider, clearing the group membership that has been assigned through the DataHub UI. Enable with care! Defaults to false.
|
||||
- `AUTH_OIDC_GROUPS_CLAIM`: Only applies if `AUTH_OIDC_EXTRACT_GROUPS_ENABLED` is set to true. This determines which OIDC claim will contain a list of string group names. Defaults to 'groups'
|
||||
- `AUTH_OIDC_GROUPS_CLAIM`: Only applies if `AUTH_OIDC_EXTRACT_GROUPS_ENABLED` is set to true. This determines which OIDC claims will contain a list of string group names. Accepts multiple claim names with comma-separated values. I.e: `groups, teams, departments`. Defaults to 'groups'.
|
||||
|
||||
|
||||
Once configuration has been updated, `datahub-frontend-react` will need to be restarted to pick up the new environment variables:
|
||||
@ -160,7 +160,7 @@ docker-compose -p datahub -f docker-compose.yml -f docker-compose.override.yml
|
||||
```
|
||||
|
||||
>Note that by default, enabling OIDC will *not* disable the dummy JAAS authentication path, which can be reached at the `/login`
|
||||
route of the React app. To disable this authentication path, additionally specify the following config:
|
||||
route of the React app. To disable this authentication path, additionally specify the following config:
|
||||
> `AUTH_JAAS_ENABLED=false`
|
||||
|
||||
### Summary
|
||||
@ -171,16 +171,16 @@ authentication to the specified identity provider.
|
||||
Once a user is authenticated by the identity provider, DataHub will extract a username from the provided claims
|
||||
and grant DataHub access to the user by setting a pair of session cookies.
|
||||
|
||||
A brief summary of the steps that occur when the user navigates to the React app are as follows:
|
||||
A brief summary of the steps that occur when the user navigates to the React app are as follows:
|
||||
|
||||
1. A `GET` to the `/authenticate` endpoint in `datahub-frontend` server is initiated
|
||||
2. The `/authenticate` attempts to authenticate the request via session cookies
|
||||
3. If auth fails, the server issues a redirect to the Identity Provider's login experience
|
||||
4. The user logs in with the Identity Provider
|
||||
5. The Identity Provider authenticates the user and redirects back to DataHub's registered login redirect URL, providing an authorization code which
|
||||
5. The Identity Provider authenticates the user and redirects back to DataHub's registered login redirect URL, providing an authorization code which
|
||||
can be used to retrieve information on behalf of the authenticated user
|
||||
6. DataHub fetches the authenticated user's profile and extracts a username to identify the user on DataHub (eg. urn:li:corpuser:username)
|
||||
7. DataHub sets session cookies for the newly authenticated user
|
||||
7. DataHub sets session cookies for the newly authenticated user
|
||||
8. DataHub redirects the user to the homepage ("/")
|
||||
|
||||
### Root user
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user