diff --git a/cedar-example-use-cases/sales_orgs/README.md b/cedar-example-use-cases/sales_orgs/README.md
index 0a952ec1..e2d319fa 100644
--- a/cedar-example-use-cases/sales_orgs/README.md
+++ b/cedar-example-use-cases/sales_orgs/README.md
@@ -1,10 +1,12 @@
-# Sales orgs policies
+# Sales org policies
-ABC has a contracted sales force, and sales folk have access to resources based on their role and the type of resource.
+In this example, ABC company has a contracted sales force, and sales folk have access to resources based on their role and the type of resource.
+
+For more information about exploring this example using the Cedar CLI, see [Cedar Example Use Cases](https://github.com/cedar-policy/cedar-examples/tree/release/4.0.x/cedar-example-use-cases).
## Use-case
-ABC has one principal type, which is a `User`. Users are distinguished by their `job` (an attribute), which is an enumeration. The job can either be _internal_, or some other _external_ type, which includes _distributor_ and _customer_. A customer is assigned to a particular distributor if it shares the distributor's customer ID. Users can be a member of zero or more `Market`s.
+ABC has one principal type, which is a `User`. Users are distinguished by their `job` (an attribute), which is an enumeration (enum). The job can be _internal_, _distributor_, _customer_, or _other_. A customer is assigned to a particular distributor if it shares the distributor's customer ID. Users can be a member of zero or more `Market`s.
ABC has two protected resources, `Presentation` and `Template` (unfortunate name clash). The creator of a resource is its `owner`, who is permitted to carry out any action on the resource. Other users are granted _direct_ access to a resource by being added to an ACL as one of two roles, _viewer_ or _editor_. A `User` can also be granted access to a `Template` via a `Market` the user is a member of, again as either a viewer or editor. In other words, a `Template` has viewer and editor users, directly, but also viewer and editor markets, which grant access to the users within them. The permissions gained by being a viewer/editor depend on whether the `User` in question has an internal or external job.
@@ -12,17 +14,654 @@ There are some rules limiting how access can be shared. Only _distributor_ `User
## Approaches
-The `static/` directory contains policies and a schema for that encodes the _viewer_ and _editor_ relations on resources, both for presentations and templates, as `Set`-typed attributes `viewers` and `editors` on the resources.
+The `static/` directory contains policies and a schema for the _viewer_ and _editor_ relations on resources, both for presentations and templates, as `Set`-typed attributes `viewers` and `editors` on the resources.
The `templated/` directory uses Cedar templates instead. We drop the `viewers` and `editors` attributes and follow a simple pattern: Whenever you would add a `User` to _resource_`.viewers`, instead link a template with `?principal` as the user and `?resource` as the viewer. Do likewise for editors. And, do similarly with viewer/editor status of `Market`s on ABC (not Cedar) `Template` resources.
-These are the only differences in the encodings.
+These are the only differences in the approaches.
+
+Expand the approach below for more details.
+
+
+
+Static
+
+### Entities
+
+#### `Job`
+An attribute that defines the user's job.
+
+#### `User`
+Represents a user that has access to resources.
+
+#### `Market`
+A way of grouping users.
+
+#### `Presentation`
+Represents a sales presentation resource that is owned by the `User` who created it.
+
+#### `Template`
+Represents a presentation template resource. A `User` gets access to a `Template` by being a member of the `Market` the `Template` was created in.
+
+### Actions
+There are unique actions for both presentations and templates.
+
+
+
+Presentations
+
+#### `InternalPrezViewActions`
+An action group that includes the following actions:
+* `viewPresentation`
+* `removeSelfAccessFromPresentation`
+* `duplicatePresentation`
+
+#### `ExternalPrezViewActions`
+An action group that includes the following actions:
+* `viewPresentation`
+* `removeSelfAccessFromPresentation`
+
+#### `PrezEditActions`
+An action group that includes the following actions:
+* `viewPresentation`
+* `removeSelfAccessFromPresentation`
+* `duplicatePresentation`
+* `editPresentation`
+* `grantViewAccessToPresentation`
+* `grantEditAccessToPresentation`
+
+
+
+
+
+Templates
+
+#### `InternalTemplateViewActions`
+An action group that includes the following actions:
+* `viewTemplate`
+* `duplicateTemplate`
+* `removeSelfAccessFromTemplate`
+
+#### `MarketTemplateViewActions`
+An action group that includes the following actions:
+* `viewTemplate`
+* `duplicateTemplate`
+
+#### `TemplateEditActions`
+An action group that includes the following actions:
+* `viewTemplate`
+* `duplicateTemplate`
+* `removeSelfAccessFromTemplate`
+* `editTemplate`
+* `removeOthersAccessToTemplate`
+* `grantViewAccessToTemplate`
+* `grantEditAccessToTemplate`
+
+
+
+### Context
+Presentations and templates have unique context.
+
+
+
+Presentations
+
+#### `target`
+A user that is the target of the action. For example, getting view or edit access to a presentation.
+
+
+
+
+
+Templates
+
+The template context helps grant view or edit access to a template based on who the user is and what market they are in.
+
+#### `targetMarket?`
+A market the template and user are in.
+
+#### `targetUser?`
+A user that is the target of the action.
+
+
+
+### Schema
+
+#### Entity types
+* `Job`
+* `User`:
+ * memberOfTypes: `Market`
+ * Attributes:
+ * `job`: a `Job`
+ * `customerId`: a String
+* `Market`
+* `Presentation`:
+ * Attributes
+ * `owner`: a `User`
+ * `viewers`: a set of `User` entities
+ * `editors`: a set of `User` entities
+* `Template`:
+ * Attributes
+ * `owner`: a `User`
+ * `viewers`: a set of `User` entities
+ * `editors`: a set of `User` entities
+ * `viewerMarkets`: a set of `Market` entities
+ * `editorMarkets`: a set of `Market` entities
+
+#### Action types
+
+
+Presentations
+
+* `InternalPrezViewActions`
+* `ExternalPrezViewActions`
+* `PrezEditActions`
+* `viewPresentation`, `removeSelfAccessFromPresentation`
+ * `memberOf`: `InternalPrezViewActions`, `ExternalPrezViewActions`, `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+* `duplicatePresentation`,
+ * `memberOf`: `InternalPrezViewActions`, `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+* `editPresentation`,
+ * `memberOf`: `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+* `grantViewAccessToPresentation`, `grantEditAccessToPresentation`
+ * `memberOf`: `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+ * context: `target`
+
+
+
+
+
+Templates
+
+* `InternalTemplateViewActions`
+* `MarketTemplateViewActions`
+* `TemplateEditActions`
+* `viewTemplate`, `duplicateTemplate`
+ * `memberOf`: `InternalTemplateViewActions`, `MarketTemplateViewActions`, `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+* `removeSelfAccessFromTemplate`,
+ * `memberOf`: `InternalTemplateViewActions`, `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+* `editTemplate`, `removeOthersAccessToTemplate`
+ * `memberOf`: `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+* `grantViewAccessToTemplate`, `grantEditAccessToTemplate`
+ * `memberOf`: `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+ * context: `targetMarket?`, `targetUser?`
+
+
+
+### Policies
+
+
+
+Presentations
+
+```
+@id("external-prez-view")
+permit(
+ principal,
+ action in Action::"ExternalPrezViewActions",
+ resource)
+when {
+ principal in resource.viewers
+};
+
+@id("internal-prez-view")
+permit(
+ principal,
+ action in Action::"InternalPrezViewActions",
+ resource)
+when {
+ principal.job == Job::"internal" &&
+ principal in resource.viewers
+};
+
+// Authorizes edit actions generally, but these limited with forbid policies
+@id("prez-edit")
+permit(
+ principal,
+ action in Action::"PrezEditActions",
+ resource)
+when {
+ resource.owner == principal ||
+ principal in resource.editors
+};
+
+// only permit sharing to non-customers
+@id("limit-prez-view-customer")
+forbid(
+ principal,
+ action == Action::"grantViewAccessToPresentation",
+ resource)
+unless {
+ context.target.job != Job::"customer" ||
+ (principal.job == Job::"distributor" &&
+ principal.customerId == context.target.customerId)
+};
+
+// forbid sharing editor access to non-internal users
+@id("limit-prez-edit-to-internal")
+forbid(
+ principal,
+ action == Action::"grantEditAccessToPresentation",
+ resource)
+when {
+ context.target.job != Job::"internal"
+};
+```
+
+
+
+
+
+Templates
+
+```
+@id("market-template-view")
+permit(
+ principal,
+ action in Action::"MarketTemplateViewActions",
+ resource)
+when {
+ principal in resource.viewerMarkets
+};
+
+@id("internal-template-view")
+permit(
+ principal,
+ action in Action::"InternalTemplateViewActions",
+ resource)
+when {
+ principal.job == Job::"internal" && principal in resource.viewers
+};
+
+// Authorizes edit actions generally, but these limited with forbid policies
+@id("template-edit")
+permit(
+ principal,
+ action in Action::"TemplateEditActions",
+ resource)
+when {
+ resource.owner == principal ||
+ principal in resource.editors ||
+ principal in resource.editorMarkets
+};
+
+// only permit sharing by internal users to non-customers
+@id("limit-template-grant-view")
+forbid(
+ principal,
+ action == Action::"grantViewAccessToTemplate",
+ resource)
+when {
+ context has targetUser && context.targetUser.job == Job::"customer" &&
+ (principal.job != Job::"distributor" ||
+ principal.customerId != context.targetUser.customerId)
+};
+
+// forbid sharing editor access to non-internal users
+@id("limit-template-grant-edit-internal")
+forbid(
+ principal,
+ action == Action::"grantEditAccessToTemplate",
+ resource)
+when {
+ context has targetUser && context.targetUser.job != Job::"internal"
+ // context.targetMarket always Ok, no matter the market
+};
+```
+
+
+
+
+
+
+
+Templated
+
+### Entities
+
+#### `Job`
+An attribute that defines the user's job.
+
+#### `User`
+Represents a user that has access to resources.
+
+#### `Market`
+A way of grouping users.
+
+#### `Presentation`
+Represents a sales presentation resource that is owned by the `User` who created it.
+
+#### `Template`
+Represents a presentation template resource. A `User` gets access to a `Template` by being a member of the `Market` the `Template` was created in.
+
+### Actions
+
+There are unique actions for both presentations and templates.
+
+
+
+Presentations
+
+#### `InternalPrezViewActions`
+An action group that includes the following actions:
+* `viewPresentation`
+* `removeSelfAccessFromPresentation`
+* `duplicatePresentation`
+
+#### `ExternalPrezViewActions`
+An action group that includes the following actions:
+* `viewPresentation`
+* `removeSelfAccessFromPresentation`
+
+#### `PrezEditActions`
+An action group that includes the following actions:
+* `viewPresentation`
+* `removeSelfAccessFromPresentation`
+* `duplicatePresentation`
+* `editPresentation`
+* `grantViewAccessToPresentation`
+* `grantEditAccessToPresentation`
+
+
+
+
+
+Templates
+
+#### `InternalTemplateViewActions`
+An action group that includes the following actions:
+* `viewTemplate`
+* `duplicateTemplate`
+* `removeSelfAccessFromTemplate`
+
+#### `MarketTemplateViewActions`
+An action group that includes the following actions:
+* `viewTemplate`
+* `duplicateTemplate`
+
+#### `TemplateEditActions`
+An action group that includes the following actions:
+* `viewTemplate`
+* `duplicateTemplate`
+* `removeSelfAccessFromTemplate`
+* `editTemplate`
+* `removeOthersAccessToTemplate`
+* `grantViewAccessToTemplate`
+* `grantEditAccessToTemplate`
+
+
+
+### Context
+Presentations and templates have unique context.
+
+
+
+Presentations
+
+#### `target`
+A user that is the target of the action. For example, getting view or edit access to a presentation.
+
+
+
+
+
+Templates
+
+The template context helps grant view or edit access to a template based on who the user is and what market they are in.
+
+#### `targetMarket?`
+A market the template and user are in.
+
+#### `targetUser?`
+A user that is the target of the action.
+
+
+
+### Schema
+
+#### Entity types
+* `Job`
+* `User`:
+ * memberOfTypes: `Market`
+ * Attributes:
+ * `job`: a `Job`
+ * `customerId`: a String
+* `Market`
+* `Presentation`:
+ * Attributes
+ * `owner`: a `User`
+* `Template`:
+ * Attributes
+ * `owner`: a `User`
+
+#### Action types
+
+
+Presentations
+
+* `InternalPrezViewActions`
+* `ExternalPrezViewActions`
+* `PrezEditActions`
+* `viewPresentation`, `removeSelfAccessFromPresentation`
+ * `memberOf`: `InternalPrezViewActions`, `ExternalPrezViewActions`, `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+* `duplicatePresentation`,
+ * `memberOf`: `InternalPrezViewActions`, `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+* `editPresentation`,
+ * `memberOf`: `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+* `grantViewAccessToPresentation`, `grantEditAccessToPresentation`
+ * `memberOf`: `PrezEditActions`
+ * principals: `User`
+ * resources: `Presentation`
+ * context: `target`
+
+
+
+
+
+Templates
+
+* `InternalTemplateViewActions`
+* `MarketTemplateViewActions`
+* `TemplateEditActions`
+* `viewTemplate`, `duplicateTemplate`
+ * `memberOf`: `InternalTemplateViewActions`, `MarketTemplateViewActions`, `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+* `removeSelfAccessFromTemplate`,
+ * `memberOf`: `InternalTemplateViewActions`, `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+* `editTemplate`, `removeOthersAccessToTemplate`
+ * `memberOf`: `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+* `grantViewAccessToTemplate`, `grantEditAccessToTemplate`
+ * `memberOf`: `TemplateEditActions`
+ * principals: `User`
+ * resources: `Template`
+ * context: `targetMarket?`, `targetUser?`
+
+
+
+### Policies
+
+
+
+Presentations
+
+```
+// Here, ?principal is a group of users allowed to view ?resource, i.e., resource.viewers above
+@createPolicyWhen("Create a template linked policy
+ when a external user is added to a prez as viewer")
+@id("external-prez-view")
+permit(principal == ?principal,
+ action in Action::"ExternalPrezViewActions",
+ resource == ?resource)
+when {
+ principal.job != Job::"internal"
+};
+
+// Here, ?principal is a group of users allowed to view ?resource
+@createPolicyWhen("Create a template linked policy
+ when a internal user is added to a prez as viewer")
+@id("internal-prez-view")
+permit(principal == ?principal,
+ action in Action::"InternalPrezViewActions",
+ resource == ?resource)
+when {
+ principal.job == Job::"internal"
+};
+
+// Here, ?principal is a group of users allowed to edit ?resource, i.e., resource.editors above
+@createPolicyWhen("Create a template linked policy
+ when a user is added to a prez as editor")
+@id("template-edit for non-owner")
+permit(
+ principal == ?principal,
+ action in Action::"PrezEditActions",
+ resource == ?resource);
+
+// Presentation owners always allowed to do what they want
+@id("template-edit for owner")
+permit(
+ principal,
+ action in Action::"PrezEditActions",
+ resource)
+when {
+ resource.owner == principal
+};
+
+// only permit sharing to non-customers
+@id("limit-prez-view-customer")
+forbid(
+ principal,
+ action == Action::"grantViewAccessToPresentation",
+ resource)
+unless {
+ context.target.job != Job::"customer" ||
+ (principal.job == Job::"distributor" &&
+ principal.customerId == context.target.customerId)
+};
+
+// forbid sharing editor access to non-internal users
+@id("limit-prez-edit-to-internal")
+forbid(
+ principal,
+ action == Action::"grantEditAccessToPresentation",
+ resource)
+when {
+ context.target.job != Job::"internal"
+};
+```
+
+
+
+
+
+Templates
+
+```
+@createPolicyWhen("Create a template linked policy
+ when a user is added to a template as market viewer")
+@id("market-template-view")
+permit(
+ principal == ?principal,
+ action in Action::"MarketTemplateViewActions",
+ resource == ?resource)
+when {
+ principal.job != Job::"internal"
+};
+
+
+@createPolicyWhen("Create a template linked policy
+ when a internal user is added to a template as viewer")
+@id("internal-template-view")
+permit(
+ principal == ?principal,
+ action in Action::"InternalTemplateViewActions",
+ resource == ?resource)
+when {
+ principal.job == Job::"internal"
+};
+
+// Authorizes edit actions generally, but these limited with forbid policies
+@id("template-edit")
+@createPolicyWhen("Create a template linked policy
+ when a user is added to a template as editor")
+permit(
+ principal == ?principal,
+ action in Action::"TemplateEditActions",
+ resource == ?resource);
+
+// Permit owners to edit templates
+permit(
+ principal,
+ action in Action::"TemplateEditActions",
+ resource)
+when {
+ principal == resource.owner
+};
+
+// only permit sharing by internal users to non-customers
+@id("limit-template-grant-view")
+forbid(
+ principal,
+ action == Action::"grantViewAccessToTemplate",
+ resource)
+when {
+ context has targetUser && context.targetUser.job == Job::"customer" &&
+ (principal.job != Job::"distributor" ||
+ principal.customerId != context.targetUser.customerId)
+};
+
+// forbid sharing editor access to non-internal users
+@id("limit-template-grant-edit-internal")
+forbid(
+ principal,
+ action == Action::"grantEditAccessToTemplate",
+ resource)
+when {
+ context has targetUser && context.targetUser.job != Job::"internal"
+ // context.targetMarket always Ok, no matter the market
+};
+```
+
+
+
+
+## Tests
-## Examples
+We use the following entities for our tests:
+* There are 3 `User` entities, `User::"Alice"`, `User::"Bob"`, or `User::"Charlie"`.
+* There is one `Presentation` entity, `Presentation::"proposal"`.
-In each directory is a `run.sh` file that carries out three authorization examples asking whether a principal, either `User::"Alce"`, `User::"Bob"`, or `User::"Charlie"`, in that order, is allowed to `Action::"viewPresentation"` on `Presentation::"proposal"`. They all use the `entities.json` file, and the `templated/` policies also use the `linked` file that expresses two template links.
+Our three users have the following permissions (expressed in the entities in the static policies, and as links in the templated ones):
+* Alice is the owner of the presentation and therefore has full access to the presentation.
+* Bob has view permissions to the presentation.
+* Charlie has no permissions to the presentation.
-The answers are, respectively, `ALLOW`, `ALLOW`, and `DENY`, for the following reasons:
-* Alice is the owner of the presentation
-* Bob is an allowed viewer of the presentation. In the `static/` policies this fact is expressed in the `entities.json` file as part of the `Presentation::"proposal"` entity. In the `templated\` policies this fact is expressed via template links, expressed in the `linked` file
-* Charlie is neither or these things (nor is he an editor of the presentation)
+Here are some authz requests for these:
+* Alice views the presentation: ALLOW, since Alice owns the presentation.
+* Bob views the presentation: ALLOW, since Bob is an allowed viewer of the presentation. In the `static/` policies this fact is expressed in the `entities.json` file as part of the `Presentation::"proposal"` entity. In the `templated\` policies this fact is expressed via template links, expressed in the `linked` file.
+* Charlie views the presentarion, DENY, since Charlie doesn't have view or edit permissions for the presentation.