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); } } }