package hudson.security; import hudson.model.Item; import hudson.model.Job; import hudson.model.JobProperty; import hudson.model.JobPropertyDescriptor; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import net.sf.json.JSONObject; import org.acegisecurity.Authentication; import org.acegisecurity.acls.sid.GrantedAuthoritySid; import org.acegisecurity.acls.sid.PrincipalSid; import org.acegisecurity.acls.sid.Sid; import org.kohsuke.stapler.StaplerRequest; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; public class AuthorizationMatrixProperty extends JobProperty> { public static final JobPropertyDescriptor DESCRIPTOR = new DescriptorImpl(); private transient ACL acl = new AclImpl(); private boolean useProjectSecurity; public boolean isUseProjectSecurity() { return useProjectSecurity; } public void setUseProjectSecurity(boolean useProjectSecurity) { this.useProjectSecurity = useProjectSecurity; } /** * List up all permissions that are granted. * * Strings are either the granted authority or the principal, which is not * distinguished. */ private final Map> grantedPermissions = new HashMap>(); private Set sids = new HashSet(); public Set getGroups() { return sids; } /** * Returns all SIDs configured in this matrix, minus "anonymous" * * @return Always non-null. */ public List getAllSIDs() { Set r = new HashSet(); for (Set set : grantedPermissions.values()) r.addAll(set); r.remove("anonymous"); String[] data = r.toArray(new String[r.size()]); Arrays.sort(data); return Arrays.asList(data); } /** * Adds to {@link #grantedPermissions}. Use of this method should be limited * during construction, as this object itself is considered immutable once * populated. */ protected void add(Permission p, String sid) { Set set = grantedPermissions.get(p); if (set == null) grantedPermissions.put(p, set = new HashSet()); set.add(sid); sids.add(sid); } @Override public JobPropertyDescriptor getDescriptor() { return DESCRIPTOR; } public static class DescriptorImpl extends JobPropertyDescriptor { @Override public JobProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException { boolean useProjectSecurity = formData.has("useProjectSecurity"); AuthorizationMatrixProperty amp = new AuthorizationMatrixProperty(); amp.setUseProjectSecurity(useProjectSecurity); for (Map.Entry r : (Set>) formData .getJSONObject("data").entrySet()) { String sid = r.getKey(); if (r.getValue() instanceof JSONObject) { for (Map.Entry e : (Set>) ((JSONObject) r .getValue()).entrySet()) { if (e.getValue()) { Permission p = Permission.fromId(e.getKey()); amp.add(p, sid); } } } } return amp; } protected DescriptorImpl() { super(AuthorizationMatrixProperty.class); } @Override public boolean isApplicable(Class jobType) { return true; } @Override public String getDisplayName() { return "Authorization Matrix"; } public List getAllGroups() { return Collections.singletonList(PermissionGroup.get(Item.class)); } } private final class AclImpl extends SidACL { protected Boolean hasPermission(Sid sid, Permission p) { String s = toString(sid); for (; p != null; p = p.impliedBy) { Set set = grantedPermissions.get(p); if (set != null && set.contains(s)) return true; } return false; } protected Boolean _hasPermission(Authentication a, Permission permission) { Boolean b = super._hasPermission(a, permission); // permissions granted to anonymous users are granted to everyone if (b == null) b = hasPermission(ANONYMOUS, permission); return b; } private String toString(Sid p) { if (p instanceof GrantedAuthoritySid) return ((GrantedAuthoritySid) p).getGrantedAuthority(); if (p instanceof PrincipalSid) return ((PrincipalSid) p).getPrincipal(); // hmm... return p.toString(); } } private Object readResolve() { acl = new AclImpl(); return this; } public ACL getACL() { return acl; } /** * Checks if the given SID has the given permission. */ public boolean hasPermission(String sid, Permission p) { for (; p != null; p = p.impliedBy) { Set set = grantedPermissions.get(p); if (set != null && set.contains(sid)) return true; } return false; } /** * Works like {@link #add(Permission, String)} but takes both parameters * from a single string of the form PERMISSIONID:sid */ private void add(String shortForm) { int idx = shortForm.indexOf(':'); add(Permission.fromId(shortForm.substring(0, idx)), shortForm .substring(idx + 1)); } /** * Persist {@link ProjectMatrixAuthorizationStrategy} as a list of IDs that * represent {@link ProjectMatrixAuthorizationStrategy#grantedPermissions}. */ public static final class ConverterImpl implements Converter { public boolean canConvert(Class type) { return type == AuthorizationMatrixProperty.class; } public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { AuthorizationMatrixProperty amp = (AuthorizationMatrixProperty) source; for (Entry> e : amp.grantedPermissions .entrySet()) { String p = e.getKey().getId(); for (String sid : e.getValue()) { writer.startNode("permission"); context.convertAnother(p + ':' + sid); writer.endNode(); } } } public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) { AuthorizationMatrixProperty as = new AuthorizationMatrixProperty(); while (reader.hasMoreChildren()) { reader.moveDown(); String id = (String) context.convertAnother(as, String.class); as.add(id); reader.moveUp(); } return as; } } }