T[] toArray(T[] array) {
return storage().toArray(array);
}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 14571c350335e80ea2f3e91016aa7905faccea91..6ff9f95cdc80f31f9f1345364fcf14fd49ad6567 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -90,8 +90,12 @@ import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
+import jenkins.security.ProjectAuthenticator;
+import jenkins.security.ProjectAuthenticatorConfiguration;
+import jenkins.security.ProjectAuthenticatorConfiguration;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
+import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.kohsuke.accmod.Restricted;
@@ -1174,6 +1178,13 @@ public abstract class AbstractProject,R extends A
return this;
}
+ /**
+ * Let the identity determined by {@link ProjectAuthenticator}.
+ */
+ public Authentication getIdentity() {
+ return ProjectAuthenticatorConfiguration.get().authenticate(this);
+ }
+
/**
* {@inheritDoc}
*
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index b99b6fbb0296808ce7e4358b1f377ce7d7ff2198..f2aaf341cde9759d84350669b9fe7e50aac3392f 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -38,6 +38,8 @@ import hudson.security.ACL;
import jenkins.model.InterruptedBuildAction;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
+import org.acegisecurity.context.SecurityContext;
+import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
@@ -236,10 +238,16 @@ public class Executor extends Thread implements ModelObject {
((Actionable) executable).addAction(action);
}
}
- setName(threadName+" : executing "+executable.toString());
- if (LOGGER.isLoggable(FINE))
- LOGGER.log(FINE, getName()+" is now executing "+executable);
- queue.execute(executable, task);
+
+ final SecurityContext savedContext = ACL.impersonate(Tasks.getIdentityOf(task));
+ try {
+ setName(threadName + " : executing " + executable.toString());
+ if (LOGGER.isLoggable(FINE))
+ LOGGER.log(FINE, getName()+" is now executing "+executable);
+ queue.execute(executable, task);
+ } finally {
+ SecurityContextHolder.setContext(savedContext);
+ }
} catch (Throwable e) {
// for some reason the executor died. this is really
// a bug in the code, but we don't want the executor to die,
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index c1b40239aaa263207de189a3ce2de31af25ecd40..bf3b14cf8cd49567127511543b979c94fe777d54 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -794,21 +794,13 @@ public class Fingerprint implements ModelObject, Saveable {
@Override
public boolean add(FingerprintFacet e) {
- try {
- facets.add(e);
- return true;
- } catch (IOException x) {
- throw new Error(x);
- }
+ facets.add(e);
+ return true;
}
@Override
public boolean remove(Object o) {
- try {
- return facets.remove((FingerprintFacet)o);
- } catch (IOException x) {
- throw new Error(x);
- }
+ return facets.remove(o);
}
@Override
diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java
index fe0a9dbcc8a51983f9dbd032fbcc0f233c5289e9..e8f7af4665461aa5d67053683b1206f2937ad8a1 100644
--- a/core/src/main/java/hudson/model/Node.java
+++ b/core/src/main/java/hudson/model/Node.java
@@ -62,6 +62,7 @@ import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONObject;
+import org.acegisecurity.Authentication;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.BindInterceptor;
import org.kohsuke.stapler.Stapler;
@@ -326,6 +327,13 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
if(l==null && getMode()== Mode.EXCLUSIVE)
return CauseOfBlockage.fromMessage(Messages._Node_BecauseNodeIsReserved(getNodeName())); // this node is reserved for tasks that are tied to it
+ Authentication identity = item.task.getIdentity();
+ if (!getACL().hasPermission(identity,AbstractProject.BUILD)) {
+ // doesn't have a permission
+ // TODO: does it make more sense to define a separate permission?
+ return CauseOfBlockage.fromMessage(Messages._Node_LackingBuildPermission(identity.getName(),getNodeName()));
+ }
+
// Check each NodeProperty to see whether they object to this node
// taking the task
for (NodeProperty prop: getNodeProperties()) {
diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java
index ef98acb5848f613eb7e08e5958d22abe51cc3e8f..bbc78d44542f2c5cc56f6f655847dd762a323023 100644
--- a/core/src/main/java/hudson/model/Run.java
+++ b/core/src/main/java/hudson/model/Run.java
@@ -1523,7 +1523,7 @@ public abstract class Run ,RunT extends Run
- * This mapping is done under two constraints:
+ * This mapping is done under the following constraints:
*
*
* -
@@ -65,6 +67,9 @@ import static java.lang.Math.*;
* See {@link SubTask#getSameNodeConstraint()}
*
-
* Label constraint. {@link SubTask}s can specify that it can be only run on nodes that has the label.
+ *
-
+ * Permission constraint. {@link SubTask}s have {@linkplain SubTask#getIdentity() identities} that need to have
+ * permissions to build on the node.
*
*
*
@@ -111,6 +116,7 @@ public class MappingWorksheet {
public final int index;
public final Computer computer;
public final Node node;
+ public final ACL nodeAcl;
private ExecutorChunk(List base, int index) {
super(base);
@@ -118,14 +124,25 @@ public class MappingWorksheet {
assert !base.isEmpty();
computer = base.get(0).getExecutor().getOwner();
node = computer.getNode();
+ nodeAcl = node.getACL();
}
/**
* Is this executor chunk and the given work chunk compatible? Can the latter be run on the former?
*/
public boolean canAccept(WorkChunk c) {
- return this.size() >= c.size()
- && (c.assignedLabel==null || c.assignedLabel.contains(node));
+ if (this.size() {
public final int index;
diff --git a/core/src/main/java/hudson/model/queue/QueueTaskFilter.java b/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
index b8b6e240c85ceb330ccc76c3d9a663936f92be3f..2399d103f9cd8b20ddd843c64a855bb3708931e0 100644
--- a/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
+++ b/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
@@ -30,6 +30,7 @@ import hudson.model.Queue;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.ResourceList;
+import org.acegisecurity.Authentication;
import java.io.IOException;
import java.util.Collection;
@@ -118,4 +119,8 @@ public abstract class QueueTaskFilter implements Queue.Task {
public Object getSameNodeConstraint() {
return base.getSameNodeConstraint();
}
+
+ public Authentication getIdentity() {
+ return base.getIdentity();
+ }
}
diff --git a/core/src/main/java/hudson/model/queue/SubTask.java b/core/src/main/java/hudson/model/queue/SubTask.java
index 315adece5177b629a28b87e3064b119da131c9ac..6a31232625f2e31c3aa1b02392c264e74fea8d21 100644
--- a/core/src/main/java/hudson/model/queue/SubTask.java
+++ b/core/src/main/java/hudson/model/queue/SubTask.java
@@ -29,7 +29,10 @@ import hudson.model.Node;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.ResourceActivity;
+import hudson.security.ACL;
+import org.acegisecurity.Authentication;
+import javax.annotation.Nonnull;
import java.io.IOException;
/**
@@ -83,4 +86,17 @@ public interface SubTask extends ResourceActivity {
* colocation constraint.
*/
Object getSameNodeConstraint();
+
+ /**
+ * Returns the identity that this task carries when it runs, for the purpose of access control.
+ *
+ * When the task execution touches other objects inside Jenkins, the access control is performed
+ * based on whether this {@link Authentication} is allowed to use them. Implementers, if you are unsure,
+ * return the identity of the user who queued the task, or {@link ACL#SYSTEM} to bypass the access control
+ * and run as the super user.
+ *
+ * @since 1.520
+ * @see Tasks#getIdentityOf(SubTask)
+ */
+ @Nonnull Authentication getIdentity();
}
diff --git a/core/src/main/java/hudson/model/queue/Tasks.java b/core/src/main/java/hudson/model/queue/Tasks.java
index cfa99357a0e7851a6bd0644990c32dfe8a1702aa..f4a1987782a64c9b7d28f4a7075631a05d2501c3 100644
--- a/core/src/main/java/hudson/model/queue/Tasks.java
+++ b/core/src/main/java/hudson/model/queue/Tasks.java
@@ -24,6 +24,8 @@
package hudson.model.queue;
import hudson.model.Queue.Task;
+import hudson.security.ACL;
+import org.acegisecurity.Authentication;
import java.util.Collection;
import java.util.Collections;
@@ -83,4 +85,21 @@ public class Tasks {
return (Task)t;
}
}
+
+ /**
+ * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
+ * on BugParade for more details.
+ */
+ private static Authentication _getIdentityOf(SubTask t) {
+ return t.getIdentity();
+ }
+
+ public static Authentication getIdentityOf(SubTask t) {
+ try {
+ return _getIdentityOf(t);
+ } catch (AbstractMethodError e) {
+ return ACL.SYSTEM;
+ }
+ }
+
}
diff --git a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java
index 31dd6855e38c2f3d6341446c147f0b85a31be1ae..9ebe8aa81fc78237c9877ec5f1c20dc2b6597d52 100644
--- a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java
+++ b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java
@@ -50,6 +50,8 @@ import org.kohsuke.stapler.StaplerResponse;
/**
* Security configuration.
*
+ * For historical reasons, most of the actual configuration values are stored in {@link Jenkins}.
+ *
* @author Kohsuke Kawaguchi
*/
@Extension(ordinal = Integer.MAX_VALUE - 210)
diff --git a/core/src/main/java/hudson/util/CopyOnWriteList.java b/core/src/main/java/hudson/util/CopyOnWriteList.java
index 9ba5de2bf31213fc0c4ce909b1a634d099d08dd2..d5a5741277744e10c149429c3bf763df6ae3476f 100644
--- a/core/src/main/java/hudson/util/CopyOnWriteList.java
+++ b/core/src/main/java/hudson/util/CopyOnWriteList.java
@@ -138,7 +138,7 @@ public class CopyOnWriteList implements Iterable {
this.core = new ArrayList();
}
- public E[] toArray(E[] array) {
+ public E[] toArray(E[] array) {
return core.toArray(array);
}
diff --git a/core/src/main/java/hudson/util/PersistedList.java b/core/src/main/java/hudson/util/PersistedList.java
index 89325d0c058a165e99ca3503b420bf712a4162e6..41b268e1a890e62ab5e9396911639add25915805 100644
--- a/core/src/main/java/hudson/util/PersistedList.java
+++ b/core/src/main/java/hudson/util/PersistedList.java
@@ -23,6 +23,7 @@
*/
package hudson.util;
+import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
@@ -34,6 +35,7 @@ import hudson.model.Describable;
import hudson.model.Saveable;
import java.io.IOException;
+import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -45,7 +47,7 @@ import java.util.List;
* @author Kohsuke Kawaguchi
* @since 1.MULTISOURCE
*/
-public class PersistedList implements Iterable {
+public class PersistedList extends AbstractList {
protected final CopyOnWriteList data = new CopyOnWriteList();
protected Saveable owner = Saveable.NOOP;
@@ -64,14 +66,18 @@ public class PersistedList implements Iterable {
this.owner = owner;
}
- public void add(T item) throws IOException {
+ @WithBridgeMethods(void.class)
+ public boolean add(T item) {
data.add(item);
- onModified();
+ _onModified();
+ return true;
}
- public void addAll(Collection extends T> items) throws IOException {
+ @WithBridgeMethods(void.class)
+ public boolean addAll(Collection extends T> items) {
data.addAll(items);
- onModified();
+ _onModified();
+ return true;
}
public void replaceBy(Collection extends T> col) throws IOException {
@@ -133,9 +139,9 @@ public class PersistedList implements Iterable {
data.replaceBy(copy);
}
- public boolean remove(T o) throws IOException {
- boolean b = data.remove(o);
- if (b) onModified();
+ public boolean remove(Object o) {
+ boolean b = data.remove((T)o);
+ if (b) _onModified();
return b;
}
@@ -167,6 +173,17 @@ public class PersistedList implements Iterable {
owner.save();
}
+ /**
+ * Version of {@link #_onModified()} that swallows an exception for compliance with {@link List}.
+ */
+ private void _onModified() {
+ try {
+ onModified();
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+ }
+
/**
* Returns the snapshot view of instances as list.
*/
@@ -177,7 +194,7 @@ public class PersistedList implements Iterable {
/**
* Gets all the {@link Describable}s in an array.
*/
- public T[] toArray(T[] array) {
+ public T[] toArray(T[] array) {
return data.toArray(array);
}
diff --git a/core/src/main/java/jenkins/security/ProjectAuthenticator.java b/core/src/main/java/jenkins/security/ProjectAuthenticator.java
new file mode 100644
index 0000000000000000000000000000000000000000..5942d465411b5b6e1d2662cd40dee50053e03a9e
--- /dev/null
+++ b/core/src/main/java/jenkins/security/ProjectAuthenticator.java
@@ -0,0 +1,37 @@
+package jenkins.security;
+
+import hudson.ExtensionPoint;
+import hudson.model.AbstractBuild;
+import hudson.model.AbstractDescribableImpl;
+import hudson.model.AbstractProject;
+import hudson.security.ACL;
+import org.acegisecurity.Authentication;
+
+/**
+ * Extension point to run {@link AbstractBuild}s under a specific identity for better access control.
+ *
+ * @author Kohsuke Kawaguchi
+ * @since 1.520
+ * @see ProjectAuthenticatorConfiguration
+ * @see AbstractProject#getIdentity()
+ */
+public abstract class ProjectAuthenticator extends AbstractDescribableImpl implements ExtensionPoint {
+ /**
+ * Determines the identity in which the build will run as.
+ *
+ * @param project
+ * The project to be built.
+ *
+ * @return
+ * returning non-null will determine the identity. If null is returned, the next
+ * configured {@link ProjectAuthenticator} will be given a chance to authenticate
+ * the executor. If everything fails, fall back to the historical behaviour of
+ * {@link ACL#SYSTEM}.
+ */
+ public abstract Authentication authenticate(AbstractProject,?> project);
+
+ @Override
+ public ProjectAuthenticatorDescriptor getDescriptor() {
+ return (ProjectAuthenticatorDescriptor)super.getDescriptor();
+ }
+}
diff --git a/core/src/main/java/jenkins/security/ProjectAuthenticatorConfiguration.java b/core/src/main/java/jenkins/security/ProjectAuthenticatorConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..b60db097ef657fb3c5c04f4f3ff9933906717f63
--- /dev/null
+++ b/core/src/main/java/jenkins/security/ProjectAuthenticatorConfiguration.java
@@ -0,0 +1,61 @@
+package jenkins.security;
+
+import hudson.Extension;
+import hudson.model.AbstractProject;
+import hudson.security.ACL;
+import hudson.util.DescribableList;
+import jenkins.model.GlobalConfiguration;
+import jenkins.model.GlobalConfigurationCategory;
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.acegisecurity.Authentication;
+import org.kohsuke.stapler.StaplerRequest;
+
+import java.io.IOException;
+
+/**
+ * Show the {@link ProjectAuthenticator} configurations on the system config page.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+@Extension
+public class ProjectAuthenticatorConfiguration extends GlobalConfiguration {
+ private final DescribableList authenticators
+ = new DescribableList(this);
+
+ public ProjectAuthenticatorConfiguration() {
+ load();
+ }
+
+ @Override
+ public GlobalConfigurationCategory getCategory() {
+ return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
+ }
+
+ public DescribableList getAuthenticators() {
+ return authenticators;
+ }
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
+ try {
+ authenticators.rebuildHetero(req,json, ProjectAuthenticatorDescriptor.all(),"authenticators");
+ return true;
+ } catch (IOException e) {
+ throw new FormException(e,"authenticators");
+ }
+ }
+
+ public Authentication authenticate(AbstractProject,?> project) {
+ for (ProjectAuthenticator auth : get().getAuthenticators()) {
+ Authentication a = auth.authenticate(project);
+ if (a!=null)
+ return a;
+ }
+ return ACL.SYSTEM;
+ }
+
+ public static ProjectAuthenticatorConfiguration get() {
+ return Jenkins.getInstance().getInjector().getInstance(ProjectAuthenticatorConfiguration.class);
+ }
+}
diff --git a/core/src/main/java/jenkins/security/ProjectAuthenticatorDescriptor.java b/core/src/main/java/jenkins/security/ProjectAuthenticatorDescriptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..7177644db3da79f8700d837acd3f0879e38b0bb8
--- /dev/null
+++ b/core/src/main/java/jenkins/security/ProjectAuthenticatorDescriptor.java
@@ -0,0 +1,19 @@
+package jenkins.security;
+
+import hudson.DescriptorExtensionList;
+import hudson.model.Descriptor;
+import jenkins.model.Jenkins;
+
+/**
+ * {@link Descriptor} for {@link ProjectAuthenticator}.
+ *
+ * @author Kohsuke Kawaguchi
+ * @since 1.520
+ */
+public abstract class ProjectAuthenticatorDescriptor extends Descriptor {
+ // nothing defined here yet
+
+ public static DescriptorExtensionList all() {
+ return Jenkins.getInstance().getDescriptorList(ProjectAuthenticator.class);
+ }
+}
diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties
index 6f5df4cbdee1b23f562d23e434c9b1315541c41a..522758b7328ba8529972753967429b224dcf4546 100644
--- a/core/src/main/resources/hudson/model/Messages.properties
+++ b/core/src/main/resources/hudson/model/Messages.properties
@@ -184,8 +184,6 @@ Label.InvalidLabel=invalid label
Label.ProvisionedFrom=Provisioned from {0}
ManageJenkinsAction.DisplayName=Manage Jenkins
MultiStageTimeSeries.EMPTY_STRING=
-Node.BecauseNodeIsReserved={0} is reserved for jobs tied to it
-Node.LabelMissing={0} doesn''t have label {1}
Queue.AllNodesOffline=All nodes of label ''{0}'' are offline
Queue.BlockedBy=Blocked by {0}
Queue.HudsonIsAboutToShutDown=Jenkins is about to shut down
@@ -309,6 +307,9 @@ ChoiceParameterDefinition.DisplayName=Choice
RunParameterDefinition.DisplayName=Run Parameter
PasswordParameterDefinition.DisplayName=Password Parameter
+Node.BecauseNodeIsReserved={0} is reserved for jobs tied to it
+Node.LabelMissing={0} doesn''t have label {1}
+Node.LackingBuildPermission={0} doesn''t have a permission to run on {1}
Node.Mode.NORMAL=Utilize this slave as much as possible
Node.Mode.EXCLUSIVE=Leave this machine for tied jobs only
diff --git a/core/src/main/resources/jenkins/security/ProjectAuthenticator/config.groovy b/core/src/main/resources/jenkins/security/ProjectAuthenticator/config.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..41d8916d73481003237a1a0e27abda4f994263b9
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/ProjectAuthenticator/config.groovy
@@ -0,0 +1,2 @@
+package jenkins.security.ProjectAuthenticator;
+// the default is empty configuration
\ No newline at end of file
diff --git a/core/src/main/resources/jenkins/security/ProjectAuthenticatorConfiguration/config.groovy b/core/src/main/resources/jenkins/security/ProjectAuthenticatorConfiguration/config.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..d1662c00caca2d4a49bd98fc4de1e88097d2489b
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/ProjectAuthenticatorConfiguration/config.groovy
@@ -0,0 +1,36 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2011, CloudBees, 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package jenkins.security.ProjectAuthenticatorConfiguration
+
+import jenkins.security.ProjectAuthenticatorDescriptor;
+
+f=namespace(lib.FormTagLib)
+
+if (!ProjectAuthenticatorDescriptor.all().isEmpty()) {
+ f.section(title:_("Access Control for Builds")) {
+ f.block() {
+ f.repeatableHeteroProperty(field:"authenticators",hasHeader:true)
+ }
+ }
+}
diff --git a/test/src/test/java/hudson/model/QueueTest.java b/test/src/test/java/hudson/model/QueueTest.java
index c19fc4e469907f0f88a71da80cb7d9493ea498e8..86c3125ffa27eabd1a796b08b2e44b08dee4bb71 100644
--- a/test/src/test/java/hudson/model/QueueTest.java
+++ b/test/src/test/java/hudson/model/QueueTest.java
@@ -33,6 +33,10 @@ import hudson.matrix.TextAxis;
import hudson.model.Cause.*;
import hudson.model.Queue.*;
import hudson.model.queue.QueueTaskFuture;
+import hudson.security.ACL;
+import hudson.security.GlobalMatrixAuthorizationStrategy;
+import hudson.security.SparseACL;
+import hudson.slaves.DumbSlave;
import hudson.tasks.Shell;
import hudson.triggers.SCMTrigger.SCMTriggerCause;
import hudson.triggers.TimerTrigger.TimerTriggerCause;
@@ -43,6 +47,13 @@ import hudson.matrix.LabelAxis;
import hudson.matrix.MatrixRun;
import hudson.slaves.DummyCloudImpl;
import hudson.slaves.NodeProvisioner;
+import jenkins.model.Jenkins;
+import jenkins.security.ProjectAuthenticator;
+import jenkins.security.ProjectAuthenticatorConfiguration;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
@@ -51,11 +62,13 @@ import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.SequenceLock;
import org.jvnet.hudson.test.TestBuilder;
+import org.jvnet.hudson.test.TestExtension;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;
+import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -354,4 +367,87 @@ public class QueueTest extends HudsonTestCase {
FreeStyleBuild b2 = assertBuildStatusSuccess(v);
assertSame(b,b2);
}
+
+ @Inject
+ ProjectAuthenticatorConfiguration pac;
+
+ /**
+ * Make sure that the running build actually carries an credential.
+ */
+ public void testAccessControl() throws Exception {
+ configureUserRealm();
+ pac.getAuthenticators().add(new ProjectAuthenticatorImpl());
+ FreeStyleProject p = createFreeStyleProject();
+ p.getBuildersList().add(new TestBuilder() {
+ @Override
+ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
+ assertEquals(alice,Jenkins.getAuthentication());
+ return true;
+ }
+ });
+ assertBuildStatusSuccess(p.scheduleBuild2(0));
+ }
+
+ @TestExtension
+ public static class ProjectAuthenticatorImpl extends ProjectAuthenticator {
+
+ @Override
+ public Authentication authenticate(AbstractProject, ?> project) {
+ return alice;
+ }
+ }
+
+ private static Authentication alice = new UsernamePasswordAuthenticationToken("alice","alice",new GrantedAuthority[0]);
+
+
+ /**
+ * Make sure that the slave assignment honors the permissions.
+ *
+ * We do this test by letting a build run twice to determine its natural home,
+ * and then introduce a security restriction to prohibit that.
+ */
+ public void testPermissionSensitiveSlaveAllocations() throws Exception {
+ jenkins.setNumExecutors(0); // restrict builds to those slaves
+ DumbSlave s1 = createSlave();
+ DumbSlave s2 = createSlave();
+
+ configureUserRealm();
+ pac.getAuthenticators().add(new ProjectAuthenticatorImpl());
+ FreeStyleProject p = createFreeStyleProject();
+ p.getBuildersList().add(new TestBuilder() {
+ @Override
+ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
+ assertEquals(alice,Jenkins.getAuthentication());
+ return true;
+ }
+ });
+
+ final FreeStyleBuild b1 = assertBuildStatusSuccess(p.scheduleBuild2(0));
+ final FreeStyleBuild b2 = assertBuildStatusSuccess(p.scheduleBuild2(0));
+
+ // scheduling algorithm would prefer running the same job on the same node
+ assertSame(b1.getBuiltOn(),b2.getBuiltOn());
+
+ // ACL that allow anyone to do anything except Alice can't build.
+ final SparseACL alicCantBuild = new SparseACL(null);
+ alicCantBuild.add(new PrincipalSid(alice), AbstractProject.BUILD, false);
+ alicCantBuild.add(new PrincipalSid("anonymous"), Jenkins.ADMINISTER, true);
+
+ GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy() {
+ @Override
+ public ACL getACL(Node node) {
+ if (node==b1.getBuiltOn())
+ return alicCantBuild;
+ return super.getACL(node);
+ }
+ };
+ auth.add(Jenkins.ADMINISTER,"anonymous");
+ jenkins.setAuthorizationStrategy(auth);
+
+ // now that we prohibit alice to do a build on the same node, the build should run elsewhere
+ for (int i=0; i<3; i++) {
+ FreeStyleBuild b3 = assertBuildStatusSuccess(p.scheduleBuild2(0));
+ assertNotSame(b3.getBuiltOnStr(), b1.getBuiltOnStr());
+ }
+ }
}