Permalink
Browse files

[FIXED JENKINS-43872] Properly escape dollar signs in env evaluation

  • Loading branch information...
abayer committed Apr 26, 2017
1 parent a443e04 commit 602a93d39a67ab263657970fa305a2d517576cf1
@@ -346,6 +346,10 @@ public class Utils {
return StringEscapeUtils.unescapeJava(s)
}

static String unescapeDollars(String s) {
return StringUtils.replace(s, Environment.DOLLAR_PLACEHOLDER, '$')
}

static List<List<String>> getEnvCredentials(Environment environment, CpsScript script) {
List<List<String>> credsTuples = new ArrayList<>()
if (environment != null) {
@@ -43,6 +43,8 @@ import org.jenkinsci.plugins.workflow.cps.EnvActionImpl
*/
@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID")
public class Environment implements Serializable {
public static final String DOLLAR_PLACEHOLDER = "___DOLLAR_SIGN___"

Map<String,EnvValue> valueMap = new TreeMap<>()
Map<String,EnvValue> credsMap = new TreeMap<>()

@@ -77,7 +79,10 @@ public class Environment implements Serializable {
public Map<String,String> resolveEnvVars(CpsScript script, boolean withContext, Environment parent = null) {
Map<String, String> overrides = getMap().collectEntries { k, v ->
String val = v.value.toString()
if (!v.isLiteral) {
if (v.isLiteral) {
// Escape dollar-signs.
val = StringUtils.replace(val, '$', DOLLAR_PLACEHOLDER)
} else {
// Switch out env.FOO for FOO, since the env global variable isn't available in the shell we're processing.
val = replaceEnvDotInCurlies(val)
}
@@ -100,6 +105,7 @@ public class Environment implements Serializable {
if (withContext) {
alreadySet.putAll(((EnvActionImpl) script.getProperty("env")).getEnvironment())
}

// Add parameters.
((Map<String, Object>) script.getProperty("params")).each { k, v ->
alreadySet.put(k, v?.toString() ?: "")
@@ -153,12 +159,12 @@ public class Environment implements Serializable {
// entries, and record that we've processed that environment variable.
bindingEnv.values.each { cKey, cVal ->
credKeys.add(cKey)
binding.setVariable(cKey, cVal)
binding.setVariable(cKey, StringUtils.replace(cVal, '$', DOLLAR_PLACEHOLDER))
}
}
}
}
} catch (_) {
} catch (Exception e) {
// Something went wrong? Don't care! We'll be processing this for real later anyway.
}
}
@@ -222,7 +228,6 @@ public class Environment implements Serializable {
resolved = StringUtils.replace(resolved, '\\', '\\\\')
alreadySet.put(nextKey, resolved)
binding.setVariable(nextKey, resolved)

unsuccessfulCount = 0
} catch (_) {
unsuccessfulCount++
@@ -235,12 +240,22 @@ public class Environment implements Serializable {

private boolean containsVariable(String var, Collection<String> keys) {
def group = (var =~ /(\$\{.*?\})/)
return group.any { m ->
def found = group.any { m ->
keys.any { k ->
String curlies = m[1]
return curlies.contains(k)
return curlies.matches(/.*\W${k}\W.*/)
}
}
if (!found) {
def explicit = (var =~ /(\$.*?)\W/)
found = explicit.any { m ->
keys.any { k ->
String single = m[1]
return single == '$' + k
}
}
}
return found
}

private String replaceEnvDotInCurlies(String inString) {
@@ -213,7 +213,7 @@ public class ModelInterpreter implements Serializable {
for (int i = 0; i < envVars.size(); i++) {
// Evaluate to deal with any as-of-yet unresolved expressions.
String toEval = Utils.prepareForEvalToString(envVars.get(i))
evaledEnv.add(Utils.unescapeFromEval((String)script.evaluate(toEval)))
evaledEnv.add(Utils.unescapeDollars(Utils.unescapeFromEval((String)script.evaluate(toEval))))
}
return {
script.withEnv(evaledEnv) {
@@ -105,6 +105,18 @@ public void environmentCrossReferences() throws Exception {
.go();
}

@Issue("JENKINS-43872")
@Test
public void envDollarQuotes() throws Exception {
expect("envDollarQuotes")
.logContains("[Pipeline] { (foo)",
"FOO is ${FOOTHAT}",
"BAR is ${FOOTHAT}BAR",
"BAZ is ${FOOTHAT}BAZ",
"SPLODE is banana")
.go();
}

@Test
public void envDotCrossRef() throws Exception {
expect("envDotCrossRef")
@@ -61,7 +61,9 @@
public static final String usernamePasswordPassword = "s3cr37";
private static final String mixedEnvCred1Id = "cred1";
private static final String mixedEnvCred2Id = "cred2";
private static final String mixedEnvCred3Id = "cred3";
private static final String mixedEnvCred1Secret = "Some secret text for 1";
private static final String mixedEnvCred3Secret = "Some $secret text for 3";
private static final String mixedEnvCred2U = "bobby";
private static final String mixedEnvCred2P = "supersecretpassword+mydogsname";
private static Folder folder;
@@ -81,6 +83,8 @@ public static void setup() throws Exception {
store.addCredentials(Domain.global(), mixedEnvCred1);
UsernamePasswordCredentialsImpl mixedEnvCred2 = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, mixedEnvCred2Id, "sample", mixedEnvCred2U, mixedEnvCred2P);
store.addCredentials(Domain.global(), mixedEnvCred2);
StringCredentialsImpl mixedEnvCred3 = new StringCredentialsImpl(CredentialsScope.GLOBAL, mixedEnvCred3Id, "test", Secret.fromString(mixedEnvCred3Secret));
store.addCredentials(Domain.global(), mixedEnvCred3);

folder = j.jenkins.createProject(Folder.class, "testFolder");
folder.addProperty(new FolderCredentialsProvider.FolderCredentialsProperty(new DomainCredentials[0]));
@@ -159,4 +163,15 @@ public void credentialsEnvCrossReference() throws Exception {
.archives("cred2.txt", mixedEnvCred2U + ":" + mixedEnvCred2P).go();
}

@Issue("JENKINS-43872")
@Test
public void credentialsDollarQuotes() throws Exception {
expect("credentialsDollarQuotes")
.logContains("SOME_VAR is SOME VALUE",
"INBETWEEN is Something **** between",
"OTHER_VAR is OTHER VALUE")
.archives("inbetween.txt", "Something " + mixedEnvCred3Secret + " between")
.archives("cred3.txt", mixedEnvCred3Secret)
.archives("cred2.txt", mixedEnvCred2U + ":" + mixedEnvCred2P).go();
}
}
@@ -0,0 +1,49 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* 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.
*/

pipeline {
environment {
SOME_VAR = "SOME VALUE"
CRED3 = credentials("cred3")
INBETWEEN = "Something ${CRED3} between"
CRED2 = credentials("cred2")
OTHER_VAR = "OTHER VALUE"
}

agent any

stages {
stage("foo") {
steps {
sh 'echo "SOME_VAR is $SOME_VAR"'
sh 'echo "OTHER_VAR is $OTHER_VAR"'
sh 'echo "INBETWEEN is $INBETWEEN"'
sh 'echo $INBETWEEN > inbetween.txt'
sh 'echo $CRED3 > cred3.txt'
sh 'echo $CRED2 > cred2.txt'
archive "**/*.txt"
}
}
}
}
@@ -0,0 +1,52 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* 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.
*/

pipeline {
environment {
FOO = '${FOOTHAT}'
BAR = "${FOO}BAR"
}
agent {
label "some-label"
}

stages {
stage("foo") {
environment {
BAZ = "${FOO}BAZ"
SPLODE = "${params.WUT ?: 'banana'}"
}

steps {
sh 'echo "FOO is $FOO"'
sh 'echo "BAR is $BAR"'
sh 'echo "BAZ is $BAZ"'
sh 'echo "SPLODE is $SPLODE"'
}
}
}
}



@@ -24,8 +24,8 @@

pipeline {
environment {
FOO = "FOO"
BAR = '${FOO}BAR'
FOO = 'FOO'
BAR = "${FOO}BAR"
}
agent {
label "some-label"

0 comments on commit 602a93d

Please sign in to comment.