Skip to content

Commit

Permalink
[FIXED JENKINS-32770] Provide a mechanism to run specific projects as…
Browse files Browse the repository at this point in the history
… ACL.SYSTEM
  • Loading branch information
stephenc committed Feb 4, 2016
1 parent c6507c7 commit 5550847
Show file tree
Hide file tree
Showing 15 changed files with 1,065 additions and 0 deletions.
@@ -0,0 +1,227 @@
/*
* The MIT License
*
* Copyright (c) 2013 IKEDA Yasuyuki
*
* 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 org.jenkinsci.plugins.authorizeproject.strategy;

import hudson.Extension;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.security.ACL;
import hudson.util.FormValidation;
import java.io.IOException;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectProperty;
import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectStrategy;
import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectStrategyDescriptor;
import org.jenkinsci.plugins.authorizeproject.GlobalQueueItemAuthenticator;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

/**
* Run builds as {@link ACL#SYSTEM}. Using this strategy becomes important when {@link GlobalQueueItemAuthenticator}
* is forcing jobs to a user other than {@link ACL#SYSTEM}.
*
* @since 1.1.1
*/
public class SystemAuthorizationStrategy extends AuthorizeProjectStrategy {

@DataBoundConstructor
public SystemAuthorizationStrategy() {
}

/**
* {@inheritDoc}
*/
@Override
public Authentication authenticate(Job<?, ?> job, Queue.Item item) {
return ACL.SYSTEM;
}

/**
* Return {@link SystemAuthorizationStrategy} configured in a job.
*
* @param job the {@link Job}
* @return the {@link SystemAuthorizationStrategy} or {@code null}
*/
protected static SystemAuthorizationStrategy getCurrentStrategy(Job<?, ?> job) {
if (job == null) {
return null;
}

AuthorizeProjectProperty prop = job.getProperty(AuthorizeProjectProperty.class);
if (prop == null) {
return null;
}

AuthorizeProjectStrategy strategy = prop.getStrategy();
if (!(strategy instanceof SystemAuthorizationStrategy)) {
return null;
}

return (SystemAuthorizationStrategy) strategy;
}

/**
* Called when XSTREAM2 instantiates this from XML configuration.
*
* When configured via REST/CLI, {@link Descriptor#newInstance(StaplerRequest, JSONObject)} is not called.
* Instead checks authentication here.
*
* @return return myself.
* @throws IOException authentication failed.
*/
private Object readResolve() throws IOException {
Jenkins instance = Jenkins.getInstance();
if (instance == null || !instance.hasPermission(Jenkins.ADMINISTER)) {
// This is called via REST/CLI.
// As REST/CLI interface saves configuration after successfully load object from the XML,
// this prevents the new configuration saved.
throw new IOException(Messages.SystemAuthorizationStrategy_readResolve());
}
return this;
}

/**
* For now we are an object with no configurable fields, so return a fixed value.
* If we add configurable fields we probably should consider removing the final.
*
* @return our hashCode.
*/
@Override
public final int hashCode() {

This comment has been minimized.

Copy link
@darxriggs

darxriggs Aug 25, 2019

Contributor

@stephenc / @ikedam Can you explain why only this strategy has an equals() and hashCode() method but the 3 other strategies don't? Should the methods be removed here or added in the others for consistency?

This comment has been minimized.

Copy link
@stephenc

stephenc Aug 25, 2019

Author Member

The others probably should have them.

This comment has been minimized.

Copy link
@ikedam

ikedam Aug 25, 2019

Member

I suppose that it’s because SystemAuthorizationStrategy can be considered as a singleton.
I’m not sure whether it have to be considered as a singleton.

I think those codes can be removed as the object equality isn’t used in authorize-project.
You can remove that and run tests to see they’re actually unnecessary. I believe tests will fail if they’re required for some logics.

This comment has been minimized.

Copy link
@stephenc

stephenc Aug 25, 2019

Author Member

I know there are a lot of cases where equality checks help. There’s some plugin code I wrote somewhere where we fall back to comparison by XStream serialised form of equals is not overridden, not sure if this object would be in scope but I would be sad to see the equals go away... happy if the others got correct impls

This comment has been minimized.

Copy link
@ikedam

ikedam Aug 26, 2019

Member

Implementing correct hashCode and equals is often difficult, and maintaining them correct is more difficult as introducing a new field without updating equals can easily cause problems.

I agree that it would be better to implement hashCode and equals if it’s easy, but actually it costs much and can cause more possible bugs.
Porting assertDataBoundEquals from test-harness might be helpful for your case.

return SystemAuthorizationStrategy.class.hashCode();
}

/**
* For now we are an object with no configurable fields, so strict instanceof establishes equality.
* If we add configurable fields we probably should consider removing the final.
*
* @param obj the object to test equality with.
* @return {@code true} if and only if this is a equivalent {@link SystemAuthorizationStrategy} instance.
*/
@Override
public final boolean equals(Object obj) {
return obj != null && SystemAuthorizationStrategy.class == obj.getClass();
}

/**
* Our descriptor
*/
@Extension(ordinal = -100)
public static class DescriptorImpl extends AuthorizeProjectStrategyDescriptor {

/**
* Flag to mark where changing a job using this strategy requires administrator permissions.
*/
private boolean permitReconfiguration;

/**
* @return the name shown in project configuration pages.
* @see hudson.model.Descriptor#getDisplayName()
*/
@Override
public String getDisplayName() {
return Messages.SystemAuthorizationStrategy_DisplayName();
}

/**
* {@inheritDoc}
*/
@Override
public boolean isEnabledByDefault() {
return false;
}

/**
* Gets the flag to mark where changing a job using this strategy requires administrator permissions.
*
* @return {@code true} if non-admins are allowed to modify jobs that are using this strategy.
*/
public boolean isPermitReconfiguration() {
return permitReconfiguration;
}

/**
* Sets the flag to mark where changing a job using this strategy requires administrator permissions.
*
* @param permitReconfiguration {@code true} if non-admins are allowed to modify jobs that are using this strategy.
*/
public void setPermitReconfiguration(boolean permitReconfiguration) {
if (permitReconfiguration != this.permitReconfiguration) {
this.permitReconfiguration = permitReconfiguration;
save();
}
}

/**
* Tests if an object is a {@link Job}
*
* @param it the object.
* @return {@code true} if and only if the supplied object is a {@link Job}
*/
@Restricted(NoExternalUse.class) // helper method for Jelly EL
public boolean isJob(Object it) {
return it instanceof Job;
}

/**
* {@inheritDoc}
*/
@Override
public void configureFromGlobalSecurity(StaplerRequest req, JSONObject js) throws FormException {
setPermitReconfiguration(js.getBoolean("permitReconfiguration"));
}

/**
* {@inheritDoc}
*/
@Override
public SystemAuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData)
throws FormException {
SystemAuthorizationStrategy result = (SystemAuthorizationStrategy) super.newInstance(req, formData);
Jenkins instance = Jenkins.getInstance();
if (instance == null || !instance.hasPermission(Jenkins.ADMINISTER)) {
Job job = req.findAncestorObject(Job.class);
if (job != null) {
if (!(permitReconfiguration && result.equals(getCurrentStrategy(job)))) {
throw new FormException(Messages.SystemAuthorizationStrategy_administersOnly(), "strategy");
}
}
}
return result;
}

public FormValidation doCheckPermitReconfiguration(String value) {
if (!Boolean.parseBoolean(value)) {
return FormValidation.warning(Messages.SystemAuthorizationStrategy_administersOnly());
}
return FormValidation.warning(Messages.SystemAuthorizationStrategy_userConfigurable());
}
}
}
Expand Up @@ -29,3 +29,7 @@ SpecificUsersAuthorizationStrategy.password.required=Required
SpecificUsersAuthorizationStrategy.password.invalid=Failed to authenticate
SpecificUsersAuthorizationStrategy.noNeedReauthentication.usage=This feature can cause a security problem. Please check CONFIGURE privilege of this project is granted only to proper users.
AnonymousAuthorizationStrategy.DisplayName=Run as anonymous
SystemAuthorizationStrategy.DisplayName=Run as SYSTEM
SystemAuthorizationStrategy.administersOnly=Only Administrators can configure the Run as SYSTEM strategy
SystemAuthorizationStrategy.userConfigurable=Non-administrators will be able to re-purpose jobs configured with this strategy
SystemAuthorizationStrategy.readResolve=Failed to authenticate to run builds as SYSTEM. In REST/CLI interface, you must be an administrator.
@@ -0,0 +1,55 @@
<!--
The MIT License
Copyright (c) 2016 Stephen Connolly
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:choose>
<j:when test="${!descriptor.isJob(it)}">
<!-- not a job so don't give job specific notices -->
</j:when>
<j:when test="${descriptor.permitReconfiguration and app.hasPermission(app.ADMINISTER)}">
<f:block>
<div class="warning">
${%Any non-administrators with permission to configure this job can retain this strategy while otherwise reconfiguring this job}
</div>
</f:block>
</j:when>
<j:when test="${instance == null and app.hasPermission(app.ADMINISTER)}">
<f:block>
<div class="warning">
${%Only users with administrator permissions will be able to reconfigure this job while retaining this strategy}
</div>
</f:block>
</j:when>
<j:when test="${instance == null}">
<f:block>
<div class="${it.hasPermission(app.ADMINISTER)?'warning':'error'}">
${%Only users with administrator permissions can select this strategy}
</div>
</f:block>
</j:when>
<j:otherwise>
<!-- don't advertise to the user that they can use this job to run as SYSTEM -->
</j:otherwise>
</j:choose>
</j:jelly>
@@ -0,0 +1,30 @@
<!--
The MIT License
Copyright (c) 2016 Stephen Connolly
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Allow non-administrators to reconfiguration jobs already configured with this strategy}" field="permitReconfiguration">
<f:checkbox/>
</f:entry>
</j:jelly>
@@ -0,0 +1,3 @@
<div>
Run a build as the SYSTEM user.
</div>

0 comments on commit 5550847

Please sign in to comment.