| 
									
										
										
										
											2021-08-20 10:58:07 -07:00
										 |  |  | package auth.sso.oidc;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  | import static auth.AuthUtils.*;
 | 
					
						
							|  |  |  | import static com.linkedin.metadata.Constants.CORP_USER_ENTITY_NAME;
 | 
					
						
							|  |  |  | import static com.linkedin.metadata.Constants.GROUP_MEMBERSHIP_ASPECT_NAME;
 | 
					
						
							|  |  |  | import static org.pac4j.play.store.PlayCookieSessionStore.*;
 | 
					
						
							|  |  |  | import static play.mvc.Results.internalServerError;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-14 13:36:47 -05:00
										 |  |  | import auth.CookieConfigs;
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  | import auth.sso.SsoManager;
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | import client.AuthServiceClient;
 | 
					
						
							| 
									
										
										
										
											2023-12-26 09:04:05 -05:00
										 |  |  | import com.fasterxml.jackson.core.type.TypeReference;
 | 
					
						
							|  |  |  | import com.fasterxml.jackson.databind.ObjectMapper;
 | 
					
						
							| 
									
										
										
										
											2021-10-20 17:09:02 -07:00
										 |  |  | import com.linkedin.common.AuditStamp;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import com.linkedin.common.CorpGroupUrnArray;
 | 
					
						
							|  |  |  | import com.linkedin.common.CorpuserUrnArray;
 | 
					
						
							|  |  |  | import com.linkedin.common.UrnArray;
 | 
					
						
							|  |  |  | import com.linkedin.common.url.Url;
 | 
					
						
							|  |  |  | import com.linkedin.common.urn.CorpGroupUrn;
 | 
					
						
							|  |  |  | import com.linkedin.common.urn.CorpuserUrn;
 | 
					
						
							|  |  |  | import com.linkedin.common.urn.Urn;
 | 
					
						
							|  |  |  | import com.linkedin.data.template.SetMode;
 | 
					
						
							|  |  |  | import com.linkedin.entity.Entity;
 | 
					
						
							| 
									
										
										
										
											2023-09-21 22:00:14 -05:00
										 |  |  | import com.linkedin.entity.client.SystemEntityClient;
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  | import com.linkedin.events.metadata.ChangeType;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import com.linkedin.identity.CorpGroupInfo;
 | 
					
						
							|  |  |  | import com.linkedin.identity.CorpUserEditableInfo;
 | 
					
						
							|  |  |  | import com.linkedin.identity.CorpUserInfo;
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  | import com.linkedin.identity.CorpUserStatus;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import com.linkedin.identity.GroupMembership;
 | 
					
						
							| 
									
										
										
										
											2021-09-28 16:30:49 -07:00
										 |  |  | import com.linkedin.metadata.Constants;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import com.linkedin.metadata.aspect.CorpGroupAspect;
 | 
					
						
							|  |  |  | import com.linkedin.metadata.aspect.CorpGroupAspectArray;
 | 
					
						
							|  |  |  | import com.linkedin.metadata.aspect.CorpUserAspect;
 | 
					
						
							|  |  |  | import com.linkedin.metadata.aspect.CorpUserAspectArray;
 | 
					
						
							|  |  |  | import com.linkedin.metadata.snapshot.CorpGroupSnapshot;
 | 
					
						
							|  |  |  | import com.linkedin.metadata.snapshot.CorpUserSnapshot;
 | 
					
						
							|  |  |  | import com.linkedin.metadata.snapshot.Snapshot;
 | 
					
						
							| 
									
										
										
										
											2022-03-29 18:32:04 -07:00
										 |  |  | import com.linkedin.metadata.utils.GenericRecordUtils;
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  | import com.linkedin.mxe.MetadataChangeProposal;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import com.linkedin.r2.RemoteInvocationException;
 | 
					
						
							|  |  |  | import java.io.UnsupportedEncodingException;
 | 
					
						
							|  |  |  | import java.net.MalformedURLException;
 | 
					
						
							|  |  |  | import java.net.URI;
 | 
					
						
							|  |  |  | import java.net.URLEncoder;
 | 
					
						
							|  |  |  | import java.nio.charset.StandardCharsets;
 | 
					
						
							|  |  |  | import java.util.ArrayList;
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  | import java.util.Arrays;
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  | import java.util.Base64;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import java.util.Collection;
 | 
					
						
							|  |  |  | import java.util.Collections;
 | 
					
						
							|  |  |  | import java.util.List;
 | 
					
						
							|  |  |  | import java.util.Map;
 | 
					
						
							|  |  |  | import java.util.Optional;
 | 
					
						
							|  |  |  | import java.util.Set;
 | 
					
						
							|  |  |  | import java.util.regex.Matcher;
 | 
					
						
							|  |  |  | import java.util.regex.Pattern;
 | 
					
						
							|  |  |  | import java.util.stream.Collectors;
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | import io.datahubproject.metadata.context.OperationContext;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import lombok.extern.slf4j.Slf4j;
 | 
					
						
							|  |  |  | import org.pac4j.core.config.Config;
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  | import org.pac4j.core.context.Cookie;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import org.pac4j.core.engine.DefaultCallbackLogic;
 | 
					
						
							|  |  |  | import org.pac4j.core.http.adapter.HttpActionAdapter;
 | 
					
						
							|  |  |  | import org.pac4j.core.profile.CommonProfile;
 | 
					
						
							|  |  |  | import org.pac4j.core.profile.ProfileManager;
 | 
					
						
							| 
									
										
										
										
											2022-12-08 20:27:51 -06:00
										 |  |  | import org.pac4j.core.profile.UserProfile;
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  | import org.pac4j.core.util.Pac4jConstants;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | import org.pac4j.play.PlayWebContext;
 | 
					
						
							|  |  |  | import play.mvc.Result;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  | import javax.annotation.Nonnull;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | /**
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |  * This class contains the logic that is executed when an OpenID Connect Identity Provider redirects
 | 
					
						
							|  |  |  |  * back to D DataHub after an authentication attempt.
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |  *
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |  * <p>On receiving a user profile from the IdP (using /userInfo endpoint), we attempt to extract
 | 
					
						
							|  |  |  |  * basic information about the user including their name, email, groups, & more. If just-in-time
 | 
					
						
							|  |  |  |  * provisioning is enabled, we also attempt to create a DataHub User ({@link CorpUserSnapshot}) for
 | 
					
						
							|  |  |  |  * the user, along with any Groups ({@link CorpGroupSnapshot}) that can be extracted, only doing so
 | 
					
						
							|  |  |  |  * if the user does not already exist.
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |  */
 | 
					
						
							|  |  |  | @Slf4j
 | 
					
						
							|  |  |  | public class OidcCallbackLogic extends DefaultCallbackLogic<Result, PlayWebContext> {
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |   private final SsoManager ssoManager;
 | 
					
						
							|  |  |  |   private final SystemEntityClient systemEntityClient;
 | 
					
						
							|  |  |  |   private final OperationContext systemOperationContext;
 | 
					
						
							|  |  |  |   private final AuthServiceClient authClient;
 | 
					
						
							|  |  |  |   private final CookieConfigs cookieConfigs;
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   public OidcCallbackLogic(
 | 
					
						
							|  |  |  |       final SsoManager ssoManager,
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       final OperationContext systemOperationContext,
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       final SystemEntityClient entityClient,
 | 
					
						
							|  |  |  |       final AuthServiceClient authClient,
 | 
					
						
							|  |  |  |       final CookieConfigs cookieConfigs) {
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |     this.ssoManager = ssoManager;
 | 
					
						
							|  |  |  |     this.systemOperationContext = systemOperationContext;
 | 
					
						
							|  |  |  |     systemEntityClient = entityClient;
 | 
					
						
							|  |  |  |     this.authClient = authClient;
 | 
					
						
							|  |  |  |     this.cookieConfigs = cookieConfigs;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @Override
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   public Result perform(
 | 
					
						
							|  |  |  |       PlayWebContext context,
 | 
					
						
							|  |  |  |       Config config,
 | 
					
						
							|  |  |  |       HttpActionAdapter<Result, PlayWebContext> httpActionAdapter,
 | 
					
						
							|  |  |  |       String defaultUrl,
 | 
					
						
							|  |  |  |       Boolean saveInSession,
 | 
					
						
							|  |  |  |       Boolean multiProfile,
 | 
					
						
							|  |  |  |       Boolean renewSession,
 | 
					
						
							|  |  |  |       String defaultClient) {
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     setContextRedirectUrl(context);
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |     final Result result =
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |         super.perform(
 | 
					
						
							|  |  |  |             context,
 | 
					
						
							|  |  |  |             config,
 | 
					
						
							|  |  |  |             httpActionAdapter,
 | 
					
						
							|  |  |  |             defaultUrl,
 | 
					
						
							|  |  |  |             saveInSession,
 | 
					
						
							|  |  |  |             multiProfile,
 | 
					
						
							|  |  |  |             renewSession,
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |             defaultClient);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Handle OIDC authentication errors.
 | 
					
						
							|  |  |  |     if (OidcResponseErrorHandler.isError(context)) {
 | 
					
						
							|  |  |  |       return OidcResponseErrorHandler.handleError(context);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // By this point, we know that OIDC is the enabled provider.
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |     final OidcConfigs oidcConfigs = (OidcConfigs) ssoManager.getSsoProvider().configs();
 | 
					
						
							|  |  |  |     return handleOidcCallback(systemOperationContext, oidcConfigs, result, getProfileManager(context));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  |   @SuppressWarnings("unchecked")
 | 
					
						
							|  |  |  |   private void setContextRedirectUrl(PlayWebContext context) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     Optional<Cookie> redirectUrl =
 | 
					
						
							|  |  |  |         context.getRequestCookies().stream()
 | 
					
						
							|  |  |  |             .filter(cookie -> REDIRECT_URL_COOKIE_NAME.equals(cookie.getName()))
 | 
					
						
							|  |  |  |             .findFirst();
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  |     redirectUrl.ifPresent(
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |         cookie ->
 | 
					
						
							|  |  |  |             context
 | 
					
						
							|  |  |  |                 .getSessionStore()
 | 
					
						
							|  |  |  |                 .set(
 | 
					
						
							|  |  |  |                     context,
 | 
					
						
							|  |  |  |                     Pac4jConstants.REQUESTED_URL,
 | 
					
						
							|  |  |  |                     JAVA_SER_HELPER.deserializeFromBytes(
 | 
					
						
							|  |  |  |                         uncompressBytes(Base64.getDecoder().decode(cookie.getValue())))));
 | 
					
						
							| 
									
										
										
										
											2023-10-17 15:50:32 -05:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   private Result handleOidcCallback(
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       final OperationContext opContext,
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       final OidcConfigs oidcConfigs,
 | 
					
						
							|  |  |  |       final Result result,
 | 
					
						
							| 
									
										
										
										
											2022-12-08 20:27:51 -06:00
										 |  |  |       final ProfileManager<UserProfile> profileManager) {
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     log.debug("Beginning OIDC Callback Handling...");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (profileManager.isAuthenticated()) {
 | 
					
						
							|  |  |  |       // If authenticated, the user should have a profile.
 | 
					
						
							| 
									
										
										
										
											2022-12-08 20:27:51 -06:00
										 |  |  |       final CommonProfile profile = (CommonProfile) profileManager.get(true).get();
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       log.debug(
 | 
					
						
							|  |  |  |           String.format(
 | 
					
						
							|  |  |  |               "Found authenticated user with profile %s", profile.getAttributes().toString()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Extract the User name required to log into DataHub.
 | 
					
						
							|  |  |  |       final String userName = extractUserNameOrThrow(oidcConfigs, profile);
 | 
					
						
							|  |  |  |       final CorpuserUrn corpUserUrn = new CorpuserUrn(userName);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |         // If just-in-time User Provisioning is enabled, try to create the DataHub user if it does
 | 
					
						
							|  |  |  |         // not exist.
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |         if (oidcConfigs.isJitProvisioningEnabled()) {
 | 
					
						
							| 
									
										
										
										
											2021-10-05 19:30:51 -07:00
										 |  |  |           log.debug("Just-in-time provisioning is enabled. Beginning provisioning process...");
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |           CorpUserSnapshot extractedUser = extractUser(corpUserUrn, profile);
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |           tryProvisionUser(opContext, extractedUser);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |           if (oidcConfigs.isExtractGroupsEnabled()) {
 | 
					
						
							|  |  |  |             // Extract groups & provision them.
 | 
					
						
							|  |  |  |             List<CorpGroupSnapshot> extractedGroups = extractGroups(profile);
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |             tryProvisionGroups(opContext, extractedGroups);
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |             // Add users to groups on DataHub. Note that this clears existing group membership for a
 | 
					
						
							|  |  |  |             // user if it already exists.
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |             updateGroupMembership(opContext, corpUserUrn, createGroupMembership(extractedGroups));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |           }
 | 
					
						
							|  |  |  |         } else if (oidcConfigs.isPreProvisioningRequired()) {
 | 
					
						
							|  |  |  |           // We should only allow logins for user accounts that have been pre-provisioned
 | 
					
						
							|  |  |  |           log.debug("Pre Provisioning is required. Beginning validation of extracted user...");
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |           verifyPreProvisionedUser(opContext, corpUserUrn);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |         }
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  |         // 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.
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |         setUserStatus(opContext,
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |             corpUserUrn,
 | 
					
						
							|  |  |  |             new CorpUserStatus()
 | 
					
						
							|  |  |  |                 .setStatus(Constants.CORP_USER_STATUS_ACTIVE)
 | 
					
						
							|  |  |  |                 .setLastModified(
 | 
					
						
							|  |  |  |                     new AuditStamp()
 | 
					
						
							|  |  |  |                         .setActor(Urn.createFromString(Constants.SYSTEM_ACTOR))
 | 
					
						
							|  |  |  |                         .setTime(System.currentTimeMillis())));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |       } catch (Exception e) {
 | 
					
						
							|  |  |  |         log.error("Failed to perform post authentication steps. Redirecting to error page.", e);
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |         return internalServerError(
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |             String.format(
 | 
					
						
							|  |  |  |                 "Failed to perform post authentication steps. Error message: %s", e.getMessage()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |       }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-03 11:54:51 -07:00
										 |  |  |       log.info("OIDC callback authentication successful for user: {}", userName);
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  |       // Successfully logged in - Generate GMS login token
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       final String accessToken = authClient.generateSessionTokenForUser(corpUserUrn.getId());
 | 
					
						
							| 
									
										
										
										
											2022-12-22 16:12:51 -06:00
										 |  |  |       return result
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |           .withSession(createSessionMap(corpUserUrn.toString(), accessToken))
 | 
					
						
							|  |  |  |           .withCookies(
 | 
					
						
							|  |  |  |               createActorCookie(
 | 
					
						
							|  |  |  |                   corpUserUrn.toString(),
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |                   cookieConfigs.getTtlInHours(),
 | 
					
						
							|  |  |  |                   cookieConfigs.getAuthCookieSameSite(),
 | 
					
						
							|  |  |  |                   cookieConfigs.getAuthCookieSecure()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     }
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |     return internalServerError(
 | 
					
						
							|  |  |  |         "Failed to authenticate current user. Cannot find valid identity provider profile in session.");
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   private String extractUserNameOrThrow(
 | 
					
						
							|  |  |  |       final OidcConfigs oidcConfigs, final CommonProfile profile) {
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     // Ensure that the attribute exists (was returned by IdP)
 | 
					
						
							|  |  |  |     if (!profile.containsAttribute(oidcConfigs.getUserNameClaim())) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       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()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final String userNameClaim = (String) profile.getAttribute(oidcConfigs.getUserNameClaim());
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     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())));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   /** Attempts to map to an OIDC {@link CommonProfile} (userInfo) to a {@link CorpUserSnapshot}. */
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   private CorpUserSnapshot extractUser(CorpuserUrn urn, CommonProfile profile) {
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     log.debug(
 | 
					
						
							|  |  |  |         String.format(
 | 
					
						
							|  |  |  |             "Attempting to extract user from OIDC profile %s", profile.getAttributes().toString()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Extracts these based on the default set of OIDC claims, described here:
 | 
					
						
							|  |  |  |     // https://developer.okta.com/blog/2017/07/25/oidc-primer-part-1
 | 
					
						
							|  |  |  |     String firstName = profile.getFirstName();
 | 
					
						
							|  |  |  |     String lastName = profile.getFamilyName();
 | 
					
						
							|  |  |  |     String email = profile.getEmail();
 | 
					
						
							|  |  |  |     URI picture = profile.getPictureUrl();
 | 
					
						
							|  |  |  |     String displayName = profile.getDisplayName();
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     String fullName =
 | 
					
						
							|  |  |  |         (String)
 | 
					
						
							|  |  |  |             profile.getAttribute("name"); // Name claim is sometimes provided, including by Google.
 | 
					
						
							| 
									
										
										
										
											2021-10-13 18:56:20 -07:00
										 |  |  |     if (fullName == null && firstName != null && lastName != null) {
 | 
					
						
							|  |  |  |       fullName = String.format("%s %s", firstName, lastName);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // TODO: Support custom claims mapping. (e.g. department, title, etc)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final CorpUserInfo userInfo = new CorpUserInfo();
 | 
					
						
							|  |  |  |     userInfo.setActive(true);
 | 
					
						
							|  |  |  |     userInfo.setFirstName(firstName, SetMode.IGNORE_NULL);
 | 
					
						
							|  |  |  |     userInfo.setLastName(lastName, SetMode.IGNORE_NULL);
 | 
					
						
							| 
									
										
										
										
											2021-10-13 18:56:20 -07:00
										 |  |  |     userInfo.setFullName(fullName, SetMode.IGNORE_NULL);
 | 
					
						
							| 
									
										
										
										
											2021-10-05 19:30:51 -07:00
										 |  |  |     userInfo.setEmail(email, SetMode.IGNORE_NULL);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     // If there is a display name, use it. Otherwise fall back to full name.
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     userInfo.setDisplayName(
 | 
					
						
							|  |  |  |         displayName == null ? userInfo.getFullName() : displayName, SetMode.IGNORE_NULL);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     final CorpUserEditableInfo editableInfo = new CorpUserEditableInfo();
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       if (picture != null) {
 | 
					
						
							|  |  |  |         editableInfo.setPictureLink(new Url(picture.toURL().toString()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     } catch (MalformedURLException e) {
 | 
					
						
							|  |  |  |       log.error("Failed to extract User Profile URL skipping.", e);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final CorpUserSnapshot corpUserSnapshot = new CorpUserSnapshot();
 | 
					
						
							|  |  |  |     corpUserSnapshot.setUrn(urn);
 | 
					
						
							|  |  |  |     final CorpUserAspectArray aspects = new CorpUserAspectArray();
 | 
					
						
							|  |  |  |     aspects.add(CorpUserAspect.create(userInfo));
 | 
					
						
							|  |  |  |     aspects.add(CorpUserAspect.create(editableInfo));
 | 
					
						
							|  |  |  |     corpUserSnapshot.setAspects(aspects);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return corpUserSnapshot;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-26 09:04:05 -05:00
										 |  |  |   public static Collection<String> getGroupNames(CommonProfile profile, Object groupAttribute, String groupsClaimName) {
 | 
					
						
							|  |  |  |       Collection<String> groupNames = Collections.emptyList();
 | 
					
						
							|  |  |  |       try {
 | 
					
						
							|  |  |  |         if (groupAttribute instanceof Collection) {
 | 
					
						
							|  |  |  |           // List of group names
 | 
					
						
							|  |  |  |           groupNames = (Collection<String>) profile.getAttribute(groupsClaimName, Collection.class);
 | 
					
						
							|  |  |  |         } else if (groupAttribute instanceof String) {
 | 
					
						
							|  |  |  |           String groupString = (String) groupAttribute;
 | 
					
						
							|  |  |  |           ObjectMapper objectMapper = new ObjectMapper();
 | 
					
						
							|  |  |  |           try {
 | 
					
						
							|  |  |  |             // Json list of group names
 | 
					
						
							|  |  |  |             groupNames = objectMapper.readValue(groupString, new TypeReference<List<String>>(){});
 | 
					
						
							|  |  |  |           } catch (Exception e) {
 | 
					
						
							|  |  |  |             groupNames = Arrays.asList(groupString.split(","));
 | 
					
						
							|  |  |  |           }
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |       } catch (Exception e) {
 | 
					
						
							|  |  |  |         log.error(String.format(
 | 
					
						
							|  |  |  |                 "Failed to parse group names: Expected to find a list of strings for attribute with name %s, found %s",
 | 
					
						
							|  |  |  |                 groupsClaimName, profile.getAttribute(groupsClaimName).getClass()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |       return groupNames;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   private List<CorpGroupSnapshot> extractGroups(CommonProfile profile) {
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     log.debug(
 | 
					
						
							|  |  |  |         String.format(
 | 
					
						
							|  |  |  |             "Attempting to extract groups from OIDC profile %s",
 | 
					
						
							|  |  |  |             profile.getAttributes().toString()));
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |     final OidcConfigs configs = (OidcConfigs) ssoManager.getSsoProvider().configs();
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     // First, attempt to extract a list of groups from the profile, using the group name attribute
 | 
					
						
							|  |  |  |     // config.
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |     final List<CorpGroupSnapshot> extractedGroups = new ArrayList<>();
 | 
					
						
							|  |  |  |     final List<String> groupsClaimNames =
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |         new ArrayList<String>(Arrays.asList(configs.getGroupsClaimName().split(",")))
 | 
					
						
							|  |  |  |             .stream().map(String::trim).collect(Collectors.toList());
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for (final String groupsClaimName : groupsClaimNames) {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (profile.containsAttribute(groupsClaimName)) {
 | 
					
						
							|  |  |  |         try {
 | 
					
						
							|  |  |  |           final List<CorpGroupSnapshot> groupSnapshots = new ArrayList<>();
 | 
					
						
							| 
									
										
										
										
											2023-12-26 09:04:05 -05:00
										 |  |  |           Collection<String> groupNames = getGroupNames(profile, profile.getAttribute(groupsClaimName), groupsClaimName);
 | 
					
						
							| 
									
										
										
										
											2022-03-15 17:41:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |           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.
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |               final String urlEncodedGroupName =
 | 
					
						
							|  |  |  |                   URLEncoder.encode(groupName, StandardCharsets.UTF_8.toString());
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |               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) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |               log.error(
 | 
					
						
							|  |  |  |                   String.format(
 | 
					
						
							|  |  |  |                       "Failed to URL encoded extracted group name %s. Skipping", groupName));
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |             }
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |           }
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |           if (groupSnapshots.isEmpty()) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |             log.warn(
 | 
					
						
							|  |  |  |                 String.format(
 | 
					
						
							|  |  |  |                     "Failed to extract groups: No OIDC claim with name %s found", groupsClaimName));
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |           } else {
 | 
					
						
							|  |  |  |             extractedGroups.addAll(groupSnapshots);
 | 
					
						
							|  |  |  |           }
 | 
					
						
							|  |  |  |         } catch (Exception e) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |           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()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |         }
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |     return extractedGroups;
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private GroupMembership createGroupMembership(final List<CorpGroupSnapshot> extractedGroups) {
 | 
					
						
							|  |  |  |     final GroupMembership groupMembershipAspect = new GroupMembership();
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |     groupMembershipAspect.setGroups(
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |         new UrnArray(
 | 
					
						
							|  |  |  |             extractedGroups.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList())));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     return groupMembershipAspect;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |   private void tryProvisionUser(@Nonnull OperationContext opContext, CorpUserSnapshot corpUserSnapshot) {
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     log.debug(String.format("Attempting to provision user with urn %s", corpUserSnapshot.getUrn()));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 1. Check if this user already exists.
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       final Entity corpUser = systemEntityClient.get(opContext, corpUserSnapshot.getUrn());
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  |       final CorpUserSnapshot existingCorpUserSnapshot = corpUser.getValue().getCorpUserSnapshot();
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |       log.debug(String.format("Fetched GMS user with urn %s", corpUserSnapshot.getUrn()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // If we find more than the key aspect, then the entity "exists".
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  |       if (existingCorpUserSnapshot.getAspects().size() <= 1) {
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |         log.debug(
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |             String.format(
 | 
					
						
							|  |  |  |                 "Extracted user that does not yet exist %s. Provisioning...",
 | 
					
						
							|  |  |  |                 corpUserSnapshot.getUrn()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |         // 2. The user does not exist. Provision them.
 | 
					
						
							|  |  |  |         final Entity newEntity = new Entity();
 | 
					
						
							|  |  |  |         newEntity.setValue(Snapshot.create(corpUserSnapshot));
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |         systemEntityClient.update(opContext, newEntity);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |         log.debug(String.format("Successfully provisioned user %s", corpUserSnapshot.getUrn()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       log.debug(
 | 
					
						
							|  |  |  |           String.format(
 | 
					
						
							|  |  |  |               "User %s already exists. Skipping provisioning", corpUserSnapshot.getUrn()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |       // Otherwise, the user exists. Skip provisioning.
 | 
					
						
							|  |  |  |     } catch (RemoteInvocationException e) {
 | 
					
						
							|  |  |  |       // Failing provisioning is something worth throwing about.
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       throw new RuntimeException(
 | 
					
						
							|  |  |  |           String.format("Failed to provision user with urn %s.", corpUserSnapshot.getUrn()), e);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |   private void tryProvisionGroups(@Nonnull OperationContext opContext, List<CorpGroupSnapshot> corpGroups) {
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     log.debug(
 | 
					
						
							|  |  |  |         String.format(
 | 
					
						
							|  |  |  |             "Attempting to provision groups with urns %s",
 | 
					
						
							|  |  |  |             corpGroups.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList())));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // 1. Check if this user already exists.
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       final Set<Urn> urnsToFetch =
 | 
					
						
							|  |  |  |           corpGroups.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toSet());
 | 
					
						
							|  |  |  |       final Map<Urn, Entity> existingGroups =
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |           systemEntityClient.batchGet(opContext, urnsToFetch);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       log.debug(String.format("Fetched GMS groups with urns %s", existingGroups.keySet()));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final List<CorpGroupSnapshot> groupsToCreate = new ArrayList<>();
 | 
					
						
							|  |  |  |       for (CorpGroupSnapshot extractedGroup : corpGroups) {
 | 
					
						
							|  |  |  |         if (existingGroups.containsKey(extractedGroup.getUrn())) {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           final Entity groupEntity = existingGroups.get(extractedGroup.getUrn());
 | 
					
						
							|  |  |  |           final CorpGroupSnapshot corpGroupSnapshot = groupEntity.getValue().getCorpGroupSnapshot();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // If more than the key aspect exists, then the group already "exists".
 | 
					
						
							|  |  |  |           if (corpGroupSnapshot.getAspects().size() <= 1) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |             log.debug(
 | 
					
						
							|  |  |  |                 String.format(
 | 
					
						
							|  |  |  |                     "Extracted group that does not yet exist %s. Provisioning...",
 | 
					
						
							|  |  |  |                     corpGroupSnapshot.getUrn()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |             groupsToCreate.add(extractedGroup);
 | 
					
						
							|  |  |  |           }
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |           log.debug(
 | 
					
						
							|  |  |  |               String.format(
 | 
					
						
							|  |  |  |                   "Group %s already exists. Skipping provisioning", corpGroupSnapshot.getUrn()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |         } else {
 | 
					
						
							|  |  |  |           // Should not occur until we stop returning default Key aspects for unrecognized entities.
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |           log.debug(
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |               String.format(
 | 
					
						
							|  |  |  |                   "Extracted group that does not yet exist %s. Provisioning...",
 | 
					
						
							|  |  |  |                   extractedGroup.getUrn()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |           groupsToCreate.add(extractedGroup);
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 20:33:53 +00:00
										 |  |  |       List<Urn> groupsToCreateUrns =
 | 
					
						
							|  |  |  |           groupsToCreate.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList());
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       log.debug(String.format("Provisioning groups with urns %s", groupsToCreateUrns));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Now batch create all entities identified to create.
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       systemEntityClient.batchUpdate(opContext,
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |           groupsToCreate.stream()
 | 
					
						
							|  |  |  |               .map(groupSnapshot -> new Entity().setValue(Snapshot.create(groupSnapshot)))
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |               .collect(Collectors.toSet()));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       log.debug(String.format("Successfully provisioned groups with urns %s", groupsToCreateUrns));
 | 
					
						
							|  |  |  |     } catch (RemoteInvocationException e) {
 | 
					
						
							|  |  |  |       // Failing provisioning is something worth throwing about.
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       throw new RuntimeException(
 | 
					
						
							|  |  |  |           String.format(
 | 
					
						
							|  |  |  |               "Failed to provision groups with urns %s.",
 | 
					
						
							|  |  |  |               corpGroups.stream().map(CorpGroupSnapshot::getUrn).collect(Collectors.toList())),
 | 
					
						
							|  |  |  |           e);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |   private void updateGroupMembership(@Nonnull OperationContext opContext, Urn urn, GroupMembership groupMembership) {
 | 
					
						
							| 
									
										
										
										
											2022-03-11 08:49:31 -08:00
										 |  |  |     log.debug(String.format("Updating group membership for user %s", urn));
 | 
					
						
							|  |  |  |     final MetadataChangeProposal proposal = new MetadataChangeProposal();
 | 
					
						
							|  |  |  |     proposal.setEntityUrn(urn);
 | 
					
						
							|  |  |  |     proposal.setEntityType(CORP_USER_ENTITY_NAME);
 | 
					
						
							|  |  |  |     proposal.setAspectName(GROUP_MEMBERSHIP_ASPECT_NAME);
 | 
					
						
							| 
									
										
										
										
											2022-03-29 18:32:04 -07:00
										 |  |  |     proposal.setAspect(GenericRecordUtils.serializeAspect(groupMembership));
 | 
					
						
							| 
									
										
										
										
											2022-03-11 08:49:31 -08:00
										 |  |  |     proposal.setChangeType(ChangeType.UPSERT);
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       systemEntityClient.ingestProposal(opContext, proposal);
 | 
					
						
							| 
									
										
										
										
											2022-03-11 08:49:31 -08:00
										 |  |  |     } catch (RemoteInvocationException e) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |       throw new RuntimeException(
 | 
					
						
							|  |  |  |           String.format("Failed to update group membership for user with urn %s", urn), e);
 | 
					
						
							| 
									
										
										
										
											2022-03-11 08:49:31 -08:00
										 |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |   private void verifyPreProvisionedUser(@Nonnull OperationContext opContext, CorpuserUrn urn) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |     // Validate that the user exists in the system (there is more than just a key aspect for them,
 | 
					
						
							|  |  |  |     // as of today).
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |     try {
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |       final Entity corpUser = systemEntityClient.get(opContext, urn);
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       log.debug(String.format("Fetched GMS user with urn %s", urn));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // If we find more than the key aspect, then the entity "exists".
 | 
					
						
							|  |  |  |       if (corpUser.getValue().getCorpUserSnapshot().getAspects().size() <= 1) {
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |         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));
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |       }
 | 
					
						
							|  |  |  |       // Otherwise, the user exists.
 | 
					
						
							|  |  |  |     } catch (RemoteInvocationException e) {
 | 
					
						
							|  |  |  |       // Failing validation is something worth throwing about.
 | 
					
						
							|  |  |  |       throw new RuntimeException(String.format("Failed to validate user with urn %s.", urn), e);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |   private void setUserStatus(@Nonnull OperationContext opContext, final Urn urn, final CorpUserStatus newStatus) throws Exception {
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  |     // Update status aspect to be active.
 | 
					
						
							|  |  |  |     final MetadataChangeProposal proposal = new MetadataChangeProposal();
 | 
					
						
							|  |  |  |     proposal.setEntityUrn(urn);
 | 
					
						
							|  |  |  |     proposal.setEntityType(Constants.CORP_USER_ENTITY_NAME);
 | 
					
						
							|  |  |  |     proposal.setAspectName(Constants.CORP_USER_STATUS_ASPECT_NAME);
 | 
					
						
							| 
									
										
										
										
											2022-03-29 18:32:04 -07:00
										 |  |  |     proposal.setAspect(GenericRecordUtils.serializeAspect(newStatus));
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  |     proposal.setChangeType(ChangeType.UPSERT);
 | 
					
						
							| 
									
										
										
										
											2024-04-16 10:12:48 -05:00
										 |  |  |     systemEntityClient.ingestProposal(opContext, proposal);
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:14:35 -07:00
										 |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 07:42:18 -07:00
										 |  |  |   private Optional<String> extractRegexGroup(final String patternStr, final String target) {
 | 
					
						
							|  |  |  |     final Pattern pattern = Pattern.compile(patternStr);
 | 
					
						
							|  |  |  |     final Matcher matcher = pattern.matcher(target);
 | 
					
						
							|  |  |  |     if (matcher.find()) {
 | 
					
						
							|  |  |  |       final String extractedValue = matcher.group();
 | 
					
						
							|  |  |  |       return Optional.of(extractedValue);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     return Optional.empty();
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 |