提交 581eb9ce 编写于 作者: V Valentina Armenise

[SECURITY-186] check user permissions when calling QUEUE API

上级 9fd92363
......@@ -109,6 +109,7 @@ import jenkins.security.QueueItemAuthenticatorConfiguration;
import jenkins.util.AtmostOneTaskExecutor;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.export.Exported;
......@@ -694,15 +695,68 @@ public class Queue extends ResourceController implements Saveable {
*/
@Exported(inline=true)
public synchronized Item[] getItems() {
Item[] r = new Item[waitingList.size() + blockedProjects.size() + buildables.size() + pendings.size()];
waitingList.toArray(r);
int idx = waitingList.size();
for (BlockedItem p : blockedProjects.values())
r[idx++] = p;
for (BuildableItem p : reverse(buildables.values()))
r[idx++] = p;
for (BuildableItem p : reverse(pendings.values()))
r[idx++] = p;
List<Item> r = new ArrayList<Item>();
for(WaitingItem p : waitingList) {
r = filterItemListBasedOnPermissions(r, p);
}
for (BlockedItem p : blockedProjects.values()){
r = filterItemListBasedOnPermissions(r, p);
}
for (BuildableItem p : reverse(buildables.values())) {
r = filterItemListBasedOnPermissions(r, p);
}
for (BuildableItem p : reverse(pendings.values())) {
r= filterItemListBasedOnPermissions(r, p);
}
Item[] items = new Item[r.size()];
r.toArray(items);
return items;
}
private List<Item> filterItemListBasedOnPermissions(List<Item> r, Item t) {
if (t.task instanceof hudson.model.Item) {
if (((hudson.model.Item)t.task).hasPermission(hudson.model.Item.READ)) {
r.add(t);
}
}
return r;
}
/**
* Returns an array of Item for which it is only visible the name of the task.
*
* Generally speaking the array is sorted such that the items that are most likely built sooner are
* at the end.
*/
@Restricted(NoExternalUse.class)
@Exported(inline=true)
public synchronized StubItem[] getDiscoverableItems() {
List<StubItem> r = new ArrayList<StubItem>();
for(WaitingItem p : waitingList) {
r = filterDiscoverableItemListBasedOnPermissions(r, p);
}
for (BlockedItem p : blockedProjects.values()){
r = filterDiscoverableItemListBasedOnPermissions(r, p);
}
for (BuildableItem p : reverse(buildables.values())) {
r = filterDiscoverableItemListBasedOnPermissions(r, p);
}
for (BuildableItem p : reverse(pendings.values())) {
r= filterDiscoverableItemListBasedOnPermissions(r, p);
}
StubItem[] items = new StubItem[r.size()];
r.toArray(items);
return items;
}
private List<StubItem> filterDiscoverableItemListBasedOnPermissions(List<StubItem> r, Item t) {
if (t.task instanceof hudson.model.Item) {
if (!((hudson.model.Item)t.task).hasPermission(hudson.model.Item.READ) && ((hudson.model.Item)t.task).hasPermission(hudson.model.Item.DISCOVER)) {
r.add(new StubItem(new StubTask(t.task)));
}
}
return r;
}
......@@ -1581,6 +1635,42 @@ public class Queue extends ResourceController implements Saveable {
}
}
/**
* A Stub class for {@link Task} which exposes only the name of the Task to be displayed when the user
* has DISCOVERY permissions only.
*/
@Restricted(NoExternalUse.class)
@ExportedBean(defaultVisibility = 999)
public static class StubTask {
private String name;
public StubTask(@Nonnull Queue.Task base) {
this.name = base.getName();
}
@Exported
public String getName() {
return name;
}
}
/**
* A Stub class for {@link Item} which exposes only the name of the Task to be displayed when the user
* has DISCOVERY permissions only.
*/
@Restricted(NoExternalUse.class)
@ExportedBean(defaultVisibility = 999)
public class StubItem {
@Exported public StubTask task;
public StubItem(StubTask task) {
this.task = task;
}
}
/**
* An optional interface for actions on Queue.Item.
......
......@@ -23,9 +23,12 @@
*/
package hudson.model;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import hudson.Launcher;
import hudson.matrix.AxisList;
import hudson.matrix.LabelAxis;
......@@ -39,11 +42,16 @@ import hudson.model.Queue.BlockedItem;
import hudson.model.Queue.Executable;
import hudson.model.Queue.WaitingItem;
import hudson.model.queue.AbstractQueueTask;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.ScheduleResult;
import hudson.model.queue.SubTask;
import hudson.security.ACL;
import hudson.security.AuthorizationMatrixProperty;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.Permission;
import hudson.security.ProjectMatrixAuthorizationStrategy;
import hudson.security.SparseACL;
import hudson.slaves.DumbSlave;
import hudson.slaves.DummyCloudImpl;
......@@ -58,7 +66,10 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
......@@ -84,7 +95,9 @@ import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.MockQueueItemAuthenticator;
import org.jvnet.hudson.test.SequenceLock;
import org.jvnet.hudson.test.SleepBuilder;
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;
......@@ -413,11 +426,11 @@ public class QueueTest extends HudsonTestCase {
FreeStyleBuild b = v.waitForStart();
assertEquals(1,b.getNumber());
assertTrue(b.isBuilding());
assertSame(p,b.getProject());
assertSame(p, b.getProject());
ev.signal(); // let the build complete
FreeStyleBuild b2 = assertBuildStatusSuccess(v);
assertSame(b,b2);
assertSame(b, b2);
}
@Inject
......@@ -579,4 +592,64 @@ public class QueueTest extends HudsonTestCase {
} catch (CancellationException e) {
}
}
public void testQueueApiOutputShouldBeFilteredByUserPermission() throws Exception {
jenkins.setSecurityRealm(createDummySecurityRealm());
ProjectMatrixAuthorizationStrategy str = new ProjectMatrixAuthorizationStrategy();
str.add(Jenkins.READ, "bob");
str.add(Jenkins.READ, "alice");
str.add(Jenkins.READ, "james");
jenkins.setAuthorizationStrategy(str);
FreeStyleProject project = createFreeStyleProject("project");
Map<Permission, Set<String>> permissions = new HashMap<Permission, Set<String>>();
permissions.put(Item.READ, Collections.singleton("bob"));
permissions.put(Item.DISCOVER, Collections.singleton("james"));
AuthorizationMatrixProperty prop1 = new AuthorizationMatrixProperty(permissions);
project.addProperty(prop1);
project.getBuildersList().add(new SleepBuilder(10));
project.scheduleBuild2(0);
WebClient webClient = new WebClient();
webClient.login("bob", "bob");
XmlPage p = webClient.goToXml("/queue/api/xml");
//bob has permission on the project and will be able to see it in the queue together with information such as the URL and the name.
for (DomNode element: p.getFirstChild().getFirstChild().getChildNodes()){
if(element.getNodeName().equals("task")){
assertEquals(((DomElement)element).getElementsByTagName("name").size(),1);
assertEquals(((DomElement) element).getElementsByTagName("name").item(0).getFirstChild().toString(), "project");
assertEquals(((DomElement)element).getElementsByTagName("url").size(),1);
}
}
WebClient webClient2 = new WebClient();
webClient2.login("alice");
XmlPage p2 = webClient2.goToXml("/queue/api/xml");
//alice does not have permission on the project and will not see it in the queue.
assertEquals("<queue></queue>", p2.getContent());
WebClient webClient3 = new WebClient();
webClient3.login("james");
XmlPage p3 = webClient3.goToXml("/queue/api/xml");
//james has DISCOVER permission on the project and will only be able to see the task name.
assertEquals("<queue><discoverableItem><task><name>project</name></task></discoverableItem></queue>",
p3.getContent());
}
//we force the project not to be executed so that it stays in the queue
@TestExtension("testQueueApiOutputShouldBeFilteredByUserPermission")
public static class MyQueueTaskDispatcher extends QueueTaskDispatcher {
@Override
public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) {
return new CauseOfBlockage() {
@Override
public String getShortDescription() {
return "blocked by canTake";
}
};
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册