/**
* *****************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi
*
*
******************************************************************************
*/
package hudson.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
/**
* {@link ACL} that checks permissions based on {@link GrantedAuthority} of the
* {@link Authentication}.
*
* @author Kohsuke Kawaguchi
*/
public abstract class SidACL extends ACL {
private static final Logger LOGGER = LoggerFactory.getLogger(SidACL.class);
@Override
public boolean hasPermission(Authentication a, Permission permission) {
if (a == SYSTEM) {
LOGGER.debug("hasPermission({},{})=>SYSTEM user has full access", a, permission);
return true;
}
Boolean b = _hasPermission(a, permission);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("hasPermission(" + a + "," + permission + ")=>" + (b == null ? "null, thus false" : b));
}
if (b == null) {
b = false; // default to rejection
}
return b;
}
/**
* Implementation that backs up
* {@link #hasPermission(Authentication, Permission)}.
*
* @return true or false if {@link #hasPermission(Sid, Permission)} returns
* it. Otherwise null, indicating that this ACL doesn't have any entry for
* it.
*/
protected Boolean _hasPermission(Authentication a, Permission permission) {
// ACL entries for this principal takes precedence
LOGGER.debug("Checking if principal {} has {}", a.getName(), permission);
Boolean b = hasPermission(new PrincipalSid(a), permission);
if (b != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("hasPermission(PrincipalSID:" + a.getPrincipal() + "," + permission + ")=>" + b);
}
return b;
}
// after that, we check if the groups this principal belongs to
// has any ACL entries.
// here we are using GrantedAuthority as a group
for (GrantedAuthority ga : a.getAuthorities()) {
LOGGER.debug("Checking if principal's role {} has {}", ga.getAuthority(), permission);
b = hasPermission(new GrantedAuthoritySid(ga), permission);
if (b != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("hasPermission(GroupSID:" + ga.getAuthority() + "," + permission + ")=>" + b);
}
return b;
}
}
// permissions granted to 'everyone' and 'anonymous' users are granted to everyone
for (Sid sid : AUTOMATIC_SIDS) {
b = hasPermission(sid, permission);
if (b != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("hasPermission(" + sid + "," + permission + ")=>" + b);
}
return b;
}
}
return null;
}
/**
* Checks if the given {@link Sid} has the given {@link Permission}.
*
* <p> {@link #hasPermission(Authentication, Permission)} is implemented by
* checking authentication's {@link GrantedAuthority} by using this method.
*
* <p> It is the implementor's responsibility to recognize
* {@link Permission#impliedBy} and take that into account.
*
* @return true if the access should be granted, false if it should be
* denied. The null value indicates that the ACL does no rule for this
* Sid/Permission combination. The caller can decide what to do &mash; such
* as consulting the higher level ACL, or denying the access (if the model
* is no-access-by-default.)
*/
protected abstract Boolean hasPermission(Sid p, Permission permission);
protected String toString(Sid p) {
if (p instanceof GrantedAuthoritySid) {
return ((GrantedAuthoritySid) p).getGrantedAuthority();
}
if (p instanceof PrincipalSid) {
return ((PrincipalSid) p).getPrincipal();
}
if (p == EVERYONE) {
return "role_everyone";
}
// hmm...
return p.toString();
}
/**
* Creates a new {@link SidACL} that first consults 'this' {@link SidACL}
* and then delegate to the given parent {@link SidACL}. By doing this at
* the {@link SidACL} level and not at the {@link ACL} level, this allows
* the child ACLs to have an explicit deny entry. Note that the combined ACL
* calls hasPermission(Sid,Permission) in the child and parent SidACLs
* directly, so if these override _hasPermission then this custom behavior
* will not be applied.
*/
public final SidACL newInheritingACL(final SidACL parent) {
final SidACL child = this;
return new SidACL() {
protected Boolean hasPermission(Sid p, Permission permission) {
Boolean b = child.hasPermission(p, permission);
if (b != null) {
return b;
}
return parent.hasPermission(p, permission);
}
};
}
}