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

com.helger.photon.security.user.UserManager Maven / Gradle / Ivy

There is a newer version: 8.1.0
Show newest version
/**
 * Copyright (C) 2014-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.photon.security.user;

import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.annotation.ReturnsMutableObject;
import com.helger.commons.callback.CallbackList;
import com.helger.commons.collection.ext.ICommonsList;
import com.helger.commons.state.EChange;
import com.helger.commons.string.StringHelper;
import com.helger.photon.basic.app.dao.IReloadableDAO;
import com.helger.photon.basic.app.dao.impl.AbstractMapBasedWALDAO;
import com.helger.photon.basic.app.dao.impl.DAOException;
import com.helger.photon.basic.audit.AuditHelper;
import com.helger.photon.security.CSecurity;
import com.helger.photon.security.object.ObjectHelper;
import com.helger.photon.security.password.GlobalPasswordSettings;
import com.helger.security.password.hash.PasswordHash;
import com.helger.security.password.salt.IPasswordSalt;
import com.helger.security.password.salt.PasswordSalt;

/**
 * This class manages the available users.
 *
 * @author Philip Helger
 */
@ThreadSafe
public class UserManager extends AbstractMapBasedWALDAO  implements IReloadableDAO
{
  private final CallbackList  m_aCallbacks = new CallbackList<> ();

  public UserManager (@Nonnull @Nonempty final String sFilename) throws DAOException
  {
    super (User.class, sFilename);
  }

  public void reload () throws DAOException
  {
    m_aRWLock.writeLockedThrowing ( () -> {
      internalRemoveAllItemsNoCallback ();
      initialRead ();
    });
  }

  public void createDefaults ()
  {
    // Create Administrator
    if (!containsWithID (CSecurity.USER_ADMINISTRATOR_ID))
      m_aRWLock.writeLocked ( () -> internalCreateItem (new User (CSecurity.USER_ADMINISTRATOR_ID,
                                                                  CSecurity.USER_ADMINISTRATOR_LOGIN,
                                                                  CSecurity.USER_ADMINISTRATOR_EMAIL,
                                                                  GlobalPasswordSettings.createUserDefaultPasswordHash (new PasswordSalt (),
                                                                                                                        CSecurity.USER_ADMINISTRATOR_PASSWORD),
                                                                  CSecurity.USER_ADMINISTRATOR_NAME,
                                                                  (String) null,
                                                                  (String) null,
                                                                  (Locale) null,
                                                                  (Map ) null,
                                                                  false)));

    // Create regular user
    if (!containsWithID (CSecurity.USER_USER_ID))
      m_aRWLock.writeLocked ( () -> internalCreateItem (new User (CSecurity.USER_USER_ID,
                                                                  CSecurity.USER_USER_LOGIN,
                                                                  CSecurity.USER_USER_EMAIL,
                                                                  GlobalPasswordSettings.createUserDefaultPasswordHash (new PasswordSalt (),
                                                                                                                        CSecurity.USER_USER_PASSWORD),
                                                                  CSecurity.USER_USER_NAME,
                                                                  (String) null,
                                                                  (String) null,
                                                                  (Locale) null,
                                                                  (Map ) null,
                                                                  false)));

    // Create guest user
    if (!containsWithID (CSecurity.USER_GUEST_ID))
      m_aRWLock.writeLocked ( () -> internalCreateItem (new User (CSecurity.USER_GUEST_ID,
                                                                  CSecurity.USER_GUEST_LOGIN,
                                                                  CSecurity.USER_GUEST_EMAIL,
                                                                  GlobalPasswordSettings.createUserDefaultPasswordHash (new PasswordSalt (),
                                                                                                                        CSecurity.USER_GUEST_PASSWORD),
                                                                  CSecurity.USER_GUEST_NAME,
                                                                  (String) null,
                                                                  (String) null,
                                                                  (Locale) null,
                                                                  (Map ) null,
                                                                  false)));
  }

  /**
   * @return The user callback list. Never null.
   */
  @Nonnull
  @ReturnsMutableObject ("design")
  public CallbackList  getUserModificationCallbacks ()
  {
    return m_aCallbacks;
  }

  /**
   * Create a new user.
   *
   * @param sLoginName
   *        Login name of the user. May neither be null nor
   *        empty.This login name must be unique over all existing users.
   * @param sEmailAddress
   *        The email address. May be null.
   * @param sPlainTextPassword
   *        The plain text password to be used. May neither be null
   *        nor empty.
   * @param sFirstName
   *        The users first name. May be null.
   * @param sLastName
   *        The users last name. May be null.
   * @param sDescription
   *        Optional description for the user. May be null.
   * @param aDesiredLocale
   *        The users default locale. May be null.
   * @param aCustomAttrs
   *        Custom attributes. May be null.
   * @return The created user or null if another user with the same
   *         email address is already present.
   * @param bDisabled
   *        true if the user is disabled
   */
  @Nullable
  public IUser createNewUser (@Nonnull @Nonempty final String sLoginName,
                              @Nullable final String sEmailAddress,
                              @Nonnull final String sPlainTextPassword,
                              @Nullable final String sFirstName,
                              @Nullable final String sLastName,
                              @Nullable final String sDescription,
                              @Nullable final Locale aDesiredLocale,
                              @Nullable final Map  aCustomAttrs,
                              final boolean bDisabled)
  {
    ValueEnforcer.notEmpty (sLoginName, "LoginName");
    ValueEnforcer.notNull (sPlainTextPassword, "PlainTextPassword");

    if (getUserOfLoginName (sLoginName) != null)
    {
      // Another user with this login name already exists
      AuditHelper.onAuditCreateFailure (User.OT, "login-name-already-in-use", sLoginName);
      return null;
    }

    // Create user
    final User aUser = new User (sLoginName,
                                 sEmailAddress,
                                 GlobalPasswordSettings.createUserDefaultPasswordHash (new PasswordSalt (),
                                                                                       sPlainTextPassword),
                                 sFirstName,
                                 sLastName,
                                 sDescription,
                                 aDesiredLocale,
                                 aCustomAttrs,
                                 bDisabled);

    m_aRWLock.writeLocked ( () -> {
      internalCreateItem (aUser);
    });
    AuditHelper.onAuditCreateSuccess (User.OT,
                                      aUser.getID (),
                                      sLoginName,
                                      sEmailAddress,
                                      sFirstName,
                                      sLastName,
                                      sDescription,
                                      aDesiredLocale,
                                      aCustomAttrs,
                                      Boolean.valueOf (bDisabled));

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserCreated (aUser, false));

    return aUser;
  }

  /**
   * Create a predefined user.
   *
   * @param sID
   *        The ID to use
   * @param sLoginName
   *        Login name of the user. May neither be null nor empty.
   *        This login name must be unique over all existing users.
   * @param sEmailAddress
   *        The email address. May be null.
   * @param sPlainTextPassword
   *        The plain text password to be used. May neither be null
   *        nor empty.
   * @param sFirstName
   *        The users first name. May be null.
   * @param sLastName
   *        The users last name. May be null.
   * @param sDescription
   *        Optional description for the user. May be null.
   * @param aDesiredLocale
   *        The users default locale. May be null.
   * @param aCustomAttrs
   *        Custom attributes. May be null.
   * @return The created user or null if another user with the same
   *         email address is already present.
   * @param bDisabled
   *        true if the user is disabled
   */
  @Nullable
  public IUser createPredefinedUser (@Nonnull @Nonempty final String sID,
                                     @Nonnull @Nonempty final String sLoginName,
                                     @Nullable final String sEmailAddress,
                                     @Nonnull final String sPlainTextPassword,
                                     @Nullable final String sFirstName,
                                     @Nullable final String sLastName,
                                     @Nullable final String sDescription,
                                     @Nullable final Locale aDesiredLocale,
                                     @Nullable final Map  aCustomAttrs,
                                     final boolean bDisabled)
  {
    ValueEnforcer.notEmpty (sLoginName, "LoginName");
    ValueEnforcer.notNull (sPlainTextPassword, "PlainTextPassword");

    if (getUserOfLoginName (sLoginName) != null)
    {
      // Another user with this login name already exists
      AuditHelper.onAuditCreateFailure (User.OT, "login-name-already-in-use", sLoginName, "predefined-user");
      return null;
    }

    // Create user
    final User aUser = new User (sID,
                                 sLoginName,
                                 sEmailAddress,
                                 GlobalPasswordSettings.createUserDefaultPasswordHash (new PasswordSalt (),
                                                                                       sPlainTextPassword),
                                 sFirstName,
                                 sLastName,
                                 sDescription,
                                 aDesiredLocale,
                                 aCustomAttrs,
                                 bDisabled);

    m_aRWLock.writeLocked ( () -> {
      internalCreateItem (aUser);
    });
    AuditHelper.onAuditCreateSuccess (User.OT,
                                      aUser.getID (),
                                      "predefined-user",
                                      sLoginName,
                                      sEmailAddress,
                                      sFirstName,
                                      sLastName,
                                      sDescription,
                                      aDesiredLocale,
                                      aCustomAttrs,
                                      Boolean.valueOf (bDisabled));

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserCreated (aUser, true));

    return aUser;
  }

  /**
   * Get the user with the specified ID.
   *
   * @param sUserID
   *        The user ID to resolve. May be null.
   * @return null if no such user exists
   */
  @Nullable
  public IUser getUserOfID (@Nullable final String sUserID)
  {
    return getOfID (sUserID);
  }

  @Nullable
  public IUser getActiveUserOfID (@Nullable final String sUserID)
  {
    final IUser aUser = getOfID (sUserID);
    return aUser == null || aUser.isDeleted () || aUser.isDisabled () ? null : aUser;
  }

  /**
   * Get the user with the specified login name
   *
   * @param sLoginName
   *        The login name to be checked. May be null.
   * @return null if no such user exists
   */
  @Nullable
  public IUser getUserOfLoginName (@Nullable final String sLoginName)
  {
    if (StringHelper.hasNoText (sLoginName))
      return null;

    return findFirst (x -> x.getLoginName ().equals (sLoginName));
  }

  /**
   * Get the user with the specified email address
   *
   * @param sEmailAddress
   *        The email address to be checked. May be null.
   * @return null if no such user exists
   */
  @Nullable
  public IUser getUserOfEmailAddress (@Nullable final String sEmailAddress)
  {
    if (StringHelper.hasNoText (sEmailAddress))
      return null;

    return findFirst (x -> sEmailAddress.equals (x.getEmailAddress ()));
  }

  /**
   * @return A non-null collection of all contained users
   *         (enabled+disabled and deleted+not-deleted)
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllUsers ()
  {
    return getAll ();
  }

  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllUsers (@Nullable final Predicate  aFilter)
  {
    return getAll (aFilter);
  }

  @Nonnegative
  public int getUserCount (@Nullable final Predicate  aFilter)
  {
    return getCount (aFilter);
  }

  /**
   * @return A non-null collection of all contained enabled and
   *         not-deleted users
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllActiveUsers ()
  {
    return getAll (x -> !x.isDeleted () && x.isEnabled ());
  }

  /**
   * @return The number of all contained enabled and not-deleted users
   */
  @Nonnegative
  public int getActiveUserCount ()
  {
    return getCount (x -> !x.isDeleted () && x.isEnabled ());
  }

  /**
   * @return A non-null collection of all contained disabled and
   *         not-deleted users
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllDisabledUsers ()
  {
    return getAll (x -> !x.isDeleted () && x.isDisabled ());
  }

  /**
   * @return A non-null collection of all contained not deleted
   *         users
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllNotDeletedUsers ()
  {
    return getAll (x -> !x.isDeleted ());
  }

  /**
   * @return A non-null collection of all contained deleted users
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllDeletedUsers ()
  {
    return getAll (x -> x.isDeleted ());
  }

  /**
   * @return true if any non-deleted, enabled user exists.
   */
  public boolean containsAnyActiveUser ()
  {
    return containsAny (x -> !x.isDeleted () && x.isEnabled ());
  }

  /**
   * Change the modifiable data of a user
   *
   * @param sUserID
   *        The ID of the user to be modified. May be null.
   * @param sNewLoginName
   *        The new login name. May not be null.
   * @param sNewEmailAddress
   *        The new email address. May be null.
   * @param sNewFirstName
   *        The new first name. May be null.
   * @param sNewLastName
   *        The new last name. May be null.
   * @param sNewDescription
   *        Optional new description for the user. May be null.
   * @param aNewDesiredLocale
   *        The new desired locale. May be null.
   * @param aNewCustomAttrs
   *        Custom attributes. May be null.
   * @param bNewDisabled
   *        true if the user is disabled
   * @return {@link EChange}
   */
  @Nonnull
  public EChange setUserData (@Nullable final String sUserID,
                              @Nonnull @Nonempty final String sNewLoginName,
                              @Nullable final String sNewEmailAddress,
                              @Nullable final String sNewFirstName,
                              @Nullable final String sNewLastName,
                              @Nullable final String sNewDescription,
                              @Nullable final Locale aNewDesiredLocale,
                              @Nullable final Map  aNewCustomAttrs,
                              final boolean bNewDisabled)
  {
    // Resolve user
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditModifyFailure (User.OT, sUserID, "no-such-user-id");
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      EChange eChange = aUser.setLoginName (sNewLoginName);
      eChange = eChange.or (aUser.setEmailAddress (sNewEmailAddress));
      eChange = eChange.or (aUser.setFirstName (sNewFirstName));
      eChange = eChange.or (aUser.setLastName (sNewLastName));
      eChange = eChange.or (aUser.setDescription (sNewDescription));
      eChange = eChange.or (aUser.setDesiredLocale (aNewDesiredLocale));
      eChange = eChange.or (aUser.setDisabled (bNewDisabled));
      eChange = eChange.or (aUser.getMutableAttributes ().clear ());
      eChange = eChange.or (aUser.getMutableAttributes ().setAttributes (aNewCustomAttrs));
      if (eChange.isUnchanged ())
        return EChange.UNCHANGED;

      ObjectHelper.setLastModificationNow (aUser);
      internalUpdateItem (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditModifySuccess (User.OT,
                                      "all",
                                      aUser.getID (),
                                      sNewLoginName,
                                      sNewEmailAddress,
                                      sNewFirstName,
                                      sNewLastName,
                                      sNewDescription,
                                      aNewDesiredLocale,
                                      aNewCustomAttrs,
                                      Boolean.valueOf (bNewDisabled));

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserUpdated (aUser));

    return EChange.CHANGED;
  }

  /**
   * Change the modifiable data of a user
   *
   * @param sUserID
   *        The ID of the user to be modified. May be null.
   * @param sNewPlainTextPassword
   *        The new password in plain text. May not be null.
   * @return {@link EChange}
   */
  @Nonnull
  public EChange setUserPassword (@Nullable final String sUserID, @Nonnull final String sNewPlainTextPassword)
  {
    // Resolve user
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditModifyFailure (User.OT, sUserID, "no-such-user-id", "password");
      return EChange.UNCHANGED;
    }

    // Create a new password salt upon password change
    final PasswordHash aPasswordHash = GlobalPasswordSettings.createUserDefaultPasswordHash (new PasswordSalt (),
                                                                                             sNewPlainTextPassword);
    m_aRWLock.writeLock ().lock ();
    try
    {
      final EChange eChange = aUser.setPasswordHash (aPasswordHash);
      if (eChange.isUnchanged ())
        return EChange.UNCHANGED;

      ObjectHelper.setLastModificationNow (aUser);
      internalUpdateItem (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditModifySuccess (User.OT, "password", sUserID);

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserPasswordChanged (aUser));

    return EChange.CHANGED;
  }

  @Nonnull
  public EChange updateUserLastLogin (@Nullable final String sUserID)
  {
    // Resolve user
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditModifyFailure (User.OT, sUserID, "no-such-user-id", "update-last-login");
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      aUser.onSuccessfulLogin ();
      internalUpdateItem (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditModifySuccess (User.OT, "update-last-login", sUserID);
    return EChange.CHANGED;
  }

  @Nonnull
  public EChange updateUserLastFailedLogin (@Nullable final String sUserID)
  {
    // Resolve user
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditModifyFailure (User.OT, sUserID, "no-such-user-id", "update-last-failed-login");
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      aUser.onFailedLogin ();
      internalUpdateItem (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditModifySuccess (User.OT, "update-last-failed-login", sUserID);

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserLastFailedLoginUpdated (aUser));

    return EChange.CHANGED;
  }

  /**
   * Delete the user with the specified ID.
   *
   * @param sUserID
   *        The ID of the user to delete
   * @return {@link EChange#CHANGED} if the user was deleted,
   *         {@link EChange#UNCHANGED} otherwise.
   */
  @Nonnull
  public EChange deleteUser (@Nullable final String sUserID)
  {
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditDeleteFailure (User.OT, "no-such-user-id", sUserID);
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      if (ObjectHelper.setDeletionNow (aUser).isUnchanged ())
      {
        AuditHelper.onAuditDeleteFailure (User.OT, "already-deleted", sUserID);
        return EChange.UNCHANGED;
      }
      internalMarkItemDeleted (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditDeleteSuccess (User.OT, sUserID);

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserDeleted (aUser));

    return EChange.CHANGED;
  }

  /**
   * Undelete the user with the specified ID.
   *
   * @param sUserID
   *        The ID of the user to undelete
   * @return {@link EChange#CHANGED} if the user was undeleted,
   *         {@link EChange#UNCHANGED} otherwise.
   */
  @Nonnull
  public EChange undeleteUser (@Nullable final String sUserID)
  {
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditUndeleteFailure (User.OT, sUserID, "no-such-user-id");
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      if (ObjectHelper.setUndeletionNow (aUser).isUnchanged ())
        return EChange.UNCHANGED;
      internalMarkItemUndeleted (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditUndeleteSuccess (User.OT, sUserID);

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserUndeleted (aUser));

    return EChange.CHANGED;
  }

  /**
   * disable the user with the specified ID.
   *
   * @param sUserID
   *        The ID of the user to disable
   * @return {@link EChange#CHANGED} if the user was disabled,
   *         {@link EChange#UNCHANGED} otherwise.
   */
  @Nonnull
  public EChange disableUser (@Nullable final String sUserID)
  {
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditModifyFailure (User.OT, sUserID, "no-such-user-id", "disable");
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      if (aUser.setDisabled (true).isUnchanged ())
        return EChange.UNCHANGED;
      internalUpdateItem (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditModifySuccess (User.OT, "disable", sUserID);

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserEnabled (aUser, false));

    return EChange.CHANGED;
  }

  /**
   * Enable the user with the specified ID.
   *
   * @param sUserID
   *        The ID of the user to enable
   * @return {@link EChange#CHANGED} if the user was enabled,
   *         {@link EChange#UNCHANGED} otherwise.
   */
  @Nonnull
  public EChange enableUser (@Nullable final String sUserID)
  {
    final User aUser = getOfID (sUserID);
    if (aUser == null)
    {
      AuditHelper.onAuditModifyFailure (User.OT, sUserID, "no-such-user-id", "enable");
      return EChange.UNCHANGED;
    }

    m_aRWLock.writeLock ().lock ();
    try
    {
      if (aUser.setDisabled (false).isUnchanged ())
        return EChange.UNCHANGED;
      internalUpdateItem (aUser);
    }
    finally
    {
      m_aRWLock.writeLock ().unlock ();
    }
    AuditHelper.onAuditModifySuccess (User.OT, "enable", sUserID);

    // Execute callback as the very last action
    m_aCallbacks.forEach (aCB -> aCB.onUserEnabled (aUser, true));

    return EChange.CHANGED;
  }

  /**
   * Check if the passed combination of user ID and password matches.
   *
   * @param sUserID
   *        The ID of the user
   * @param sPlainTextPassword
   *        The plan text password to validate.
   * @return true if the password hash matches the stored hash for
   *         the specified user, false otherwise.
   */
  public boolean areUserIDAndPasswordValid (@Nullable final String sUserID, @Nullable final String sPlainTextPassword)
  {
    // No password is not allowed
    if (sPlainTextPassword == null)
      return false;

    // Is there such a user?
    final IUser aUser = getOfID (sUserID);
    if (aUser == null)
      return false;

    // Now compare the hashes
    final String sPasswordHashAlgorithm = aUser.getPasswordHash ().getAlgorithmName ();
    final IPasswordSalt aSalt = aUser.getPasswordHash ().getSalt ();
    final PasswordHash aPasswordHash = GlobalPasswordSettings.createUserPasswordHash (sPasswordHashAlgorithm,
                                                                                      aSalt,
                                                                                      sPlainTextPassword);
    return aUser.getPasswordHash ().equals (aPasswordHash);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy