package org.apereo.cas.adaptors.generic;
import com.google.common.base.Throwables;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.RememberMeUsernamePasswordCredential;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.Set;
/**
* An authentication handler that routes requests to Apache Shiro.
* Credentials are assumed to be username and password.
* @author Misagh Moayyed
* @since 4.2
*/
public class ShiroAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ShiroAuthenticationHandler.class);
private final Set<String> requiredRoles;
private final Set<String> requiredPermissions;
public ShiroAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
final Set<String> requiredRoles, final Set<String> requiredPermissions) {
super(name, servicesManager, principalFactory, null);
this.requiredRoles = requiredRoles;
this.requiredPermissions = requiredPermissions;
}
@Override
protected HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential transformedCredential,
final String originalPassword)
throws GeneralSecurityException, PreventedException {
try {
final UsernamePasswordToken token = new UsernamePasswordToken(transformedCredential.getUsername(),
transformedCredential.getPassword());
if (transformedCredential instanceof RememberMeUsernamePasswordCredential) {
token.setRememberMe(RememberMeUsernamePasswordCredential.class.cast(transformedCredential).isRememberMe());
}
final Subject currentUser = getCurrentExecutingSubject();
currentUser.login(token);
checkSubjectRolesAndPermissions(currentUser);
return createAuthenticatedSubjectResult(transformedCredential, currentUser);
} catch (final UnknownAccountException uae) {
throw new AccountNotFoundException(uae.getMessage());
} catch (final IncorrectCredentialsException ice) {
throw new FailedLoginException(ice.getMessage());
} catch (final LockedAccountException|ExcessiveAttemptsException lae) {
throw new AccountLockedException(lae.getMessage());
} catch (final ExpiredCredentialsException eae) {
throw new CredentialExpiredException(eae.getMessage());
} catch (final DisabledAccountException eae) {
throw new AccountDisabledException(eae.getMessage());
} catch (final AuthenticationException e){
throw new FailedLoginException(e.getMessage());
}
}
/**
* Check subject roles and permissions.
*
* @param currentUser the current user
* @throws FailedLoginException the failed login exception in case roles or permissions are absent
*/
protected void checkSubjectRolesAndPermissions(final Subject currentUser) throws FailedLoginException {
if (this.requiredRoles != null) {
for (final String role : this.requiredRoles) {
if (!currentUser.hasRole(role)) {
throw new FailedLoginException("Required role " + role + " does not exist");
}
}
}
if (this.requiredPermissions != null) {
for (final String perm : this.requiredPermissions) {
if (!currentUser.isPermitted(perm)) {
throw new FailedLoginException("Required permission " + perm + " cannot be located");
}
}
}
}
/**
* Create authenticated subject result.
*
* @param credential the credential
* @param currentUser the current user
* @return the handler result
*/
protected HandlerResult createAuthenticatedSubjectResult(final Credential credential, final Subject currentUser) {
final String username = currentUser.getPrincipal().toString();
return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
}
/**
* Gets current executing subject.
*
* @return the current executing subject
*/
protected Subject getCurrentExecutingSubject() {
return SecurityUtils.getSubject();
}
/**
* Sets shiro configuration to the path of the resource
* that points to the {@code shiro.ini} file.
*
* @param resource the resource
*/
public void loadShiroConfiguration(final Resource resource) {
try {
final Resource shiroResource = ResourceUtils.prepareClasspathResourceIfNeeded(resource);
if (shiroResource != null && shiroResource.exists()) {
final String location = shiroResource.getURI().toString();
LOGGER.debug("Loading Shiro configuration from [{}]", location);
final Factory<SecurityManager> factory = new IniSecurityManagerFactory(location);
final SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
} else {
LOGGER.debug("Shiro configuration is not defined");
}
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
}