Write policies
Create policies that permit or forbid access, then update or delete them as your rules change. Each policy has a stable id; writing to the same id replaces it.
Use the server client with an admin connection to a fresh database with the extension installed. The later pages check access against the policies you create here.
Put a policy in Cedar text
Section titled “Put a policy in Cedar text”Give putPolicy a stable ID and the rule in Cedar text. This rule lets engineers on the eng team view documents in the reports folder.
const policy = `permit ( principal in Team::"eng", action == Action::"view", resource in Folder::"reports");`const savedTeamPolicy = await permissions.cedar.admin.putPolicy({ policyId: 'eng-view-reports', policy,})if (savedTeamPolicy.error) throw savedTeamPolicy.errorselect permissions.cedar_put_policy( policy_id => 'eng-view-reports', policy => 'permit ( principal in Team::"eng", action == Action::"view", resource in Folder::"reports");');Writing to an existing policy id replaces that policy. The function checks syntax and reports the line and column of an error. It does not validate against a Cedar schema:
const broken = await permissions.cedar.admin.putPolicy({ policyId: 'broken', policy: 'permit (principal, action, resource) when {',})// broken.error.details: cedar:parse_error line=1 column=44select permissions.cedar_put_policy( policy_id => 'broken', policy => 'permit (principal, action, resource) when {');-- ERROR: unexpected end of input at line 1, column 44: expected an expression-- DETAIL: cedar:parse_error line=1 column=44-- HINT: Check the policy against the Cedar grammar: https://docs.cedarpolicy.com/policies/syntax-grammar.htmlEach call takes exactly one policy. Text with two policies is an error. Call the function once for each policy.
Use a condition
Section titled “Use a condition”A when condition must be true for the policy to apply. An unless condition must be false. In a condition, principal, action, resource and context are the parts of the request. An attribute of an entity comes from the stored entity (see Entities).
This policy lets the owner of a document edit it. The attribute owner of the document is an entity:
const ownerEdit = `permit ( principal, action == Action::"edit", resource)when { resource.owner == principal };`const savedOwnerPolicy = await permissions.cedar.admin.putPolicy({ policyId: 'owner-edit', policy: ownerEdit,})if (savedOwnerPolicy.error) throw savedOwnerPolicy.errorselect permissions.cedar_put_policy( policy_id => 'owner-edit', policy => 'permit ( principal, action == Action::"edit", resource)when { resource.owner == principal };');Put a policy in JSON
Section titled “Put a policy in JSON”Cedar also has a JSON policy format. Use policy_json for a policy that a program writes, or one that the Cedar CLI translated. The extension stores the JSON as you give it. It rejects JSON with an unknown key, a wrong op, or a wrong shape, and the error names the path.
This forbid policy denies every action on a document with the level confidential:
const noConfidential = { effect: 'forbid', principal: { op: 'All' }, action: { op: 'All' }, resource: { op: 'All' }, conditions: [ { kind: 'when', body: { '==': { left: { '.': { left: { Var: 'resource' }, attr: 'level' }, }, right: { Value: 'confidential' }, }, }, }, ],}const savedForbid = await permissions.cedar.admin.putPolicy({ policyId: 'no-confidential', policyJson: noConfidential,})if (savedForbid.error) throw savedForbid.errorselect permissions.cedar_put_policy( policy_id => 'no-confidential', policy_json => '{ "effect": "forbid", "principal": {"op": "All"}, "action": {"op": "All"}, "resource": {"op": "All"}, "conditions": [ { "kind": "when", "body": { "==": { "left": { ".": { "left": {"Var": "resource"}, "attr": "level" } }, "right": {"Value": "confidential"} } } } ]}');Pass either policy for text or policyJson for JSON (policy_json in SQL). A call with both is the error cedar:invalid_argument. A call with neither is the error cedar:null_argument. A policy that has a slot (?principal or ?resource) is a template. See Templates.
The policy id
Section titled “The policy id”The policyId argument names the policy. An admin check returns that ID in data.diagnostics.reason (reasons in the SQL response).
An @id("...") annotation in the text does not name the policy. It stays an annotation, and it does not have to agree with policy_id. The JSON format has no id field, so an id always comes from the call. Cedar’s own call for one policy, Policy::parse, also takes the id as an argument.
Put each policy with its own call and a stable id. The extension has no function that loads a file with many policies. A loader that names policies by position would change the ids when you add a policy at the top of the file. Split the file in your own code and call cedar_put_policy once for each policy.
Static policies, template links and templates share one set of ids. A put can replace a policy that has the same id. If the id belongs to a template or a link, the put raises cedar:id_in_use.
Annotations
Section titled “Annotations”An annotation is metadata on a policy. It has no effect on a decision. A JSON value of null is the same as "". Use annotations for notes, owners or ticket numbers. The put stores them, and cedar_policies returns them:
const notes = `@id("old-name")@advice("Ask the security team first.")forbid (principal, action, resource)when { context.maintenance };`const savedNotes = await permissions.cedar.admin.putPolicy({ policyId: 'notes', policy: notes,})if (savedNotes.error) throw savedNotes.errorconst { data: annotatedPolicies } = await permissions.cedar.admin.policies()const found = annotatedPolicies?.find((p) => p.policyId === 'notes')// found?.annotations:// { id: 'old-name', advice: 'Ask the security team first.' }select permissions.cedar_put_policy( policy_id => 'notes', policy => '@id("old-name")@advice("Ask the security team first.")forbid (principal, action, resource)when { context.maintenance };');select policy_id, annotationsfrom permissions.cedar_policies()where policy_id = 'notes';-- notes | {"id": "old-name", "advice": "Ask the security team first."}List policies
Section titled “List policies”Use policies() to list every policy, ordered by ID. Each row includes its effect, original Cedar text (when supplied), JSON representation (est), annotations, and template details for a linked policy:
const { data: policies } = await permissions.cedar.admin.policies()// [// { policyId: 'eng-view-reports', effect: 'permit', ... },// { policyId: 'no-confidential', effect: 'forbid', ... },// ...// ]select policy_id, effect, template_idfrom permissions.cedar_policies();-- eng-view-reports | permit | (null)-- no-confidential | forbid | (null)-- notes | forbid | (null)-- owner-edit | permit | (null)source is null for a policy that you put as JSON. est always holds the policy in the JSON format, so you can read a text policy as JSON.
Delete a policy
Section titled “Delete a policy”Use deletePolicy to remove a policy or a template link by ID. An unknown ID returns an error with a hint listing the IDs that exist.
const removedNotes = await permissions.cedar.admin.deletePolicy({ policyId: 'notes' })if (removedNotes.error) throw removedNotes.errorselect permissions.cedar_delete_policy(policy_id => 'notes');The policy is gone, so a second delete fails:
const deletedAgain = await permissions.cedar.admin.deletePolicy({ policyId: 'notes',})// deletedAgain.error.details: cedar:unknown_policy policy_id='notes'select permissions.cedar_delete_policy(policy_id => 'notes');-- ERROR: unknown policy 'notes'-- DETAIL: cedar:unknown_policy policy_id='notes'-- HINT: Policy ids: eng-view-reports, no-confidential, owner-edit.Limits
Section titled “Limits”A policy has at most 64 KiB of text, 4,096 expression nodes and 64 levels of depth. Limits lists every limit. The number of policies has no limit. A check reads only the policies whose scope can match the request. A policy with the scope All can match every request.
Next step
Section titled “Next step”Continue with Store entities, then Use policy templates. The access checks later in the walkthrough use both.