| /* |
| |
| Derby - Class org.apache.derbyBuild.SecurityPolicyGenerator |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derbyBuild; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.text.MessageFormat; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Properties; |
| import javax.xml.parsers.*; |
| import org.w3c.dom.*; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| |
| /** |
| * <p> |
| * This tool generates policy files as well as documentation for the Derby Security Guide. |
| * This tool consumes securityPolicies.xml and generates a number of policy |
| * files and DITA source files. Those files are written to the generated source tree |
| * and to the compiled classes tree. See securityPolicies.dtd for a description |
| * of the input XML grammar and its concepts. |
| * </p> |
| */ |
| public class SecurityPolicyGenerator extends Task |
| { |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // CONSTANTS |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** Descriptions of the security policies (relative to the base directory) */ |
| private static final String SOURCE_DIR = "java/org.apache.derby.engine/org/apache/derby/security"; |
| private static final String POLICY_DESCRIPTORS = "securityPolicies.xml"; |
| private static final String PRODUCT_PROPERTIES = "securityProduct.properties"; |
| private static final String TEST_PROPERTIES = "securityTests.properties"; |
| private static final String DOC_EXAMPLE_PROPERTIES = "securityDocExample.properties"; |
| |
| private static final String PERMISSION = "permission "; |
| private static final String STATEMENT_END = ";"; |
| private static final String PRINCIPAL = "principal"; |
| |
| private static final String TAB = " "; |
| private static final String COMMENT = "// "; |
| private static final String NEWLINE = "\n"; |
| |
| private static final String DO_NOT_EDIT = |
| "<!--\n" + |
| "\n" + |
| " DO NOT EDIT THIS FILE! THIS FILE IS GENERATED BY SecurityPolicyGenerator\n" + |
| " FROM POLICY DESCRIPTORS IN securityPolicy.xml.\n" + |
| "\n" + |
| "-->\n"; |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // STATE |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** Base directory of the user's Derby sandbox */ |
| private File _baseDirectory; |
| |
| private Properties _productProperties; |
| private Properties _testProperties; |
| private Properties _docExampleProperties; |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // CONSTRUCTORS |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * <p> |
| * Let Ant conjure us out of thin air. |
| * </p> |
| */ |
| public SecurityPolicyGenerator() |
| {} |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // Task BEHAVIOR |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** <p>Let Ant set the base directory of the Derby sandbox.</p>*/ |
| public void setBaseDirectory( String baseDirectory ) { _baseDirectory = new File(baseDirectory);} |
| |
| /** |
| * <p> |
| * Read the policy descriptor file and generate policies for |
| * various Derby configurations. Also generate documentation on |
| * those policies. |
| * </p> |
| */ |
| public void execute() |
| throws BuildException |
| { |
| try |
| { |
| _productProperties = loadProperties(PRODUCT_PROPERTIES); |
| _testProperties = loadProperties(TEST_PROPERTIES); |
| _docExampleProperties = loadProperties(DOC_EXAMPLE_PROPERTIES); |
| |
| HashMap<String,Policy> policyMap = parsePolicies(); |
| |
| printPolicies(policyMap); |
| } |
| catch (Exception e) |
| { |
| e.printStackTrace(); |
| throw new BuildException( "Could not generate security policies: " + e.getMessage(), e ); |
| } |
| |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // LOAD THE DESCRIPTOR FILE AND PARSE IT INTO A LIST OF POLICIES |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| private HashMap<String,Policy> parsePolicies() throws Exception |
| { |
| HashMap<String,Policy> retval = new HashMap<String,Policy>(); |
| String descriptorFileName = sourceFileName(POLICY_DESCRIPTORS); |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| DocumentBuilder builder = factory.newDocumentBuilder(); |
| Document doc = builder.parse(descriptorFileName); |
| Element root = doc.getDocumentElement(); // framing "policies" element |
| NodeList policies = root.getElementsByTagName("policy"); |
| |
| int policyCount = policies.getLength(); |
| for (int idx = 0; idx < policyCount; idx++) |
| { |
| parsePolicy(retval, (Element) policies.item(idx)); |
| } |
| |
| return retval; |
| } |
| |
| /** Parse a policy and add it to the evolving map of policies */ |
| private void parsePolicy(HashMap<String,Policy> allPolicies, Element policyElement) |
| throws Exception |
| { |
| int indentLevel = 0; |
| String policyName = getLoneText(policyElement, "name"); |
| Output output = parseOutput(policyElement); |
| Policy policy = new Policy(policyName, output, indentLevel); |
| |
| allPolicies.put(policyName, policy); |
| |
| // include parent policies |
| NodeList includeList = policyElement.getElementsByTagName("include"); |
| |
| if (includeList != null) |
| { |
| int includeCount = includeList.getLength(); |
| |
| for (int idx = 0; idx < includeCount; idx++) |
| { |
| String parentPolicyName = MessageBuilder.squeezeText((Element) includeList.item(idx)); |
| Policy parentPolicy = allPolicies.get(parentPolicyName); |
| |
| policy.include(parentPolicy); |
| } |
| } |
| |
| // grant permissions to jars |
| NodeList jarList = policyElement.getElementsByTagName("jar"); |
| |
| if (jarList != null) |
| { |
| int jarCount = jarList.getLength(); |
| |
| for (int idx = 0; idx < jarCount; idx++) |
| { |
| Element jarElement = (Element) jarList.item(idx); |
| |
| parseJar(policy, jarElement, indentLevel); |
| } |
| } |
| } |
| |
| /** Parse the output descriptor */ |
| private Output parseOutput(Element policyElement) |
| throws Exception |
| { |
| Element outputElement = MessageBuilder.getFirstChild(policyElement, "output"); |
| if (outputElement == null) { return null; } |
| |
| File outputFile = new File |
| (_baseDirectory.getAbsolutePath() + "/" + getLoneText(outputElement, "file")); |
| |
| String propertiesName = getLoneText(outputElement, "properties"); |
| Properties outputProperties = null; |
| |
| switch(propertiesName) |
| { |
| case PRODUCT_PROPERTIES: |
| outputProperties = _productProperties; |
| break; |
| |
| case TEST_PROPERTIES: |
| outputProperties = _testProperties; |
| break; |
| |
| case DOC_EXAMPLE_PROPERTIES: |
| outputProperties = _docExampleProperties; |
| break; |
| |
| default: throw new Exception("Unknown properties file name: " + propertiesName); |
| } |
| |
| Doc doc = parseDoc(outputElement); |
| |
| return new Output(outputFile, outputProperties, doc); |
| } |
| |
| /** Parse optional documentation directives */ |
| private Doc parseDoc(Element outputElement) |
| throws Exception |
| { |
| Element docElement = MessageBuilder.getFirstChild(outputElement, "doc"); |
| if (docElement == null) { return null; } |
| |
| String title = |
| MessageBuilder.squeezeText(MessageBuilder.getFirstChild(docElement, "title")); |
| String shortDesc = |
| MessageBuilder.squeezeText(MessageBuilder.getFirstChild(docElement, "shortDesc")); |
| String majorIndexTerm = |
| MessageBuilder.squeezeText(MessageBuilder.getFirstChild(docElement, "majorIndexTerm")); |
| String minorIndexTerm = |
| MessageBuilder.squeezeText(MessageBuilder.getFirstChild(docElement, "minorIndexTerm")); |
| String longDesc = |
| MessageBuilder.squeezeText(MessageBuilder.getFirstChild(docElement, "longDesc")); |
| |
| return new Doc(title, shortDesc, majorIndexTerm, minorIndexTerm, longDesc); |
| } |
| |
| /** Parse permission grants to a jar file */ |
| private void parseJar(Policy policy, Element jarElement, int indentLevel) |
| throws Exception |
| { |
| String targetName = getLoneText(jarElement, "name"); |
| GrantTarget grantTarget = policy.findGrantTarget(targetName); |
| |
| if (grantTarget == null) |
| { |
| grantTarget = new GrantTarget(targetName, indentLevel); |
| policy.add(grantTarget); |
| } |
| |
| // process blocks of permissions |
| NodeList blockList = jarElement.getElementsByTagName("block"); |
| |
| if (blockList != null) |
| { |
| int blockCount = blockList.getLength(); |
| |
| for (int idx = 0; idx < blockCount; idx++) |
| { |
| Element blockElement = (Element) blockList.item(idx); |
| |
| parseBlock(grantTarget, blockElement, indentLevel + 1); |
| } |
| } |
| } |
| |
| /** Parse a block of permissions */ |
| private void parseBlock(GrantTarget grantTarget, Element blockElement, int indentLevel) |
| throws Exception |
| { |
| Element commentElement = MessageBuilder.getFirstChild(blockElement, "comment"); |
| String comment = (commentElement == null) ? null : MessageBuilder.squeezeText(commentElement); |
| PermissionBlock block = new PermissionBlock(comment, indentLevel); |
| |
| grantTarget.add(block); |
| |
| // first process deleted permissions |
| NodeList deletionList = blockElement.getElementsByTagName("d"); |
| |
| if (deletionList != null) |
| { |
| int deletionCount = deletionList.getLength(); |
| |
| for (int idx = 0; idx < deletionCount; idx++) |
| { |
| Element deletionElement = (Element) deletionList.item(idx); |
| String deletedPermission = parsePermission(deletionElement); |
| |
| grantTarget.deletePermission(deletedPermission); |
| } |
| } |
| |
| // now process permission adds |
| NodeList addList = blockElement.getElementsByTagName("a"); |
| |
| if (addList != null) |
| { |
| int addCount = addList.getLength(); |
| |
| for (int idx = 0; idx < addCount; idx++) |
| { |
| Element addElement = (Element) addList.item(idx); |
| String addPermission = parsePermission(addElement); |
| |
| block.add(addPermission); |
| } |
| } |
| } |
| |
| /** Get the permission from a permission node */ |
| private String parsePermission(Element permissionNode) throws Exception |
| { |
| String text = MessageBuilder.squeezeText(permissionNode); |
| |
| return PERMISSION + text + STATEMENT_END; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // PRINT THE POLICIES |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| private void printPolicies(HashMap<String,Policy> policyMap) throws Exception |
| { |
| for (Policy policy : policyMap.values()) |
| { |
| // only print policies which have output instructions |
| Output output = policy.output; |
| if (output == null) { continue; } |
| |
| File outputFile = output.file; |
| outputFile.getParentFile().mkdirs(); |
| |
| try (PrintWriter writer = new PrintWriter(outputFile)) |
| { |
| policy.print(writer); |
| writer.flush(); |
| } |
| } |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // GENERALLY USEFUL MINIONS |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** Return the single text child element of the parent element */ |
| private String getLoneText(Element parentElement, String childName) |
| throws Exception |
| { |
| Element firstElement = MessageBuilder.getFirstChild(parentElement, childName); |
| Node textChild = firstElement.getFirstChild(); |
| |
| if (textChild == null) { return ""; } |
| else { return textChild.getNodeValue(); } |
| } |
| |
| /** Load a properties file */ |
| private Properties loadProperties(String shortFileName) throws IOException |
| { |
| String fullFileName = sourceFileName(shortFileName); |
| FileInputStream is = new FileInputStream(fullFileName); |
| Properties retval = new Properties(); |
| |
| retval.load(is); |
| |
| return retval; |
| } |
| |
| /** Turn a short source file name into an absolute file name */ |
| private String sourceFileName(String shortFileName) |
| { |
| return _baseDirectory.getAbsolutePath() + "/" + SOURCE_DIR + "/" + shortFileName; |
| } |
| |
| /** |
| * <p> |
| * Print a comment. |
| * </p> |
| */ |
| private static void printComment(PrintWriter writer, String comment, int indentLevel) |
| { |
| String tabAndComment = tab(indentLevel) + COMMENT; |
| |
| // put comment markers at the beginning of every line |
| String prettyComment = tabAndComment + comment.replace(NEWLINE, NEWLINE + tabAndComment); |
| |
| writer.println(prettyComment); |
| } |
| |
| /** |
| * <p> |
| * Construct a tab level. |
| * </p> |
| */ |
| private static String tab(int indentLevel) |
| { |
| StringBuilder buffer = new StringBuilder(); |
| for (int idx = 0; idx < indentLevel; idx++) { buffer.append(TAB); } |
| return buffer.toString(); |
| } |
| |
| /** |
| * <p> |
| * Echo a message to the console. |
| * </p> |
| */ |
| private void echo( String text ) |
| { |
| log( text, Project.MSG_WARN ); |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // NESTED CLASSES |
| // |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** A block of permissions */ |
| public static final class PermissionBlock extends ArrayList<String> |
| { |
| public final String comment; |
| public final int indentLevel; |
| |
| public PermissionBlock(String comment, int indentLevel) |
| { |
| this.comment = comment; |
| this.indentLevel = indentLevel; |
| } |
| |
| // print the permission block |
| public void print(PrintWriter writer, boolean forDocumentation) throws IOException |
| { |
| String tab = tab(indentLevel); |
| if (comment != null) { printComment(writer, comment, indentLevel); } |
| |
| for (String permission : this) |
| { |
| // If this is for documentation, then replace angle brackets |
| // with xml entities. |
| if (forDocumentation) |
| { |
| permission = permission |
| .replace("<", "<") |
| .replace(">", ">") |
| ; |
| } |
| |
| writer.print(tab); |
| writer.println(permission); |
| } |
| writer.println(); |
| } |
| |
| public PermissionBlock cloneMe() |
| { |
| PermissionBlock clone = new PermissionBlock(comment, indentLevel); |
| clone.addAll(this); |
| |
| return clone; |
| } |
| } |
| |
| /** A target (usually a jar file) which is granted permissions */ |
| public static final class GrantTarget extends ArrayList<PermissionBlock> |
| { |
| public final String logicalName; |
| public final int indentLevel; |
| |
| public GrantTarget(String logicalName, int indentLevel) |
| { |
| this.logicalName = logicalName; |
| this.indentLevel = indentLevel; |
| } |
| |
| /** Delete a permission from all blocks */ |
| public void deletePermission(String permission) |
| { |
| for (PermissionBlock block : this) |
| { |
| block.remove(permission); |
| } |
| } |
| |
| // print the granted permissions |
| public void print |
| ( |
| PrintWriter writer, |
| Properties outputProperties, |
| boolean forDocumentation |
| ) throws IOException |
| { |
| String tab = tab(indentLevel); |
| String actualName = outputProperties.getProperty(logicalName, logicalName); |
| boolean isPrincipal = actualName.startsWith(PRINCIPAL); |
| |
| writer.print(tab); |
| writer.print("grant"); |
| if (actualName.length() > 0) |
| { |
| writer.print(" "); |
| if (isPrincipal) |
| { |
| writer.println(actualName); |
| } |
| else |
| { |
| writer.print("codeBase \""); |
| writer.print(actualName); |
| writer.println("\""); |
| } |
| } |
| else { writer.println(""); } |
| |
| writer.print(tab); |
| writer.println("{"); |
| |
| for (PermissionBlock block : this) |
| { |
| block.print(writer, forDocumentation); |
| } |
| |
| writer.print(tab); |
| writer.println("};"); |
| writer.println(); |
| } |
| |
| public GrantTarget cloneMe() |
| { |
| GrantTarget clone = new GrantTarget(logicalName, indentLevel); |
| |
| for (PermissionBlock block : this) |
| { |
| clone.add(block.cloneMe()); |
| } |
| |
| return clone; |
| } |
| } |
| |
| /** A descriptor for documentation directives */ |
| public static final class Doc |
| { |
| public final String title; |
| public final String shortDesc; |
| public final String majorIndexTerm; |
| public final String minorIndexTerm; |
| public final String longDesc; |
| |
| public Doc |
| ( |
| String title, |
| String shortDesc, |
| String majorIndexTerm, |
| String minorIndexTerm, |
| String longDesc |
| ) |
| { |
| this.title = title; |
| this.shortDesc = shortDesc; |
| this.majorIndexTerm = majorIndexTerm; |
| this.minorIndexTerm = minorIndexTerm; |
| this.longDesc = longDesc; |
| } |
| |
| // print the dita header |
| public void printDitaHeader(MessageBuilder.XMLWriter writer, String referenceID) throws IOException |
| { |
| writer.println(MessageBuilder.REF_GUIDE_BOILERPLATE); |
| writer.println("<!--"); |
| writer.println(MessageBuilder.APACHE_LICENSE); |
| writer.println("-->"); |
| writer.println(DO_NOT_EDIT); |
| |
| writer.beginTag("reference", "id=\"" + referenceID + "\" xml:lang=\"en-us\""); |
| { |
| writer.writeTextElement("title", title); |
| writer.writeTextElement("shortdesc", shortDesc); |
| |
| writer.beginTag("prolog"); |
| { |
| writer.beginTag("metadata"); |
| { |
| writer.beginTag("keywords"); |
| { |
| writer.beginTag("indexterm"); |
| { |
| writer.indent(); |
| writer.println(majorIndexTerm); |
| writer.writeTextElement("indexterm", minorIndexTerm); |
| } |
| writer.endTag(); |
| } |
| writer.endTag(); |
| } |
| writer.endTag(); |
| } |
| writer.endTag(); |
| |
| writer.beginTag("refbody"); |
| { |
| writer.beginTag("section"); |
| { |
| writer.writeTextElement("p", longDesc); |
| |
| writer.beginTag("codeblock"); |
| { |
| // caller fills in the codeblock, the bulk of the policy file |
| } |
| // terminated by printDitaFooter() |
| } |
| // terminated by printDitaFooter() |
| } |
| // terminated by printDitaFooter() |
| } |
| // terminated by printDitaFooter() |
| } |
| |
| // print the dita header |
| public void printDitaFooter(MessageBuilder.XMLWriter writer) throws IOException |
| { |
| writer.endTag(); // end codeblock |
| writer.endTag(); // end section |
| writer.endTag(); // end refbody |
| writer.endTag(); // end reference |
| } |
| } |
| |
| /** A descriptor for where to write the policy file */ |
| public static final class Output |
| { |
| public final File file; |
| public final Properties properties; |
| public final Doc doc; |
| |
| public Output(File file, Properties properties, Doc doc) |
| { |
| this.file = file; |
| this.properties = properties; |
| this.doc = doc; |
| } |
| |
| /** |
| * The DITA reference id is the file stub. |
| */ |
| public String ditaReferenceID() |
| { |
| String shortName = file.getName(); |
| int dotIdx = shortName.lastIndexOf("."); |
| |
| return shortName.substring(0, dotIdx); |
| } |
| } |
| |
| /** A policy, consisting of GrantTargets */ |
| public static final class Policy extends ArrayList<GrantTarget> |
| { |
| public final String name; |
| public final Output output; |
| public final int indentLevel; |
| |
| public Policy(String name, Output output, int indentLevel) |
| { |
| this.name = name; |
| this.output = output; |
| this.indentLevel = indentLevel; |
| } |
| |
| /** Include a parent policy */ |
| public void include(Policy parent) |
| { |
| for (GrantTarget source : parent) |
| { |
| add(source.cloneMe()); |
| } |
| } |
| |
| /** |
| * Add a GrantTarget, merging into an existing target of the |
| * same name if it exists. |
| */ |
| @Override |
| public boolean add(GrantTarget source) |
| { |
| GrantTarget destination = findGrantTarget(source.logicalName); |
| |
| if (destination == null) { return super.add(source); } |
| else |
| { |
| return destination.addAll(source); |
| } |
| } |
| |
| /** Find a GrantTarget */ |
| public GrantTarget findGrantTarget(String targetName) |
| { |
| for (GrantTarget candidate : this) |
| { |
| if (targetName.equals(candidate.logicalName)) { return candidate; } |
| } |
| |
| return null; |
| } |
| |
| // print the policy file |
| public void print(PrintWriter writer) throws IOException |
| { |
| String tab = tab(indentLevel); |
| MessageBuilder.XMLWriter xmlWriter = null; |
| Doc doc = null; |
| boolean forDocumentation = false; |
| |
| if (output != null) |
| { |
| doc = output.doc; |
| if (doc != null) |
| { |
| forDocumentation = true; |
| |
| // Frame the Security Guide page with boilerplate |
| xmlWriter = new MessageBuilder.XMLWriter(writer); |
| |
| doc.printDitaHeader(xmlWriter, output.ditaReferenceID()); |
| } |
| } |
| |
| // |
| // If we are printing a Security Guide page, then don't redundantly print |
| // the Apache license, which printDitaHeader() already wrote. |
| // |
| if (!forDocumentation) |
| { |
| printComment(writer, MessageBuilder.APACHE_LICENSE, indentLevel); |
| writer.println(); |
| } |
| |
| Properties outputProperties = (output == null) ? new Properties() : output.properties; |
| |
| for (GrantTarget grantTarget : this) |
| { |
| grantTarget.print(writer, outputProperties, forDocumentation); |
| } |
| writer.println(); |
| |
| if (forDocumentation) |
| { |
| doc.printDitaFooter(xmlWriter); |
| xmlWriter.flush(); |
| } |
| } |
| } |
| |
| } |
| |