rule

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2019 License: Apache-2.0 Imports: 7 Imported by: 0

README

Policy rules DSL

A DSL is proposed which (a) determines which policies are applicable to a submission, (b) determines which repositories are relevant to the policies, and (c) contains information which the policy service can use to sort repositories into.

This DSL contains a $schema field that specifies the schema of the DSL being used (it almost certainly will evolve), and an include-policies field which has a list of rules for including policies. The DSL file is a configuration file provided to the policy service.

An example of JHU's current policy rules is as follows:

{
    "$schema": "https://oa-pass.github.io/pass-policy-service/schemas/policy_config_1.0.json",
    "policy-rules": [
        {
            "description": "Must deposit to one of the repositories indicated by primary funder",
            "policy-id": "${submission.grants.primaryFunder.policy}",
            "type": "funder",
            "repositories": [
                {
                    "repository-id": "${policy.repositories}"
                }
            ]
        },
        {
            "description": "Must deposit to one of the repositories indicated by direct funder",
            "policy-id": "${submission.grants.directFunder.policy}",
            "type": "funder",
            "repositories": [
                {
                    "repository-id": "${policy.repositories}"
                }
            ]
        },
        {
            "description": "Members of the JHU community must deposit into JScholarship, or some other repository.",
            "policy-id": "/policies/5e/2e/16/92/5e2e1692-c128-4fb4-b1a0-95c0e355defd",
            "type": "institution",
            "conditions": [
                {
                    "endsWith": {
                        "@johnshopkins.edu": "${header.Ajp_eppn}"
                    }
                }
            ],
            "repositories": [
                {
                    "repository-id": "/repositories/41/96/0a/92/41960a92-d3f8-4616-86a6-9e9cadc1a269",
                    "selected": true
                },
                {
                    "repository-id": "*"
                }
            ]
        }
    ]
}

In this example:

  • All policies for funders of grants associated with the submission (direct or primary) will be included
    • As far as repositories are concerned, all repositories listed under of a role will be grouped into a "one-of" bucket. For example, if ${policy.repositories.id} expands to two repositories (by way of a policy linking to two repositories), then both repositories are in the same one-of bucket. The policy service will be be smart enough to promote one-of groups containing only a single item to the required list, or demoting repositories in a one-of bucker to optional if all any other repository in the one-of bucket is required by a different policy. The jScholarship policy will be added only for users for whom the request Eppn header contains the substring @johnshopkins.edu. Effectively, this means everybody from JHU will get this policy added.
    • As far as its repository is concerned, it is required by default, but becomes optional if any other repository is required.

The top-level fields in the DSL are:

  • $schema: Required. It must point to a known JSON schema for the policy service DSL
  • policy-rules: Contains a list of policy inclusion rules

Policy inclusion rules are JSON objects containing the following fields:

  • description: A human readable description of the rule. Optional.
  • policy-id: a string containing a a single policy URI, or a variable substitution resulting in one or more policy URIs
    • In the case of a variable substitution resulting in many URIs, it is equivalent to creating multiple policy rules, each one containing a single policy-id from that list.
      repositories: contains a list of repository description JSON objects, specifying which repositories satisfy the given policy.
  • condition: Optional. JSON object describing a condition where the policy is included only if the condition evaluates to true. If this field is not present, it is presumed that inclusion of the policy is unconditional. See the schema for more details, but conditions include:
    • equals: true if two strings are equal
    • endsWith: true if a string ends with another
    • contains: true if a string contains another as a substring
    • anyOf: true if any of the given list of conditions are true
    • noneOf: true if none of the given list of conditions are true

Repositories are JSON objects with the following fields:

  • repository-id: the URI of the repository resource in Fedora, or * to mean "any".
  • selected: (optional boolean) if true, the repository will be indicated as "selected" by default in the result to Ember.

Variable substitution

Any key or value of the form ${variable} is a variable.

At time of rule evaluation, the following variables are available:

  • submission: the submission object
  • header: the list of Http headers in the request, including all shibboleth headers.

Variables can use dot notation, which means different things in context

  • For headers, header.NAME means the value of the header NAME
  • For repository objects, object.FIELD means the value of the field FIELD
    • If FIELD is a uri pointing to another repository object, then ${object.FIELD} is itself a repository object
    • If FIELD is a string, then ${object.FIELD} is a string. Further dots will attempt to treat that string as a JSON blob (e.g. ${submission.metadata.author})
    • if FIELD is a list of URIs of objects, then ${object.FIELD} is a list of objects.

A graph of objects can be navigated via dot notation, e.g. ${submission.grants.primaryFunder} is a list of Funder objects. ${submission.grants.primaryFunder.id} is a list of URIs.

In the case of policy rules, where a policy-id is a list of URIs, the variables available to repositories block are based of one matching value. You can imagine this translating to N rules (each one with a policy URI from the list), with each repository block inheriting the values of the policy rule that contains it. For example:

 {
        "description": "Must deposit to one of the repositories indicated by primary funder",
        "policy-id": "${submission.grants.primaryFunder.policy}",
        "repositories": [
            {
                "repository-id": "${policy.repositories}",
            }
        ]
}

In this case for each matching policy, the value of ${submission.grants.primaryFunder.policy} is fixed inside the repositories block. That is to say, submission is the given submission object, (as it always is), ${submission.grants} is the single grant object used in producing the ${submission.grants.primaryFunder.policy} value for this particular policy, etc.

As a shortcut, ${policy} is an alias for ${submission.grants.primaryFunder.policy}, and is a repository object. Any such dot segment can function as an alias, as long as it is unambiguous

Documentation

Index

Constants

View Source
const (
	SubmissionVariable = "submission" // ${submission}
	HeaderVariable     = "header"     // ${header}
)

Known variable names

Variables

This section is empty.

Functions

func IsVariable

func IsVariable(text string) bool

IsVariable determines if a string is a variable (e.g. of the form '${foo.bar.baz}')

Types

type Condition

type Condition map[string]interface{}

Condition in the policy rules DSL is a json object that determines whether a policy applies or not. It is generally of the form

{
   "anyOf": [
      {"equals":{"one": "two"}},
      {"endsWith":{"one": "gone"}}
   ]
}

func (Condition) Apply

func (c Condition) Apply(variables VariableResolver) (bool, error)

Apply evaluates a condition using the given variable resolver.

type Context

type Context struct {
	SubmissionURI string
	Headers       map[string][]string
	PassClient    PassEntityFetcher
	// contains filtered or unexported fields
}

Context establishes a rule evaluation/resolution context.

func (*Context) Pin

func (c *Context) Pin(variable, value string) VariablePinner

func (*Context) Resolve

func (c *Context) Resolve(vari string) ([]string, error)

Resolve resolves a variable of the form ${a.b.c.d}, returning a list of strings.

type DSL

type DSL struct {
	Schema   string   `json:"$schema"`
	Policies []Policy `json:"policy-rules"`
}

DSL encapsulates to a policy rules document

func Validate

func Validate(rulesDoc []byte) (*DSL, error)

Validate validates a serialized policy rules document with respect to the implementation's expected JSON schema, and attempts to parse it.

func (*DSL) Resolve added in v0.1.0

func (d *DSL) Resolve(variables VariablePinner) ([]Policy, error)

type PassEntityFetcher

type PassEntityFetcher interface {
	FetchEntity(url string, entityPointer interface{}) error
}

PassEntityFetcher retrieves the JSON-LD content at the given url, and un-marshals it into the provided struct.

entityPointer is expected to be a pointer to a struct or map (i.e. anything encoding/json can un-marshal into). An error will be returned otherwise.

type Policy

type Policy struct {
	ID           string       `json:"policy-id"`
	Description  string       `json:"description"`
	Type         string       `json:"type"`
	Repositories []Repository `json:"repositories"`
	Conditions   []Condition  `json:"conditions"`
}

Policy encapsulates a policy rule.

func (Policy) Resolve

func (p Policy) Resolve(variables VariablePinner) (policies []Policy, err error)

Resolve interpolates any variables in a policy. if the policy ID resolves to a list, it returns a list of resolved policies, each one with an ID from that list.

type PolicyResolver added in v0.1.0

type PolicyResolver interface {
	Resolve(variables VariablePinner) ([]Policy, error)
}

type Repository

type Repository struct {
	ID       string `json:"repository-id"`
	Selected bool   `json:"selected"`
}

func (Repository) Resolve

func (r Repository) Resolve(variables VariableResolver) ([]Repository, error)

type Requirements added in v0.1.0

type Requirements struct {
	Required []Repository   `json:"required"`
	OneOf    [][]Repository `json:"one-of"`
	Optional []Repository   `json:"optional"`
}

Requirements encapsulates deposit requirements by sorting repositories into "required", 'oneOf', and 'optional' buckets

func AnalyzeRequirements added in v0.1.0

func AnalyzeRequirements(policies []Policy) *Requirements

AnalyzeRequirements analyzes a list of policies, and returns repository requirements

func (*Requirements) Keep added in v0.1.0

func (r *Requirements) Keep(keep []Repository) *Requirements

Keep removes any repositories that are not in the keep list) Such repositories are presumed to have been deposited to by some means outside of pass. For example, for requirements OneOf: {a, b} with keep {a}, the result is Optional {a}

func (*Requirements) TranslateURIs added in v0.1.0

func (r *Requirements) TranslateURIs(replace func(string) (string, bool)) *Requirements

TranslateURIs applies the given function to all URIs (mutating them), and returns itself

type VariablePinner

type VariablePinner interface {
	VariableResolver
	Pin(variable, value string) VariablePinner // return a VariablePinner that pins a variable to the given value
}

VariablePinner is a variable resolver that can pin variables to specific values

type VariableResolver

type VariableResolver interface {
	Resolve(varString string) ([]string, error)
}

VariableResolver resolves a variable (a string of form "${foo.bar.baz}"), and returns a list of values

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL