/**
   * Manually log out the specified user
   *
   * @param sUserID The user ID to log out
   * @return {@link EChange} if something changed
   */
  @Nonnull
  public EChange logoutUser(@Nullable final String sUserID) {
    m_aRWLock.writeLock().lock();
    LoginInfo aInfo;
    try {
      aInfo = m_aLoggedInUsers.remove(sUserID);
      if (aInfo == null) {
        AuditHelper.onAuditExecuteSuccess("logout", sUserID, "user-not-logged-in");
        return EChange.UNCHANGED;
      }

      // Ensure that the SessionUser is empty. This is only relevant if user is
      // manually logged out without destructing the underlying session
      final SessionUserHolder aSUH =
          SessionUserHolder.getInstanceIfInstantiatedInScope(aInfo.getSessionScope());
      if (aSUH != null) aSUH._reset();

      // Set logout time - in case somebody has a strong reference to the
      // LoginInfo object
      aInfo.setLogoutDTNow();
    } finally {
      m_aRWLock.writeLock().unlock();
    }

    s_aLogger.info(
        "Logged out user '"
            + sUserID
            + "' after "
            + new Period(aInfo.getLoginDT(), aInfo.getLogoutDT()).toString());
    AuditHelper.onAuditExecuteSuccess("logout", sUserID);

    // Execute callback as the very last action
    for (final IUserLogoutCallback aUserLogoutCallback : m_aUserLogoutCallbacks.getAllCallbacks())
      try {
        aUserLogoutCallback.onUserLogout(aInfo);
      } catch (final Throwable t) {
        s_aLogger.error(
            "Failed to invoke onUserLogout callback on "
                + aUserLogoutCallback.toString()
                + "("
                + aInfo.toString()
                + ")",
            t);
      }

    return EChange.CHANGED;
  }
 /**
  * @return The user currently logged in this session or <code>null</code> if no user is logged in.
  */
 @Nullable
 public IUser getCurrentUser() {
   final SessionUserHolder aSUH = SessionUserHolder.getInstanceIfInstantiated();
   return aSUH == null ? null : aSUH.m_aUser;
 }
 /**
  * @return The ID of the user logged in this session or <code>null</code> if no user is logged in.
  */
 @Nullable
 public String getCurrentUserID() {
   final SessionUserHolder aSUH = SessionUserHolder.getInstanceIfInstantiated();
   return aSUH == null ? null : aSUH.m_sUserID;
 }
  /**
   * Login the passed user and require a set of certain roles, the used needs to have to login here.
   *
   * @param aUser The user to log-in. May be <code>null</code>. When the user is <code>null</code>
   *     the login must fail.
   * @param sPlainTextPassword Plain text password to use. May be <code>null</code>.
   * @param aRequiredRoleIDs A set of required role IDs, the user needs to have. May be <code>null
   *     </code>.
   * @return Never <code>null</code> login status.
   */
  @Nonnull
  public ELoginResult loginUser(
      @Nullable final IUser aUser,
      @Nullable final String sPlainTextPassword,
      @Nullable final Collection<String> aRequiredRoleIDs) {
    if (aUser == null) return ELoginResult.USER_NOT_EXISTING;

    final String sUserID = aUser.getID();

    // Deleted user?
    if (aUser.isDeleted()) {
      AuditHelper.onAuditExecuteFailure("login", sUserID, "user-is-deleted");
      return _onLoginError(sUserID, ELoginResult.USER_IS_DELETED);
    }

    // Disabled user?
    if (aUser.isDisabled()) {
      AuditHelper.onAuditExecuteFailure("login", sUserID, "user-is-disabled");
      return _onLoginError(sUserID, ELoginResult.USER_IS_DISABLED);
    }

    // Are all roles present?
    if (!SecurityHelper.hasUserAllRoles(sUserID, aRequiredRoleIDs)) {
      AuditHelper.onAuditExecuteFailure(
          "login",
          sUserID,
          "user-is-missing-required-roles",
          StringHelper.getToString(aRequiredRoleIDs));
      return _onLoginError(sUserID, ELoginResult.USER_IS_MISSING_ROLE);
    }

    // Check the password
    final UserManager aUserMgr = PhotonSecurityManager.getUserMgr();
    if (!aUserMgr.areUserIDAndPasswordValid(sUserID, sPlainTextPassword)) {
      AuditHelper.onAuditExecuteFailure("login", sUserID, "invalid-password");
      return _onLoginError(sUserID, ELoginResult.INVALID_PASSWORD);
    }

    // Check if the password hash needs to be updated
    final String sExistingPasswordHashAlgorithmName = aUser.getPasswordHash().getAlgorithmName();
    final String sDefaultPasswordHashAlgorithmName =
        GlobalPasswordSettings.getPasswordHashCreatorManager()
            .getDefaultPasswordHashCreatorAlgorithmName();
    if (!sExistingPasswordHashAlgorithmName.equals(sDefaultPasswordHashAlgorithmName)) {
      // This implicitly implies using the default hash creator algorithm
      // This automatically saves the file
      aUserMgr.setUserPassword(sUserID, sPlainTextPassword);
      s_aLogger.info(
          "Updated password hash of user '"
              + sUserID
              + "' from algorithm '"
              + sExistingPasswordHashAlgorithmName
              + "' to '"
              + sDefaultPasswordHashAlgorithmName
              + "'");
    }

    boolean bLoggedOutUser = false;
    LoginInfo aInfo;
    m_aRWLock.writeLock().lock();
    try {
      if (m_aLoggedInUsers.containsKey(sUserID)) {
        // The user is already logged in
        if (isLogoutAlreadyLoggedInUser()) {
          // Explicitly log out
          logoutUser(sUserID);

          // Just a short check
          if (m_aLoggedInUsers.containsKey(sUserID))
            throw new IllegalStateException("Failed to logout '" + sUserID + "'");

          AuditHelper.onAuditExecuteSuccess("logout-in-login", sUserID);
          bLoggedOutUser = true;
        } else {
          AuditHelper.onAuditExecuteFailure("login", sUserID, "user-already-logged-in");
          return _onLoginError(sUserID, ELoginResult.USER_ALREADY_LOGGED_IN);
        }
      }

      final SessionUserHolder aSUH = SessionUserHolder.getInstance();
      if (aSUH.hasUser()) {
        // This session already has a user
        s_aLogger.warn(
            "The session user holder already has the user ID '"
                + aSUH.getUserID()
                + "' so the new ID '"
                + sUserID
                + "' will not be set!");
        AuditHelper.onAuditExecuteFailure("login", sUserID, "session-already-has-user");
        return _onLoginError(sUserID, ELoginResult.SESSION_ALREADY_HAS_USER);
      }

      aInfo = new LoginInfo(aUser, ScopeManager.getSessionScope());
      m_aLoggedInUsers.put(sUserID, aInfo);
      aSUH.setUser(this, aUser);
    } finally {
      m_aRWLock.writeLock().unlock();
    }

    s_aLogger.info(
        "Logged in user '" + sUserID + "' with login name '" + aUser.getLoginName() + "'");
    AuditHelper.onAuditExecuteSuccess("login", sUserID, aUser.getLoginName());

    // Execute callback as the very last action
    for (final IUserLoginCallback aUserLoginCallback : m_aUserLoginCallbacks.getAllCallbacks())
      try {
        aUserLoginCallback.onUserLogin(aInfo);
      } catch (final Throwable t) {
        s_aLogger.error(
            "Failed to invoke onUserLogin callback on "
                + aUserLoginCallback.toString()
                + "("
                + aInfo.toString()
                + ")",
            t);
      }

    return bLoggedOutUser ? ELoginResult.SUCCESS_WITH_LOGOUT : ELoginResult.SUCCESS;
  }