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

de.svenkubiak.ninja.auth.services.Authentications Maven / Gradle / Ivy

package de.svenkubiak.ninja.auth.services;

import java.time.LocalDateTime;

import ninja.Context;
import ninja.Cookie;
import ninja.Cookie.Builder;
import ninja.utils.NinjaConstant;
import ninja.utils.NinjaProperties;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.mindrot.jbcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import de.svenkubiak.ninja.auth.enums.Constants;
import de.svenkubiak.ninja.auth.utils.CryptoUtils;

/**
 * 
 * @author svenkubiak
 *
 */
@Singleton
public class Authentications {
    private static final Logger LOG = LoggerFactory.getLogger(Authentications.class); 
    private static final String SEPARATOR = "####";
    private static final int INDEX_0 = 0;
    private static final int INDEX_1 = 1;
    private static final int INDEX_2 = 2;
    private static final int COOKIE_VARS = 3;
    private static final int TWO_WEEKS_SECONDS = 1209600;
    private static final int THIRTY_MINUTES = 30;
    private static final int LOG_ROUNDS = 12;
    private NinjaProperties ninjaProperties;
    private CryptoUtils cryptoUtils;

    @Inject
    public Authentications(CryptoUtils cryptoUtils, NinjaProperties ninjaProperties) {
        this.cryptoUtils = cryptoUtils;
        this.ninjaProperties = ninjaProperties;
    }
    
    /**
     * Retrieves the username of the current authenticated user from the
     * authentication cookie.
     * 
     * @param context The current ninja context
     * 
     * @return The username of the authenticated user or null if none is found
     */
    public String getAuthenticatedUser(Context context) {
        Preconditions.checkNotNull(context, "Valid context is required for getAuthenticatedUser");
        
        Cookie cookie = context.getCookie(getCookieName());
        if (cookie != null && StringUtils.isNotBlank(cookie.getValue())) {
            String value = cookie.getValue();
            if (ninjaProperties.getBooleanWithDefault(Constants.AUTH_COOKIE_ENCRYPT.get(), false)) {
                value = cryptoUtils.decrypt(value);
            }
            
            String [] cookieValue = value.split(SEPARATOR);
            if (cookieValue.length == COOKIE_VARS) {
                String hash = cookieValue[INDEX_0];
                String username = cookieValue[INDEX_1];
                String expires = cookieValue[INDEX_2];
                
                if (StringUtils.isNotBlank(hash) && LocalDateTime.now().isBefore(LocalDateTime.parse(expires)) && getHash(username, expires).equals(hash)) {
                    return username;
                }
            }
        }
        
        return null;
    }
    
    /**
     * Generates a hash value for a given clear text password using JBCrypt
     * 
     * @param password The clear text password to hash
     * 
     * @return The hashed value 
     */
    public String getHashedPassword(String password) {
        Preconditions.checkNotNull(password, "Password is required for getHashedPassword");
        
        return BCrypt.hashpw(password, BCrypt.gensalt(LOG_ROUNDS));
    }
    
    /**
     * Checks a clear text password against a previously hashed JBCrypt one. Use
     * this method to check if a user has entered the correct password.
     * 
     * @param password The clear text password
     * @param hash The with {@link #getHashedPassword(String)} created hash value
     * 
     * @return True if the password is valid, false otherwise
     */
    public boolean authenticate(String password, String hash) {
        Preconditions.checkNotNull(password, "Password is required for authenticate");
        Preconditions.checkNotNull(password, "Hashed password is required for authenticate");
        
        boolean authenticated = false;
        try {
            authenticated = BCrypt.checkpw(password, hash);
        } catch (IllegalArgumentException e) {
            LOG.error("Failed to check password against hash", e);
        }
        
        return authenticated;
    }
    
    /**
     * Performs a logout in the current context by removing the authentication cookie
     * 
     * @param context The current ninja context
     */
    public void logout(Context context) {
        Preconditions.checkNotNull(context, "Valid context is required for logout");
        
        if (context.hasCookie(getCookieName())) {
            context.unsetCookie(Cookie.builder(context.getCookie(getCookieName())).build());
        }
    }
    
    /**
     * Return the name of the authentication cookie
     * 
     * @return The name of the authentication cookie
     */
    private String getCookieName() {
        String prefix = ninjaProperties.get("application.cookie.prefix") + "-";
        return prefix + ninjaProperties.getWithDefault(Constants.AUTH_COOKIE_NAME.get(), Constants.DEFAULT_COOKIE_SUFFIX.get());
    }
    
    /**
     * Performs a login by putting a given username in the authentication cookie. By default
     * the cookie is valid until the browser is closed or 30 minutes. If remember is passed as true, the
     * cookie will get an expiration date which is by default two weeks. This can be customized
     * via application.conf.
     * 
     * VERY IMPORTANT: Only call this method if you are certain that the user is correctly authenticated!
     * 
     * @param context The current ninja context
     * @param username The username to login in
     * @param remember If the user should stay logged in (default: two weeks)
     */
    public void login(Context context, String username, boolean remember) {
        Preconditions.checkNotNull(context, "Valid context is required for login");
        Preconditions.checkNotNull(username, "Username is required for login");
        
        if (StringUtils.isNotBlank(StringUtils.trimToNull(username))) {
            String expires = getExpires(remember);
            
            StringBuilder buffer = new StringBuilder();
            buffer.append(getHash(username, expires))
                .append(SEPARATOR)
                .append(username)
                .append(SEPARATOR)
                .append(expires);
            
            Builder builder = Cookie.builder(getCookieName(), getCookieValue(buffer.toString()))
                .setDomain(ninjaProperties.get(NinjaConstant.applicationCookieDomain))
                .setSecure(ninjaProperties.getBooleanWithDefault(NinjaConstant.sessionTransferredOverHttpsOnly, true))
                .setHttpOnly(true);
            
            if (remember) {
                builder.setMaxAge(ninjaProperties.getIntegerWithDefault(Constants.AUTH_COOKIE_EXPIRES.get(), TWO_WEEKS_SECONDS));
            }
            
            context.addCookie(builder.build());            
        }
    }

    /**
     * Returns the encrypted cookie value if encryption is enabled
     * 
     * @param buffer The cookie value
     * @return The encrypted cookie value of the cleartext value of the cookie
     */
    private String getCookieValue(String buffer) {
        Preconditions.checkNotNull(buffer, "Cookie value is required for getCookieValue");
        
        if (ninjaProperties.getBooleanWithDefault(Constants.AUTH_COOKIE_ENCRYPT.get(), false)) {
            return cryptoUtils.encrypt(buffer);
        }
        
        return buffer;
    }
    
    /**
     * Return the string representation of a LocalDateTime of the expiration date of the 
     * authentication
     * 
     * @param remember If the user wants to stay logged in, even if the browser is closed
     * @return The configured AUTH_COOKIE_EXPIRES value or default TWO_WEEKS_SECONDS or 30 minutes in the future if remember = false
     */
    private String getExpires(boolean remember) {
        if (remember) {
            return LocalDateTime.now().plusSeconds(ninjaProperties.getIntegerWithDefault(Constants.AUTH_COOKIE_EXPIRES.get(), TWO_WEEKS_SECONDS)).toString();
        }

        return LocalDateTime.now().plusMinutes(THIRTY_MINUTES).toString();
    }

    /**
     * Creates a SHA-512 hash for a given username by hashing it with a given timestamp
     * and the ninja application secret. 
     * 
     * @param username The username to create the hash
     * @param timestamp The timestamp to create the hash
     * 
     * @return The hashed value
     */
    private String getHash(String username, String timestamp) {
        Preconditions.checkNotNull(username, "Username is required for getSignature");
        Preconditions.checkNotNull(timestamp, "Timestamp is required for getSignature");
        
        return DigestUtils.sha512Hex(username + timestamp + ninjaProperties.get(Constants.APPLICATION_SECRET.get()));
    }
    
    /**
     * Convenient function to check if there is an authenticated user in the
     * authentication cookie
     * 
     * @param context The current ninja context
     * @return True if there is an authenticated user, false otherwise
     */
    public boolean hasAuthenticatedUser(Context context) {
        Preconditions.checkNotNull(context, "Valid context is required for hasAuthenticatedUser");
        
        return StringUtils.isNotBlank(getAuthenticatedUser(context));
    }
    
    /**
     * Convenient function to check if a given username is the authenticated user in the
     * authentication cookie
     * 
     * @param context The current ninja context
     * @return True if there is an authenticated user with the given username, false otherwise
     */
    public boolean isAuthenticated(Context context, String username) {
        Preconditions.checkNotNull(context, "Valid context is required for isAuthenticated");
        Preconditions.checkNotNull(username, "Username is required for isAuthenticated");
        
        return username.equals(getAuthenticatedUser(context));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy