| /* |
| * Copyright 2005-2008 Les Hazlewood, Jeremy Haile |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jsecurity.subject; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.jsecurity.authc.AuthenticationException; |
| import org.jsecurity.authc.AuthenticationToken; |
| import org.jsecurity.authc.InetAuthenticationToken; |
| import org.jsecurity.authz.AuthorizationException; |
| import org.jsecurity.authz.Permission; |
| import org.jsecurity.authz.UnauthenticatedException; |
| import org.jsecurity.mgt.SecurityManager; |
| import org.jsecurity.session.Session; |
| |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * <p>Implementation of the <tt>Subject</tt> interface that delegates |
| * method calls to an underlying {@link org.jsecurity.mgt.SecurityManager SecurityManager} instance for security checks. |
| * It is essentially a <tt>SecurityManager</tt> proxy.</p> |
| * |
| * <p>This implementation does not maintain state such as roles and permissions (only a subject |
| * identifier, such as a user primary key or username) for better performance in a stateless |
| * architecture. It instead asks the underlying <tt>SecurityManager</tt> every time to perform |
| * the authorization check.</p> |
| * |
| * <p>A common misconception in using this implementation is that an EIS resource (RDBMS, etc) would |
| * be "hit" every time a method is called. This is not necessarily the case and is |
| * up to the implementation of the underlying <tt>SecurityManager</tt> instance. If caching of authorization |
| * data is desired (to eliminate EIS round trips and therefore improve database performance), it is considered |
| * much more elegant to let the underlying <tt>SecurityManager</tt> implementation manage caching, not this class. A |
| * <tt>SecurityManager</tt> is considered a business-tier component, where caching strategies are better suited.</p> |
| * |
| * <p>Applications from large and clustered to simple and vm local all benefit from |
| * stateless architectures. This implementation plays a part in the stateless programming |
| * paradigm and should be used whenever possible.</p> |
| * |
| * @author Les Hazlewood |
| * @author Jeremy Haile |
| * @since 0.1 |
| */ |
| public class DelegatingSubject implements Subject { |
| |
| protected transient final Log log = LogFactory.getLog(getClass()); |
| |
| protected Object principal = null; |
| protected boolean authenticated = false; |
| protected InetAddress inetAddress = null; |
| protected Session session = null; |
| protected boolean invalidated = false; |
| |
| protected SecurityManager securityManager; |
| |
| protected static InetAddress getLocalHost() { |
| try { |
| return InetAddress.getLocalHost(); |
| } catch (UnknownHostException e) { |
| return null; |
| } |
| } |
| |
| public DelegatingSubject(SecurityManager securityManager) { |
| this(null, false, getLocalHost(), null, securityManager); |
| } |
| |
| public DelegatingSubject(Object principal, boolean authenticated, InetAddress inetAddress, |
| Session session, SecurityManager securityManager) { |
| if (securityManager == null) { |
| throw new IllegalArgumentException("SecurityManager argument cannot be null."); |
| } |
| this.securityManager = securityManager; |
| this.principal = principal; |
| if (principal instanceof Collection) { |
| //noinspection unchecked |
| this.principal = Collections.unmodifiableCollection((Collection) principal); |
| } |
| |
| this.authenticated = authenticated; |
| |
| if (inetAddress != null) { |
| this.inetAddress = inetAddress; |
| } else { |
| this.inetAddress = getLocalHost(); |
| } |
| this.session = session; |
| } |
| |
| protected void assertValid() throws InvalidSubjectException { |
| if (isInvalidated()) { |
| String msg = "The Subject has been invalidated. It can no longer be used."; |
| throw new InvalidSubjectException(msg); |
| } |
| } |
| |
| protected boolean isInvalidated() { |
| return invalidated; |
| } |
| |
| protected void setInvalidated(boolean invalidated) { |
| this.invalidated = invalidated; |
| } |
| |
| public org.jsecurity.mgt.SecurityManager getSecurityManager() { |
| return securityManager; |
| } |
| |
| protected boolean hasPrincipal() { |
| return getPrincipal() != null; |
| } |
| |
| /** |
| * Returns the InetAddress associated with the client who created/is interacting with this Subject. |
| * |
| * @return the InetAddress associated with the client who created/is interacting with this Subject. |
| */ |
| public InetAddress getInetAddress() { |
| assertValid(); |
| return this.inetAddress; |
| } |
| |
| /** |
| * @see Subject#getPrincipal() |
| */ |
| public Object getPrincipal() { |
| return this.principal; |
| } |
| |
| /** |
| * @see Subject#getPrincipalByType(Class) () |
| */ |
| public <T> T getPrincipalByType(Class<T> principalType) { |
| assertValid(); |
| if (this.principal instanceof Collection) { |
| Collection c = (Collection) this.principal; |
| for (Object o : c) { |
| if (principalType.isAssignableFrom(o.getClass())) { |
| //noinspection unchecked |
| return (T) o; |
| } |
| } |
| } else { |
| if (principalType.isAssignableFrom(this.principal.getClass())) { |
| //noinspection unchecked |
| return (T) this.principal; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @see Subject#getAllPrincipalsByType(Class)() |
| */ |
| public <T> Collection<T> getAllPrincipalsByType(Class<T> principalType) { |
| assertValid(); |
| List<T> principalsOfType = new ArrayList<T>(); |
| |
| if (principal != null) { |
| if (principal instanceof Collection) { |
| Collection c = (Collection) principal; |
| if (!c.isEmpty()) { |
| for (Object o : c) { |
| if (principalType.isAssignableFrom(o.getClass())) { |
| //noinspection unchecked |
| principalsOfType.add((T) o); |
| } |
| } |
| } |
| } else { |
| if (principalType.isAssignableFrom(principal.getClass())) { |
| //noinspection unchecked |
| principalsOfType.add((T) principal); |
| } |
| } |
| } |
| return principalsOfType; |
| } |
| |
| public boolean isPermitted(String permission) { |
| assertValid(); |
| return hasPrincipal() && securityManager.isPermitted(getPrincipal(), permission); |
| } |
| |
| public boolean isPermitted(Permission permission) { |
| assertValid(); |
| return hasPrincipal() && securityManager.isPermitted(getPrincipal(), permission); |
| } |
| |
| public boolean[] isPermitted(String... permissions) { |
| assertValid(); |
| if (hasPrincipal()) { |
| return securityManager.isPermitted(getPrincipal(), permissions); |
| } else { |
| return new boolean[permissions.length]; |
| } |
| } |
| |
| public boolean[] isPermitted(List<Permission> permissions) { |
| assertValid(); |
| if (hasPrincipal()) { |
| return securityManager.isPermitted(getPrincipal(), permissions); |
| } else { |
| return new boolean[permissions.size()]; |
| } |
| } |
| |
| public boolean isPermittedAll(String... permissions) { |
| assertValid(); |
| return hasPrincipal() && securityManager.isPermittedAll(getPrincipal(), permissions); |
| } |
| |
| public boolean isPermittedAll(Collection<Permission> permissions) { |
| assertValid(); |
| return hasPrincipal() && securityManager.isPermittedAll(getPrincipal(), permissions); |
| } |
| |
| protected void assertAuthzCheckPossible() throws AuthorizationException { |
| if (!hasPrincipal()) { |
| String msg = "Account data has not yet been associated with this Subject instance" + |
| "(this can be done by executing " + Subject.class.getName() + ".login(AuthenticationToken) )." + |
| "Therefore, authorization operations are not possible (a Subject/Account identity is required first). " + |
| "Denying authorization."; |
| throw new UnauthenticatedException(msg); |
| } |
| } |
| |
| public void checkPermission(String permission) throws AuthorizationException { |
| assertValid(); |
| assertAuthzCheckPossible(); |
| securityManager.checkPermission(getPrincipal(), permission); |
| } |
| |
| public void checkPermission(Permission permission) throws AuthorizationException { |
| assertValid(); |
| assertAuthzCheckPossible(); |
| securityManager.checkPermission(getPrincipal(), permission); |
| } |
| |
| public void checkPermissions(String... permissions) |
| throws AuthorizationException { |
| assertValid(); |
| assertAuthzCheckPossible(); |
| securityManager.checkPermissions(getPrincipal(), permissions); |
| } |
| |
| public void checkPermissions(Collection<Permission> permissions) |
| throws AuthorizationException { |
| assertValid(); |
| assertAuthzCheckPossible(); |
| securityManager.checkPermissions(getPrincipal(), permissions); |
| } |
| |
| public boolean hasRole(String roleIdentifier) { |
| assertValid(); |
| return hasPrincipal() && securityManager.hasRole(getPrincipal(), roleIdentifier); |
| } |
| |
| public boolean[] hasRoles(List<String> roleIdentifiers) { |
| assertValid(); |
| if (hasPrincipal()) { |
| return securityManager.hasRoles(getPrincipal(), roleIdentifiers); |
| } else { |
| return new boolean[roleIdentifiers.size()]; |
| } |
| } |
| |
| public boolean hasAllRoles(Collection<String> roleIdentifiers) { |
| assertValid(); |
| return hasPrincipal() && securityManager.hasAllRoles(getPrincipal(), roleIdentifiers); |
| } |
| |
| public void checkRole(String role) throws AuthorizationException { |
| assertValid(); |
| assertAuthzCheckPossible(); |
| securityManager.checkRole(getPrincipal(), role); |
| } |
| |
| public void checkRoles(Collection<String> roles) throws AuthorizationException { |
| assertValid(); |
| assertAuthzCheckPossible(); |
| securityManager.checkRoles(getPrincipal(), roles); |
| } |
| |
| public void login(AuthenticationToken token) throws AuthenticationException { |
| assertValid(); |
| Subject authcSecCtx = securityManager.login(token); |
| Object principals = authcSecCtx.getPrincipal(); |
| if (principals instanceof Collection && ((Collection) principals).isEmpty()) { |
| principals = null; |
| } |
| if (principals == null) { |
| String msg = "Principals returned from securityManager.login( token ) returned a null or " + |
| "empty value. This value must be non null, and if a collection, the collection must " + |
| "be populated with one or more elements. Please check the SecurityManager " + |
| "implementation to ensure this happens after a successful login attempt."; |
| throw new IllegalStateException(msg); |
| } |
| this.principal = principals; |
| this.authenticated = true; |
| if (token instanceof InetAuthenticationToken) { |
| InetAddress addy = ((InetAuthenticationToken) token).getInetAddress(); |
| if (addy != null) { |
| this.inetAddress = addy; |
| } |
| } |
| } |
| |
| public boolean isAuthenticated() { |
| assertValid(); |
| return authenticated; |
| } |
| |
| public Session getSession() { |
| return getSession(true); |
| } |
| |
| public Session getSession(boolean create) { |
| assertValid(); |
| if (this.session == null && create) { |
| this.session = securityManager.start(getInetAddress()); |
| } |
| return this.session; |
| } |
| |
| public void logout() { |
| if (isInvalidated()) { |
| return; |
| } |
| |
| try { |
| this.securityManager.logout(getPrincipal()); |
| } finally { |
| setInvalidated(true); |
| this.session = null; |
| this.principal = null; |
| this.authenticated = false; |
| this.inetAddress = null; |
| this.securityManager = null; |
| } |
| } |
| |
| } |