diff --git a/cedar-example-use-cases/run.sh b/cedar-example-use-cases/run.sh index 9edce43..282e7ff 100755 --- a/cedar-example-use-cases/run.sh +++ b/cedar-example-use-cases/run.sh @@ -20,6 +20,18 @@ validate "tags_n_roles" "policies.cedar" "policies.cedarschema" authorize "tags_n_roles" "policies.cedar" "entities.json" "policies.cedarschema" format "tags_n_roles" "policies.cedar" +# Sales org static +echo -e "\nTesting Sales Orgs (static)..." +validate "sales_orgs/static" "policies.cedar" "policies.cedarschema" +authorize "sales_orgs/static" "policies.cedar" "entities.json" "policies.cedarschema" +#format "sales_orgs/static" "policies.cedar" + +# Sales org templated +echo -e "\nTesting Sales Orgs (templated)..." +validate "sales_orgs/templated" "policies.cedar" "policies.cedarschema" "linked" +authorize "sales_orgs/templated" "policies.cedar" "entities.json" "policies.cedarschema" "linked" +#format "sales_orgs/templated" "policies.cedar" + # Hotel chains echo -e "\nTesting Hotels (static)..." validate "hotel_chains/static" "policies.cedar" "policies.cedarschema" diff --git a/cedar-example-use-cases/sales_orgs/README.md b/cedar-example-use-cases/sales_orgs/README.md new file mode 100644 index 0000000..0a952ec --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/README.md @@ -0,0 +1,28 @@ +# Sales orgs policies + +ABC has a contracted sales force, and sales folk have access to resources based on their role and the type of resource. + +## 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 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. + +There are some rules limiting how access can be shared. Only _distributor_ `User`s can share with _customer_ `User`s, and in particular those with their customer ID. And only _internal_ `User`s can be granted editor access to resources. + +## 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 `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. + +## Examples + +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. + +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) diff --git a/cedar-example-use-cases/sales_orgs/static/ALLOW/alice_view.json b/cedar-example-use-cases/sales_orgs/static/ALLOW/alice_view.json new file mode 100644 index 0000000..ef7aa0c --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/static/ALLOW/alice_view.json @@ -0,0 +1,6 @@ +{ + "principal":"User::\"Alice\"", + "action":"Action::\"viewPresentation\"", + "resource":"Presentation::\"proposal\"", + "context":{ } +} \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/static/ALLOW/bob_view.json b/cedar-example-use-cases/sales_orgs/static/ALLOW/bob_view.json new file mode 100644 index 0000000..9b9f7e1 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/static/ALLOW/bob_view.json @@ -0,0 +1,6 @@ +{ + "principal":"User::\"Bob\"", + "action":"Action::\"viewPresentation\"", + "resource":"Presentation::\"proposal\"", + "context":{ } +} \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/static/DENY/charlie_view.json b/cedar-example-use-cases/sales_orgs/static/DENY/charlie_view.json new file mode 100644 index 0000000..989a6ea --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/static/DENY/charlie_view.json @@ -0,0 +1,6 @@ +{ + "principal":"User::\"Charlie\"", + "action":"Action::\"viewPresentation\"", + "resource":"Presentation::\"proposal\"", + "context":{ } +} \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/static/entities.json b/cedar-example-use-cases/sales_orgs/static/entities.json new file mode 100644 index 0000000..8ec2d55 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/static/entities.json @@ -0,0 +1,42 @@ +[ + { + "uid": { "type": "User", "id": "Alice"}, + "attrs": { + "job": { "type": "Job", "id": "internal" }, + "customerId": "1AYZ" + }, + "parents": [{"type": "Market", "id": "YoungAdult"}] + }, + { + "uid": { "type": "Market", "id": "YoungAdult" }, + "attrs": {}, + "parents": [] + }, + { + "uid": { "type": "User", "id": "Bob"} , + "attrs": { + "job": { "type": "Job", "id": "customer" }, + "customerId": "1AYZ" + }, + "parents": [{"type": "Market", "id": "YoungAdult"}] + }, + { + "uid": { "type": "User", "id": "Charlie"} , + "attrs": { + "job": { "type": "Job", "id": "distributor" }, + "customerId": "1AYZ" + }, + "parents": [] + }, + { + "uid": { "type": "Presentation", "id": "proposal"}, + "attrs": { + "owner": { "type": "User", "id": "Alice" }, + "viewers": [ + { "type": "User", "id": "Bob" } + ], + "editors": [] + }, + "parents": [] + } +] diff --git a/cedar-example-use-cases/sales_orgs/static/policies.cedar b/cedar-example-use-cases/sales_orgs/static/policies.cedar new file mode 100644 index 0000000..8d26e0b --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/static/policies.cedar @@ -0,0 +1,106 @@ +// PRESENTATION POLICIES +@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" +}; + +// TEMPLATE POLICIES +@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 +}; diff --git a/cedar-example-use-cases/sales_orgs/static/policies.cedarschema b/cedar-example-use-cases/sales_orgs/static/policies.cedarschema new file mode 100644 index 0000000..ecf12f6 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/static/policies.cedarschema @@ -0,0 +1,80 @@ +// Entities +// entity Job enum ["internal", "distributor", "customer", "other"]; +entity Job; +entity User in [Market] { + job: Job, + customerId: String, // irrelevant for Job::"internal" +}; +entity Market; +entity Presentation { + owner: User, + viewers: Set, + editors: Set, +}; +entity Template { + owner: User, + viewers: Set, + editors: Set, + viewerMarkets: Set, + editorMarkets: Set, +}; +// Actions -- Presentations +action InternalPrezViewActions; +action ExternalPrezViewActions; +action PrezEditActions; +action viewPresentation, removeSelfAccessFromPresentation + in [InternalPrezViewActions, ExternalPrezViewActions, PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + }; +action duplicatePresentation in [InternalPrezViewActions, PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + }; +action editPresentation in [PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + }; +// granting access depends on who it is -- context has target +action grantViewAccessToPresentation, grantEditAccessToPresentation + in [PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + context: { target: User, }, + }; + +// Actions -- Templates +action InternalTemplateViewActions; +action MarketTemplateViewActions; +action TemplateEditActions; +action viewTemplate, duplicateTemplate + in [InternalTemplateViewActions, TemplateEditActions, + MarketTemplateViewActions] + appliesTo { + principal: User, + resource: Template, + }; +action removeSelfAccessFromTemplate + in [InternalTemplateViewActions, TemplateEditActions] + appliesTo { + principal: User, + resource: Template + }; +action editTemplate, removeOthersAccessToTemplate in [TemplateEditActions] + appliesTo { + principal: User, + resource: Template + }; +// granting access depends on who, or what, it is -- spec. in context +action grantViewAccessToTemplate, grantEditAccessToTemplate + in [TemplateEditActions] + appliesTo { + principal: User, + resource: Template, + context: { targetMarket?: Market, targetUser?: User }, + }; + diff --git a/cedar-example-use-cases/sales_orgs/templated/ALLOW/alice_view.json b/cedar-example-use-cases/sales_orgs/templated/ALLOW/alice_view.json new file mode 100644 index 0000000..ef7aa0c --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/ALLOW/alice_view.json @@ -0,0 +1,6 @@ +{ + "principal":"User::\"Alice\"", + "action":"Action::\"viewPresentation\"", + "resource":"Presentation::\"proposal\"", + "context":{ } +} \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/templated/ALLOW/bob_view.json b/cedar-example-use-cases/sales_orgs/templated/ALLOW/bob_view.json new file mode 100644 index 0000000..9b9f7e1 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/ALLOW/bob_view.json @@ -0,0 +1,6 @@ +{ + "principal":"User::\"Bob\"", + "action":"Action::\"viewPresentation\"", + "resource":"Presentation::\"proposal\"", + "context":{ } +} \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/templated/DENY/charlie_view.json b/cedar-example-use-cases/sales_orgs/templated/DENY/charlie_view.json new file mode 100644 index 0000000..989a6ea --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/DENY/charlie_view.json @@ -0,0 +1,6 @@ +{ + "principal":"User::\"Charlie\"", + "action":"Action::\"viewPresentation\"", + "resource":"Presentation::\"proposal\"", + "context":{ } +} \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/templated/NOTES b/cedar-example-use-cases/sales_orgs/templated/NOTES new file mode 100644 index 0000000..a53b60f --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/NOTES @@ -0,0 +1,4 @@ +These policies were systematically converted from the `static/` policies +by replacing each policy that references `resource.viewers`, `resource.editors`, etc. with a template-linked policy instead. Then, each user that would have been a member of `resource.viewers` is linked against the relevant templates instead. + +One interesting thing here is that you have to remember to link _all_ of the policies for each viewer, whereas in the `static/` policies just require updating the resource's `viewers` attribute a single time. Having to remember to do multiple links is a source of potential bugs, especially as new templates might get added over time, since the code needs to be updated to perform the links. diff --git a/cedar-example-use-cases/sales_orgs/templated/entities.json b/cedar-example-use-cases/sales_orgs/templated/entities.json new file mode 100644 index 0000000..995f2f8 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/entities.json @@ -0,0 +1,38 @@ +[ + { + "uid": { "type": "User", "id": "Alice"}, + "attrs": { + "job": { "type": "Job", "id": "internal" }, + "customerId": "1AYZ" + }, + "parents": [{"type": "Market", "id": "YoungAdult"}] + }, + { + "uid": { "type": "Market", "id": "YoungAdult" }, + "attrs": {}, + "parents": [] + }, + { + "uid": { "type": "User", "id": "Bob"} , + "attrs": { + "job": { "type": "Job", "id": "customer" }, + "customerId": "1AYZ" + }, + "parents": [{"type": "Market", "id": "YoungAdult"}] + }, + { + "uid": { "type": "User", "id": "Charlie"} , + "attrs": { + "job": { "type": "Job", "id": "distributor" }, + "customerId": "1AYZ" + }, + "parents": [] + }, + { + "uid": { "type": "Presentation", "id": "proposal"}, + "attrs": { + "owner": { "type": "User", "id": "Alice" } + }, + "parents": [] + } +] diff --git a/cedar-example-use-cases/sales_orgs/templated/linked b/cedar-example-use-cases/sales_orgs/templated/linked new file mode 100644 index 0000000..9dc9cb5 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/linked @@ -0,0 +1,18 @@ +[ + { + "template_id": "external-prez-view", + "link_id": "BobView", + "args": { + "?principal": "User::\"Bob\"", + "?resource": "Presentation::\"proposal\"" + } + }, + { + "template_id": "internal-prez-view", + "link_id": "BobViewInternal", + "args": { + "?principal": "User::\"Bob\"", + "?resource": "Presentation::\"proposal\"" + } + } +] \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/templated/policies.cedar b/cedar-example-use-cases/sales_orgs/templated/policies.cedar new file mode 100644 index 0000000..96e74a6 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/policies.cedar @@ -0,0 +1,128 @@ +// 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" +}; + + +// TEMPLATE POLICIES +@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 +}; \ No newline at end of file diff --git a/cedar-example-use-cases/sales_orgs/templated/policies.cedarschema b/cedar-example-use-cases/sales_orgs/templated/policies.cedarschema new file mode 100644 index 0000000..03b8f52 --- /dev/null +++ b/cedar-example-use-cases/sales_orgs/templated/policies.cedarschema @@ -0,0 +1,75 @@ +// Entities +// entity Job enum ["internal", "distributor", "customer", "other"]; +entity Job; +entity User in [Market] { + job: Job, + customerId: String, // irrelevant for Job::"internal" +}; +entity Market; +entity Presentation { + owner: User, +}; +entity Template { + owner: User, +}; + +// Actions -- Presentations +action InternalPrezViewActions; +action ExternalPrezViewActions; +action PrezEditActions; +action viewPresentation, removeSelfAccessFromPresentation + in [InternalPrezViewActions, ExternalPrezViewActions, PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + }; +action duplicatePresentation in [InternalPrezViewActions, PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + }; +action editPresentation in [PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + }; +// granting access depends on who it is -- context has target +action grantViewAccessToPresentation, grantEditAccessToPresentation + in [PrezEditActions] + appliesTo { + principal: User, + resource: Presentation, + context: { target: User, }, + }; + +// Actions -- Templates +action InternalTemplateViewActions; +action MarketTemplateViewActions; +action TemplateEditActions; +action viewTemplate, duplicateTemplate + in [InternalTemplateViewActions, TemplateEditActions, + MarketTemplateViewActions] + appliesTo { + principal: User, + resource: Template, + }; +action removeSelfAccessFromTemplate + in [InternalTemplateViewActions, TemplateEditActions] + appliesTo { + principal: User, + resource: Template + }; +action editTemplate, removeOthersAccessToTemplate in [TemplateEditActions] + appliesTo { + principal: User, + resource: Template + }; +// granting access depends on who, or what, it is -- spec. in context +action grantViewAccessToTemplate, grantEditAccessToTemplate + in [TemplateEditActions] + appliesTo { + principal: User, + resource: Template, + context: { targetMarket?: Market, targetUser?: User }, + }; +