diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java index 39ec2328d1..0c4e615987 100644 --- a/core/src/main/java/hudson/model/Item.java +++ b/core/src/main/java/hudson/model/Item.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -196,6 +196,7 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Permission.DELETE); public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Permission.CONFIGURE); public static final Permission READ = new Permission(PERMISSIONS,"Read", Permission.READ); + public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._AbstractProject_ExtendedReadPermission_Description(), CONFIGURE, Boolean.getBoolean("hudson.security.ExtendedReadPermission")); public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._AbstractProject_BuildPermission_Description(), Permission.UPDATE); public static final Permission WORKSPACE = new Permission(PERMISSIONS, "Workspace", Messages._AbstractProject_WorkspacePermission_Description(), Permission.READ); } diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index e42812b14f..60be4f8c7e 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -977,7 +977,7 @@ public abstract class Job, RunT extends Run set = grantedPermissions.get(p); - if(set!=null && set.contains(sid)) + if(set!=null && set.contains(sid) && p.getEnabled()) return true; } return false; @@ -155,7 +155,7 @@ public class GlobalMatrixAuthorizationStrategy extends AuthorizationStrategy { */ public boolean hasExplicitPermission(String sid, Permission p) { Set set = grantedPermissions.get(p); - return set != null && set.contains(sid); + return set != null && set.contains(sid) && p.getEnabled(); } /** @@ -266,7 +266,7 @@ public class GlobalMatrixAuthorizationStrategy extends AuthorizationStrategy { } public boolean showPermission(Permission p) { - return true; + return p.getEnabled(); } public FormValidation doCheckName(@QueryParameter String value ) throws IOException, ServletException { diff --git a/core/src/main/java/hudson/security/Permission.java b/core/src/main/java/hudson/security/Permission.java index 5d3eaf3f56..8050a26428 100644 --- a/core/src/main/java/hudson/security/Permission.java +++ b/core/src/main/java/hudson/security/Permission.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -82,6 +82,18 @@ public final class Permission { */ public final Permission impliedBy; + /** + * Whether this permission is available for use. + * + *

+ * This allows us to dynamically enable or disable the visibility of + * permissions, so administrators can control the complexitity of their + * permission matrix. + * + * @since 1.325 + */ + public boolean enabled; + /** * Defines a new permission. * @@ -106,7 +118,7 @@ public final class Permission { * @param impliedBy * See {@link #impliedBy}. */ - public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy) { + public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy, boolean enable) { if(!JSONUtils.isJavaIdentifier(name)) throw new IllegalArgumentException(name+" is not a Java identifier"); this.owner = group.owner; @@ -114,11 +126,16 @@ public final class Permission { this.name = name; this.description = description; this.impliedBy = impliedBy; + this.enabled = enable; group.add(this); ALL.add(this); } + public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy) { + this(group, name, description, impliedBy, true); + } + /** * @deprecated since 1.257. * Use {@link #Permission(PermissionGroup, String, Localizable, Permission)} @@ -171,6 +188,14 @@ public final class Permission { return "Permission["+owner+','+name+']'; } + public void setEnabled(boolean enable) { + enabled = enable; + } + + public boolean getEnabled() { + return enabled; + } + /** * Returns all the {@link Permission}s available in the system. * @return diff --git a/core/src/main/resources/hudson/model/AbstractProject/sidepanel.jelly b/core/src/main/resources/hudson/model/AbstractProject/sidepanel.jelly index c80319575d..4cf0be80c6 100644 --- a/core/src/main/resources/hudson/model/AbstractProject/sidepanel.jelly +++ b/core/src/main/resources/hudson/model/AbstractProject/sidepanel.jelly @@ -54,7 +54,14 @@ THE SOFTWARE. - + + + + + + + + diff --git a/core/src/main/resources/hudson/model/Job/configure.jelly b/core/src/main/resources/hudson/model/Job/configure.jelly index d040c8d843..c5ee2464dd 100644 --- a/core/src/main/resources/hudson/model/Job/configure.jelly +++ b/core/src/main/resources/hudson/model/Job/configure.jelly @@ -26,7 +26,7 @@ THE SOFTWARE. Config page. derived class specific entries should go to configure-entries.jsp --> - + @@ -67,10 +67,12 @@ THE SOFTWARE. - - - - + + + + + + diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index e8aa4c0bf0..05b4e947da 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -43,6 +43,10 @@ AbstractProject.WorkspacePermission.Description=\ This permission grants the ability to retrieve the contents of a workspace \ Hudson checked out for performing builds. If you don''t want an user to access \ the source code, you can do so by revoking this permission. +AbstractProject.ExtendedReadPermission.Description=\ + This permission grants read-only access to project configurations. Please be \ + aware that sensitive information in your builds, such as passwords, will be \ + exposed to a wider audience by granting this permission. Api.MultipleMatch=XPath "{0}" matched {1} nodes. \ Create XPath that only matches one, or use the "wrapper" query parameter to wrap them all under a root element. diff --git a/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java b/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java new file mode 100644 index 0000000000..848b159045 --- /dev/null +++ b/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java @@ -0,0 +1,105 @@ +package hudson.security; + +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.html.HtmlButton; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.Item; +import org.jvnet.hudson.test.HudsonTestCase; +import org.jvnet.hudson.test.recipes.LocalData; + +/** + * + * @author dty + */ +public class ExtendedReadPermissionTest extends HudsonTestCase { + /** + * alice: Job/Configure+Read + * bob: Job/Read + * charlie: Job/ExtendedRead+Read + */ + + private void setPermissionEnabled(boolean enabled) throws Exception { + Item.EXTENDED_READ.setEnabled(enabled); + } + + + @LocalData + public void testReadOnlyConfigAccessWithPermissionEnabled() throws Exception { + setPermissionEnabled(true); + + AuthorizationStrategy as = hudson.getAuthorizationStrategy(); + assertTrue("Expecting GlobalMatrixAuthorizationStrategy", (as instanceof GlobalMatrixAuthorizationStrategy)); + GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as; + assertTrue("Charlie should have extended read for this test", gas.hasExplicitPermission("charlie",Item.EXTENDED_READ)); + + WebClient wc = new WebClient().login("charlie","charlie"); + HtmlPage page = wc.goTo("job/a/configure"); + HtmlForm form = page.getFormByName("config"); + HtmlButton saveButton = getButtonByCaption(form,"Save"); + assertNull(saveButton); + } + + @LocalData + public void testReadOnlyConfigAccessWithPermissionDisabled() throws Exception { + setPermissionEnabled(false); + + AuthorizationStrategy as = hudson.getAuthorizationStrategy(); + assertTrue("Expecting GlobalMatrixAuthorizationStrategy", (as instanceof GlobalMatrixAuthorizationStrategy)); + GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as; + assertFalse("Charlie should not have extended read for this test", gas.hasExplicitPermission("charlie",Item.EXTENDED_READ)); + + WebClient wc = new WebClient().login("charlie","charlie"); + try { + HtmlPage page = wc.goTo("job/a/configure"); + } + catch (FailingHttpStatusCodeException e) { + assertEquals(403,e.getStatusCode()); + return; + } + + fail("Charlie should not have been able to access the configuration page"); + } + + @LocalData + public void testNoConfigAccessWithPermissionEnabled() throws Exception { + setPermissionEnabled(true); + + AuthorizationStrategy as = hudson.getAuthorizationStrategy(); + assertTrue("Expecting GlobalMatrixAuthorizationStrategy", (as instanceof GlobalMatrixAuthorizationStrategy)); + GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as; + assertFalse("Bob should not have extended read for this test", gas.hasExplicitPermission("bob",Item.EXTENDED_READ)); + + WebClient wc = new WebClient().login("bob","bob"); + try { + HtmlPage page = wc.goTo("job/a/configure"); + } + catch (FailingHttpStatusCodeException e) { + assertEquals(403,e.getStatusCode()); + return; + } + + fail("Bob should not have been able to access the configuration page"); + } + +/* + @LocalData + public void testConfigureLink() throws Exception { + + } + + @LocalData + public void testViewConfigurationLink() throws Exception { + + } + + @LocalData + public void testMatrixWithPermissionEnabled() throws Exception { + } + + @LocalData + public void testMatrixWithPermissionDisabled() throws Exception { + + } + */ +} diff --git a/test/src/test/resources/hudson/security/ExtendedReadPermissionTest.zip b/test/src/test/resources/hudson/security/ExtendedReadPermissionTest.zip new file mode 100644 index 0000000000..2084f20e15 Binary files /dev/null and b/test/src/test/resources/hudson/security/ExtendedReadPermissionTest.zip differ