package io.mangoo.routing.bindings;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.StringUtils;
import com.google.inject.Inject;
import io.mangoo.cache.Cache;
import io.mangoo.configuration.Config;
import io.mangoo.enums.CacheName;
import io.mangoo.enums.Required;
import io.mangoo.helpers.TwoFactorHelper;
import io.mangoo.models.OAuthUser;
import io.mangoo.providers.CacheProvider;
import io.mangoo.utils.CodecUtils;
/**
* Convenient class for handling authentication
*
* @author svenkubiak
*
*/
public class Authentication {
private Config config;
private LocalDateTime expires;
private OAuthUser oAuthUser;
private String authenticatedUser;
private Cache cache;
private TwoFactorHelper twoFactorHelper;
private boolean twoFactor;
private boolean remember;
private boolean loggedOut;
@Inject
public Authentication(CacheProvider cacheProvider, Config config, TwoFactorHelper twoFactorHelper) {
Objects.requireNonNull(cacheProvider, Required.CACHE_PROVIDER.toString());
Objects.requireNonNull(config, Required.CONFIG.toString());
Objects.requireNonNull(twoFactorHelper, Required.TWO_FACTOR_HELPER.toString());
this.cache = cacheProvider.getCache(CacheName.AUTH);
this.config = config;
this.twoFactorHelper = twoFactorHelper;
}
public Authentication withExpires(LocalDateTime expires) {
Objects.requireNonNull(expires, Required.EXPIRES.toString());
if (this.expires == null) {
this.expires = expires;
}
return this;
}
public Authentication withAuthenticatedUser(String authenticatedUser) {
if (StringUtils.isBlank(this.authenticatedUser)) {
this.authenticatedUser = authenticatedUser;
}
return this;
}
/**
* Retrieves the current authenticated user
*
* @return The username of the current authenticated user or null
*/
public String getAuthenticatedUser() {
return this.authenticatedUser;
}
/**
* Returns the LocalDateTime when the authentication expires
*
* @return A LocalDateTime object or null if unset
*/
public LocalDateTime getExpires() {
return this.expires;
}
/**
* @return True if the user wants to logout, false otherwise
*/
public boolean isLogout() {
return this.loggedOut;
}
/**
*
* @return True if the user wants to stay logged in, false otherwise
*/
public boolean isRememberMe() {
return this.remember;
}
/**
* @return True if two factor authentication is enabled for this user
*/
public boolean isTwoFactor() {
return this.twoFactor;
}
/**
* Sets an OAuthUser to the current authentication.
* Can only be set once!
*
* @param oAuthUser An OAuthUser
*/
public void withOAuthUser(OAuthUser oAuthUser) {
if (this.oAuthUser == null) {
this.oAuthUser = oAuthUser;
}
}
/**
* Retrieves the current OAuthUser from the authentication
* Note: This is only available during a OAuth authentication in a
* method that is annotated with @OAuthCallbackFilter
*
* @return The current OAuthUser instance or null if undefined
*/
public OAuthUser getOAuthUser() {
return this.oAuthUser;
}
/**
* Creates a hashed value of a given clear text password and checks if the
* value matches a given, already hashed password
*
* @deprecated As of release 4.3.0, replaced by {@link #validLogin(String, String, String)}
*
* @param username The username to authenticate
* @param password The clear text password
* @param hash The previously hashed password to check
* @return True if the new hashed password matches the hash, false otherwise
*/
@Deprecated
public boolean login(String username, String password, String hash) {
Objects.requireNonNull(username, Required.USERNAME.toString());
Objects.requireNonNull(password, Required.PASSWORD.toString());
Objects.requireNonNull(hash, Required.HASH.toString());
boolean authenticated = false;
if (!userHasLock(username) && CodecUtils.checkJBCrypt(password, hash)) {
this.authenticatedUser = username;
authenticated = true;
} else {
this.cache.increment(username);
}
return authenticated;
}
/**
* Creates a hashed value of a given clear text password and checks if the
* value matches a given, already hashed password
*
* @param username The username to authenticate
* @param password The clear text password
* @param hash The previously hashed password to check
* @return True if the new hashed password matches the hash, false otherwise
*/
public boolean validLogin(String username, String password, String hash) {
Objects.requireNonNull(username, Required.USERNAME.toString());
Objects.requireNonNull(password, Required.PASSWORD.toString());
Objects.requireNonNull(hash, Required.HASH.toString());
boolean authenticated = false;
if (!userHasLock(username) && CodecUtils.checkJBCrypt(password, hash)) {
this.authenticatedUser = username;
authenticated = true;
} else {
this.cache.increment(username);
}
return authenticated;
}
/**
* Sets the remember me functionality
* @deprecated As of release 4.3.0, replaced by {@link #rememberMe(boolean)}
*
* @param remember true or false
*/
@Deprecated
public void remember(boolean remember) {
this.remember = remember;
}
/**
* Sets the remember me functionality, default is false
*/
/**
* Sets the remember me functionality, default is false
*
* @param rememmber True for activatin remember me, false otherwise
*/
public void rememberMe(boolean rememmber) {
this.remember = rememmber;
}
/**
* Sets the requirement of the two factor authentication, default is false
*/
/**
* Sets the requirement of the two factor authentication, default is false
*
* @param twoFactor True for enabling two factor authentication, false otherwise
* @return Authentication object
*/
public Authentication twoFactorAuthentication(boolean twoFactor) {
this.twoFactor = twoFactor;
return this;
}
/**
* Checks if a username is locked because of to many failed login attempts
*
* @param username The username to check
* @return true if the user has a lock, false otherwise
*/
public boolean userHasLock(String username) {
Objects.requireNonNull(username, Required.USERNAME.toString());
boolean lock = false;
AtomicInteger counter = this.cache.getCounter(username);
if (counter != null && counter.get() > this.config.getAuthenticationLock()) {
lock = true;
}
return lock;
}
/**
* Checks if a given number for 2FA is valid for the given secret
*
* @param secret The plaintext secret to use for checking
* @param number The number entered by the user
* @return True if number is valid, false otherwise
*/
public boolean validSecondFactor(String secret, int number) {
Objects.requireNonNull(secret, Required.SECRET.toString());
boolean valid = this.twoFactorHelper.validateCurrentNumber(number, secret);
if (valid) {
this.twoFactor = false;
}
return valid;
}
/**
* Performs a logout of the currently authenticated user
*/
public void logout() {
this.loggedOut = true;
}
/**
* Checks if the authentication contains an authenticated user
*
* @return True if authentication contains an authenticated user, false otherwise
*/
public boolean hasAuthenticatedUser() {
return StringUtils.isNotBlank(this.authenticatedUser) || this.oAuthUser != null;
}
/**
* Checks if the given user name is authenticated
* @deprecated As of release 4.3.0, will be removed in 5.0.0
*
* @param username The user name to check
* @return True if the given user name is authenticates
*/
@Deprecated
public boolean isAuthenticated(String username) {
Objects.requireNonNull(username, Required.USERNAME.toString());
return username.equals(this.authenticatedUser);
}
}