package controllers; import static auth.AuthUtils.*; import static org.pac4j.core.client.IndirectClient.ATTEMPTED_AUTHENTICATION_SUFFIX; import static org.pac4j.play.store.PlayCookieSessionStore.*; import auth.AuthUtils; import auth.CookieConfigs; import auth.JAASConfigs; import auth.NativeAuthenticationConfigs; import auth.sso.SsoManager; import client.AuthServiceClient; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.linkedin.common.urn.CorpuserUrn; import com.linkedin.common.urn.Urn; import com.typesafe.config.Config; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Optional; import javax.annotation.Nonnull; import javax.inject.Inject; import org.apache.commons.httpclient.InvalidRedirectLocationException; import org.apache.commons.lang3.StringUtils; import org.pac4j.core.client.Client; import org.pac4j.core.context.Cookie; import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.RedirectionAction; import org.pac4j.play.PlayWebContext; import org.pac4j.play.http.PlayHttpActionAdapter; import org.pac4j.play.store.PlaySessionStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.data.validation.Constraints; import play.libs.Json; import play.mvc.Controller; import play.mvc.Http; import play.mvc.Result; import play.mvc.Results; import security.AuthenticationManager; public class AuthenticationController extends Controller { 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"; private final Logger _logger = LoggerFactory.getLogger(AuthenticationController.class.getName()); private final CookieConfigs _cookieConfigs; private final JAASConfigs _jaasConfigs; private final NativeAuthenticationConfigs _nativeAuthenticationConfigs; private final boolean _verbose; @Inject private org.pac4j.core.config.Config _ssoConfig; @Inject private PlaySessionStore _playSessionStore; @Inject private SsoManager _ssoManager; @Inject AuthServiceClient _authClient; @Inject public AuthenticationController(@Nonnull Config configs) { _cookieConfigs = new CookieConfigs(configs); _jaasConfigs = new JAASConfigs(configs); _nativeAuthenticationConfigs = new NativeAuthenticationConfigs(configs); _verbose = configs.hasPath(AUTH_VERBOSE_LOGGING) && configs.getBoolean(AUTH_VERBOSE_LOGGING); } /** * Route used to perform authentication, or redirect to log in if authentication fails. * *
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 TODO: Implement built-in support for LDAP auth. Currently dummy jaas authentication is the
   * default.
   */
  @Nonnull
  public Result logIn(Http.Request request) {
    boolean jaasEnabled = _jaasConfigs.isJAASEnabled();
    _logger.debug(String.format("Jaas authentication enabled: %b", jaasEnabled));
    boolean nativeAuthenticationEnabled =
        _nativeAuthenticationConfigs.isNativeAuthenticationEnabled();
    _logger.debug(String.format("Native authentication enabled: %b", nativeAuthenticationEnabled));
    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);
    }
    final JsonNode json = request.body().asJson();
    final String username = json.findPath(USER_NAME).textValue();
    final String password = json.findPath(PASSWORD).textValue();
    if (StringUtils.isBlank(username)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "User name must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    JsonNode invalidCredsJson = Json.newObject().put("message", "Invalid Credentials");
    boolean loginSucceeded = tryLogin(username, password);
    if (!loginSucceeded) {
      _logger.info("Login failed for user: {}", username);
      return Results.badRequest(invalidCredsJson);
    }
    final Urn actorUrn = new CorpuserUrn(username);
    _logger.info("Login successful for user: {}, urn: {}", username, actorUrn);
    final String accessToken = _authClient.generateSessionTokenForUser(actorUrn.getId());
    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 =
        _nativeAuthenticationConfigs.isNativeAuthenticationEnabled();
    _logger.debug(String.format("Native authentication enabled: %b", nativeAuthenticationEnabled));
    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);
    }
    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();
    if (StringUtils.isBlank(fullName)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Full name must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    if (StringUtils.isBlank(email)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Email must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    if (_nativeAuthenticationConfigs.isEnforceValidEmailEnabled()) {
      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);
      }
    }
    if (StringUtils.isBlank(password)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Password must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    if (StringUtils.isBlank(title)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Title must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    if (StringUtils.isBlank(inviteToken)) {
      JsonNode invalidCredsJson =
          Json.newObject().put("message", "Invite token must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    final Urn userUrn = new CorpuserUrn(email);
    final String userUrnString = userUrn.toString();
    _authClient.signUp(userUrnString, fullName, email, title, password, inviteToken);
    _logger.info("Signed up user {} using invite tokens", userUrnString);
    final String accessToken = _authClient.generateSessionTokenForUser(userUrn.getId());
    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 =
        _nativeAuthenticationConfigs.isNativeAuthenticationEnabled();
    _logger.debug(String.format("Native authentication enabled: %b", nativeAuthenticationEnabled));
    if (!nativeAuthenticationEnabled) {
      String message = "Native authentication is not enabled on the server.";
      final ObjectNode error = Json.newObject();
      error.put("message", message);
      return badRequest(error);
    }
    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();
    if (StringUtils.isBlank(email)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Email must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    if (StringUtils.isBlank(password)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Password must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    if (StringUtils.isBlank(resetToken)) {
      JsonNode invalidCredsJson = Json.newObject().put("message", "Reset token must not be empty.");
      return Results.badRequest(invalidCredsJson);
    }
    final Urn userUrn = new CorpuserUrn(email);
    final String userUrnString = userUrn.toString();
    _authClient.resetNativeUserCredentials(userUrnString, password, resetToken);
    final String accessToken = _authClient.generateSessionTokenForUser(userUrn.getId());
    return createSession(userUrnString, accessToken);
  }
  private Optional