eu.uqasar.service.AuthenticationService.java Source code

Java tutorial

Introduction

Here is the source code for eu.uqasar.service.AuthenticationService.java

Source

package eu.uqasar.service;

/*
 * #%L
 * U-QASAR
 * %%
 * Copyright (C) 2012 - 2015 U-QASAR Consortium
 * %%
 * 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.
 * #L%
 */

import eu.uqasar.exception.auth.NoPendingRegistrationException;
import eu.uqasar.exception.auth.RegistrationCancelledException;
import eu.uqasar.exception.auth.RegistrationNotYetConfirmedException;
import eu.uqasar.exception.auth.UnknownUserException;
import eu.uqasar.exception.auth.WrongUserCredentialsException;
import eu.uqasar.exception.auth.register.UserMailAlreadyExistsException;
import eu.uqasar.exception.auth.register.UserNameAlreadyExistsException;
import eu.uqasar.exception.auth.reset.NoPendingResetPWRequestException;
import eu.uqasar.exception.auth.reset.ResetPWRequestTimeoutException;
import eu.uqasar.model.user.RegistrationStatus;
import eu.uqasar.model.user.User;
import eu.uqasar.model.user.UserSource;
import static eu.uqasar.model.user.UserSource.LDAP;
import eu.uqasar.service.settings.LdapSettingsService;
import eu.uqasar.service.user.UserService;
import eu.uqasar.util.ldap.LdapManager;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.naming.NamingException;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.util.lang.Args;
import org.jboss.solder.logging.Logger;

/**
 *
 *
 */
@ApplicationScoped
public class AuthenticationService {

    public static final String PW_PATTERN = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!$%&@#_,\\.\\-|<>]).{6,256})";

    @Inject
    Logger logger;

    @Inject
    UserService userService;

    @Inject
    LdapSettingsService ldapSettingsService;

    /**
     *
     * @return @throws NoSuchAlgorithmException
     */
    public static byte[] generateSalt() throws NoSuchAlgorithmException {
        // VERY important to use SecureRandom instead of just Random
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");

        // Generate a 8 byte (64 bit) salt as recommended by RSA PKCS5
        byte[] salt = new byte[8];
        random.nextBytes(salt);

        return salt;
    }

    /**
     *
     * @param password
     * @param salt
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static byte[] getEncryptedPassword(String password, byte[] salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        // PBKDF2 with SHA-1 as the hashing algorithm. Note that the NIST
        // specifically names SHA-1 as an acceptable hashing algorithm for
        // PBKDF2
        String algorithm = "PBKDF2WithHmacSHA1";
        // SHA-1 generates 160 bit hashes, so that's what makes sense here
        int derivedKeyLength = 160;
        // Pick an iteration count that works for you. The NIST recommends at
        // least 1,000 iterations:
        // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
        // iOS 4.x reportedly uses 10,000:
        // http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/
        int iterations = 20000;
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength);
        SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm);
        return f.generateSecret(spec).getEncoded();
    }

    /**
     *
     * @return
     */
    private String generateToken() {
        return UUID.randomUUID().toString();
    }

    public void checkMailAlreadyRegistered(final String email) throws UserMailAlreadyExistsException {
        // check if user already exists
        User existingUser = userService.getByMail(email);
        if ((existingUser != null) && !existingUser.getRegistrationStatus().equals(RegistrationStatus.CANCELLED)) {
            // user already exists and has pending or confirmed registration
            throw new UserMailAlreadyExistsException("A user with the given e-Mail address already exists!");
        }
    }

    public void checkUserNameAlreadyRegistered(final String username) throws UserNameAlreadyExistsException {
        // check if user already exists
        User existingUser = userService.getByUserName(username);
        if ((existingUser != null) && !existingUser.getRegistrationStatus().equals(RegistrationStatus.CANCELLED)) {
            // user already exists and has pending or confirmed registration
            throw new UserNameAlreadyExistsException("A user with the given username already exists!");
        }
    }

    /**
     *
     * @param email
     * @return
     * @throws NoPendingRegistrationException
     */
    public User resetRegistrationInformation(String email) throws NoPendingRegistrationException {
        // check if user already exists
        User user = userService.getByMail(email);
        if ((user == null) || !user.getRegistrationStatus().equals(RegistrationStatus.PENDING)) {
            // user doesn't exist or has already confirmed/cancelled
            // registration
            throw new NoPendingRegistrationException();
        }

        // set new registration information
        user.setRegistrationToken(generateToken());
        user.setRegistrationStatus(RegistrationStatus.PENDING);
        user.setRegistrationDate(new Date());
        return userService.update(user);
    }

    public User register(User user, final String password) {
        try {
            byte[] salt = generateSalt();
            byte[] encryptedPassword = getEncryptedPassword(password, salt);

            user.setPassword(encryptedPassword);
            user.setPwSalt(salt);

            // set registration information
            user.setRegistrationToken(generateToken());
            user.setRegistrationStatus(RegistrationStatus.PENDING);
            user.setRegistrationDate(new Date());

            user = userService.create(user);
            return user;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
            logger.error(ex.getMessage(), ex);
            throw new RuntimeException("An unexpected error occured during registration!");
        }
    }

    public User registerLdapBasedUser(User user, boolean needToConfirmRegistration) {
        if (needToConfirmRegistration) {
            // set registration information
            user.setRegistrationToken(generateToken());
            user.setRegistrationStatus(RegistrationStatus.PENDING);
            user.setRegistrationDate(new Date());
        } else {
            user.setRegistrationStatus(RegistrationStatus.CONFIRMED);
            user.setRegistrationDate(new Date());
        }
        user.setSource(UserSource.LDAP);
        user = userService.create(user);
        return user;
    }

    public User confirmRegistration(User user) {
        user.setRegistrationStatus(RegistrationStatus.CONFIRMED);
        user.setRegistrationToken(null);
        user = userService.update(user);
        return user;
    }

    public void cancelRegistration(User user) {
        userService.delete(user);
    }

    public User authenticate(final String login, final String password) throws UnknownUserException,
            WrongUserCredentialsException, RegistrationCancelledException, RegistrationNotYetConfirmedException {
        User user = userService.getByMail(login);
        if (user == null) {
            user = userService.getByLdapLogin(login);
            if (user == null) {
                throw new UnknownUserException();
            }
        } else if (user.getRegistrationStatus() == RegistrationStatus.CANCELLED) {
            throw new RegistrationCancelledException();
        }

        boolean authenticated = authenticate(password, user);
        if (!authenticated) {
            throw new WrongUserCredentialsException();
        } else if (authenticated && user.getRegistrationStatus() == RegistrationStatus.PENDING) {
            throw new RegistrationNotYetConfirmedException();
        }
        return user;
    }

    private boolean authenticate(String attemptedPassword, User user) {
        if (user.getSource() == LDAP) {
            try {
                LdapManager manager = LdapManager.getInstance(ldapSettingsService);
                return manager.authenticateBySAMAccountName(user.getUserName(), attemptedPassword);
            } catch (NamingException ex) {
                logger.error(ex.getMessage(), ex);
                return false;
            }
        } else {
            return authenticate(attemptedPassword, user.getPassword(), user.getPwSalt());
        }
    }

    private boolean authenticate(String attemptedPassword, byte[] encryptedPassword, byte[] salt) {
        try {
            // Encrypt the clear-text password using the same salt that was used to
            // encrypt the original password
            byte[] encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt);

            // Authentication succeeds if encrypted password that the user entered
            // is equal to the stored hash
            return Arrays.equals(encryptedPassword, encryptedAttemptedPassword);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            logger.error(e);
            throw new RuntimeException(
                    "An unexpected error occurred while trying to authenticate against a password!", e);
        }
    }

    public User requestNewPassword(final String mail) {
        // get user by mail
        User user = userService.getByMail(mail);
        // check if user is registered properly (and confirmed)
        if (user == null || user.getRegistrationStatus() != RegistrationStatus.CONFIRMED) {
            return null;
        }

        // set reset token and date
        user.setResetPWRequestToken(generateToken());
        user.setResetPWRequestDate(new Date());
        // update user
        return userService.update(user);
    }

    public boolean checkNonEmptyPasswords(final String password, final String passwordConfirmation) {
        return !StringUtils.isBlank(password) && !StringUtils.isBlank(passwordConfirmation);
    }

    public boolean checkPasswordsEqual(final String password, final String passwordConfirmation) {
        return StringUtils.equals(password, passwordConfirmation);
    }

    public boolean updateUserPassword(User user, final String password, final String passwordConfirmation) {
        Args.notNull(user, "User may not be null!");
        Args.notEmpty(password, "Password may not be null or empty!");
        Args.notEmpty(passwordConfirmation, "Password confirmation may not be null or empty!");
        try {
            byte[] salt = generateSalt();
            byte[] encryptedPassword = getEncryptedPassword(password, salt);

            user.setPassword(encryptedPassword);
            user.setPwSalt(salt);
            userService.update(user);
            return true;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException("An unexpected error occurred!", e);
        }
    }

    public User resetPassword(User user, final String password) {
        // generate hashed + salted password and store user with password
        // and salt in db
        try {
            byte[] salt = generateSalt();
            byte[] encryptedPassword = getEncryptedPassword(password, salt);

            user.setPassword(encryptedPassword);
            user.setPwSalt(salt);

            // set registration information
            user.setResetPWRequestToken(null);
            user.setResetPWRequestDate(null);

            return userService.update(user);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException("An unexpected error occurred!", e);
        }
    }

    public User getUserByPWResetToken(final String token)
            throws NoPendingResetPWRequestException, ResetPWRequestTimeoutException {
        if ((token == null) || token.isEmpty()) {
            throw new NoPendingResetPWRequestException("No pending request for resetting password found!");
        }
        User user = userService.getByPWResetToken(token);
        if (user == null) {
            throw new NoPendingResetPWRequestException("No pending request for resetting password found!");
        }
        // check if reset password token has timed out
        if (isResetPWRequestTimeout(user)) {
            throw new ResetPWRequestTimeoutException("Your request for resetting your password has timed out!");
        }
        return user;
    }

    /**
     * Checks if request for resetting password has been more than half a day
     * ago.
     * <p>
     * @param user
     * @return
     */
    private boolean isResetPWRequestTimeout(User user) {
        Date now = new Date();
        long halfDay = 43200000L;
        return (now.getTime() - user.getResetPWRequestDate().getTime()) > halfDay;
    }
}