Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Job Dsl Whitelisting Option #968

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d67f641
Initial Job DSL Whitelist Changes
Dec 8, 2016
3a0c819
Info file and fixed tests.
Dec 8, 2016
3208094
Finished up info section.
Dec 14, 2016
90edd03
CodeNarc Fixes
Dec 14, 2016
7318a98
Last CodeNarc Fix
Dec 14, 2016
228ba26
Merge branch 'master' into master
smoyen Dec 30, 2016
05167e5
Switching to Node view of Whitelist for non-external classes
Jan 4, 2017
c764257
Merged branch master into master
Jan 4, 2017
564c1bb
Fixed CodeNarc Errors
Jan 4, 2017
c9ea431
Fixed merge issue
Jan 5, 2017
3f3e7f6
Updating test
Jan 5, 2017
77a3cba
unused import
Jan 5, 2017
af2ce0a
fixed test
Jan 5, 2017
42386b6
Added Test. Fixed some node processing functionality.
Jan 5, 2017
f853d9d
Added tests to get 100% coverage on VerifyNodeAgainstWhitelistHelper
Jan 6, 2017
af98006
Fixed CodeNarc issues
Jan 6, 2017
5894cc5
Some more changes for new whitelisting strategy
Jan 7, 2017
52b6028
Updates for backend and UI. still need to test and finish UI help pages
Jan 9, 2017
1476027
code narc fixes
Jan 9, 2017
280eb26
fixed test
Jan 9, 2017
7f9a192
some more
Jan 9, 2017
cfe5438
small updates to tests
Jan 9, 2017
64e9bc1
JenkinsJobManagement Tests. Fixed another test.
Jan 10, 2017
f7a1249
changed quotes
Jan 10, 2017
0ee2fb2
quick fix to test index numbers
Jan 10, 2017
d754ee4
not sure how these got deleted, but fixing deleted files.
Jan 10, 2017
10a8b1a
Everything done except help files (fingers crossed)
Jan 10, 2017
1592072
removing blank lines
Jan 10, 2017
62f8bae
Finished UI and Tests
Jan 10, 2017
abadc41
fixed two test issues
Jan 10, 2017
2ba2ec6
Merged branch master into master
Jan 16, 2017
f555de1
Support for whitelisting additional config block types
Feb 1, 2017
9d6d99d
Add support for more custom config blocks
Feb 2, 2017
25829b0
Added DslContext for two new job-dsl step fields.
Feb 3, 2017
ba1c3b6
Make sure new params aren't private
Feb 3, 2017
8a904c1
Add whitelist options to dsl generation.
Feb 8, 2017
076ff4c
Updated StepContextSpec to include new job dsl options
Feb 8, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion job-dsl-api-viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<select class="version-select form-control">
<!-- ADD VERSIONS HERE! -->
<option value="build/data/dsl.json">v1.58-SNAPSHOT</option>
<option value="build/data/dsl.json">GM-v1.57</option>
<option value="build/data/job-dsl-core-1.57-apidoc.json">v1.57</option>
<option value="build/data/job-dsl-core-1.56-apidoc.json">v1.56</option>
<option value="build/data/job-dsl-core-1.55-apidoc.json">v1.55</option>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,42 @@ abstract class AbstractJobManagement implements JobManagement {
this.outputStream = out
}

/**
* We set this for Jenkins Job Management, since we need it for processing Job-Dsl. For other Job Management types
* we will simply return false.
*/
@Override
boolean isRestrictedRawJobDsl() {
false
}

/**
* We set this for Jenkins Job Management, since we need it for processing Job-Dsl. For other Job Management types
* we will simply return an empty node.
*/
@Override
Node getAllowedRawJobdslElementsAsNode() {
new Node(null, null)
}

/**
* We set this for Jenkins Job Management, since we need it for processing Job-Dsl. For other Job Management types
* we will simply return false.
*/
@Override
boolean isRestrictedExternalJobDsl() {
false
}

/**
* We set this for Jenkins Job Management, since we need it for processing Job-Dsl. For other Job Management types
* we will simply return an empty array.
*/
@Override
String[] getAllowedExternalClassesThatDefineJobDslBlocks() {
[]
}

@Override
void logDeprecationWarning() {
List<StackTraceElement> currentStackTrack = DslScriptHelper.stackTrace
Expand Down
34 changes: 34 additions & 0 deletions job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/Item.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package javaposse.jobdsl.dsl

abstract class Item extends AbstractContext {
String name
boolean isRestrictedRawJobDsl
boolean isRestrictedExternalJobDsl

private final List<Closure> configureBlocks = []

protected Item(JobManagement jobManagement, String name) {
super(jobManagement)
this.name = name
this.isRestrictedExternalJobDsl = (jobManagement != null) ? jobManagement.isRestrictedExternalJobDsl() : false
this.isRestrictedRawJobDsl = (jobManagement != null) ? jobManagement.isRestrictedRawJobDsl() : false
}

@Deprecated
Expand All @@ -26,6 +30,36 @@ abstract class Item extends AbstractContext {
* @see <a href="https://github.com/jenkinsci/job-dsl-plugin/wiki/The-Configure-Block">The Configure Block</a>
*/
void configure(Closure configureBlock) {
// verify that no restrictions are violated before we add the configure blocks to be processed
if (this.isRestrictedRawJobDsl) {
WhitelistHelper.verifyRawJobDsl(configureBlock, jobManagement.allowedRawJobdslElementsAsNode, null)
}
if (this.isRestrictedExternalJobDsl) {
WhitelistHelper.verifyExternalClassThatDefinesConfigureBlock(configureBlock,
jobManagement.allowedExternalClassesThatDefineJobDslBlocks)
}
configureBlocks << configureBlock
}

/**
* Allows direct manipulation of the generated XML.
* This configure method focus' on configure call that stem from the Job class, where the
* original config block must be passed in so we can evaluate where it came from.
*
* @see <a href="https://github.com/jenkinsci/job-dsl-plugin/wiki/The-Configure-Block">The Configure Block</a>
*/
void configure(Closure configureBlock, Closure originalConfigureBlock) {
// verify that no restrictions are violated before we add the configure blocks to be processed
if (this.isRestrictedRawJobDsl) {
WhitelistHelper.verifyRawJobDsl(configureBlock, jobManagement.allowedRawJobdslElementsAsNode,
originalConfigureBlock)
}
// when this method is called, we are checking the original configure block. This is the object that will
// have the correct class information that it was inherited from
if (this.isRestrictedExternalJobDsl) {
WhitelistHelper.verifyExternalClassThatDefinesConfigureBlock(originalConfigureBlock,
jobManagement.allowedExternalClassesThatDefineJobDslBlocks)
}
configureBlocks << configureBlock
}

Expand Down
28 changes: 14 additions & 14 deletions job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/Job.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ abstract class Job extends Item {
}
ContextHelper.executeInContext(envClosure, envContext)

configure { Node project ->
configure ({ Node project ->
project / 'properties' / 'EnvInjectJobProperty' {
envContext.addInfoToBuilder(delegate)
on(true)
Expand All @@ -110,7 +110,7 @@ abstract class Job extends Item {
overrideBuildParameters(envContext.overrideBuildParameters)
contributors().children().addAll(envContext.contributorsContext.contributors)
}
}
}, envClosure)
}

/**
Expand Down Expand Up @@ -509,12 +509,12 @@ abstract class Job extends Item {
BuildParametersContext context = new BuildParametersContext(jobManagement, this)
ContextHelper.executeInContext(closure, context)

configure { Node project ->
configure ({ Node project ->
Node node = project / 'properties' / 'hudson.model.ParametersDefinitionProperty' / 'parameterDefinitions'
context.buildParameterNodes.values().each {
node << it
}
}
}, closure)
}

/**
Expand All @@ -527,7 +527,7 @@ abstract class Job extends Item {
if (!context.scmNodes.empty) {
checkState(context.scmNodes.size() == 1, 'Outside "multiscm", only one SCM can be specified')

configure { Node project ->
configure ({ Node project ->
Node scm = project / scm
if (scm) {
// There can only be only one SCM, so remove if there
Expand All @@ -536,7 +536,7 @@ abstract class Job extends Item {

// Assuming append the only child
project << context.scmNodes[0]
}
}, closure)
}
}

Expand Down Expand Up @@ -573,11 +573,11 @@ abstract class Job extends Item {
TriggerContext context = new TriggerContext(jobManagement, this)
ContextHelper.executeInContext(closure, context)

configure { Node project ->
configure ({ Node project ->
context.triggerNodes.each {
project / 'triggers' << it
}
}
}, closure)
}

/**
Expand All @@ -589,11 +589,11 @@ abstract class Job extends Item {
WrapperContext context = new WrapperContext(jobManagement, this)
ContextHelper.executeInContext(closure, context)

configure { Node project ->
configure ({ Node project ->
context.wrapperNodes.each {
project / 'buildWrappers' << it
}
}
}, closure)
}

/**
Expand All @@ -617,11 +617,11 @@ abstract class Job extends Item {
StepContext context = new StepContext(jobManagement, this)
ContextHelper.executeInContext(closure, context)

configure { Node project ->
configure ({ Node project ->
context.stepNodes.each {
project / 'builders' << it
}
}
}, closure)
}

/**
Expand All @@ -631,11 +631,11 @@ abstract class Job extends Item {
PublisherContext context = new PublisherContext(jobManagement, this)
ContextHelper.executeInContext(closure, context)

configure { Node project ->
configure ({ Node project ->
context.publisherNodes.each {
project / 'publishers' << it
}
}
}, closure)
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,26 @@ interface JobManagement {
*/
boolean isMinimumPluginVersionInstalled(String pluginShortName, String version)

/**
* Returns string of xml for valid elements that job dsl processor is allowed to use
*/
boolean isRestrictedRawJobDsl()

/**
* Returns string of xml for valid elements that job dsl processor is allowed to use
*/
Node getAllowedRawJobdslElementsAsNode()

/**
* Returns the whitelist for external classes this job dsl processor can inherit job dsl blocks for
*/
boolean isRestrictedExternalJobDsl()

/**
* Returns the whitelist for external classes this job dsl processor can inherit job dsl blocks for
*/
String[] getAllowedExternalClassesThatDefineJobDslBlocks()

/**
* Returns {@code true} if the currently running version of Jenkins is equal to or greater than the specified
* version.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package javaposse.jobdsl.dsl

import java.util.logging.Logger
import java.util.logging.Level

/**
* Helper class for checking if nodes proceesed are valid according the user specified Whitelist
*/
class WhitelistHelper {
private static final Logger LOGGER = Logger.getLogger(WhitelistHelper.name)

private WhitelistHelper() {
}

static void verifyExternalClassThatDefinesConfigureBlock(Closure configureBlock,
String[] allowedExternalClasses) {
if (isClosureFromExternalClass(configureBlock)) {
// if this closure is defined in an external class, let's check it
String parentClassName = configureBlock.thisObject['name']
verifyExternalClass(parentClassName, allowedExternalClasses)
}
// if it's not we're good!
}

private static boolean isClosureFromExternalClass(Closure closure) {
if (closure?.hasProperty('thisObject')) {
return closure.thisObject instanceof Class
} else {
return false
}
}

/**
* Verify that the external class that defines a job dsl block is on the list of allowed
* external classes defining job dsl
*/
private static void verifyExternalClass(String externalClassName, String[] externalClassWhitelist) {
if (!externalClassWhitelist.contains(externalClassName)) {
throw new DslScriptException(String.format('The parent class for the job dsl on this line - %s - is ' +
'not added to the whitelist. To avoid this error, ' +
'either add this class to the whitelist, or turn whitelisting off', externalClassName))
}
}

/**
* Verify that configure block is valid according to the whitelist node
*/
static void verifyRawJobDsl(Closure configureBlock, Node whitelistNode, Closure originalConfigureBlock) {
if (!(isClosureFromExternalClass(configureBlock) || isClosureFromExternalClass(originalConfigureBlock))) {
// if this closure is not from an external class - we have to check the raw job dsl
Node node = new Node(null, 'project')

if (configureBlock) {
// convert configure block to node form - with parent as root node
Closure dup = ((Closure) configureBlock.clone())
dup.delegate = new MissingPropertyToStringDelegate(node)

use(NodeEnhancement) {
dup.call(node)
}

// check that all children for the node representing this configure block are valid
// we don't need to check root since that's simply 'project'
verifyNodeChildren(node, whitelistNode)
}
}
// if it's from an external class... we're good!
}

/**
* Verify that all children of a node do not violate the whitelist by passing in two nodes; the node
* whose children need to be verified, and the node representing the whitelist at that same level
*/
static void verifyNodeChildren(Node nodeToVerify, Node whitelistNode) {
// we need to check that each nodeToVerify child has a matching element in the whitelist node
// at the correct level
nodeToVerify.children().each {
if (it instanceof Node) {
Node childNode = ((Node) it)
verifyNode(childNode, whitelistNode)
}
}
}

/**
* Verify that a node does not violate the whitelist by passing in two nodes; the node that
* needs to be verified, and the node representing the whitelist at one level higher that the nodeToVerify
*/
static void verifyNode(Node nodeToVerify, Node whitelistNodeParent) {
// get whitelist node children names
String nodeToVerifyName = nodeToVerify.name()
List<String> whitelistNodeChildrenNames = getNodeChildrenNames(whitelistNodeParent)
if (whitelistNodeChildrenNames.size() <= 0) {
LOGGER.log(Level.FINE, String.format('No children for whitelist node - ' +
"${whitelistNodeParent.name()} - so current jobdsl node - ${nodeToVerifyName} - " +
'and all children are valid, since all parents of current jobdsl node were in whitelist'))
}
// if whitelist node has children, we check if nodeToVerify is valid
else if (whitelistNodeChildrenNames.contains(nodeToVerifyName)) {
// since we assume no duplicate names for sibling children in the whitelist node tree (convention of
// writing your whitelist xml), we will always take the first one that matches
NodeList matchingWhitelistNodeList = ((NodeList)whitelistNodeParent[nodeToVerifyName])
Node matchingWhitelistNode = ((Node)matchingWhitelistNodeList.get(0))

// recursively call same function on children nodes of nodeToVerify
verifyNodeChildren(nodeToVerify, matchingWhitelistNode)
}
else {
throw new DslScriptException(String.format("Your DSL element - ${nodeToVerify.name().toString()} - is " +
'not listed as a whitelisted element. Whitelisted elements at this ' +
"level include - ${whitelistNodeChildrenNames.toString()}"))
}
}

static List<String> getNodeChildrenNames(Node node) {
List<String> whitelistNodeChildrenNames = []
node.children().each {
if (it instanceof Node) {
whitelistNodeChildrenNames.add(it.name().toString())
}
}
whitelistNodeChildrenNames
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class DslContext implements Context {
boolean ignoreExisting = false
String additionalClasspath
String lookupStrategy = 'JENKINS_ROOT'
String allowedElementsForRawJobDslAsXml = ''
String allowedExternalClassesThatDefineJobDslBlocks = ''

/**
* Sets the Job DSL script.
Expand Down Expand Up @@ -60,6 +62,20 @@ class DslContext implements Context {
this.removedJobAction = action
}

/**
* Specifies what elements are allowed for Raw Job Dsl
*/
void allowedElementsForRawJobDslAsXml(String allowedElements) {
this.allowedElementsForRawJobDslAsXml = allowedElements
}

/**
* Specifies what elements are allowed for Raw Job Dsl
*/
void allowedExternalClassesThatDefineJobDslBlocks(String allowedClasses) {
this.allowedExternalClassesThatDefineJobDslBlocks = allowedClasses
}

/**
* Specifies the action to be taken for views that have been removed from DSL scripts.
*
Expand Down
Loading