| 
									
										
										
										
											2021-08-20 10:58:07 -07:00
										 |  |  | package auth;
 | 
					
						
							| 
									
										
										
										
											2021-03-11 13:38:35 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import com.linkedin.common.urn.CorpuserUrn;
 | 
					
						
							|  |  |  | import java.time.Duration;
 | 
					
						
							|  |  |  | import java.time.temporal.ChronoUnit;
 | 
					
						
							| 
									
										
										
										
											2022-12-22 16:12:51 -06:00
										 |  |  | import java.util.HashMap;
 | 
					
						
							|  |  |  | import java.util.Map;
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  | import javax.annotation.Nonnull;
 | 
					
						
							|  |  |  | import lombok.extern.slf4j.Slf4j;
 | 
					
						
							|  |  |  | import play.mvc.Http;
 | 
					
						
							| 
									
										
										
										
											2021-03-11 13:38:35 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | @Slf4j
 | 
					
						
							| 
									
										
										
										
											2021-03-11 13:38:35 -08:00
										 |  |  | public class AuthUtils {
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   /**
 | 
					
						
							|  |  |  |    * The config path that determines whether Metadata Service Authentication is enabled.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * <p>When enabled, the frontend server will proxy requests to the Metadata Service without
 | 
					
						
							|  |  |  |    * requiring them to have a valid frontend-issued Session Cookie. This effectively means
 | 
					
						
							|  |  |  |    * delegating the act of authentication to the Metadata Service. It is critical that if Metadata
 | 
					
						
							|  |  |  |    * Service authentication is enabled at the frontend service layer, it is also enabled in the
 | 
					
						
							|  |  |  |    * Metadata Service itself. Otherwise, unauthenticated traffic may reach the Metadata itself.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * <p>When disabled, the frontend server will require that all requests have a valid Session
 | 
					
						
							|  |  |  |    * Cookie associated with them. Otherwise, requests will be denied with an Unauthorized error.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   public static final String METADATA_SERVICE_AUTH_ENABLED_CONFIG_PATH =
 | 
					
						
							|  |  |  |       "metadataService.auth.enabled";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** The attribute inside session cookie representing a GMS-issued access token */
 | 
					
						
							|  |  |  |   public static final String SESSION_COOKIE_GMS_TOKEN_NAME = "token";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * An ID used to identify system callers that are internal to DataHub. Provided via configuration.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   public static final String SYSTEM_CLIENT_ID_CONFIG_PATH = "systemClientId";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * An Secret used to authenticate system callers that are internal to DataHub. Provided via
 | 
					
						
							|  |  |  |    * configuration.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   public static final String SYSTEM_CLIENT_SECRET_CONFIG_PATH = "systemClientSecret";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Cookie name for redirect url that is manually separated from the session to reduce size */
 | 
					
						
							|  |  |  |   public static final String REDIRECT_URL_COOKIE_NAME = "REDIRECT_URL";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static final CorpuserUrn DEFAULT_ACTOR_URN = new CorpuserUrn("datahub");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static final String LOGIN_ROUTE = "/login";
 | 
					
						
							|  |  |  |   public static final String USER_NAME = "username";
 | 
					
						
							|  |  |  |   public static final String PASSWORD = "password";
 | 
					
						
							|  |  |  |   public static final String ACTOR = "actor";
 | 
					
						
							|  |  |  |   public static final String ACCESS_TOKEN = "token";
 | 
					
						
							|  |  |  |   public static final String FULL_NAME = "fullName";
 | 
					
						
							|  |  |  |   public static final String EMAIL = "email";
 | 
					
						
							|  |  |  |   public static final String TITLE = "title";
 | 
					
						
							|  |  |  |   public static final String INVITE_TOKEN = "inviteToken";
 | 
					
						
							|  |  |  |   public static final String RESET_TOKEN = "resetToken";
 | 
					
						
							| 
									
										
										
										
											2023-12-26 14:34:10 -06:00
										 |  |  |   public static final String BASE_URL = "baseUrl";
 | 
					
						
							|  |  |  |   public static final String OIDC_ENABLED = "oidcEnabled";
 | 
					
						
							|  |  |  |   public static final String CLIENT_ID = "clientId";
 | 
					
						
							|  |  |  |   public static final String CLIENT_SECRET = "clientSecret";
 | 
					
						
							|  |  |  |   public static final String DISCOVERY_URI = "discoveryUri";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static final String USER_NAME_CLAIM = "userNameClaim";
 | 
					
						
							|  |  |  |   public static final String USER_NAME_CLAIM_REGEX = "userNameClaimRegex";
 | 
					
						
							|  |  |  |   public static final String SCOPE = "scope";
 | 
					
						
							|  |  |  |   public static final String CLIENT_NAME = "clientName";
 | 
					
						
							|  |  |  |   public static final String CLIENT_AUTHENTICATION_METHOD = "clientAuthenticationMethod";
 | 
					
						
							|  |  |  |   public static final String JIT_PROVISIONING_ENABLED = "jitProvisioningEnabled";
 | 
					
						
							|  |  |  |   public static final String PRE_PROVISIONING_REQUIRED = "preProvisioningRequired";
 | 
					
						
							|  |  |  |   public static final String EXTRACT_GROUPS_ENABLED = "extractGroupsEnabled";
 | 
					
						
							|  |  |  |   public static final String GROUPS_CLAIM = "groupsClaim";
 | 
					
						
							|  |  |  |   public static final String RESPONSE_TYPE = "responseType";
 | 
					
						
							|  |  |  |   public static final String RESPONSE_MODE = "responseMode";
 | 
					
						
							|  |  |  |   public static final String USE_NONCE = "useNonce";
 | 
					
						
							|  |  |  |   public static final String READ_TIMEOUT = "readTimeout";
 | 
					
						
							|  |  |  |   public static final String EXTRACT_JWT_ACCESS_TOKEN_CLAIMS = "extractJwtAccessTokenClaims";
 | 
					
						
							| 
									
										
										
										
											2024-01-24 17:36:30 -08:00
										 |  |  |   // Retained for backwards compatibility
 | 
					
						
							|  |  |  |   public static final String PREFERRED_JWS_ALGORITHM = "preferredJwsAlgorithm";
 | 
					
						
							|  |  |  |   public static final String PREFERRED_JWS_ALGORITHM_2 = "preferredJwsAlgorithm2";
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Determines whether the inbound request should be forward to downstream Metadata Service. Today,
 | 
					
						
							|  |  |  |    * this simply checks for the presence of an "Authorization" header or the presence of a valid
 | 
					
						
							|  |  |  |    * session cookie issued by the frontend.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * <p>Note that this method DOES NOT actually verify the authentication token of an inbound
 | 
					
						
							|  |  |  |    * request. That will be handled by the downstream Metadata Service. Until then, the request
 | 
					
						
							|  |  |  |    * should be treated as UNAUTHENTICATED.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * <p>Returns true if the request is eligible to be forwarded to GMS, false otherwise.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   public static boolean isEligibleForForwarding(Http.Request req) {
 | 
					
						
							|  |  |  |     return hasValidSessionCookie(req) || hasAuthHeader(req);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Returns true if a request has a valid session cookie issued by the frontend server. Note that
 | 
					
						
							|  |  |  |    * this DOES NOT verify whether the token within the session cookie will be accepted by the
 | 
					
						
							|  |  |  |    * downstream GMS service.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * <p>Note that we depend on the presence of 2 cookies, one accessible to the browser and one not,
 | 
					
						
							|  |  |  |    * as well as their agreement to determine authentication status.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   public static boolean hasValidSessionCookie(final Http.Request req) {
 | 
					
						
							|  |  |  |     Map<String, String> sessionCookie = req.session().data();
 | 
					
						
							|  |  |  |     return sessionCookie.containsKey(ACCESS_TOKEN)
 | 
					
						
							|  |  |  |         && sessionCookie.containsKey(ACTOR)
 | 
					
						
							|  |  |  |         && req.getCookie(ACTOR).isPresent()
 | 
					
						
							|  |  |  |         && req.session().data().get(ACTOR).equals(req.getCookie(ACTOR).get().value());
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Returns true if a request includes the Authorization header, false otherwise */
 | 
					
						
							|  |  |  |   public static boolean hasAuthHeader(final Http.Request req) {
 | 
					
						
							|  |  |  |     return req.getHeaders().contains(Http.HeaderNames.AUTHORIZATION);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Creates a client authentication cookie (actor cookie) with a specified TTL in hours.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * @param actorUrn the urn of the authenticated actor, e.g. "urn:li:corpuser:datahub"
 | 
					
						
							|  |  |  |    * @param ttlInHours the number of hours until the actor cookie expires after being set
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   public static Http.Cookie createActorCookie(
 | 
					
						
							|  |  |  |       @Nonnull final String actorUrn,
 | 
					
						
							|  |  |  |       @Nonnull final Integer ttlInHours,
 | 
					
						
							|  |  |  |       @Nonnull final String sameSite,
 | 
					
						
							|  |  |  |       final boolean isSecure) {
 | 
					
						
							|  |  |  |     return Http.Cookie.builder(ACTOR, actorUrn)
 | 
					
						
							|  |  |  |         .withHttpOnly(false)
 | 
					
						
							|  |  |  |         .withMaxAge(Duration.of(ttlInHours, ChronoUnit.HOURS))
 | 
					
						
							|  |  |  |         .withSameSite(convertSameSiteValue(sameSite))
 | 
					
						
							|  |  |  |         .withSecure(isSecure)
 | 
					
						
							|  |  |  |         .build();
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static Map<String, String> createSessionMap(
 | 
					
						
							|  |  |  |       final String userUrnStr, final String accessToken) {
 | 
					
						
							|  |  |  |     final Map<String, String> sessionAttributes = new HashMap<>();
 | 
					
						
							|  |  |  |     sessionAttributes.put(ACTOR, userUrnStr);
 | 
					
						
							|  |  |  |     sessionAttributes.put(ACCESS_TOKEN, accessToken);
 | 
					
						
							|  |  |  |     return sessionAttributes;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private AuthUtils() {}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private static Http.Cookie.SameSite convertSameSiteValue(@Nonnull final String sameSiteValue) {
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       return Http.Cookie.SameSite.valueOf(sameSiteValue);
 | 
					
						
							|  |  |  |     } catch (IllegalArgumentException e) {
 | 
					
						
							|  |  |  |       log.warn(
 | 
					
						
							|  |  |  |           String.format(
 | 
					
						
							|  |  |  |               "Invalid AUTH_COOKIE_SAME_SITE value: %s. Using LAX instead.", sameSiteValue),
 | 
					
						
							|  |  |  |           e);
 | 
					
						
							|  |  |  |       return Http.Cookie.SameSite.LAX;
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  |     }
 | 
					
						
							| 
									
										
										
										
											2023-12-06 11:02:42 +05:30
										 |  |  |   }
 | 
					
						
							| 
									
										
										
										
											2021-03-11 13:38:35 -08:00
										 |  |  | }
 |