Package com.getperka.flatpack.policy

Source Code of com.getperka.flatpack.policy.PolicyParser

package com.getperka.flatpack.policy;

/*
* #%L
* FlatPack Security Policy
* %%
* Copyright (C) 2012 - 2013 Perka Inc.
* %%
* Licensed 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.
* #L%
*/

import java.util.ArrayList;
import java.util.List;

import org.parboiled.Action;
import org.parboiled.BaseParser;
import org.parboiled.Context;
import org.parboiled.Parboiled;
import org.parboiled.Rule;
import org.parboiled.annotations.Cached;
import org.parboiled.annotations.DontExtend;
import org.parboiled.annotations.DontLabel;
import org.parboiled.annotations.Label;
import org.parboiled.support.StringVar;
import org.parboiled.support.Var;

import com.getperka.flatpack.ext.Property;
import com.getperka.flatpack.ext.PropertyPath;
import com.getperka.flatpack.policy.pst.ActionDefinition;
import com.getperka.flatpack.policy.pst.AllowBlock;
import com.getperka.flatpack.policy.pst.AllowRule;
import com.getperka.flatpack.policy.pst.GroupBlock;
import com.getperka.flatpack.policy.pst.GroupDefinition;
import com.getperka.flatpack.policy.pst.HasInheritFrom;
import com.getperka.flatpack.policy.pst.HasName;
import com.getperka.flatpack.policy.pst.Ident;
import com.getperka.flatpack.policy.pst.PackagePolicy;
import com.getperka.flatpack.policy.pst.PolicyBlock;
import com.getperka.flatpack.policy.pst.PolicyFile;
import com.getperka.flatpack.policy.pst.PolicyNode;
import com.getperka.flatpack.policy.pst.PropertyList;
import com.getperka.flatpack.policy.pst.PropertyPolicy;
import com.getperka.flatpack.policy.pst.TypePolicy;
import com.getperka.flatpack.security.SecurityAction;
import com.getperka.flatpack.security.SecurityGroup;

/**
* The grammar definition for the policy file.
* <p>
* The method names in this type use parboiled's <a
* href="https://github.com/sirthias/parboiled/wiki/Style-Guide">naming scheme</a>, where methods
* returning {@link Rule} objects are named with a capitalized first letter. Since most rules will
* push an object onto the value stack, the rules are generally named in accordance with the object
* type that they will push.
* <p>
* Also note that due to parboiled's extensive class rewriting, the code-coverage numbers on this
* class are completely wrong.
*/
class PolicyParser extends BaseParser<Object> {
  private static final String WILDCARD = "*";
  private static final PolicyParser parser = Parboiled.createParser(PolicyParser.class);

  /**
   * Return a new instance of a PolicyParser.
   */
  public static PolicyParser get() {
    return parser.newInstance();
  }

  /**
   * The top-level parse rule.
   */
  public Rule PolicyFile() {
    Var<PolicyFile> x = new Var<PolicyFile>(new PolicyFile());
    return Sequence(
        PolicyBlock(x),
        EOI);
  }

  /**
   * Tweak the value-stack push method to record the line number on which the current rule started.
   */
  @Override
  public boolean push(Object value) {
    if (value instanceof PolicyNode) {
      PolicyNode x = (PolicyNode) value;
      int startIndex = getContext().getStartIndex();
      x.setLineNumber(getContext().getInputBuffer().getPosition(startIndex).line);
    }
    return super.push(value);
  }

  /**
   * Makes string literals passed to Rule objects also consume any trailing whitespace.
   */
  @Override
  @DontExtend
  protected Rule fromStringLiteral(String string) {
    return Sequence(String(string), WS());
  }

  /**
   * An individual ACL rule:
   *
   * <pre>
   * groupName none
   *
   * groupName to verbName.actionName
   *
   * inheritedProperty.groupName to ...
   * </pre>
   */
  Rule AclRule() {
    return Sequence(
        FirstOf(
            Sequence(
                Ident(Property.class),
                ".",
                Ident(SecurityGroup.class),
                new Action<Object>() {
                  @Override
                  public boolean run(Context<Object> ctx) {
                    Ident<SecurityGroup> group = popIdent(SecurityGroup.class);
                    Ident<Property> property = popIdent(Property.class);
                    push(new Ident<SecurityGroup>(SecurityGroup.class, property, group));
                    return true;
                  }
                }),
            WildcardOrIdent(SecurityGroup.class)),
        FirstOf(
            // Special syntax for a zero-length list
            Sequence("none", ACTION(push(new ArrayList<Ident<SecurityAction>>()))),
            // Or require at least one
            Sequence(
                "to",
                OneOrListOf(VerbActionOrWildcard(), Ident.class, ",")
            )
        ),
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            AllowRule x = new AllowRule();
            x.setSecurityActions(popIdentList(SecurityAction.class));
            x.setGroupName(popIdent(SecurityGroup.class));
            push(x);
            return true;
          }
        });
  }

  /**
   * Defines an action verb.
   *
   * <pre>
   * verb name = action, anotherAction, ...;
   * </pre>
   */
  Rule ActionDef() {
    final Var<ActionDefinition> var = new Var<ActionDefinition>(new ActionDefinition());
    return Sequence(
        "action",
        NodeName(ActionDefinition.class, var),
        "=",
        OneOrListOf(Ident(SecurityAction.class), Ident.class, ","),
        ";",
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            ActionDefinition x = var.get();
            x.setActions(popIdentList(SecurityAction.class));
            push(x);
            return true;
          }
        });
  }

  /**
   * An inheritable block which holds zero or more {@link AllowRule}. Also supports a single-line
   * version.
   *
   * <pre>
   * allow [ only ] [ inherit somePropertyName ] {
   *   aclRule;
   *   ...;
   *   aclRule;
   * }
   * </pre>
   *
   * <pre>
   * allow [ inherit somePropertyName ] [ aclRule ];
   * </pre>
   */
  Rule Allow() {
    final Var<AllowBlock> var = new Var<AllowBlock>(new AllowBlock());
    final Var<Boolean> only = new Var<Boolean>(false);
    return Sequence(
        "allow",
        Optional("only", ACTION(only.set(true))),
        MaybeInherit(Property.class, var),
        ZeroOneOrBlock(AclRule(), AllowRule.class),
        new Action<Object>() {
          @Override
          @SuppressWarnings("unchecked")
          public boolean run(Context<Object> ctx) {
            AllowBlock x = var.get();
            x.setAclRules((List<AllowRule>) pop());
            x.setOnly(only.get());
            push(x);
            return true;
          }
        });
  }

  /**
   * Implement single-line and multi-line comments.
   */
  @DontLabel
  Rule Comment() {
    return FirstOf(
        Sequence(
            String("//"),
            ZeroOrMore(NoneOf(new char[] { '\n', '\r' })),
            WS()),
        Sequence(
            String("/*"),
            ZeroOrMore(
            FirstOf(
                NoneOf(WILDCARD),
                Sequence(String(WILDCARD), NoneOf("/")))),
            String("*/"),
            WS()));
  }

  @Cached
  <R, E> Rule CompoundIdent(final Class<R> referentType, final Class<E> elementType) {
    @SuppressWarnings("rawtypes")
    final Var<List<Ident>> parts = new Var<List<Ident>>(new ArrayList<Ident>());
    return Sequence(
        Ident(elementType),
        popToList(Ident.class, parts),
        ZeroOrMore(".", Ident(elementType), popToList(Ident.class, parts)),
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            @SuppressWarnings({ "unchecked", "rawtypes" })
            List<Ident<?>> list = (List) parts.get();
            if (list.size() == 1) {
              push(new Ident<R>(referentType, list.get(0).getSimpleName()));
            } else {
              push(new Ident<R>(referentType, list));
            }
            return true;
          }
        });
  }

  /**
   * An inheritable group which holds zero or more {@link GroupDefinition}.
   *
   * <pre>
   * group [ inherit somePropertyName ] {
   *   groupDefinition;
   *   ...
   *   groupDefinition;
   * }
   * </pre>
   *
   * <pre>
   * group [ inherit somePropertyName ] [ groupDefinition ];
   * </pre>
   */
  Rule Group() {
    final Var<GroupBlock> var = new Var<GroupBlock>(new GroupBlock());
    return Sequence(
        "group",
        MaybeInherit(Property.class, var),
        ZeroOneOrBlock(GroupDefinition(), GroupDefinition.class),
        new Action<Object>() {
          @Override
          @SuppressWarnings("unchecked")
          public boolean run(Context<Object> ctx) {
            GroupBlock x = var.get();
            x.setDefinitions((List<GroupDefinition>) pop());
            push(x);
            return true;
          }
        });
  }

  /**
   * Associates one or more {@link PropertyPath property paths} with a group name.
   *
   * <pre>
   * groupName = some.property.path [ , another.path ]
   * groupName empty
   * </pre>
   */
  Rule GroupDefinition() {
    final Var<GroupDefinition> var = new Var<GroupDefinition>(new GroupDefinition());
    return Sequence(
        NodeName(SecurityGroup.class, var),
        FirstOf(
            Sequence(
                "empty",
                ACTION(push(new ArrayList<Object>()))
            ),
            Sequence(
                "=",
                OneOrListOf(CompoundIdent(PropertyPath.class, Property.class), Ident.class, ","))),
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            GroupDefinition x = var.get();
            x.setPaths(popIdentList(PropertyPath.class));
            push(x);
            return true;
          }
        });
  }

  /**
   * A lazy, by-name reference to another object. The syntax for these identifiers uses the same
   * rules as Java identifiers.
   */
  @Cached
  <R> Rule Ident(Class<R> referentType) {
    StringVar x = new StringVar();
    return Sequence(
        ANY,
        ACTION(Character.isJavaIdentifierStart(matchedChar()) && x.append(matchedChar())),
        ZeroOrMore(
            ANY,
            ACTION(Character.isJavaIdentifierPart(matchedChar()) && x.append(matchedChar()))
        ),
        ACTION(push(new Ident<R>(referentType, x.get()))),
        WS());
  }

  /**
   * Support rule to allow an optional {@code inherit ident} clause. If the inherit clause is
   * present, an {@link Ident} will be created and passed to
   * {@link HasInheritFrom#setInheritFrom(Ident) target}.
   */
  @Cached
  <P extends HasInheritFrom<R>, R> Rule MaybeInherit(final Class<R> clazz, final Var<P> target) {
    return Optional(
        "inherit",
        Ident(clazz),
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            target.get().setInheritFrom(popIdent(clazz));
            return true;
          }
        });
  }

  /**
   * Support rule to parse a single {@link Ident} and set {@code x}'s {@link HasName#setName(Ident)
   * name}.
   */
  @Cached
  <P extends HasName<R>, R> Rule NodeName(final Class<R> clazz, final Var<P> x) {
    return Sequence(
        FirstOf(
            Ident(clazz),
            ACTION(push(new Ident<R>(clazz, "$" + getContext().getPosition().line)))
        ),
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            Ident<R> ident = popIdent(clazz);
            P node = x.get();
            node.setName(ident);
            if (ident.getReferentType().isInstance(node)) {
              ident.setReferent(ident.getReferentType().cast(node));
            }
            return true;
          }
        });
  }

  /**
   * A hack when generics get in the way.
   */
  @Cached
  @SuppressWarnings({ "unchecked", "rawtypes" })
  Rule NodeNameRaw(Class clazz, final Var x) {
    return NodeName(clazz, x);
  }

  /**
   * Matches at least one instance of {@code r}, which must push exactly one value onto the stack.
   * The matched values will be added to a list, which will be placed on the stack.
   */
  @Cached
  <T extends PolicyNode> Rule OneOrListOf(Rule r, Class<T> clazz, String separator) {
    Var<List<T>> var = new Var<List<T>>(new ArrayList<T>());
    return Sequence(
        r,
        ACTION(popToList(clazz, var)),
        ZeroOrMore(
            separator,
            r,
            ACTION(popToList(clazz, var))),
        ACTION(clazz == null || push(var.get())));
  }

  Rule PackagePolicy() {
    Var<PackagePolicy> x = new Var<PackagePolicy>(new PackagePolicy());
    return Sequence(
        "package",
        NodeName(PackagePolicy.class, x),
        "{",
        PolicyBlock(x),
        "}");
  }

  /**
   * Main contents blocks.
   */
  @Cached
  Rule PolicyBlock(Var<? extends PolicyBlock> x) {
    return Sequence(
        WS(),
        ZeroOrMore(FirstOf(
            Sequence(Allow(), ACTION(x.get().getAllows().add((AllowBlock) pop()))),
            Sequence(PackagePolicy(), ACTION(x.get().getPackagePolicies()
                .add((PackagePolicy) pop()))),
            Sequence(TypePolicy(), ACTION(x.get().getTypePolicies().add((TypePolicy) pop()))),
            Sequence(ActionDef(), ACTION(x.get().getVerbs().add((ActionDefinition) pop())))
        )),
        ACTION(push(x.get())));
  }

  <T> Ident<T> popIdent(Class<T> clazz) {
    return ((Ident<?>) pop()).cast(clazz);
  }

  <T> List<Ident<T>> popIdentList(Class<T> clazz) {
    @SuppressWarnings("unchecked")
    List<Ident<T>> toReturn = (List<Ident<T>>) pop();
    for (Ident<T> ident : toReturn) {
      ident.cast(clazz);
    }
    return toReturn;
  }

  /**
   * Utility method to pop a value from the value stack, cast it to {@code clazz} and add it to
   * {@code list}. if {@code clazz} is {@code null}, this method is a no-op.
   */
  <T> boolean popToList(Class<T> clazz, Var<List<T>> list) {
    if (clazz != null) {
      list.get().add(clazz.cast(pop()));
    }
    return true;
  }

  /**
   * One or more property-name references.
   *
   * <pre>
   * property ident [ , ident [ ... ] ];
   * </pre>
   */
  Rule PropertyList() {
    return Sequence(
        "property",
        OneOrListOf(Ident(Property.class), Ident.class, ","),
        ";",
        new Action<Object>() {
          @Override
          public boolean run(Context<Object> ctx) {
            PropertyList x = new PropertyList();
            x.setPropertyNames(popIdentList(Property.class));
            push(x);
            return true;
          }
        });
  }

  /**
   * An named block that contains {@link AllowBlock} and {@link PropertyList} nodes.
   *
   * <pre>
   * policy name {
   *   property a, b, c;
   *   allow {
   *     ...
   *   }
   * }
   * </pre>
   */
  Rule PropertyPolicy() {
    Var<PropertyPolicy> x = new Var<PropertyPolicy>(new PropertyPolicy());
    return Sequence(
        "policy",
        NodeName(PropertyPolicy.class, x),
        "{",
        ZeroOrMore(FirstOf(
            Sequence(
                Allow(),
                ACTION(x.get().getAllows().add((AllowBlock) pop()))),
            Sequence(
                PropertyList(),
                ACTION(x.get().getPropertyLists().add((PropertyList) pop())))
        )),
        "}",
        ACTION(push(x.get())));
  }

  /**
   * Defines policies for an entity type.
   *
   * <pre>
   * type typeName {
   *   allow { ... }
   *   group { ... }
   *   policy { ... }
   *   verb ...;
   * }
   * </pre>
   */
  Rule TypePolicy() {
    final Var<TypePolicy> x = new Var<TypePolicy>(new TypePolicy());
    return Sequence(
        "type",
        NodeNameRaw(Class.class, x),
        "{",
        ZeroOrMore(FirstOf(
            Sequence(Allow(), ACTION(x.get().getAllows().add((AllowBlock) pop()))),
            Sequence(Group(), ACTION(x.get().getGroups().add((GroupBlock) pop()))),
            Sequence(PropertyPolicy(), ACTION(x.get().getPolicies().add((PropertyPolicy) pop()))),
            Sequence(ActionDef(), ACTION(x.get().getVerbs().add((ActionDefinition) pop()))))),
        "}",
        ACTION(push(x.get())));
  }

  /**
   * A reference to an action. This may be a single {@link Ident} or a wildcard, possibly qualified
   * by a verb name.
   *
   * <pre>
   * *
   * *.*
   * Foo.bar
   * Foo.*
   * bar
   * </pre>
   */
  Rule VerbActionOrWildcard() {
    return FirstOf(
        Sequence(
            WILDCARD,
            Optional(".", WILDCARD),
            ACTION(push(new Ident<SecurityAction>(SecurityAction.class, "*")))),
        Sequence(
            Ident(ActionDefinition.class),
            ".",
            WildcardOrIdent(SecurityAction.class),
            ACTION(swap()
              && push(new Ident<SecurityAction>(SecurityAction.class,
                  popIdent(ActionDefinition.class),
                  popIdent(SecurityAction.class))))),
        Ident(SecurityAction.class)

    );
  }

  /**
   * A single identifier or a wildcard.
   */
  <R> Rule WildcardOrIdent(Class<R> referentType) {
    return FirstOf(
        Sequence(WILDCARD, ACTION(push(new Ident<R>(referentType, WILDCARD)))),
        Ident(referentType));
  }

  /**
   * Matches whitespace or {@code #} comments.
   */
  @Label("whitespace")
  Rule WS() {
    return Sequence(
        ZeroOrMore(AnyOf(new char[] { ' ', '\n', '\r', '\t' })),
        Optional(Comment()));
  }

  /**
   * A utility rule to allow another rule to be specified zero, one, or any number of times.
   *
   * <pre>
   * {
   *   rule;
   *   rule;
   *   ...
   * }
   *
   * rule;
   *
   * ;
   * </pre>
   */
  @Cached
  <T> Rule ZeroOneOrBlock(Rule r, Class<T> clazz) {
    Var<List<T>> var = new Var<List<T>>(new ArrayList<T>());
    return Sequence(
        FirstOf(
            Sequence(
                "{",
                ZeroOrMore(r, ";", ACTION(popToList(clazz, var))),
                "}"),
            Sequence(
                r,
                ";",
                ACTION(popToList(clazz, var))),
            ";"),
        ACTION(clazz == null || push(var.get())));
  }

}
TOP

Related Classes of com.getperka.flatpack.policy.PolicyParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.