package org.jenkinsci.plugins.workflow.cps;

import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.AbstractWhitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.ProxyWhitelist;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * {@link Whitelist} implementation for CPS flow execution.
 *
 * @author Kohsuke Kawaguchi
 */
class CpsWhitelist extends AbstractWhitelist {
    private CpsWhitelist() {}

    @Override
    public boolean permitsMethod(Method method, Object receiver, Object[] args) {
        if (receiver instanceof CpsScript) {
            String name = method.getName();
            if (name.equals("invokeMethod")) {
                // CpsScript dispatches to the DSL class
                return true;
            }
            if (name.equals("evaluate")) {
                // evaluate() family of methods are reimplemented in CpsScript for safe manner
                // but we can't allow arbitrary Script.evaluate() calls as that will escape sandbox
                return true;
            }
            if (name.equals("println") || name.equals("print") || name.equals("printf")) {
                // These are just aliases for EchoStep.
                return true;
            }
            if (name.equals("getProperty") && args.length == 1) {
                if (CpsScript.PROP_ENV.equals(args[0])) {
                    return true;
                } else if (CpsScript.PROP_BUILD.equals(args[0])) {
                    return true;
                }
            }
        }
        // TODO JENKINS-24982: it would be nice if AnnotatedWhitelist accepted @Whitelisted on an override
        if (receiver instanceof EnvActionImpl) {
            String name = method.getName();
            if (name.equals("getProperty") || name.equals("setProperty")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean permitsConstructor(Constructor<?> constructor, Object[] args) {
        if (constructor.getDeclaringClass()== GStringImpl.class)
            return true;

        return false;
    }

    @Override
    public boolean permitsStaticMethod(Method method, Object[] args) {
        // type coercive cast. In particular, this is used to build GString. See com.cloudbees.groovy.cps.Builder.gstring
        if (method.getDeclaringClass()==ScriptBytecodeAdapter.class && method.getName().equals("asType"))
            return true;

        return false;
    }

    /**
     * Stuff we whitelist specifically for CPS, with the rest of the installed rules combined.
     */
    private static Whitelist INSTANCE;

    public static synchronized Whitelist get() {
        if (INSTANCE==null) {
            INSTANCE = new ProxyWhitelist(new CpsWhitelist(),Whitelist.all());
        }
        return INSTANCE;
    }
}

@@ -439,16 +439,98 @@
 .Method;
+%0Aimport java.util.Map;%0Aimport java.util.WeakHashMap;%0Aimport jenkins.model.Jenkins;
 %0A%0A/**%0A *
@@ -2880,26 +2880,92 @@
 tic 
-Whitelist INSTANCE
+final Map%3CJenkins,Whitelist%3E wrappedByJenkins = new WeakHashMap%3CJenkins,Whitelist%3E()
 ;%0A%0A 
@@ -3024,22 +3024,60 @@
     
-if (INSTANCE
+Jenkins j = Jenkins.getInstance();%0A        if (j 
 ==
+ 
 null
@@ -3092,24 +3092,158 @@
         
-INSTANCE
+return new ProxyWhitelist();%0A        %7D%0A        Whitelist wrapped = wrappedByJenkins.get(j);%0A        if (wrapped == null) %7B%0A            wrapped
  = new P
@@ -3275,16 +3275,17 @@
 elist(),
+ 
 Whitelis
@@ -3294,16 +3294,62 @@
 all());%0A
+            wrappedByJenkins.put(j, wrapped);%0A
         
@@ -3369,16 +3369,15 @@
 urn 
-INSTANCE
+wrapped
 ;%0A  

Static state bug. Analogous to: https://github.com/jenkinsci/script-security-plugin/pull/8