Skip to content

Commit

Permalink
[JENKINS-16956] More compatible behavior: if no QIA’s are configured …
Browse files Browse the repository at this point in the history
…yet, fall back to running as SYSTEM.

Also make a best effort to issue a warning in the log if downstream builds might skipped due to lack of authentication;
or if downstream build permissions might not be checked due to legacy behavior.
  • Loading branch information
jglick committed Apr 2, 2014
1 parent 48e92ef commit d31ff2f
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 16 deletions.
23 changes: 22 additions & 1 deletion core/src/main/java/hudson/tasks/BuildTrigger.java
Expand Up @@ -59,6 +59,8 @@
import javax.annotation.CheckForNull;
import jenkins.model.DependencyDeclarer;
import jenkins.model.Jenkins;
import jenkins.security.QueueItemAuthenticatorConfiguration;
import jenkins.security.QueueItemAuthenticatorDescriptor;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.apache.commons.lang.StringUtils;
Expand Down Expand Up @@ -206,7 +208,26 @@ public int compare(Dependency lhs, Dependency rhs) {

Authentication auth = Jenkins.getAuthentication(); // from build
if (auth.equals(ACL.SYSTEM)) { // i.e., unspecified
auth = Jenkins.ANONYMOUS;
if (QueueItemAuthenticatorDescriptor.all().isEmpty()) {
if (downstreamProjects.isEmpty()) {
return true;
}
logger.println(Messages.BuildTrigger_warning_you_have_no_plugins_providing_ac());
} else if (QueueItemAuthenticatorConfiguration.get().getAuthenticators().isEmpty()) {
if (downstreamProjects.isEmpty()) {
return true;
}
logger.println(Messages.BuildTrigger_warning_access_control_for_builds_in_glo());
} else {
// This warning must be printed even if downstreamProjects is empty.
// Otherwise you could effectively escalate DISCOVER to READ just by trying different project names and checking whether a warning was printed or not.
// If there were an API to determine whether any DependencyDeclarer’s in this project requested downstream project names,
// then we could suppress the warnings in case none did; but if any do, yet Items.fromNameList etc. ignore unknown projects,
// that has to be treated the same as if there really are downstream projects but the anonymous user cannot see them.
// For the above two cases, it is OK to suppress the warning when there are no downstream projects, since running as SYSTEM we would be able to see them anyway.
logger.println(Messages.BuildTrigger_warning_this_build_has_no_associated_aut());
auth = Jenkins.ANONYMOUS;
}
}

for (Dependency dep : downstreamProjects) {
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/resources/hudson/tasks/Messages.properties
Expand Up @@ -47,6 +47,9 @@ BuildTrigger.NoSuchProject=No such project \u2018{0}\u2019. Did you mean \u2018{
BuildTrigger.NoProjectSpecified=No project specified
BuildTrigger.NotBuildable={0} is not buildable
BuildTrigger.Triggering=Triggering a new build of {0}
BuildTrigger.warning_access_control_for_builds_in_glo=Warning: \u2018Access Control for Builds\u2019 in global security configuration is empty, so falling back to legacy behavior of permitting any downstream builds to be triggered
BuildTrigger.warning_this_build_has_no_associated_aut=Warning: this build has no associated authentication, so build permissions may be lacking, and downstream projects which cannot even be seen by an anonymous user will be silently skipped
BuildTrigger.warning_you_have_no_plugins_providing_ac=Warning: you have no plugins providing access control for builds, so falling back to legacy behavior of permitting any downstream builds to be triggered
BuildTrigger.you_have_no_permission_to_build_=You have no permission to build {0}

CommandInterpreter.CommandFailed=command execution failed
Expand Down
64 changes: 49 additions & 15 deletions test/src/test/java/hudson/tasks/BuildTriggerTest.java
Expand Up @@ -48,10 +48,12 @@
import jenkins.model.Jenkins;
import jenkins.security.QueueItemAuthenticator;
import jenkins.security.QueueItemAuthenticatorConfiguration;
import jenkins.security.QueueItemAuthenticatorDescriptor;
import org.acegisecurity.Authentication;
import org.jvnet.hudson.test.ExtractResourceSCM;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.MockBuilder;
import org.jvnet.hudson.test.TestExtension;

/**
* Tests for hudson.tasks.BuildTrigger
Expand Down Expand Up @@ -158,31 +160,27 @@ public void testDownstreamProjectSecurity() throws Exception {
auth.add(Computer.BUILD, "anonymous");
jenkins.setAuthorizationStrategy(auth);
final FreeStyleProject upstream = createFreeStyleProject("upstream");
QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new QueueItemAuthenticator() {
@Override public Authentication authenticate(Queue.Item item) {
return item.task == upstream ? User.get("alice").impersonate() : null;
}
private Object writeReplace() {return "";}
});
QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new QIA(Collections.singletonMap("upstream", "alice")));
Map<Permission,Set<String>> perms = new HashMap<Permission,Set<String>>();
perms.put(Item.READ, Collections.singleton("alice"));
perms.put(Item.CONFIGURE, Collections.singleton("alice"));
upstream.addProperty(new AuthorizationMatrixProperty(perms));
FreeStyleProject downstream = createFreeStyleProject("downstream");
String downstreamName = "d0wnstr3am"; // do not clash with English messages!
FreeStyleProject downstream = createFreeStyleProject(downstreamName);
WebClient wc = createWebClient();
wc.login("alice");
HtmlPage page = wc.getPage(upstream, "configure");
HtmlForm config = page.getFormByName("config");
config.getButtonByCaption("Add post-build action").click(); // lib/hudson/project/config-publishers2.jelly
page.getAnchorByText("Build other projects").click();
HtmlTextInput childProjects = config.getInputByName("buildTrigger.childProjects");
childProjects.setValueAttribute("downstream");
childProjects.setValueAttribute(downstreamName);
submit(config);
// DependencyGraph is rebuilt as SYSTEM so is always complete even if configuring user does not know it:
assertEquals(Collections.singletonList(downstream), upstream.getDownstreamProjects());
// Downstream projects whose existence we are not aware of will silently not be triggered:
FreeStyleBuild b = buildAndAssertSuccess(upstream);
assertLogNotContains("downstream", b);
assertLogNotContains(downstreamName, b);
waitUntilNoActivity();
assertNull(downstream.getLastBuild());
Map<Permission,Set<String>> grantedPermissions = new HashMap<Permission,Set<String>>();
Expand All @@ -191,7 +189,7 @@ public void testDownstreamProjectSecurity() throws Exception {
// If we can see them, but not build them, that is a warning (but this is in cleanUp so the build is still considered a success):
downstream.addProperty(amp);
b = buildAndAssertSuccess(upstream);
assertLogContains("downstream", b);
assertLogContains(downstreamName, b);
waitUntilNoActivity();
assertNull(downstream.getLastBuild());
// If we can build them, then great:
Expand All @@ -200,17 +198,18 @@ public void testDownstreamProjectSecurity() throws Exception {
amp = new AuthorizationMatrixProperty(grantedPermissions);
downstream.addProperty(amp);
b = buildAndAssertSuccess(upstream);
assertLogContains("downstream", b);
assertLogContains(downstreamName, b);
waitUntilNoActivity();
FreeStyleBuild b2 = downstream.getLastBuild();
assertNotNull(b2);
Cause.UpstreamCause cause = b2.getCause(Cause.UpstreamCause.class);
assertNotNull(cause);
assertEquals(b, cause.getUpstreamRun());
// Now for legacy behavior: we should run as anonymous. Which would normally have no permissions:
QueueItemAuthenticatorConfiguration.get().getAuthenticators().clear();
// Now if we have configured some QIA’s but they are not active on this job, we should run as anonymous. Which would normally have no permissions:
QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new QIA(Collections.<String,String>emptyMap()));
b = buildAndAssertSuccess(upstream);
assertLogNotContains("downstream", b);
assertLogNotContains(downstreamName, b);
assertLogContains(Messages.BuildTrigger_warning_this_build_has_no_associated_aut(), b);
waitUntilNoActivity();
assertEquals(1, downstream.getLastBuild().number);
// Unless we explicitly granted them:
Expand All @@ -220,9 +219,44 @@ public void testDownstreamProjectSecurity() throws Exception {
amp = new AuthorizationMatrixProperty(grantedPermissions);
downstream.addProperty(amp);
b = buildAndAssertSuccess(upstream);
assertLogContains("downstream", b);
assertLogContains(downstreamName, b);
waitUntilNoActivity();
assertEquals(2, downstream.getLastBuild().number);
FreeStyleProject simple = createFreeStyleProject("simple");
FreeStyleBuild b3 = buildAndAssertSuccess(simple);
// See discussion in BuildTrigger for why this is necessary:
assertLogContains(Messages.BuildTrigger_warning_this_build_has_no_associated_aut(), b3);
// Finally, in legacy mode we run as SYSTEM:
grantedPermissions.clear(); // similar behavior but different message if DescriptorImpl removed
downstream.removeProperty(amp);
amp = new AuthorizationMatrixProperty(grantedPermissions);
downstream.addProperty(amp);
QueueItemAuthenticatorConfiguration.get().getAuthenticators().clear();
b = buildAndAssertSuccess(upstream);
assertLogContains(downstreamName, b);
assertLogContains(Messages.BuildTrigger_warning_access_control_for_builds_in_glo(), b);
waitUntilNoActivity();
assertEquals(3, downstream.getLastBuild().number);
b3 = buildAndAssertSuccess(simple);
assertLogNotContains(Messages.BuildTrigger_warning_access_control_for_builds_in_glo(), b3);
}
public static final class QIA extends QueueItemAuthenticator {
private final Map<String,String> jobsToUsers;
public QIA(Map<String,String> jobsToUsers) {
this.jobsToUsers = jobsToUsers;
}
@Override public Authentication authenticate(Queue.Item item) {
String user = null;
if (item.task instanceof Item) {
user = jobsToUsers.get(((Item) item.task).getFullName());
}
return user != null ? User.get(user).impersonate() : null;
}
@TestExtension("testDownstreamProjectSecurity") public static final class DescriptorImpl extends QueueItemAuthenticatorDescriptor {
@Override public String getDisplayName() {
return "Test QIA";
}
}
}

}

0 comments on commit d31ff2f

Please sign in to comment.