Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions realm-policy/RealmPolicy/blog-publishing.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
"grants": [
{
"operation": "read",
"anonymous": true,
"where": ".published == true"
"where": "actor() == \"anonymous\" and .published == true"
},
{
"operation": "read"
Expand All @@ -27,18 +26,24 @@
"grants": [
{
"operation": "read",
"anonymous": true
"where": "actor() == \"anonymous\""
},
{
"operation": "read"
},
{
"operation": "query",
"anonymous": true
"where": "actor() == \"anonymous\""
},
{
"operation": "query"
}
]
}
],
"cardInfo": {
"name": "Blog publishing",
"summary": "Visitors open published posts and open and search every category; any signed-in user may also read drafts."
"summary": "Visitors open published posts and open and search every category; any signed-in user also reads drafts and searches categories."
}
},
"meta": {
Expand Down
11 changes: 6 additions & 5 deletions realm-policy/Spec/operation-grant.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"name": "Operation Grant",
"summary": "An operation, an optional BXL condition, and whether callers who aren't signed in are admitted."
},
"readMe": "## What it is\n\nOne grant in a `PolicyRule`. It admits a caller to one operation on cards of the rule's type when its condition holds.\n\n```ts\nimport { OperationGrant } from '@cardstack/catalog/realm-policy/realm-policy';\n\n@field grants = containsMany(OperationGrant);\n```\n\n## Fields\n\n* **`operation`**: the operation name as a caller invokes it. That is a base operation such as `read`, `create`, `update`, `delete` or `query`, or the name of an operation a card declares. A grant on a named operation does not grant the base operation it is built on. A search runs as `query`, so a type callers should find in a search needs a `query` grant: a `read` grant opens a card by its URL only. A `query` grant's condition becomes a search filter, which can't express everything a condition can (it can't test a yes/no field, for one), and a `query` grant whose condition it can't express is inactive.\n* **`where`**: a BXL boolean expression over the target card and the caller (`actor()`), stored as written. Absent means the grant is unconditional. In a policy document it is a bare string of BXL.\n\n A condition checks the saved card, so it should read fields the card stores. A computed field, or a field of a linked card, exists only in the search index's copy of the card, so a condition that reads one compiles as inactive and grants nothing. To read the index's copy on purpose, accepting that it lags the card until the card is indexed again, write the condition as `{ \"bxl\": \"...\", \"snapshot\": true }`. A `create` can't use one, since the card it would mint isn't indexed yet.\n\n A write's condition is checked against the card as it was before the write. So it can't stop the write from changing the field it checks: a grant to `update` drafts lets its caller publish one in the same write.\n* **`anonymous`**: whether the grant also admits a caller who isn't signed in. Off, which is the default, it admits only signed-in callers.\n* **`actingUser`**: for an anonymous grant on a write, the key in the governed realm's `realm.json` `config` settings whose value is the Matrix user id the write is made as. That user must be able to write the realm. An anonymous grant on a write without it is left out of the policy, for signed-in callers too.\n\n## Callers who aren't signed in\n\n`anonymous` may be set on a grant for a base operation other than `explain` or `validate`, or for an operation a card declares on such a base. It may not be set on a grant for a named query: setting it there leaves the whole grant out, for signed-in callers too.\n\nA caller who isn't signed in has no actor. A grant whose `where` reads `actor()`, or whose operation does, never admits one, though it still applies to signed-in callers. So an anonymous grant is scoped by what the target holds, not by who is asking.\n\nThe realm, not the policy, names the user an anonymous write is made as. The policy can live in another realm, and its writers must not decide whose identity this realm's writes carry, so `actingUser` holds a key and the governed realm maps it to a user:\n\n```json\n\"attributes\": {\n \"policy\": \"https://app.example/org/policies/newsroom\",\n \"config\": { \"submitter\": \"@submitter:example.com\" }\n}\n```\n\n## The examples\n\n1. **Unconditional**: `read` with no condition, for every signed-in caller, including one the realm's own permissions don't let read it.\n2. **Conditioned on the card**: `update` while the card isn't `published`.\n3. **Conditioned on the caller**: `delete` when the caller is one of the card's `authorIds`.\n4. **Open to visitors**: `read` of `published` cards for callers who aren't signed in.\n5. **An anonymous write**: `create` by callers who aren't signed in, made as the user the realm's `submitter` setting names.\n\n## Non-goals\n\nA grant can't refuse anything, can't open any operation on a policy card or the realm's settings card, reads included, and can't open `explain` or `validate`, which no policy may grant.\n",
"readMe": "## What it is\n\nOne grant in a `PolicyRule`. It admits a caller to one operation on cards of the rule's type when its condition holds.\n\n```ts\nimport { OperationGrant } from '@cardstack/catalog/realm-policy/realm-policy';\n\n@field grants = containsMany(OperationGrant);\n```\n\n## Fields\n\n* **`operation`**: the operation name as a caller invokes it. That is a base operation such as `read`, `create`, `update`, `delete` or `query`, or the name of an operation a card declares. A grant on a named operation does not grant the base operation it is built on. A search runs as `query`, so a type callers should find in a search needs a `query` grant: a `read` grant opens a card by its URL only. A `query` grant's condition becomes a search filter, which can't express everything a condition can (it can't test a yes/no field, for one), and a `query` grant whose condition it can't express is inactive.\n* **`where`**: a BXL boolean expression over the target card and the caller (`actor()`), stored as written. It can also read the governed realm's settings (`realmConfig()`) and this policy card's fields (`policy()`). Absent means the grant is unconditional. In a policy document it is a bare string of BXL.\n\n A condition checks the saved card, so it should read fields the card stores. A computed field, or a field of a linked card, exists only in the search index's copy of the card, so a condition that reads one compiles as inactive and grants nothing. To read the index's copy on purpose, accepting that it lags the card until the card is indexed again, write the condition as `{ \"bxl\": \"...\", \"snapshot\": true }`. A `create` can't use one, since the card it would mint isn't indexed yet.\n\n A write's condition is checked against the card as it was before the write. So it can't stop the write from changing the field it checks: a grant to `update` drafts lets its caller publish one in the same write.\n* **`actingUser`**, **`blocklist`**, **`rateLimitRequests`**, **`rateLimitWindowSeconds`**: BXL expressions that say how the grant treats callers who aren't signed in (below). Each is used only for such a caller.\n\n## Callers who aren't signed in\n\nFor a caller who isn't signed in, `actor()` is `\"anonymous\"`. A grant admits one only when its `where` names that text, such as `actor() == \"anonymous\"`, or `actor() == \"anonymous\" and .published == true`. A grant whose `where` never names it admits only signed-in callers, even one whose condition would hold for anybody. So a grant written for signed-in callers never starts admitting everyone, and one meant for everyone is two grants: one naming `\"anonymous\"`, and one for signed-in callers.\n\nOnly a grant for a base operation other than `explain` or `validate`, or for an operation a card declares on such a base, can name `\"anonymous\"`. A grant for a named query that names it is left out, for signed-in callers too. An operation whose program reads `actor()` can't be run by a caller who isn't signed in, so such a grant never admits one.\n\nEach expression can read the governed realm's settings (`realmConfig(\"key\")`), this policy card's fields (`policy(\"field\")`), or be written out:\n\n* **`actingUser`**: for a grant on a write, the Matrix user a write by a caller who isn't signed in is made as, such as `realmConfig(\"submitter\")`, `policy(\"writer\")`, `\"@writer:example.com\"`, or a user the card names, `instance().owner`. That user must be able to write the realm. Without one, the grant admits no write by such a caller. A signed-in caller's writes are made as themselves, and reads never use it.\n* **`blocklist`**: the addresses the grant refuses, as a comma-separated string of IP addresses and CIDR ranges, such as `\"192.0.2.1, 10.0.0.0/8\"`, or a list of them. One that can't be read refuses every caller the grant would admit.\n* **`rateLimitRequests`** and **`rateLimitWindowSeconds`**: how many requests one address may make through the grant in a window, as whole numbers. Each one left out, or that produces no whole number, is the platform's default, which the grant's views show. Each grant counts its own requests.\n\nThe blocklist and rate limit can't read the card a request names (`instance()`). They are settled before the card is read, so a refused address gets the same answer whatever card it asks for, and costs the realm nothing more.\n\n## The examples\n\n1. **Unconditional**: `read` with no condition, for every signed-in caller, including one the realm's own permissions don't let read it.\n2. **Conditioned on the card**: `update` while the card isn't `published`.\n3. **Conditioned on the caller**: `delete` when the caller is one of the card's `authorIds`.\n4. **Open to visitors**: `read` of `published` cards for callers who aren't signed in, limited to the requests the realm's `visitorRequests` setting allows, and refusing the addresses its `blockedIps` setting lists.\n5. **An anonymous write**: `create` by callers who aren't signed in, made as the user the realm's `submitter` setting names.\n\n## Non-goals\n\nA grant can't refuse access the realm already permits. Its `blocklist` only turns away callers who aren't signed in. No grant can open any operation on a policy card or the realm's settings card, reads included, and can't open `explain` or `validate`, which no policy may grant.\n",
"containedExamples": [
{
"operation": "read"
Expand All @@ -31,13 +31,14 @@
},
{
"operation": "read",
"anonymous": true,
"where": ".published == true"
"where": "actor() == \"anonymous\" and .published == true",
"rateLimitRequests": "realmConfig(\"visitorRequests\")",
"blocklist": "realmConfig(\"blockedIps\")"
},
{
"operation": "create",
"anonymous": true,
"actingUser": "submitter"
"where": "actor() == \"anonymous\"",
"actingUser": "realmConfig(\"submitter\")"
}
]
},
Expand Down
15 changes: 10 additions & 5 deletions realm-policy/Spec/policy-rule.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"name": "Policy Rule",
"summary": "A target card type plus the operation grants that apply to it."
},
"readMe": "## What it is\n\nA rule in a `RealmPolicy`: a target card type (`targetType`, a code ref) and the grants (`grants`, a list of `OperationGrant`) that apply to it. A rule governs its target type and every subtype of it, so a rule on a base type reaches every card that adopts from it.\n\n```ts\nimport { PolicyRule } from '@cardstack/catalog/realm-policy/realm-policy';\n\n@field rules = containsMany(PolicyRule);\n```\n\nIn a policy document a rule is plain data. A relative module in `targetType` resolves against the policy card:\n\n```json\n{\n \"targetType\": { \"module\": \"../../7af9aa-blog-app/blog-post\", \"name\": \"BlogPost\" },\n \"grants\": [\n { \"operation\": \"read\", \"anonymous\": true, \"where\": \".published == true\" },\n { \"operation\": \"read\" }\n ]\n}\n```\n\n## How rules combine\n\nRules don't compete. For a card, every rule whose target type is the card's type or one of its ancestors contributes its grants, and an operation is allowed when any one of those grants admits it. Several rules may name the same type; their order means nothing.\n\n## The examples\n\n1. **Blog posts**: visitors open published posts, and any signed-in user reads every post, drafts included.\n2. **Blog categories**: everyone, signed in or not, opens and searches every category.\n\n## Editing\n\nThe edit format lists the rule's grants, each opening with the one-line summary the policy's view shows for it, with buttons to add and remove grants.\n\n## Non-goals\n\nA rule has no condition of its own and no priority. Conditions belong to grants, and a type with no rule gets nothing beyond the realm's own permissions.\n",
"readMe": "## What it is\n\nA rule in a `RealmPolicy`: a target card type (`targetType`, a code ref) and the grants (`grants`, a list of `OperationGrant`) that apply to it. A rule governs its target type and every subtype of it, so a rule on a base type reaches every card that adopts from it.\n\n```ts\nimport { PolicyRule } from '@cardstack/catalog/realm-policy/realm-policy';\n\n@field rules = containsMany(PolicyRule);\n```\n\nIn a policy document a rule is plain data. A relative module in `targetType` resolves against the policy card:\n\n```json\n{\n \"targetType\": { \"module\": \"../../7af9aa-blog-app/blog-post\", \"name\": \"BlogPost\" },\n \"grants\": [\n { \"operation\": \"read\", \"where\": \"actor() == \\\"anonymous\\\" and .published == true\" },\n { \"operation\": \"read\" }\n ]\n}\n```\n\n## How rules combine\n\nRules don't compete. For a card, every rule whose target type is the card's type or one of its ancestors contributes its grants, and an operation is allowed when any one of those grants admits it. Several rules may name the same type; their order means nothing.\n\n## The examples\n\n1. **Blog posts**: visitors open published posts, and any signed-in user reads every post, drafts included.\n2. **Blog categories**: everyone, signed in or not, opens and searches every category.\n\n## Editing\n\nThe edit format lists the rule's grants, each opening with the one-line summary the policy's view shows for it, with buttons to add and remove grants.\n\n## Non-goals\n\nA rule has no condition of its own and no priority. Conditions belong to grants, and a type with no rule gets nothing beyond the realm's own permissions.\n",
"containedExamples": [
{
"targetType": {
Expand All @@ -26,8 +26,7 @@
"grants": [
{
"operation": "read",
"anonymous": true,
"where": ".published == true"
"where": "actor() == \"anonymous\" and .published == true"
},
{
"operation": "read"
Expand All @@ -42,11 +41,17 @@
"grants": [
{
"operation": "read",
"anonymous": true
"where": "actor() == \"anonymous\""
},
{
"operation": "read"
},
{
"operation": "query",
"anonymous": true
"where": "actor() == \"anonymous\""
},
{
"operation": "query"
}
]
}
Expand Down
Loading
Loading