提交 2d09a0e5 编写于 作者: K Kohsuke Kawaguchi

[FIXED JENKINS-18285] Merge the feature branch

......@@ -61,6 +61,9 @@ Upcoming changes</a>
<li class=bug>
“Projects tied to slave” shows unrelated Maven module jobs.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17451">issue 17451</a>)
<li class=rfe>
Executors running the builds can be now a subject of access control.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18285">issue 18285</a>)
</ul>
</div><!--=TRUNK-END=-->
......
......@@ -192,13 +192,7 @@ THE SOFTWARE.
<dependency>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-annotation</artifactId>
<version>1.4</version>
<exclusions> <!-- https://github.com/infradna/bridge-method-injector/issues/1 -->
<exclusion>
<artifactId>annotation-indexer</artifactId>
<groupId>org.jvnet.hudson</groupId>
</exclusion>
</exclusions>
<version>1.8</version>
</dependency>
<dependency><!-- until we get this version through Stapler -->
......@@ -655,7 +649,7 @@ THE SOFTWARE.
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<!-- version specified in grandparent pom -->
<version>1.5-SNAPSHOT</version>
<executions>
<execution>
<goals>
......
......@@ -142,7 +142,7 @@ public class ExtensionListView {
}
@Override
public T[] toArray(T[] array) {
public <T> T[] toArray(T[] array) {
return storage().toArray(array);
}
......
......@@ -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<P extends AbstractProject<P,R>,R extends A
return this;
}
/**
* Let the identity determined by {@link ProjectAuthenticator}.
*/
public Authentication getIdentity() {
return ProjectAuthenticatorConfiguration.get().authenticate(this);
}
/**
* {@inheritDoc}
*
......
......@@ -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,
......
......@@ -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
......
......@@ -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()) {
......
......@@ -1523,7 +1523,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
}
/**
* Used in {@link RunExecution#run} to indicates that a fatal error in a build
* Used in {@link Run.RunExecution#run} to indicates that a fatal error in a build
* is reported to {@link BuildListener} and the build should be simply aborted
* without further recording a stack trace.
*/
......
......@@ -26,6 +26,8 @@ package hudson.model.queue;
import hudson.model.Queue;
import hudson.model.Queue.Task;
import hudson.security.ACL;
import org.acegisecurity.Authentication;
import java.util.Collection;
import java.util.Collections;
......@@ -57,4 +59,12 @@ public abstract class AbstractQueueTask implements Queue.Task {
public Object getSameNodeConstraint() {
return null;
}
/**
* This default implementation is the historical behaviour, but this is no longer desirable. Please override.
* See {@link SubTask#getIdentity()} for the contract.
*/
public Authentication getIdentity() {
return ACL.SYSTEM;
}
}
......@@ -26,6 +26,8 @@ package hudson.model.queue;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.ResourceList;
import hudson.security.ACL;
import org.acegisecurity.Authentication;
/**
* Partial default implementation of {@link SubTask} to avoid
......@@ -53,4 +55,8 @@ public abstract class AbstractSubTask implements SubTask {
public ResourceList getResourceList() {
return new ResourceList();
}
public Authentication getIdentity() {
return getOwnerTask().getIdentity();
}
}
......@@ -25,6 +25,7 @@ package hudson.model.queue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Executor;
import hudson.model.Label;
......@@ -35,6 +36,7 @@ import hudson.model.Queue.Executable;
import hudson.model.Queue.JobOffer;
import hudson.model.Queue.Task;
import hudson.model.labels.LabelAssignmentAction;
import hudson.security.ACL;
import java.util.AbstractList;
import java.util.ArrayList;
......@@ -57,7 +59,7 @@ import static java.lang.Math.*;
* which determines where each {@link SubTask} gets executed.
*
* <p>
* This mapping is done under two constraints:
* This mapping is done under the following constraints:
*
* <ul>
* <li>
......@@ -65,6 +67,9 @@ import static java.lang.Math.*;
* See {@link SubTask#getSameNodeConstraint()}
* <li>
* Label constraint. {@link SubTask}s can specify that it can be only run on nodes that has the label.
* <li>
* Permission constraint. {@link SubTask}s have {@linkplain SubTask#getIdentity() identities} that need to have
* permissions to build on the node.
* </ul>
*
* <p>
......@@ -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<ExecutorSlot> 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()<c.size())
return false; // too small compared towork
if (c.assignedLabel!=null && !c.assignedLabel.contains(node))
return false; // label mismatch
for (SubTask task : c) {
if (!nodeAcl.hasPermission(task.getIdentity(), AbstractProject.BUILD))
return false; // tasks don't have a permission to run on this node
}
return true;
}
/**
......@@ -154,6 +171,9 @@ public class MappingWorksheet {
}
}
/**
* {@link SubTask}s that need to run on the same node.
*/
public class WorkChunk extends ReadOnlyList<SubTask> {
public final int index;
......
......@@ -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();
}
}
......@@ -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();
}
......@@ -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;
}
}
}
......@@ -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)
......
......@@ -138,7 +138,7 @@ public class CopyOnWriteList<E> implements Iterable<E> {
this.core = new ArrayList<E>();
}
public E[] toArray(E[] array) {
public <E> E[] toArray(E[] array) {
return core.toArray(array);
}
......
......@@ -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<T> implements Iterable<T> {
public class PersistedList<T> extends AbstractList<T> {
protected final CopyOnWriteList<T> data = new CopyOnWriteList<T>();
protected Saveable owner = Saveable.NOOP;
......@@ -64,14 +66,18 @@ public class PersistedList<T> implements Iterable<T> {
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<T> implements Iterable<T> {
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<T> implements Iterable<T> {
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<T> implements Iterable<T> {
/**
* Gets all the {@link Describable}s in an array.
*/
public T[] toArray(T[] array) {
public <T> T[] toArray(T[] array) {
return data.toArray(array);
}
......
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<ProjectAuthenticator> 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();
}
}
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<ProjectAuthenticator,ProjectAuthenticatorDescriptor> authenticators
= new DescribableList<ProjectAuthenticator, ProjectAuthenticatorDescriptor>(this);
public ProjectAuthenticatorConfiguration() {
load();
}
@Override
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
}
public DescribableList<ProjectAuthenticator, ProjectAuthenticatorDescriptor> 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);
}
}
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<ProjectAuthenticator> {
// nothing defined here yet
public static DescriptorExtensionList<ProjectAuthenticator,ProjectAuthenticatorDescriptor> all() {
return Jenkins.getInstance().getDescriptorList(ProjectAuthenticator.class);
}
}
......@@ -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
......
package jenkins.security.ProjectAuthenticator;
// the default is empty configuration
\ No newline at end of file
/*
* 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)
}
}
}
......@@ -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());
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册