Skip to content

Commit

Permalink
Sales orgs example (#152)
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Hicks <[email protected]>
  • Loading branch information
mwhicks1 authored Jun 7, 2024
1 parent f9de8ed commit b0ff6ef
Show file tree
Hide file tree
Showing 16 changed files with 567 additions and 0 deletions.
12 changes: 12 additions & 0 deletions cedar-example-use-cases/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
28 changes: 28 additions & 0 deletions cedar-example-use-cases/sales_orgs/README.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"principal":"User::\"Alice\"",
"action":"Action::\"viewPresentation\"",
"resource":"Presentation::\"proposal\"",
"context":{ }
}
6 changes: 6 additions & 0 deletions cedar-example-use-cases/sales_orgs/static/ALLOW/bob_view.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"principal":"User::\"Bob\"",
"action":"Action::\"viewPresentation\"",
"resource":"Presentation::\"proposal\"",
"context":{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"principal":"User::\"Charlie\"",
"action":"Action::\"viewPresentation\"",
"resource":"Presentation::\"proposal\"",
"context":{ }
}
42 changes: 42 additions & 0 deletions cedar-example-use-cases/sales_orgs/static/entities.json
Original file line number Diff line number Diff line change
@@ -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": []
}
]
106 changes: 106 additions & 0 deletions cedar-example-use-cases/sales_orgs/static/policies.cedar
Original file line number Diff line number Diff line change
@@ -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
};
80 changes: 80 additions & 0 deletions cedar-example-use-cases/sales_orgs/static/policies.cedarschema
Original file line number Diff line number Diff line change
@@ -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<User>,
editors: Set<User>,
};
entity Template {
owner: User,
viewers: Set<User>,
editors: Set<User>,
viewerMarkets: Set<Market>,
editorMarkets: Set<Market>,
};
// 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 },
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"principal":"User::\"Alice\"",
"action":"Action::\"viewPresentation\"",
"resource":"Presentation::\"proposal\"",
"context":{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"principal":"User::\"Bob\"",
"action":"Action::\"viewPresentation\"",
"resource":"Presentation::\"proposal\"",
"context":{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"principal":"User::\"Charlie\"",
"action":"Action::\"viewPresentation\"",
"resource":"Presentation::\"proposal\"",
"context":{ }
}
4 changes: 4 additions & 0 deletions cedar-example-use-cases/sales_orgs/templated/NOTES
Original file line number Diff line number Diff line change
@@ -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.
38 changes: 38 additions & 0 deletions cedar-example-use-cases/sales_orgs/templated/entities.json
Original file line number Diff line number Diff line change
@@ -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": []
}
]
18 changes: 18 additions & 0 deletions cedar-example-use-cases/sales_orgs/templated/linked
Original file line number Diff line number Diff line change
@@ -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\""
}
}
]
Loading

0 comments on commit b0ff6ef

Please sign in to comment.