package jenkins.security.s2m; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import hudson.Functions; import hudson.model.Failure; import jenkins.model.Jenkins; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static hudson.Functions.isWindows; /** * Config file that lists {@link FilePathRule} rules. * * @author Kohsuke Kawaguchi */ class FilePathRuleConfig extends ConfigDirectory<FilePathRule,List<FilePathRule>> { FilePathRuleConfig(File file) { super(file); } @Override protected List<FilePathRule> create() { return new ArrayList<FilePathRule>(); } @Override protected List<FilePathRule> readOnly(List<FilePathRule> base) { return ImmutableList.copyOf(base); } @Override protected FilePathRule parse(String line) { line = line.trim(); if (line.isEmpty()) return null; line = line.replace("<BUILDDIR>","<JOBDIR>/builds/<BUILDID>"); line = line.replace("<BUILDID>","(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9]-[0-9][0-9]-[0-9][0-9]|[0-9]+)"); line = line.replace("<JOBDIR>","<JENKINS_HOME>/jobs/.+"); line = line.replace("<JENKINS_HOME>","\\Q"+Jenkins.getInstance().getRootDir().getPath()+"\\E"); // config file is always /-separated even on Windows, so bring it back to \-separation. // This is done in the context of regex, so it has to be \\, which means in the source code it is \\\\ if (isWindows()) line = line.replace("/","\\\\"); Matcher m = PARSER.matcher(line); if (!m.matches()) throw new Failure("Invalid filter rule line: "+line); try { return new FilePathRule( Pattern.compile(m.group(3)), createOpMatcher(m.group(2)), m.group(1).equals("allow")); } catch (Exception e) { throw new Failure("Invalid filter rule line: "+line+"\n"+ Functions.printThrowable(e)); } } private OpMatcher createOpMatcher(String token) { if (token.equals("all")) return OpMatcher.ALL; final ImmutableSet ops = ImmutableSet.copyOf(token.split(",")); return new OpMatcher() { @Override public boolean matches(String op) { return ops.contains(op); } }; } public boolean checkFileAccess(String op, File path) throws SecurityException { String pathStr = null; for (FilePathRule rule : get()) { if (rule.op.matches(op)) { if (pathStr==null) { // do not canonicalize, so that JENKINS_HOME that spans across // multiple volumes via symlinks can look logically like one unit. pathStr = path.getPath(); if (isWindows()) // Windows accepts '/' as separator, but for rule matching we want to normalize for consistent comparison pathStr = pathStr.replace('/','\\'); } if (rule.path.matcher(pathStr).matches()) { // exclusion rule is only to bypass later path rules within #filePathRules, // and we still want other FilePathFilters to whitelist/blacklist access. // therefore I'm not throwing a SecurityException here return rule.allow; } } } return false; } /** * */ private static final Pattern PARSER = Pattern.compile("(allow|deny)\\s+([a-z,]+)\\s+(.*)"); }