All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mx.path.model.mdx.web.controller.AuthenticationController Maven / Gradle / Ivy

The newest version!
package com.mx.path.model.mdx.web.controller;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

import com.google.common.hash.Hashing;
import com.mx.path.core.common.accessor.BadRequestException;
import com.mx.path.core.common.lang.Strings;
import com.mx.path.core.context.Session;
import com.mx.path.core.context.Session.SessionState;
import com.mx.path.gateway.accessor.AccessorResponse;
import com.mx.path.gateway.context.Scope;
import com.mx.path.model.mdx.model.authorization.HtmlPage;
import com.mx.path.model.mdx.model.id.Authentication;
import com.mx.path.model.mdx.model.id.ForgotUsername;
import com.mx.path.model.mdx.model.id.ResetPassword;
import com.mx.path.model.mdx.model.id.UnlockUser;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "{clientId}", produces = BaseController.MDX_MEDIA)
public class AuthenticationController extends BaseController {

  @SuppressWarnings("MagicNumber")
  @RequestMapping(value = "/authentications", method = RequestMethod.POST, consumes = MDX_MEDIA)
  public final ResponseEntity authenticate(@PathVariable("clientId") String clientId, HttpServletRequest request) {
    return versioned(request)
        .defaultVersion(Authentication.class, Authentication.class, authentication -> {
          return authenticate(clientId, authentication);
        })
        .version(20240213, com.mx.path.model.mdx.model.id.v20240213.Authentication.class, com.mx.path.model.mdx.model.id.v20240213.Authentication.class, authentication -> {
          return authenticate(clientId, authentication);
        })
        .execute();
  }

  /**
   * Legacy and MDXOnDemand version of authenticate
   *
   * @param clientId
   * @param requestAuthentication
   * @return
   */
  @RequestMapping(value = { "/sessions" }, method = RequestMethod.POST, consumes = MDX_ONDEMAND_MEDIA, produces = MDX_ONDEMAND_MEDIA)
  public final ResponseEntity authenticate(@PathVariable("clientId") String clientId, @RequestBody Authentication requestAuthentication) {
    ensureFeature("identity");

    // Delete existing session if it exists;
    Session.deleteCurrent();
    Session.createSession();

    // Store clientId
    Session.current().setClientId(clientId);
    Session.current().setDeviceId(requestAuthentication.getDeviceId());
    Session.current().setDeviceMake(requestAuthentication.getDeviceMake());
    Session.current().setDeviceModel(requestAuthentication.getDeviceModel());
    Session.current().setDeviceOperatingSystem(requestAuthentication.getDeviceOperatingSystem());
    Session.current().setDeviceOperatingSystemVersion(requestAuthentication.getDeviceOperatingSystemVersion());
    Session.current().setDeviceHeight(requestAuthentication.getDeviceHeight());
    Session.current().setDeviceLatitude(requestAuthentication.getDeviceLatitude());
    Session.current().setDeviceLongitude(requestAuthentication.getDeviceLongitude());
    Session.current().setDeviceWidth(requestAuthentication.getDeviceWidth());

    // Store login for troubleshooting failed authentication
    dumpEncryptedLoginHashToSession(requestAuthentication.getLogin());

    AccessorResponse accessorResponse;
    accessorResponse = getAuthenticationResult(requestAuthentication);

    accessorResponse.getResult().withId(Session.current().getId());
    HttpHeaders headers = new HttpHeaders();
    headers.add("mx-session-key", Session.current().getId());

    // Session is authenticated if the user id is set.
    if (accessorResponse.getResult().getUserId() != null) {
      Session.current().setUserId(accessorResponse.getResult().getUserId());
      Session.current().setSessionState(SessionState.AUTHENTICATED);
    }

    HttpStatus status = HttpStatus.OK;
    if (accessorResponse.getResult().getChallenges() != null
        && accessorResponse.getResult().getChallenges().size() > 0) {
      status = HttpStatus.ACCEPTED;
    }

    return new ResponseEntity<>(accessorResponse.getResult().wrapped(), createMultiMapForResponse(accessorResponse.getHeaders(), headers),
        status);
  }

  @RequestMapping(value = "/authentications/{sessionId}/callback", method = RequestMethod.GET, produces = "text/html")
  public final ResponseEntity callback(@RequestParam("token") String token) {
    HtmlPage htmlPage = gateway().id().callback(token).getResult();
    return ResponseEntity.ok().body(htmlPage.getContent());
  }

  @RequestMapping(value = "/authentications/{sessionKey}", method = RequestMethod.DELETE)
  public final ResponseEntity logout() {
    try {
      String sessionKey = null;
      if (Session.current() != null) {
        sessionKey = Session.current().getId();
      }
      gateway().id().logout(sessionKey);
    } finally {
      Session.deleteCurrent();
    }

    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }

  @SuppressWarnings("MagicNumber")
  @RequestMapping(value = "/authentications/{sessionId}", method = RequestMethod.PUT, consumes = MDX_MEDIA)
  public final ResponseEntity resumeMfa(@PathVariable("clientId") String clientId, @PathVariable("sessionId") String sessionKey, HttpServletRequest request) {
    if (!Objects.equals(sessionKey, Session.current().getId())) {
      throw new BadRequestException("Session key mismatch. Header and path session keys don't match.", "Session key mismatch. Header and path session keys don't match.").withReport(true);
    }

    return versioned(request)
        .defaultVersion(Authentication.class, Authentication.class, authentication -> {
          AccessorResponse response = gateway()
              .id()
              .resumeMFA(authentication);
          response.getResult().withId(Session.current().getId());

          HttpHeaders headers = new HttpHeaders();
          headers.add("mx-session-key", response.getResult().getId());

          // Session is authenticated if the user id is set.
          if (response.getResult().getUserId() != null) {
            Session.current().setUserId(response.getResult().getUserId());
            Session.current().setSessionState(SessionState.AUTHENTICATED);
          }

          HttpStatus status = HttpStatus.OK;
          if (response.getResult().getChallenges() != null
              && response.getResult().getChallenges().size() > 0) {
            status = HttpStatus.ACCEPTED;
          }

          return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders(), headers), status);
        }).version(20240213, com.mx.path.model.mdx.model.id.v20240213.Authentication.class, com.mx.path.model.mdx.model.id.v20240213.Authentication.class, authentication -> {
          AccessorResponse response = gateway()
              .id()
              .resumeMFA(authentication);
          response.getResult().withId(Session.current().getId());

          HttpHeaders headers = new HttpHeaders();
          headers.add("mx-session-key", response.getResult().getId());

          // Session is authenticated if the user id is set.
          if (response.getResult().getUserId() != null) {
            Session.current().setUserId(response.getResult().getUserId());
            Session.current().setSessionState(SessionState.AUTHENTICATED);
          }

          HttpStatus status = HttpStatus.OK;
          if (response.getResult().getChallenges() != null
              && response.getResult().getChallenges().size() > 0) {
            status = HttpStatus.ACCEPTED;
          }

          return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders(), headers), status);
        }).execute();
  }

  @SuppressWarnings("MagicNumber")
  @RequestMapping(value = "/authentications/start", method = RequestMethod.POST, consumes = MDX_MEDIA)
  public final ResponseEntity start(@PathVariable("clientId") String clientId, HttpServletRequest request) {

    // Delete existing session if it exists;
    Session.deleteCurrent();
    Session.createSession();

    return versioned(request)
        .defaultVersion(Authentication.class, Authentication.class, authentication -> {
          // Store clientId
          Session.current().setClientId(clientId);
          Session.current().setDeviceId(authentication.getDeviceId());
          Session.current().setDeviceMake(authentication.getDeviceMake());
          Session.current().setDeviceModel(authentication.getDeviceModel());
          Session.current().setDeviceOperatingSystem(authentication.getDeviceOperatingSystem());
          Session.current().setDeviceOperatingSystemVersion(authentication.getDeviceOperatingSystemVersion());
          Session.current().setDeviceHeight(authentication.getDeviceHeight());
          Session.current().setDeviceLatitude(authentication.getDeviceLatitude());
          Session.current().setDeviceLongitude(authentication.getDeviceLongitude());
          Session.current().setDeviceWidth(authentication.getDeviceWidth());

          AccessorResponse response = gateway().id().startAuthentication(authentication);
          response.getResult().withId(Session.current().getId());
          HttpHeaders headers = new HttpHeaders();
          headers.add("mx-session-key", Session.current().getId());

          // Return 204 if challenges are null to indicate non-federated login
          // Return 202 to trigger follow-up PUT /authentications/{session_key} call w/ token when challenges are NOT null
          // Return 200 if user_id is NOT null, no follow-up PUT /authentications/{session_key} call needed
          HttpStatus status = HttpStatus.NO_CONTENT;
          if (response.getResult().getChallenges() != null
              && response.getResult().getChallenges().size() > 0) {
            status = HttpStatus.ACCEPTED;
          } else if (Strings.isNotBlank(response.getResult().getUserId())) {
            status = HttpStatus.OK;
            Session.current().setUserId(response.getResult().getUserId());
            Session.current().setSessionState(SessionState.AUTHENTICATED);
          }
          return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders(), headers), status);
        }).version(20240213, com.mx.path.model.mdx.model.id.v20240213.Authentication.class, com.mx.path.model.mdx.model.id.v20240213.Authentication.class, authentication -> {
          // Store clientId
          Session.current().setClientId(clientId);
          Session.current().setDeviceId(authentication.getDeviceId());
          Session.current().setDeviceMake(authentication.getDeviceMake());
          Session.current().setDeviceModel(authentication.getDeviceModel());
          Session.current().setDeviceOperatingSystem(authentication.getDeviceOperatingSystem());
          Session.current().setDeviceOperatingSystemVersion(authentication.getDeviceOperatingSystemVersion());
          Session.current().setDeviceHeight(authentication.getDeviceHeight());
          Session.current().setDeviceLatitude(authentication.getDeviceLatitude());
          Session.current().setDeviceLongitude(authentication.getDeviceLongitude());
          Session.current().setDeviceWidth(authentication.getDeviceWidth());

          AccessorResponse response = gateway().id().startAuthentication(authentication);
          response.getResult().withId(Session.current().getId());
          HttpHeaders headers = new HttpHeaders();
          headers.add("mx-session-key", Session.current().getId());

          // Return 204 if challenges are null to indicate non-federated login
          // Return 202 to trigger follow-up PUT /authentications/{session_key} call w/ token when challenges are NOT null
          // Return 200 if user_id is NOT null, no follow-up PUT /authentications/{session_key} call needed
          HttpStatus status = HttpStatus.NO_CONTENT;
          if (response.getResult().getChallenges() != null
              && response.getResult().getChallenges().size() > 0) {
            status = HttpStatus.ACCEPTED;
          } else if (Strings.isNotBlank(response.getResult().getUserId())) {
            status = HttpStatus.OK;
            Session.current().setUserId(response.getResult().getUserId());
            Session.current().setSessionState(SessionState.AUTHENTICATED);
          }
          return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders(), headers), status);
        }).execute();
  }

  /***
   * Initiates the reset password workflow.
   *
   * Always creates a new session, discards old session
   *
   * @return
   */
  @RequestMapping(value = "/reset_password", method = RequestMethod.POST)
  public final ResponseEntity resetPassword() {
    //This endpoint always creates a new session when it called even if there is an existing session being passed
    Session.deleteCurrent();
    Session.createSession();

    AccessorResponse response = gateway().id().resetPassword();

    HttpHeaders headers = new HttpHeaders();
    headers.add("mx-session-key", Session.current().getId());
    return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders(), headers), HttpStatus.OK);
  }

  @RequestMapping(value = "/reset_password/challenges/{challengeId}", method = RequestMethod.PUT, consumes = MDX_MEDIA)
  public final ResponseEntity resetPassword(@RequestBody ResetPassword resetPasswordAuthentication) {
    AccessorResponse response = gateway().id().answerResetPassword(resetPasswordAuthentication);
    ResetPassword result = response.getResult();
    // Return 202 returning challenge questions
    HttpStatus status = HttpStatus.NO_CONTENT;
    if (result.getChallenge() != null || result.getChallenges() != null
        && result.getChallenges().size() > 0) {
      status = HttpStatus.ACCEPTED;
    }
    return new ResponseEntity<>(result.wrapped(), createMultiMapForResponse(response.getHeaders()), status);
  }

  @RequestMapping(value = "/forgot_username", method = RequestMethod.POST)
  public final ResponseEntity forgotUsername() {
    AccessorResponse response = gateway().id().forgotUsername();
    return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), HttpStatus.OK);
  }

  @RequestMapping(value = "/forgot_username/challenges/{challengeId}", method = RequestMethod.PUT, consumes = MDX_MEDIA)
  public final ResponseEntity forgotUsername(@RequestBody ForgotUsername forgotUsernameRequest) {
    AccessorResponse response = gateway().id().answerForgotUsername(forgotUsernameRequest);
    ForgotUsername result = response.getResult();
    // Return 202 returning challenge questions
    HttpStatus status = HttpStatus.NO_CONTENT;
    if (result.getChallenge() != null) {
      status = HttpStatus.ACCEPTED;
    }
    return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), status);
  }

  @RequestMapping(value = "/unlock_user", method = RequestMethod.POST)
  public final ResponseEntity unlockUser(@RequestBody UnlockUser unlockUser) {
    AccessorResponse response = gateway().id().unlockUser(unlockUser);
    UnlockUser result = response.getResult();
    // Return 202 returning challenge questions
    HttpStatus status = HttpStatus.NO_CONTENT;
    if (result.getChallenges() != null && !result.getChallenges().isEmpty()) {
      status = HttpStatus.ACCEPTED;
    }
    return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), status);
  }

  // Package private

  final void dumpEncryptedLoginHashToSession(String login) {
    if (Strings.isNotBlank(login)) {
      String sha256hex = Hashing.sha256()
          .hashString(login, StandardCharsets.UTF_8)
          .toString();
      Session.current().sput(Scope.Session, "loginHash", sha256hex);
    }
  }

  @SuppressWarnings("PMD.UselessParentheses")
  final AccessorResponse getAuthenticationResult(Authentication requestAuthentication) {
    AccessorResponse result;
    if (Strings.isNotBlank(requestAuthentication.getToken())
        || Strings.isNotBlank(requestAuthentication.getAccessToken())
        || (Strings.isNotBlank(requestAuthentication.getLogin())
            && requestAuthentication.getPassword() != null
            && requestAuthentication.getPassword().length > 0)) {
      // ---------------------------------------------
      // Login/Password OR token Authentication
      result = gateway().id().authenticate(requestAuthentication);
    } else if (Strings.isNotBlank(requestAuthentication.getUserkey())) {
      // ---------------------------------------------
      // OnDemand Authentication
      result = gateway().id().authenticateWithUserKey(requestAuthentication);

    } else {
      // ---------------------------------------------
      // Invalid authentication request

      throw new BadRequestException("Invalid authentication body");
    }
    return result;
  }

  @SuppressWarnings("PMD.UselessParentheses")
  final AccessorResponse getAuthenticationResult(com.mx.path.model.mdx.model.id.v20240213.Authentication requestAuthentication) {
    AccessorResponse result;
    if (Strings.isNotBlank(requestAuthentication.getToken())
        || Strings.isNotBlank(requestAuthentication.getAccessToken())
        || (Strings.isNotBlank(requestAuthentication.getLogin())
            && requestAuthentication.getPassword() != null
            && requestAuthentication.getPassword().length > 0)) {
      // ---------------------------------------------
      // Login/Password OR token Authentication
      result = gateway().id().authenticate(requestAuthentication);
    } else {
      // ---------------------------------------------
      // Invalid authentication request

      throw new BadRequestException("Invalid authentication body");
    }

    return result;
  }

  /**
   * 20240213 version of authenticate
   *
   * @param clientId
   * @param requestAuthentication
   * @return
   */
  protected final ResponseEntity authenticate(@PathVariable("clientId") String clientId, @RequestBody com.mx.path.model.mdx.model.id.v20240213.Authentication requestAuthentication) {
    ensureFeature("identity");

    // Delete existing session if it exists;
    Session.deleteCurrent();
    Session.createSession();

    // Store clientId
    Session.current().setClientId(clientId);
    Session.current().setDeviceId(requestAuthentication.getDeviceId());
    Session.current().setDeviceMake(requestAuthentication.getDeviceMake());
    Session.current().setDeviceModel(requestAuthentication.getDeviceModel());
    Session.current().setDeviceOperatingSystem(requestAuthentication.getDeviceOperatingSystem());
    Session.current().setDeviceOperatingSystemVersion(requestAuthentication.getDeviceOperatingSystemVersion());
    Session.current().setDeviceHeight(requestAuthentication.getDeviceHeight());
    Session.current().setDeviceLatitude(requestAuthentication.getDeviceLatitude());
    Session.current().setDeviceLongitude(requestAuthentication.getDeviceLongitude());
    Session.current().setDeviceWidth(requestAuthentication.getDeviceWidth());

    // Store login for troubleshooting failed authentication
    dumpEncryptedLoginHashToSession(requestAuthentication.getLogin());

    AccessorResponse accessorResponse;
    accessorResponse = getAuthenticationResult(requestAuthentication);

    accessorResponse.getResult().withId(Session.current().getId());
    HttpHeaders headers = new HttpHeaders();
    headers.add("mx-session-key", Session.current().getId());

    // Session is authenticated if the user id is set.
    if (accessorResponse.getResult().getUserId() != null) {
      Session.current().setUserId(accessorResponse.getResult().getUserId());
      Session.current().setSessionState(SessionState.AUTHENTICATED);
    }

    HttpStatus status = HttpStatus.OK;
    if (accessorResponse.getResult().getChallenges() != null
        && accessorResponse.getResult().getChallenges().size() > 0) {
      status = HttpStatus.ACCEPTED;
    }

    return new ResponseEntity<>(accessorResponse.getResult().wrapped(), createMultiMapForResponse(accessorResponse.getHeaders(), headers),
        status);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy