2021-08-20 10:58:07 -07:00
|
|
|
package controllers;
|
2021-03-11 13:38:35 -08:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
import static auth.AuthUtils.*;
|
|
|
|
import static org.pac4j.core.client.IndirectClient.ATTEMPTED_AUTHENTICATION_SUFFIX;
|
|
|
|
import static org.pac4j.play.store.PlayCookieSessionStore.*;
|
2025-05-01 21:19:58 -05:00
|
|
|
import static utils.FrontendConstants.FALLBACK_LOGIN;
|
|
|
|
import static utils.FrontendConstants.GUEST_LOGIN;
|
|
|
|
import static utils.FrontendConstants.PASSWORD_LOGIN;
|
|
|
|
import static utils.FrontendConstants.PASSWORD_RESET;
|
|
|
|
import static utils.FrontendConstants.SIGN_UP_LINK_LOGIN;
|
2023-12-06 11:02:42 +05:30
|
|
|
|
2022-09-22 18:26:42 -07:00
|
|
|
import auth.AuthUtils;
|
2023-02-14 13:36:47 -05:00
|
|
|
import auth.CookieConfigs;
|
2025-03-04 01:52:52 +05:30
|
|
|
import auth.GuestAuthenticationConfigs;
|
2022-09-22 18:26:42 -07:00
|
|
|
import auth.JAASConfigs;
|
|
|
|
import auth.NativeAuthenticationConfigs;
|
|
|
|
import auth.sso.SsoManager;
|
2021-11-22 16:33:14 -08:00
|
|
|
import client.AuthServiceClient;
|
2021-03-11 13:38:35 -08:00
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
|
|
import com.linkedin.common.urn.CorpuserUrn;
|
2021-11-22 16:33:14 -08:00
|
|
|
import com.linkedin.common.urn.Urn;
|
2021-03-11 13:38:35 -08:00
|
|
|
import com.typesafe.config.Config;
|
2024-01-09 14:15:50 -06:00
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URISyntaxException;
|
2021-08-04 11:55:03 -07:00
|
|
|
import java.net.URLEncoder;
|
2022-12-08 20:27:51 -06:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2023-10-17 15:50:32 -05:00
|
|
|
import java.util.Base64;
|
2021-08-04 11:55:03 -07:00
|
|
|
import java.util.Optional;
|
2022-09-22 18:26:42 -07:00
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
import javax.inject.Inject;
|
2024-01-09 14:15:50 -06:00
|
|
|
import org.apache.commons.httpclient.InvalidRedirectLocationException;
|
2021-03-11 13:38:35 -08:00
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import org.pac4j.core.client.Client;
|
2024-10-28 09:05:16 -05:00
|
|
|
import org.pac4j.core.context.CallContext;
|
2023-10-17 15:50:32 -05:00
|
|
|
import org.pac4j.core.context.Cookie;
|
2024-10-28 09:05:16 -05:00
|
|
|
import org.pac4j.core.context.WebContext;
|
2023-01-11 10:45:18 -08:00
|
|
|
import org.pac4j.core.exception.http.FoundAction;
|
2022-12-08 20:27:51 -06:00
|
|
|
import org.pac4j.core.exception.http.RedirectionAction;
|
2021-03-11 13:38:35 -08:00
|
|
|
import org.pac4j.play.PlayWebContext;
|
|
|
|
import org.pac4j.play.http.PlayHttpActionAdapter;
|
2024-10-28 09:05:16 -05:00
|
|
|
import org.pac4j.play.store.PlayCookieSessionStore;
|
2021-06-25 10:56:45 -07:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2023-11-14 14:06:33 -06:00
|
|
|
import play.data.validation.Constraints;
|
2021-03-11 13:38:35 -08:00
|
|
|
import play.libs.Json;
|
|
|
|
import play.mvc.Controller;
|
|
|
|
import play.mvc.Http;
|
|
|
|
import play.mvc.Result;
|
2022-12-08 20:27:51 -06:00
|
|
|
import play.mvc.Results;
|
2021-03-11 13:38:35 -08:00
|
|
|
import security.AuthenticationManager;
|
|
|
|
|
|
|
|
public class AuthenticationController extends Controller {
|
2023-12-06 11:02:42 +05:30
|
|
|
public static final String AUTH_VERBOSE_LOGGING = "auth.verbose.logging";
|
|
|
|
private static final String AUTH_REDIRECT_URI_PARAM = "redirect_uri";
|
|
|
|
private static final String ERROR_MESSAGE_URI_PARAM = "error_msg";
|
|
|
|
private static final String SSO_DISABLED_ERROR_MESSAGE = "SSO is not configured";
|
|
|
|
|
|
|
|
private static final String SSO_NO_REDIRECT_MESSAGE =
|
|
|
|
"SSO is configured, however missing redirect from idp";
|
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
private static final Logger logger =
|
|
|
|
LoggerFactory.getLogger(AuthenticationController.class.getName());
|
|
|
|
private final CookieConfigs cookieConfigs;
|
|
|
|
private final JAASConfigs jaasConfigs;
|
|
|
|
private final NativeAuthenticationConfigs nativeAuthenticationConfigs;
|
2025-03-04 01:52:52 +05:30
|
|
|
private final GuestAuthenticationConfigs guestAuthenticationConfigs;
|
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
private final boolean verbose;
|
2023-12-06 11:02:42 +05:30
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
@Inject private org.pac4j.core.config.Config ssoConfig;
|
2023-12-06 11:02:42 +05:30
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
@Inject private PlayCookieSessionStore playCookieSessionStore;
|
2023-12-06 11:02:42 +05:30
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
@Inject private SsoManager ssoManager;
|
2023-12-06 11:02:42 +05:30
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
@Inject AuthServiceClient authClient;
|
2023-12-06 11:02:42 +05:30
|
|
|
|
|
|
|
@Inject
|
|
|
|
public AuthenticationController(@Nonnull Config configs) {
|
2024-10-28 09:05:16 -05:00
|
|
|
cookieConfigs = new CookieConfigs(configs);
|
|
|
|
jaasConfigs = new JAASConfigs(configs);
|
|
|
|
nativeAuthenticationConfigs = new NativeAuthenticationConfigs(configs);
|
2025-03-04 01:52:52 +05:30
|
|
|
guestAuthenticationConfigs = new GuestAuthenticationConfigs(configs);
|
2024-10-28 09:05:16 -05:00
|
|
|
verbose = configs.hasPath(AUTH_VERBOSE_LOGGING) && configs.getBoolean(AUTH_VERBOSE_LOGGING);
|
2023-12-06 11:02:42 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Route used to perform authentication, or redirect to log in if authentication fails.
|
|
|
|
*
|
|
|
|
* <p>If indirect SSO (eg. oidc) is configured, this route will redirect to the identity provider
|
|
|
|
* (Indirect auth). If not, we will fall back to the default username / password login experience
|
|
|
|
* (Direct auth).
|
|
|
|
*/
|
|
|
|
@Nonnull
|
|
|
|
public Result authenticate(Http.Request request) {
|
|
|
|
|
|
|
|
// TODO: Call getAuthenticatedUser and then generate a session cookie for the UI if the user is
|
|
|
|
// authenticated.
|
|
|
|
|
|
|
|
final Optional<String> maybeRedirectPath =
|
|
|
|
Optional.ofNullable(request.getQueryString(AUTH_REDIRECT_URI_PARAM));
|
2024-01-09 14:15:50 -06:00
|
|
|
String redirectPath = maybeRedirectPath.orElse("/");
|
|
|
|
try {
|
|
|
|
URI redirectUri = new URI(redirectPath);
|
|
|
|
if (redirectUri.getScheme() != null || redirectUri.getAuthority() != null) {
|
2024-10-28 09:05:16 -05:00
|
|
|
throw new InvalidRedirectLocationException(
|
|
|
|
"Redirect location must be relative to the base url, cannot "
|
|
|
|
+ "redirect to other domains: "
|
|
|
|
+ redirectPath,
|
|
|
|
redirectPath);
|
2024-01-09 14:15:50 -06:00
|
|
|
}
|
|
|
|
} catch (URISyntaxException | InvalidRedirectLocationException e) {
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.warn(e.getMessage());
|
2024-01-09 14:15:50 -06:00
|
|
|
redirectPath = "/";
|
|
|
|
}
|
2023-12-06 11:02:42 +05:30
|
|
|
|
|
|
|
if (AuthUtils.hasValidSessionCookie(request)) {
|
|
|
|
return Results.redirect(redirectPath);
|
2021-03-11 13:38:35 -08:00
|
|
|
}
|
|
|
|
|
2025-03-04 01:52:52 +05:30
|
|
|
if (guestAuthenticationConfigs.isGuestEnabled()
|
|
|
|
&& guestAuthenticationConfigs.getGuestPath().equals(redirectPath)) {
|
|
|
|
final String accessToken =
|
2025-05-01 21:19:58 -05:00
|
|
|
authClient.generateSessionTokenForUser(
|
|
|
|
guestAuthenticationConfigs.getGuestUser(), GUEST_LOGIN);
|
2025-03-04 01:52:52 +05:30
|
|
|
redirectPath =
|
|
|
|
"/"; // We requested guest login by accessing {guestPath} URL. It is not really a target.
|
|
|
|
CorpuserUrn guestUserUrn = new CorpuserUrn(guestAuthenticationConfigs.getGuestUser());
|
|
|
|
return Results.redirect(redirectPath)
|
|
|
|
.withSession(createSessionMap(guestUserUrn.toString(), accessToken))
|
|
|
|
.withCookies(
|
|
|
|
createActorCookie(
|
|
|
|
guestUserUrn.toString(),
|
|
|
|
cookieConfigs.getTtlInHours(),
|
|
|
|
cookieConfigs.getAuthCookieSameSite(),
|
|
|
|
cookieConfigs.getAuthCookieSecure()));
|
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
// 1. If SSO is enabled, redirect to IdP if not authenticated.
|
2024-10-28 09:05:16 -05:00
|
|
|
if (ssoManager.isSsoEnabled()) {
|
2023-12-06 11:02:42 +05:30
|
|
|
return redirectToIdentityProvider(request, redirectPath)
|
|
|
|
.orElse(
|
|
|
|
Results.redirect(
|
|
|
|
LOGIN_ROUTE
|
|
|
|
+ String.format("?%s=%s", ERROR_MESSAGE_URI_PARAM, SSO_NO_REDIRECT_MESSAGE)));
|
2021-03-11 13:38:35 -08:00
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
// 2. If either JAAS auth or Native auth is enabled, fallback to it
|
2024-10-28 09:05:16 -05:00
|
|
|
if (jaasConfigs.isJAASEnabled()
|
|
|
|
|| nativeAuthenticationConfigs.isNativeAuthenticationEnabled()) {
|
2023-12-06 11:02:42 +05:30
|
|
|
return Results.redirect(
|
|
|
|
LOGIN_ROUTE
|
|
|
|
+ String.format("?%s=%s", AUTH_REDIRECT_URI_PARAM, encodeRedirectUri(redirectPath)));
|
2022-10-31 16:39:26 -07:00
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
// 3. If no auth enabled, fallback to using default user account & redirect.
|
|
|
|
// Generate GMS session token, TODO:
|
2025-05-01 21:19:58 -05:00
|
|
|
final String accessToken =
|
|
|
|
authClient.generateSessionTokenForUser(DEFAULT_ACTOR_URN.getId(), FALLBACK_LOGIN);
|
2023-12-06 11:02:42 +05:30
|
|
|
return Results.redirect(redirectPath)
|
|
|
|
.withSession(createSessionMap(DEFAULT_ACTOR_URN.toString(), accessToken))
|
|
|
|
.withCookies(
|
|
|
|
createActorCookie(
|
|
|
|
DEFAULT_ACTOR_URN.toString(),
|
2024-10-28 09:05:16 -05:00
|
|
|
cookieConfigs.getTtlInHours(),
|
|
|
|
cookieConfigs.getAuthCookieSameSite(),
|
|
|
|
cookieConfigs.getAuthCookieSecure()));
|
2023-12-06 11:02:42 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/** Redirect to the identity provider for authentication. */
|
|
|
|
@Nonnull
|
|
|
|
public Result sso(Http.Request request) {
|
2024-10-28 09:05:16 -05:00
|
|
|
if (ssoManager.isSsoEnabled()) {
|
2023-12-06 11:02:42 +05:30
|
|
|
return redirectToIdentityProvider(request, "/")
|
|
|
|
.orElse(
|
|
|
|
Results.redirect(
|
|
|
|
LOGIN_ROUTE
|
|
|
|
+ String.format("?%s=%s", ERROR_MESSAGE_URI_PARAM, SSO_NO_REDIRECT_MESSAGE)));
|
|
|
|
}
|
|
|
|
return Results.redirect(
|
|
|
|
LOGIN_ROUTE + String.format("?%s=%s", ERROR_MESSAGE_URI_PARAM, SSO_DISABLED_ERROR_MESSAGE));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log in a user based on a username + password.
|
|
|
|
*
|
|
|
|
* <p>TODO: Implement built-in support for LDAP auth. Currently dummy jaas authentication is the
|
|
|
|
* default.
|
|
|
|
*/
|
|
|
|
@Nonnull
|
|
|
|
public Result logIn(Http.Request request) {
|
2024-10-28 09:05:16 -05:00
|
|
|
boolean jaasEnabled = jaasConfigs.isJAASEnabled();
|
|
|
|
logger.debug(String.format("Jaas authentication enabled: %b", jaasEnabled));
|
2023-12-06 11:02:42 +05:30
|
|
|
boolean nativeAuthenticationEnabled =
|
2024-10-28 09:05:16 -05:00
|
|
|
nativeAuthenticationConfigs.isNativeAuthenticationEnabled();
|
|
|
|
logger.debug(String.format("Native authentication enabled: %b", nativeAuthenticationEnabled));
|
2023-12-06 11:02:42 +05:30
|
|
|
boolean noAuthEnabled = !jaasEnabled && !nativeAuthenticationEnabled;
|
|
|
|
if (noAuthEnabled) {
|
|
|
|
String message = "Neither JAAS nor native authentication is enabled on the server.";
|
|
|
|
final ObjectNode error = Json.newObject();
|
|
|
|
error.put("message", message);
|
|
|
|
return Results.badRequest(error);
|
2021-03-11 13:38:35 -08:00
|
|
|
}
|
2021-08-04 11:55:03 -07:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
final JsonNode json = request.body().asJson();
|
|
|
|
final String username = json.findPath(USER_NAME).textValue();
|
|
|
|
final String password = json.findPath(PASSWORD).textValue();
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(username)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "User name must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Invalid Credentials");
|
|
|
|
boolean loginSucceeded = tryLogin(username, password);
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (!loginSucceeded) {
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.info("Login failed for user: {}", username);
|
2023-12-06 11:02:42 +05:30
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
final Urn actorUrn = new CorpuserUrn(username);
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.info("Login successful for user: {}, urn: {}", username, actorUrn);
|
2025-05-01 21:19:58 -05:00
|
|
|
final String accessToken =
|
|
|
|
authClient.generateSessionTokenForUser(actorUrn.getId(), PASSWORD_LOGIN);
|
2023-12-06 11:02:42 +05:30
|
|
|
return createSession(actorUrn.toString(), accessToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sign up a native user based on a name, email, title, and password. The invite token must match
|
|
|
|
* an existing invite token.
|
|
|
|
*/
|
|
|
|
@Nonnull
|
|
|
|
public Result signUp(Http.Request request) {
|
|
|
|
boolean nativeAuthenticationEnabled =
|
2024-10-28 09:05:16 -05:00
|
|
|
nativeAuthenticationConfigs.isNativeAuthenticationEnabled();
|
|
|
|
logger.debug(String.format("Native authentication enabled: %b", nativeAuthenticationEnabled));
|
2023-12-06 11:02:42 +05:30
|
|
|
if (!nativeAuthenticationEnabled) {
|
|
|
|
String message = "Native authentication is not enabled on the server.";
|
|
|
|
final ObjectNode error = Json.newObject();
|
|
|
|
error.put("message", message);
|
|
|
|
return Results.badRequest(error);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
final JsonNode json = request.body().asJson();
|
|
|
|
final String fullName = json.findPath(FULL_NAME).textValue();
|
|
|
|
final String email = json.findPath(EMAIL).textValue();
|
|
|
|
final String title = json.findPath(TITLE).textValue();
|
|
|
|
final String password = json.findPath(PASSWORD).textValue();
|
|
|
|
final String inviteToken = json.findPath(INVITE_TOKEN).textValue();
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(fullName)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Full name must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(email)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Email must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2024-10-28 09:05:16 -05:00
|
|
|
if (nativeAuthenticationConfigs.isEnforceValidEmailEnabled()) {
|
2023-12-06 11:02:42 +05:30
|
|
|
Constraints.EmailValidator emailValidator = new Constraints.EmailValidator();
|
|
|
|
if (!emailValidator.isValid(email)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Email must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(password)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Password must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(title)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Title must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(inviteToken)) {
|
|
|
|
JsonNode invalidCredsJson =
|
|
|
|
Json.newObject().put("message", "Invite token must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
final Urn userUrn = new CorpuserUrn(email);
|
|
|
|
final String userUrnString = userUrn.toString();
|
2024-10-28 09:05:16 -05:00
|
|
|
authClient.signUp(userUrnString, fullName, email, title, password, inviteToken);
|
|
|
|
logger.info("Signed up user {} using invite tokens", userUrnString);
|
2025-05-01 21:19:58 -05:00
|
|
|
final String accessToken =
|
|
|
|
authClient.generateSessionTokenForUser(userUrn.getId(), SIGN_UP_LINK_LOGIN);
|
2023-12-06 11:02:42 +05:30
|
|
|
return createSession(userUrnString, accessToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Reset a native user's credentials based on a username, old password, and new password. */
|
|
|
|
@Nonnull
|
|
|
|
public Result resetNativeUserCredentials(Http.Request request) {
|
|
|
|
boolean nativeAuthenticationEnabled =
|
2024-10-28 09:05:16 -05:00
|
|
|
nativeAuthenticationConfigs.isNativeAuthenticationEnabled();
|
|
|
|
logger.debug(String.format("Native authentication enabled: %b", nativeAuthenticationEnabled));
|
2023-12-06 11:02:42 +05:30
|
|
|
if (!nativeAuthenticationEnabled) {
|
|
|
|
String message = "Native authentication is not enabled on the server.";
|
|
|
|
final ObjectNode error = Json.newObject();
|
|
|
|
error.put("message", message);
|
|
|
|
return badRequest(error);
|
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
final JsonNode json = request.body().asJson();
|
|
|
|
final String email = json.findPath(EMAIL).textValue();
|
|
|
|
final String password = json.findPath(PASSWORD).textValue();
|
|
|
|
final String resetToken = json.findPath(RESET_TOKEN).textValue();
|
2022-06-08 21:13:22 -04:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(email)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Email must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
2022-06-08 21:13:22 -04:00
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(password)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Password must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
2021-08-20 07:42:18 -07:00
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
if (StringUtils.isBlank(resetToken)) {
|
|
|
|
JsonNode invalidCredsJson = Json.newObject().put("message", "Reset token must not be empty.");
|
|
|
|
return Results.badRequest(invalidCredsJson);
|
2023-01-11 10:45:18 -08:00
|
|
|
}
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
final Urn userUrn = new CorpuserUrn(email);
|
|
|
|
final String userUrnString = userUrn.toString();
|
2024-10-28 09:05:16 -05:00
|
|
|
authClient.resetNativeUserCredentials(userUrnString, password, resetToken);
|
2025-05-01 21:19:58 -05:00
|
|
|
final String accessToken =
|
|
|
|
authClient.generateSessionTokenForUser(userUrn.getId(), PASSWORD_RESET);
|
2023-12-06 11:02:42 +05:30
|
|
|
return createSession(userUrnString, accessToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Optional<Result> redirectToIdentityProvider(
|
|
|
|
Http.RequestHeader request, String redirectPath) {
|
2024-10-28 09:05:16 -05:00
|
|
|
CallContext ctx = buildCallContext(request);
|
|
|
|
|
|
|
|
final Client client = ssoManager.getSsoProvider().client();
|
|
|
|
configurePac4jSessionStore(ctx, client, redirectPath);
|
2023-12-06 11:02:42 +05:30
|
|
|
try {
|
2024-10-28 09:05:16 -05:00
|
|
|
final Optional<RedirectionAction> action = client.getRedirectionAction(ctx);
|
|
|
|
return action.map(act -> new PlayHttpActionAdapter().adapt(act, ctx.webContext()));
|
2023-12-06 11:02:42 +05:30
|
|
|
} catch (Exception e) {
|
2024-10-28 09:05:16 -05:00
|
|
|
if (verbose) {
|
|
|
|
logger.error(
|
2023-12-06 11:02:42 +05:30
|
|
|
"Caught exception while attempting to redirect to SSO identity provider! It's likely that SSO integration is mis-configured",
|
|
|
|
e);
|
|
|
|
} else {
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.error(
|
2023-12-06 11:02:42 +05:30
|
|
|
"Caught exception while attempting to redirect to SSO identity provider! It's likely that SSO integration is mis-configured");
|
|
|
|
}
|
|
|
|
return Optional.of(
|
|
|
|
Results.redirect(
|
|
|
|
String.format(
|
|
|
|
"/login?error_msg=%s",
|
|
|
|
URLEncoder.encode(
|
|
|
|
"Failed to redirect to Single Sign-On provider. Please contact your DataHub Administrator, "
|
|
|
|
+ "or refer to server logs for more information.",
|
|
|
|
StandardCharsets.UTF_8))));
|
2021-08-04 11:55:03 -07:00
|
|
|
}
|
2023-12-06 11:02:42 +05:30
|
|
|
}
|
|
|
|
|
2024-10-28 09:05:16 -05:00
|
|
|
private CallContext buildCallContext(Http.RequestHeader request) {
|
|
|
|
// First create PlayWebContext from the request
|
|
|
|
PlayWebContext webContext = new PlayWebContext(request);
|
|
|
|
|
|
|
|
// Then create CallContext using the web context and session store
|
|
|
|
return new CallContext(webContext, playCookieSessionStore);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void configurePac4jSessionStore(CallContext ctx, Client client, String redirectPath) {
|
|
|
|
WebContext context = ctx.webContext();
|
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
// Set the originally requested path for post-auth redirection. We split off into a separate
|
|
|
|
// cookie from the session
|
|
|
|
// to reduce size of the session cookie
|
|
|
|
FoundAction foundAction = new FoundAction(redirectPath);
|
2024-10-28 09:05:16 -05:00
|
|
|
byte[] javaSerBytes =
|
|
|
|
((PlayCookieSessionStore) ctx.sessionStore()).getSerializer().serializeToBytes(foundAction);
|
2023-12-06 11:02:42 +05:30
|
|
|
String serialized = Base64.getEncoder().encodeToString(compressBytes(javaSerBytes));
|
|
|
|
context.addResponseCookie(new Cookie(REDIRECT_URL_COOKIE_NAME, serialized));
|
|
|
|
// This is to prevent previous login attempts from being cached.
|
|
|
|
// We replicate the logic here, which is buried in the Pac4j client.
|
2024-10-28 09:05:16 -05:00
|
|
|
Optional<Object> attempt =
|
|
|
|
playCookieSessionStore.get(context, client.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
|
|
|
|
if (attempt.isPresent() && !"".equals(attempt.get())) {
|
|
|
|
logger.debug(
|
2023-12-06 11:02:42 +05:30
|
|
|
"Found previous login attempt. Removing it manually to prevent unexpected errors.");
|
2024-10-28 09:05:16 -05:00
|
|
|
playCookieSessionStore.set(context, client.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "");
|
2023-12-06 11:02:42 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private String encodeRedirectUri(final String redirectUri) {
|
|
|
|
return URLEncoder.encode(redirectUri, StandardCharsets.UTF_8);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean tryLogin(String username, String password) {
|
|
|
|
boolean loginSucceeded = false;
|
|
|
|
|
|
|
|
// First try jaas login, if enabled
|
2024-10-28 09:05:16 -05:00
|
|
|
if (jaasConfigs.isJAASEnabled()) {
|
2023-12-06 11:02:42 +05:30
|
|
|
try {
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.debug("Attempting JAAS authentication for user: {}", username);
|
2023-12-06 11:02:42 +05:30
|
|
|
AuthenticationManager.authenticateJaasUser(username, password);
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.debug("JAAS authentication successful. Login succeeded");
|
2023-12-06 11:02:42 +05:30
|
|
|
loginSucceeded = true;
|
|
|
|
} catch (Exception e) {
|
2024-10-28 09:05:16 -05:00
|
|
|
if (verbose) {
|
|
|
|
logger.debug("JAAS authentication error. Login failed", e);
|
2023-12-06 11:02:42 +05:30
|
|
|
} else {
|
2024-10-28 09:05:16 -05:00
|
|
|
logger.debug("JAAS authentication error. Login failed");
|
2022-06-08 21:13:22 -04:00
|
|
|
}
|
2023-12-06 11:02:42 +05:30
|
|
|
}
|
2022-06-08 21:13:22 -04:00
|
|
|
}
|
2022-09-22 18:26:42 -07:00
|
|
|
|
2023-12-06 11:02:42 +05:30
|
|
|
// If jaas login fails or is disabled, try native auth login
|
2024-10-28 09:05:16 -05:00
|
|
|
if (nativeAuthenticationConfigs.isNativeAuthenticationEnabled() && !loginSucceeded) {
|
2023-12-06 11:02:42 +05:30
|
|
|
final Urn userUrn = new CorpuserUrn(username);
|
|
|
|
final String userUrnString = userUrn.toString();
|
|
|
|
loginSucceeded =
|
2024-10-28 09:05:16 -05:00
|
|
|
loginSucceeded || authClient.verifyNativeUserCredentials(userUrnString, password);
|
2022-10-07 14:08:43 -07:00
|
|
|
}
|
2023-12-06 11:02:42 +05:30
|
|
|
|
|
|
|
return loginSucceeded;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Result createSession(String userUrnString, String accessToken) {
|
|
|
|
return Results.ok()
|
|
|
|
.withSession(createSessionMap(userUrnString, accessToken))
|
|
|
|
.withCookies(
|
|
|
|
createActorCookie(
|
|
|
|
userUrnString,
|
2024-10-28 09:05:16 -05:00
|
|
|
cookieConfigs.getTtlInHours(),
|
|
|
|
cookieConfigs.getAuthCookieSameSite(),
|
|
|
|
cookieConfigs.getAuthCookieSecure()));
|
2023-12-06 11:02:42 +05:30
|
|
|
}
|
|
|
|
}
|