Java tutorial
/* * The MIT License * * Copyright (c) 2018, 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. */ package jenkins.security.stapler; import com.google.common.annotations.VisibleForTesting; import hudson.BulkChange; import hudson.Extension; import hudson.ExtensionList; import hudson.model.Saveable; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.Function; import org.kohsuke.stapler.WebApp; import org.kohsuke.stapler.lang.FieldRef; import javax.annotation.Nonnull; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Fill the list of getter methods that are whitelisted for Stapler * Each item in the set are formatted to correspond exactly to what {@link Function#getDisplayName()} returns */ @Restricted(NoExternalUse.class) @Extension public class StaticRoutingDecisionProvider extends RoutingDecisionProvider implements Saveable { private static final Logger LOGGER = Logger.getLogger(StaticRoutingDecisionProvider.class.getName()); private Set<String> whitelistSignaturesFromFixedList; private Set<String> whitelistSignaturesFromUserControlledList; private Set<String> blacklistSignaturesFromFixedList; private Set<String> blacklistSignaturesFromUserControlledList; public StaticRoutingDecisionProvider() { reload(); } /** * Return the singleton instance of this class, typically for script console use */ public static StaticRoutingDecisionProvider get() { return ExtensionList.lookupSingleton(StaticRoutingDecisionProvider.class); } /** * @see Function#getSignature() * @see FieldRef#getSignature() */ @Nonnull public synchronized Decision decide(@Nonnull String signature) { if (whitelistSignaturesFromFixedList == null || whitelistSignaturesFromUserControlledList == null || blacklistSignaturesFromFixedList == null || blacklistSignaturesFromUserControlledList == null) { reload(); } LOGGER.log(Level.CONFIG, "Checking whitelist for " + signature); // priority to blacklist if (blacklistSignaturesFromFixedList.contains(signature) || blacklistSignaturesFromUserControlledList.contains(signature)) { return Decision.REJECTED; } if (whitelistSignaturesFromFixedList.contains(signature) || whitelistSignaturesFromUserControlledList.contains(signature)) { return Decision.ACCEPTED; } return Decision.UNKNOWN; } public synchronized void reload() { reloadFromDefault(); reloadFromUserControlledList(); resetMetaClassCache(); } @VisibleForTesting synchronized void resetAndSave() { this.whitelistSignaturesFromFixedList = new HashSet<>(); this.whitelistSignaturesFromUserControlledList = new HashSet<>(); this.blacklistSignaturesFromFixedList = new HashSet<>(); this.blacklistSignaturesFromUserControlledList = new HashSet<>(); this.save(); } private void resetMetaClassCache() { // to allow the change to be effective, i.e. rebuild the MetaClass using the new whitelist WebApp.get(Jenkins.get().servletContext).clearMetaClassCache(); } private synchronized void reloadFromDefault() { try (InputStream is = StaticRoutingDecisionProvider.class.getResourceAsStream("default-whitelist.txt")) { whitelistSignaturesFromFixedList = new HashSet<>(); blacklistSignaturesFromFixedList = new HashSet<>(); parseFileIntoList(IOUtils.readLines(is, StandardCharsets.UTF_8), whitelistSignaturesFromFixedList, blacklistSignaturesFromFixedList); } catch (IOException e) { throw new ExceptionInInitializerError(e); } LOGGER.log(Level.FINE, "Found {0} getter in the standard whitelist", whitelistSignaturesFromFixedList.size()); } public synchronized StaticRoutingDecisionProvider add(@Nonnull String signature) { if (this.whitelistSignaturesFromUserControlledList.add(signature)) { LOGGER.log(Level.INFO, "Signature [{0}] added to the whitelist", signature); save(); resetMetaClassCache(); } else { LOGGER.log(Level.INFO, "Signature [{0}] was already present in the whitelist", signature); } return this; } public synchronized StaticRoutingDecisionProvider addBlacklistSignature(@Nonnull String signature) { if (this.blacklistSignaturesFromUserControlledList.add(signature)) { LOGGER.log(Level.INFO, "Signature [{0}] added to the blacklist", signature); save(); resetMetaClassCache(); } else { LOGGER.log(Level.INFO, "Signature [{0}] was already present in the blacklist", signature); } return this; } public synchronized StaticRoutingDecisionProvider remove(@Nonnull String signature) { if (this.whitelistSignaturesFromUserControlledList.remove(signature)) { LOGGER.log(Level.INFO, "Signature [{0}] removed from the whitelist", signature); save(); resetMetaClassCache(); } else { LOGGER.log(Level.INFO, "Signature [{0}] was not present in the whitelist", signature); } return this; } public synchronized StaticRoutingDecisionProvider removeBlacklistSignature(@Nonnull String signature) { if (this.blacklistSignaturesFromUserControlledList.remove(signature)) { LOGGER.log(Level.INFO, "Signature [{0}] removed from the blacklist", signature); save(); resetMetaClassCache(); } else { LOGGER.log(Level.INFO, "Signature [{0}] was not present in the blacklist", signature); } return this; } /** * Saves the configuration info to the disk. */ public synchronized void save() { if (BulkChange.contains(this)) { return; } File file = getConfigFile(); try { List<String> allSignatures = new ArrayList<>(whitelistSignaturesFromUserControlledList); blacklistSignaturesFromUserControlledList.stream().map(signature -> "!" + signature) .forEach(allSignatures::add); FileUtils.writeLines(file, allSignatures); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to save " + file.getAbsolutePath(), e); } } /** * Loads the data from the disk into this object. * * <p> * The constructor of the derived class must call this method. * (If we do that in the base class, the derived class won't * get a chance to set default values.) */ private synchronized void reloadFromUserControlledList() { File file = getConfigFile(); if (!file.exists()) { if ((whitelistSignaturesFromUserControlledList != null && whitelistSignaturesFromUserControlledList.isEmpty()) || (blacklistSignaturesFromUserControlledList != null && blacklistSignaturesFromUserControlledList.isEmpty())) { LOGGER.log(Level.INFO, "No whitelist source file found at " + file + " so resetting user-controlled whitelist"); } whitelistSignaturesFromUserControlledList = new HashSet<>(); blacklistSignaturesFromUserControlledList = new HashSet<>(); return; } LOGGER.log(Level.INFO, "Whitelist source file found at " + file); try { whitelistSignaturesFromUserControlledList = new HashSet<>(); blacklistSignaturesFromUserControlledList = new HashSet<>(); parseFileIntoList(FileUtils.readLines(file, StandardCharsets.UTF_8), whitelistSignaturesFromUserControlledList, blacklistSignaturesFromUserControlledList); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to load " + file.getAbsolutePath(), e); } } private File getConfigFile() { return new File( WHITELIST_PATH == null ? new File(Jenkins.get().getRootDir(), "stapler-whitelist.txt").toString() : WHITELIST_PATH); } private void parseFileIntoList(List<String> lines, Set<String> whitelist, Set<String> blacklist) { lines.stream().filter(line -> !line.matches("#.*|\\s*")).forEach(line -> { if (line.startsWith("!")) { String withoutExclamation = line.substring(1); if (!withoutExclamation.isEmpty()) { blacklist.add(withoutExclamation); } } else { whitelist.add(line); } }); } /** Allow script console access */ public static String WHITELIST_PATH = SystemProperties .getString(StaticRoutingDecisionProvider.class.getName() + ".whitelist"); }