package com.hwlcn.security.subject.support; import com.hwlcn.security.authc.AuthenticationException; import com.hwlcn.security.authc.AuthenticationToken; import com.hwlcn.security.authc.HostAuthenticationToken; import com.hwlcn.security.authz.AuthorizationException; import com.hwlcn.security.authz.Permission; import com.hwlcn.security.authz.UnauthenticatedException; import com.hwlcn.security.mgt.SecurityManager; import com.hwlcn.security.session.InvalidSessionException; import com.hwlcn.security.session.ProxiedSession; import com.hwlcn.security.session.Session; import com.hwlcn.security.session.SessionException; import com.hwlcn.security.session.mgt.DefaultSessionContext; import com.hwlcn.security.session.mgt.SessionContext; import com.hwlcn.security.subject.ExecutionException; import com.hwlcn.security.subject.PrincipalCollection; import com.hwlcn.security.subject.Subject; import com.hwlcn.security.subject.SubjectBuilder; import com.hwlcn.security.util.CollectionUtils; import com.hwlcn.security.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; public class DelegatingSubject implements Subject { private static final Logger log = LoggerFactory.getLogger(DelegatingSubject.class); private static final String RUN_AS_PRINCIPALS_SESSION_KEY = DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY"; protected PrincipalCollection principals; protected boolean authenticated; protected String host; protected Session session; protected boolean sessionCreationEnabled; protected transient SecurityManager securityManager; public DelegatingSubject(SecurityManager securityManager) { this(null, false, null, null, securityManager); } public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, SecurityManager securityManager) { this(principals, authenticated, host, session, true, securityManager); } public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, boolean sessionCreationEnabled, SecurityManager securityManager) { if (securityManager == null) { throw new IllegalArgumentException("SecurityManager argument cannot be null."); } this.securityManager = securityManager; this.principals = principals; this.authenticated = authenticated; this.host = host; if (session != null) { this.session = decorate(session); } this.sessionCreationEnabled = sessionCreationEnabled; } protected Session decorate(Session session) { if (session == null) { throw new IllegalArgumentException("session cannot be null"); } return new StoppingAwareProxiedSession(session, this); } public SecurityManager getSecurityManager() { return securityManager; } protected boolean hasPrincipals() { return !CollectionUtils.isEmpty(getPrincipals()); } public String getHost() { return this.host; } private Object getPrimaryPrincipal(PrincipalCollection principals) { if (!CollectionUtils.isEmpty(principals)) { return principals.getPrimaryPrincipal(); } return null; } public Object getPrincipal() { return getPrimaryPrincipal(getPrincipals()); } public PrincipalCollection getPrincipals() { List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack(); return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0); } public boolean isPermitted(String permission) { return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission); } public boolean isPermitted(Permission permission) { return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission); } public boolean[] isPermitted(String... permissions) { if (hasPrincipals()) { return securityManager.isPermitted(getPrincipals(), permissions); } else { return new boolean[permissions.length]; } } public boolean[] isPermitted(List<Permission> permissions) { if (hasPrincipals()) { return securityManager.isPermitted(getPrincipals(), permissions); } else { return new boolean[permissions.size()]; } } public boolean isPermittedAll(String... permissions) { return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions); } public boolean isPermittedAll(Collection<Permission> permissions) { return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions); } protected void assertAuthzCheckPossible() throws AuthorizationException { if (!hasPrincipals()) { String msg = "This subject is anonymous - it does not have any identifying principals and " + "authorization operations require an identity to check against. A Subject instance will " + "acquire these identifying principals automatically after a successful login is performed " + "be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " + "functionality is enabled by the SecurityManager. This exception can also occur when a " + "previously logged-in Subject has logged out which " + "makes it anonymous again. Because an identity is currently not known due to any of these " + "conditions, authorization is denied."; throw new UnauthenticatedException(msg); } } public void checkPermission(String permission) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkPermission(getPrincipals(), permission); } public void checkPermission(Permission permission) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkPermission(getPrincipals(), permission); } public void checkPermissions(String... permissions) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkPermissions(getPrincipals(), permissions); } public void checkPermissions(Collection<Permission> permissions) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkPermissions(getPrincipals(), permissions); } public boolean hasRole(String roleIdentifier) { return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier); } public boolean[] hasRoles(List<String> roleIdentifiers) { if (hasPrincipals()) { return securityManager.hasRoles(getPrincipals(), roleIdentifiers); } else { return new boolean[roleIdentifiers.size()]; } } public boolean hasAllRoles(Collection<String> roleIdentifiers) { return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers); } public void checkRole(String role) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkRole(getPrincipals(), role); } public void checkRoles(String... roleIdentifiers) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkRoles(getPrincipals(), roleIdentifiers); } public void checkRoles(Collection<String> roles) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkRoles(getPrincipals(), roles); } public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); Subject subject = securityManager.login(this, token); PrincipalCollection principals; String host = null; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals == null || principals.isEmpty()) { String msg = "Principals returned from securityManager.login( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = decorate(session); } else { this.session = null; } } public boolean isAuthenticated() { return authenticated; } public boolean isRemembered() { PrincipalCollection principals = getPrincipals(); return principals != null && !principals.isEmpty() && !isAuthenticated(); } /** * Returns {@code true} if this Subject is allowed to create sessions, {@code false} otherwise. * * @return {@code true} if this Subject is allowed to create sessions, {@code false} otherwise. * @since 1.2 */ protected boolean isSessionCreationEnabled() { return this.sessionCreationEnabled; } public Session getSession() { return getSession(true); } public Session getSession(boolean create) { if (log.isTraceEnabled()) { log.trace("attempting to get session; create = " + create + "; session is null = " + (this.session == null) + "; session has id = " + (this.session != null && session.getId() != null)); } if (this.session == null && create) { if (!isSessionCreationEnabled()) { String msg = "Session creation has been disabled for the current subject. This exception indicates " + "that there is either a programming error (using a session when it should never be " + "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " + "for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " + "for more."; throw new DisabledSessionException(msg); } if (log.isTraceEnabled()) { log.trace("Starting session for host {}", getHost()); } SessionContext sessionContext = createSessionContext(); Session session = this.securityManager.start(sessionContext); this.session = decorate(session); } return this.session; } protected SessionContext createSessionContext() { SessionContext sessionContext = new DefaultSessionContext(); if (StringUtils.hasText(host)) { sessionContext.setHost(host); } return sessionContext; } private void clearRunAsIdentitiesInternal() { try { clearRunAsIdentities(); } catch (SessionException se) { log.debug("Encountered session exception trying to clear 'runAs' identities during logout. This " + "can generally safely be ignored.", se); } } public void logout() { try { clearRunAsIdentitiesInternal(); this.securityManager.logout(this); } finally { this.session = null; this.principals = null; this.authenticated = false; } } private void sessionStopped() { this.session = null; } public <V> V execute(Callable<V> callable) throws ExecutionException { Callable<V> associated = associateWith(callable); try { return associated.call(); } catch (Throwable t) { throw new ExecutionException(t); } } public void execute(Runnable runnable) { Runnable associated = associateWith(runnable); associated.run(); } public <V> Callable<V> associateWith(Callable<V> callable) { return new SubjectCallable<V>(this, callable); } public Runnable associateWith(Runnable runnable) { if (runnable instanceof Thread) { String msg = "This implementation does not support Thread arguments because of JDK ThreadLocal " + "inheritance mechanisms required by Security. Instead, the method argument should be a non-Thread " + "Runnable and the return value from this method can then be given to an ExecutorService or " + "another Thread."; throw new UnsupportedOperationException(msg); } return new SubjectRunnable(this, runnable); } private class StoppingAwareProxiedSession extends ProxiedSession { private final DelegatingSubject owner; private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) { super(target); owner = owningSubject; } public void stop() throws InvalidSessionException { super.stop(); owner.sessionStopped(); } } public void runAs(PrincipalCollection principals) { if (!hasPrincipals()) { String msg = "This subject does not yet have an identity. Assuming the identity of another " + "Subject is only allowed for Subjects with an existing identity. Try logging this subject in " + "first, or using the " + SubjectBuilder.class.getName() + " to build ad hoc Subject instances " + "with identities as necessary."; throw new IllegalStateException(msg); } pushIdentity(principals); } public boolean isRunAs() { List<PrincipalCollection> stack = getRunAsPrincipalsStack(); return !CollectionUtils.isEmpty(stack); } public PrincipalCollection getPreviousPrincipals() { PrincipalCollection previousPrincipals = null; List<PrincipalCollection> stack = getRunAsPrincipalsStack(); int stackSize = stack != null ? stack.size() : 0; if (stackSize > 0) { if (stackSize == 1) { previousPrincipals = this.principals; } else { assert stack != null; previousPrincipals = stack.get(1); } } return previousPrincipals; } public PrincipalCollection releaseRunAs() { return popIdentity(); } private List<PrincipalCollection> getRunAsPrincipalsStack() { Session session = getSession(false); if (session != null) { return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY); } return null; } private void clearRunAsIdentities() { Session session = getSession(false); if (session != null) { session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY); } } private void pushIdentity(PrincipalCollection principals) throws NullPointerException { if (CollectionUtils.isEmpty(principals)) { String msg = "Specified Subject principals cannot be null or empty for 'run as' functionality."; throw new NullPointerException(msg); } List<PrincipalCollection> stack = getRunAsPrincipalsStack(); if (stack == null) { stack = new CopyOnWriteArrayList<PrincipalCollection>(); } stack.add(0, principals); Session session = getSession(); session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack); } private PrincipalCollection popIdentity() { PrincipalCollection popped = null; List<PrincipalCollection> stack = getRunAsPrincipalsStack(); if (!CollectionUtils.isEmpty(stack)) { popped = stack.remove(0); Session session; if (!CollectionUtils.isEmpty(stack)) { session = getSession(); session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack); } else { clearRunAsIdentities(); } } return popped; } }