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

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

上级 9fd92363
...@@ -109,6 +109,7 @@ import jenkins.security.QueueItemAuthenticatorConfiguration; ...@@ -109,6 +109,7 @@ import jenkins.security.QueueItemAuthenticatorConfiguration;
import jenkins.util.AtmostOneTaskExecutor; import jenkins.util.AtmostOneTaskExecutor;
import org.acegisecurity.AccessDeniedException; import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication; import org.acegisecurity.Authentication;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.Exported;
...@@ -694,15 +695,68 @@ public class Queue extends ResourceController implements Saveable { ...@@ -694,15 +695,68 @@ public class Queue extends ResourceController implements Saveable {
*/ */
@Exported(inline=true) @Exported(inline=true)
public synchronized Item[] getItems() { public synchronized Item[] getItems() {
Item[] r = new Item[waitingList.size() + blockedProjects.size() + buildables.size() + pendings.size()]; List<Item> r = new ArrayList<Item>();
waitingList.toArray(r);
int idx = waitingList.size(); for(WaitingItem p : waitingList) {
for (BlockedItem p : blockedProjects.values()) r = filterItemListBasedOnPermissions(r, p);
r[idx++] = p; }
for (BuildableItem p : reverse(buildables.values())) for (BlockedItem p : blockedProjects.values()){
r[idx++] = p; r = filterItemListBasedOnPermissions(r, p);
for (BuildableItem p : reverse(pendings.values())) }
r[idx++] = 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; return r;
} }
...@@ -1581,6 +1635,42 @@ public class Queue extends ResourceController implements Saveable { ...@@ -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. * An optional interface for actions on Queue.Item.
......
...@@ -23,9 +23,12 @@ ...@@ -23,9 +23,12 @@
*/ */
package hudson.model; 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.HtmlFileInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import hudson.Launcher; import hudson.Launcher;
import hudson.matrix.AxisList; import hudson.matrix.AxisList;
import hudson.matrix.LabelAxis; import hudson.matrix.LabelAxis;
...@@ -39,11 +42,16 @@ import hudson.model.Queue.BlockedItem; ...@@ -39,11 +42,16 @@ import hudson.model.Queue.BlockedItem;
import hudson.model.Queue.Executable; import hudson.model.Queue.Executable;
import hudson.model.Queue.WaitingItem; import hudson.model.Queue.WaitingItem;
import hudson.model.queue.AbstractQueueTask; import hudson.model.queue.AbstractQueueTask;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.QueueTaskFuture; import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.ScheduleResult; import hudson.model.queue.ScheduleResult;
import hudson.model.queue.SubTask; import hudson.model.queue.SubTask;
import hudson.security.ACL; import hudson.security.ACL;
import hudson.security.AuthorizationMatrixProperty;
import hudson.security.GlobalMatrixAuthorizationStrategy; import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.Permission;
import hudson.security.ProjectMatrixAuthorizationStrategy;
import hudson.security.SparseACL; import hudson.security.SparseACL;
import hudson.slaves.DumbSlave; import hudson.slaves.DumbSlave;
import hudson.slaves.DummyCloudImpl; import hudson.slaves.DummyCloudImpl;
...@@ -58,7 +66,10 @@ import java.io.IOException; ...@@ -58,7 +66,10 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
...@@ -84,7 +95,9 @@ import org.jvnet.hudson.test.Bug; ...@@ -84,7 +95,9 @@ import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase; import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.MockQueueItemAuthenticator; import org.jvnet.hudson.test.MockQueueItemAuthenticator;
import org.jvnet.hudson.test.SequenceLock; import org.jvnet.hudson.test.SequenceLock;
import org.jvnet.hudson.test.SleepBuilder;
import org.jvnet.hudson.test.TestBuilder; import org.jvnet.hudson.test.TestBuilder;
import org.jvnet.hudson.test.TestExtension;
import org.mortbay.jetty.Server; import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector; import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.servlet.ServletHandler; import org.mortbay.jetty.servlet.ServletHandler;
...@@ -413,11 +426,11 @@ public class QueueTest extends HudsonTestCase { ...@@ -413,11 +426,11 @@ public class QueueTest extends HudsonTestCase {
FreeStyleBuild b = v.waitForStart(); FreeStyleBuild b = v.waitForStart();
assertEquals(1,b.getNumber()); assertEquals(1,b.getNumber());
assertTrue(b.isBuilding()); assertTrue(b.isBuilding());
assertSame(p,b.getProject()); assertSame(p, b.getProject());
ev.signal(); // let the build complete ev.signal(); // let the build complete
FreeStyleBuild b2 = assertBuildStatusSuccess(v); FreeStyleBuild b2 = assertBuildStatusSuccess(v);
assertSame(b,b2); assertSame(b, b2);
} }
@Inject @Inject
...@@ -579,4 +592,64 @@ public class QueueTest extends HudsonTestCase { ...@@ -579,4 +592,64 @@ public class QueueTest extends HudsonTestCase {
} catch (CancellationException e) { } 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.
先完成此消息的编辑!
想要评论请 注册