class AmbariPamAuthenticationProvider extends AmbariAuthenticationProvider {

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    if (isPamEnabled()) {
      if (authentication.getName() == null) {
        LOG.info("Authentication failed: no username provided");
        throw new InvalidUsernamePasswordCombinationException("");
      }

      String userName = authentication.getName().trim();

      if (authentication.getCredentials() == null) {
        LOG.info("Authentication failed: no credentials provided: {}", userName);
        throw new InvalidUsernamePasswordCombinationException(userName);
      }

      Users users = getUsers();

      UserEntity userEntity = users.getUserEntity(userName);
      String password = String.valueOf(authentication.getCredentials());
      String ambariUsername;
      String localUsername;

      // Determine what the Ambari and local username values are.  Most of the time these should be
      // the same, however it is possible for the user names to be different in the event a user has
      // multiple authentication sources.
      if (userEntity == null) {
        ambariUsername = userName;
        localUsername = userName;
      } else {
        // If the user exists, the username to be used with PAM may be stored with the PAM-specific UserAuthenticationEntity
        // Else, use the UserEntity#getLocalUsername value
        // Else, use the UserEntity#getUserName value
        UserAuthenticationEntity authenticationEntity = getAuthenticationEntity(userEntity, UserAuthenticationType.PAM);

        ambariUsername = userEntity.getUserName();

        if (authenticationEntity == null) {
          localUsername = userEntity.getLocalUsername();
        } else {
          localUsername = authenticationEntity.getAuthenticationKey();

          if (StringUtils.isEmpty(localUsername)) {
            localUsername = userEntity.getLocalUsername();
          }
        }

        if (StringUtils.isEmpty(localUsername)) {
          localUsername = ambariUsername;
        }
      }

      // Perform authentication....
      UnixUser unixUser = performPAMAuthentication(ambariUsername, localUsername, password);

      if (unixUser != null) {
        // Authentication was successful via PAM.  Make sure that the user exists and has a PAM
        // authentication entry.
        if (userEntity == null) {
          // TODO: Ensure automatically creating users when authenticating with PAM is allowed.
          try {
            userEntity = users.createUser(ambariUsername, unixUser.getUserName(), ambariUsername, true);
          } catch (AmbariException e) {
            LOG.error(String.format("Failed to add the user, %s: %s", ambariUsername, e.getLocalizedMessage()), e);
            throw new AmbariAuthenticationException(ambariUsername, "Unexpected error has occurred", false, e);
          }
        } else {
          // Ensure the user is allowed to login....
          try {
            users.validateLogin(userEntity, ambariUsername);
          } catch (AccountDisabledException | TooManyLoginFailuresException e) {
            if (getConfiguration().showLockedOutUserMessage()) {
              throw e;
            } else {
              // Do not give away information about the existence or status of a user
              throw new InvalidUsernamePasswordCombinationException(userName, false, e);
            }
          }
        }

        UserAuthenticationEntity authenticationEntity = getAuthenticationEntity(userEntity, UserAuthenticationType.PAM);
        // TODO: Ensure automatically adding the PAM authentication method for users when authenticating is allowed.
        if (authenticationEntity == null) {
          try {
            users.addPamAuthentication(userEntity, unixUser.getUserName());
          } catch (AmbariException e) {
            LOG.error(String.format("Failed to add the PAM authentication method for %s: %s", ambariUsername, e.getLocalizedMessage()), e);
            throw new AmbariAuthenticationException(ambariUsername, "Unexpected error has occurred", false, e);
          }
        }

        if (isAutoGroupCreationAllowed()) {
          synchronizeGroups(unixUser, userEntity);
        }

        AmbariUserDetails userDetails = new AmbariUserDetailsImpl(users.getUser(userEntity), null, users.getUserAuthorities(userEntity));
        return new AmbariUserAuthentication(password, userDetails, true);
      }


      // The user was not authenticated, catch-all fail
      LOG.debug(String.format("Authentication failed: password does not match stored value: %s", localUsername));
      throw new InvalidUsernamePasswordCombinationException(ambariUsername);
    } else {
      return null;
    }
  }

  @Inject public  AmbariPamAuthenticationProvider(Users users, PamAuthenticationFactory pamAuthenticationFactory, Configuration configuration);

}