package gov.nysenate.openleg.service.auth;
import gov.nysenate.openleg.model.auth.AdminUser;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.mindrot.jbcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Collection;
@Component
public class AdminLoginAuthRealm extends OpenLegAuthorizingRealm
{
private static final Logger logger = LoggerFactory.getLogger(AdminLoginAuthRealm.class);
/** The IP whitelist is used here to restrict access to admin login to internal IPs only. */
@Value("${api.auth.ip.whitelist}") private String ipWhitelist;
private static class BCryptCredentialsMatcher implements CredentialsMatcher {
/**
* Compare a hashed password from the Auth token to the stored hash.
* @param token The authentication credentials submitted by the user during a login attempt
* @param info The valid authenticaton info to compare the token to
* @return Whether or not the login credentials are valid
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
String newPass = new String(userToken.getPassword());
return (BCrypt.checkpw(newPass, info.getCredentials().toString()));
}
}
private static BCryptCredentialsMatcher credentialsMatcher = new BCryptCredentialsMatcher();
@Autowired
private AdminUserService adminUserService;
@Value("${default.admin.user}") private String defaultAdminName;
@Value("${default.admin.password}") private String defaultAdminPass;
@PostConstruct
public void setup() {
if (!adminUserService.adminInDb(defaultAdminName))
adminUserService.createUser(defaultAdminName, defaultAdminPass, true, true);
}
/**
* This method will call the queryForAuthenticationInfo method in order to retrieve
* authentication info about the given admin. If the query returns a valid admin account,
* then this method will return an AuthenticationInfo for that admin account.
*
* @param token The given authentication information
* @return Either valid AuthenticationInfo for the given token or null if the account is not valid
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if (token != null && token instanceof UsernamePasswordToken) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
logger.info("Attempting login with Admin Realm from IP {}", usernamePasswordToken.getHost());
if (usernamePasswordToken.getHost().matches(ipWhitelist)) {
return queryForAuthenticationInfo(usernamePasswordToken);
}
else {
logger.warn("Blocking admin login from unauthorized IP {}", usernamePasswordToken.getHost());
throw new AuthenticationException("Admin login from unauthorized IP address.");
}
}
throw new UnsupportedTokenException(getName() + " only supports UsernamePasswordToken");
}
/**
* This method uses the AdminUser service to query the database and see if
* the given username.
* @param info The given UsernamePasswordToken
* @return A new SimpleAuthenticationInfo object if the user is a valid Admin, or AuthenticationException
*/
protected AuthenticationInfo queryForAuthenticationInfo(UsernamePasswordToken info) {
String username = info.getUsername();
AdminUser admin = adminUserService.getAdminUser(username);
return new SimpleAuthenticationInfo(admin.getUsername(), admin.getPassword(), getName());
}
/**
* This method will return the Authorization Information for a particular admin
* @param principals The identifying attributes of the currently active user
* @return A SimpleAuthorizationInfo object containing the roles and permissions of the user.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Collection collection = principals.fromRealm(getName());
if (!collection.isEmpty()) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String principal = collection.iterator().next().toString();
logger.info("Determining admin roles for {}", principal);
if (adminUserService.isMasterAdmin(principal)) {
info.addRole(OpenLegRole.MASTER_ADMIN.name());
}
return info;
}
return null;
}
/**
* Use the BCrypt credentials matcher.
* @return The BCrypt credentials matcher.
*/
@Override
public CredentialsMatcher getCredentialsMatcher() {
return credentialsMatcher;
}
}