@intentius/chant-lexicon-cedar 0.124.30 → 0.124.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentcore/embed.js +250 -0
- package/dist/agentcore/enforcement.js +81 -0
- package/dist/agentcore/scan.js +93 -0
- package/dist/avp/ambient.js +100 -0
- package/dist/avp/client.js +318 -0
- package/dist/avp/describe-resources.js +144 -0
- package/dist/avp/embed.js +125 -0
- package/dist/avp/live-export.js +151 -0
- package/dist/avp/ownership.js +144 -0
- package/dist/avp/statement.js +45 -0
- package/dist/avp/store.js +105 -0
- package/dist/avp/testdata/mock-transport.js +100 -0
- package/dist/codegen/docs-cli.js +6 -0
- package/dist/codegen/docs.js +133 -0
- package/dist/codegen/emit.js +331 -0
- package/dist/codegen/generate-cli.js +18 -0
- package/dist/codegen/generate.js +122 -0
- package/dist/codegen/naming.js +90 -0
- package/dist/codegen/package.js +33 -0
- package/dist/commands.js +86 -0
- package/dist/composites/deny-by-default-set.js +51 -0
- package/dist/composites/index.js +9 -0
- package/dist/composites/owner-can-manage.js +48 -0
- package/dist/config.js +159 -0
- package/dist/coverage.js +112 -0
- package/dist/detect.js +61 -0
- package/dist/dogwood/cli.js +477 -0
- package/dist/dogwood/event-schema.js +219 -0
- package/dist/dogwood/index.js +32 -0
- package/dist/dogwood/macros.js +175 -0
- package/dist/dogwood/policy.js +58 -0
- package/dist/dogwood/replay-activity.js +313 -0
- package/dist/dogwood/replay-op.js +134 -0
- package/dist/dogwood/scan.js +234 -0
- package/dist/dogwood/serialize.js +199 -0
- package/dist/dogwood/temporal.js +354 -0
- package/dist/dogwood/trace.js +269 -0
- package/dist/dogwood/upstream.js +40 -0
- package/dist/dogwood/window.js +82 -0
- package/dist/gate-policy-pack.js +189 -0
- package/dist/gate-policy.js +168 -0
- package/dist/gate-schema.js +122 -0
- package/dist/generated/index.js +94 -0
- package/dist/generated/runtime.js +2 -0
- package/dist/import/adapter.js +54 -0
- package/dist/import/clause-text.js +166 -0
- package/dist/import/generator.js +178 -0
- package/dist/import/parser.js +273 -0
- package/dist/index.js +79 -0
- package/dist/init-templates.js +330 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +65 -0
- package/dist/lint/audit-lineage.js +23 -0
- package/dist/lint/post-synth/cedar-helpers.js +123 -0
- package/dist/lint/post-synth/cedc010.js +59 -0
- package/dist/lint/post-synth/cedc011.js +29 -0
- package/dist/lint/post-synth/cedc012.js +34 -0
- package/dist/lint/post-synth/cedc013.js +45 -0
- package/dist/lint/post-synth/cedc014.js +54 -0
- package/dist/lint/post-synth/cede010.js +51 -0
- package/dist/lint/post-synth/cede011.js +36 -0
- package/dist/lint/post-synth/ceds010.js +31 -0
- package/dist/lint/post-synth/ceds011.js +22 -0
- package/dist/lint/post-synth/ceds012.js +31 -0
- package/dist/lint/post-synth/dogwood-helpers.js +94 -0
- package/dist/lint/post-synth/dwdc010.js +38 -0
- package/dist/lint/post-synth/dwdc011.js +34 -0
- package/dist/lint/post-synth/dwdc012.js +24 -0
- package/dist/lint/post-synth/dwdc013.js +29 -0
- package/dist/lint/post-synth/dwde010.js +84 -0
- package/dist/lint/post-synth/dwde011.js +67 -0
- package/dist/lint/post-synth/dwds010.js +21 -0
- package/dist/lint/post-synth/index.js +36 -0
- package/dist/lint/post-synth/wasm-helpers.js +234 -0
- package/dist/lint/rules/index.js +9 -0
- package/dist/lint/rules/policy-shape.js +114 -0
- package/dist/lsp/completions.js +52 -0
- package/dist/lsp/hover.js +56 -0
- package/dist/lsp/registry.d.ts.map +1 -1
- package/dist/lsp/registry.js +51 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/index.js +82 -0
- package/dist/mcp/policy-coverage.js +134 -0
- package/dist/op/activities/index.js +16 -0
- package/dist/package-cli.js +18 -0
- package/dist/plugin.js +304 -0
- package/dist/policy-text.js +117 -0
- package/dist/schema-artifact.js +66 -0
- package/dist/serializer.js +203 -0
- package/dist/spec/fetch.js +96 -0
- package/dist/spec/parse.js +164 -0
- package/dist/spec/pin.js +170 -0
- package/dist/spec/wasm.js +194 -0
- package/dist/validate-cli.js +7 -0
- package/dist/validate.js +74 -0
- package/package.json +7 -7
- package/src/codegen/package.ts +2 -2
- package/src/coverage.ts +2 -2
- package/src/import/generator.ts +2 -2
- package/src/lsp/registry.ts +2 -1
- package/src/spec/fetch.ts +2 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal windows — the one thing dogwood's past-only operators cannot omit.
|
|
3
|
+
*
|
|
4
|
+
* `formerly`, `previous` and `since` all take a mandatory `within <n><unit>`
|
|
5
|
+
* clause; the unit is one of `s`, `m`, `h`, `d` and nothing else. Upstream's
|
|
6
|
+
* `temporal/grammar.pest` spells that out:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* within = { "within" ~ within_payload }
|
|
10
|
+
* within_payload = { param_ref | (integer ~ time_unit) }
|
|
11
|
+
* time_unit = { "s" | "m" | "h" | "d" }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Typing the window as a value rather than as free text is what makes
|
|
15
|
+
* "a `formerly` without a window" unrepresentable in the builders (#1658's
|
|
16
|
+
* third lint wall) — you cannot call {@link formerly} without one.
|
|
17
|
+
*
|
|
18
|
+
* The `param_ref` arm (`within ?w`) is legal only inside a macro body; it is
|
|
19
|
+
* modelled by {@link MacroWindow} rather than by this type, so an ordinary
|
|
20
|
+
* policy cannot accidentally emit a sigil the expander would reject.
|
|
21
|
+
*/
|
|
22
|
+
const WINDOW_TEXT = /^(\d+)([smhd])$/;
|
|
23
|
+
const PARAM_TEXT = /^\?[A-Za-z_][A-Za-z0-9_]*$/;
|
|
24
|
+
/** True when the payload is `?w` rather than `1h`. */
|
|
25
|
+
export function isWindowParam(value) {
|
|
26
|
+
return "param" in value;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `within ?w` inside a macro body.
|
|
30
|
+
*
|
|
31
|
+
* Exported from `./macros.ts` too, which is where the sigil rules are
|
|
32
|
+
* documented and where a reader looking for it will be.
|
|
33
|
+
*/
|
|
34
|
+
export function windowParam(param) {
|
|
35
|
+
if (!PARAM_TEXT.test(param)) {
|
|
36
|
+
throw new Error(`dogwood: a window parameter carries the "?" sigil — got "${param}"`);
|
|
37
|
+
}
|
|
38
|
+
return { param };
|
|
39
|
+
}
|
|
40
|
+
/** Seconds in one of each unit — the scale `max_window` comparisons run on. */
|
|
41
|
+
const UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
42
|
+
/** Narrow a {@link WindowLike} to the structured form, validating as it goes. */
|
|
43
|
+
export function window(value) {
|
|
44
|
+
if (typeof value !== "string") {
|
|
45
|
+
if (!Number.isInteger(value.value) || value.value < 0) {
|
|
46
|
+
throw new Error(`dogwood: a temporal window must be a non-negative integer, got ${String(value.value)}`);
|
|
47
|
+
}
|
|
48
|
+
if (!(value.unit in UNIT_SECONDS)) {
|
|
49
|
+
throw new Error(`dogwood: a temporal window unit must be s, m, h or d, got "${String(value.unit)}"`);
|
|
50
|
+
}
|
|
51
|
+
return { value: value.value, unit: value.unit };
|
|
52
|
+
}
|
|
53
|
+
const match = WINDOW_TEXT.exec(value);
|
|
54
|
+
if (!match) {
|
|
55
|
+
throw new Error(`dogwood: "${value}" is not a temporal window — write an integer and one of s, m, h, d (for example "1h")`);
|
|
56
|
+
}
|
|
57
|
+
return { value: Number(match[1]), unit: match[2] };
|
|
58
|
+
}
|
|
59
|
+
/** `1h` — the surface form, in a `within` clause or as a macro-call argument. */
|
|
60
|
+
export function renderWindow(value) {
|
|
61
|
+
const w = window(value);
|
|
62
|
+
return `${w.value}${w.unit}`;
|
|
63
|
+
}
|
|
64
|
+
/** A `within` payload: `1h`, or the `?w` a macro body defers to its call site. */
|
|
65
|
+
export function renderWindowValue(value) {
|
|
66
|
+
return isWindowParam(value) ? value.param : renderWindow(value);
|
|
67
|
+
}
|
|
68
|
+
/** Narrow a window argument, letting a `?w` parameter through unchanged. */
|
|
69
|
+
export function windowValue(value) {
|
|
70
|
+
return typeof value === "object" && "param" in value ? windowParam(value.param) : window(value);
|
|
71
|
+
}
|
|
72
|
+
/** The window in seconds, so `90m` and `1h` are comparable to a `max_window`. */
|
|
73
|
+
export function windowSeconds(value) {
|
|
74
|
+
const w = window(value);
|
|
75
|
+
return w.value * UNIT_SECONDS[w.unit];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Upstream's fallback cap. `ServiceSchema::defaults()` and an event schema
|
|
79
|
+
* with no `max_window` directive both look back at most 24 hours, per the
|
|
80
|
+
* comment atop `event_schema/grammar.pest` ("absent → a 24h default").
|
|
81
|
+
*/
|
|
82
|
+
export const DEFAULT_MAX_WINDOW = { value: 24, unit: "h" };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A starter policy pack for gates (chant#3182): rules a platform team writes
|
|
3
|
+
* once and puts on every gate, the way Sentinel or OPA policies run against a
|
|
4
|
+
* Terraform plan. Each builder returns one cedar `Policy` over the plan
|
|
5
|
+
* summary core puts in `context.plan` (`GatePlanSummary`, declared by
|
|
6
|
+
* `GATE_CEDAR_SCHEMA`), so an Op needs no context code of its own.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { gatePolicy, protectStateful, capDeletes, permitLowRiskAgents } from "@intentius/chant-lexicon-cedar";
|
|
10
|
+
*
|
|
11
|
+
* export const prodGate = gatePolicy("prod", [
|
|
12
|
+
* protectStateful(),
|
|
13
|
+
* capDeletes({ max: 5 }),
|
|
14
|
+
* permitLowRiskAgents({ maxChanges: 10 }),
|
|
15
|
+
* ]);
|
|
16
|
+
*
|
|
17
|
+
* gate("approve-prod", { plan: plan.out.planDigest, approval: { policy: prodGate, mode: "log-only" } });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* How the rules combine is Cedar's: a `forbid` beats every `permit`, and with
|
|
21
|
+
* no permit the answer is deny. The pack's forbids are the limits and its
|
|
22
|
+
* permits say when an agent may pass a gate on its own. A gate's quorum is
|
|
23
|
+
* unchanged by any of it, since a deny never removes a human's approval
|
|
24
|
+
* (`tallyGateApprovals`), and in `log-only` mode, the default, every decision
|
|
25
|
+
* is recorded and none binds, so a team can watch a rule against real plans
|
|
26
|
+
* before switching the gate to `enforce`.
|
|
27
|
+
*
|
|
28
|
+
* Every forbid fails closed: on a gate whose context has no `plan` (no change
|
|
29
|
+
* set behind the gate's `plan`), it applies. A limit nobody can check is not
|
|
30
|
+
* a reason to let an agent through.
|
|
31
|
+
*/
|
|
32
|
+
import { Policy } from "./generated/index.js";
|
|
33
|
+
import { escapeCedarString } from "./policy-text.js";
|
|
34
|
+
import { GATE_AGENT_TYPE, PASS_GATE_ACTION } from "./gate-policy.js";
|
|
35
|
+
/**
|
|
36
|
+
* Resource types whose delete or replacement loses data: databases, buckets,
|
|
37
|
+
* volumes, queues with retention, and keys. Terraform-provider type names
|
|
38
|
+
* and the CloudFormation names the aws lexicon plans with.
|
|
39
|
+
*/
|
|
40
|
+
export const STATEFUL_TYPES = [
|
|
41
|
+
// AWS, terraform
|
|
42
|
+
"aws_db_instance", "aws_rds_cluster", "aws_rds_cluster_instance", "aws_rds_global_cluster",
|
|
43
|
+
"aws_dynamodb_table", "aws_dynamodb_global_table", "aws_s3_bucket", "aws_efs_file_system",
|
|
44
|
+
"aws_ebs_volume", "aws_elasticache_cluster", "aws_elasticache_replication_group",
|
|
45
|
+
"aws_docdb_cluster", "aws_neptune_cluster", "aws_redshift_cluster", "aws_opensearch_domain",
|
|
46
|
+
"aws_elasticsearch_domain", "aws_msk_cluster", "aws_kinesis_stream", "aws_sqs_queue",
|
|
47
|
+
"aws_kms_key", "aws_secretsmanager_secret", "aws_backup_vault", "aws_fsx_lustre_file_system",
|
|
48
|
+
// AWS, CloudFormation
|
|
49
|
+
"AWS::RDS::DBInstance", "AWS::RDS::DBCluster", "AWS::RDS::GlobalCluster", "AWS::DynamoDB::Table",
|
|
50
|
+
"AWS::DynamoDB::GlobalTable", "AWS::S3::Bucket", "AWS::EFS::FileSystem", "AWS::EC2::Volume",
|
|
51
|
+
"AWS::ElastiCache::CacheCluster", "AWS::ElastiCache::ReplicationGroup", "AWS::DocDB::DBCluster",
|
|
52
|
+
"AWS::Neptune::DBCluster", "AWS::Redshift::Cluster", "AWS::OpenSearchService::Domain",
|
|
53
|
+
"AWS::MSK::Cluster", "AWS::Kinesis::Stream", "AWS::SQS::Queue", "AWS::KMS::Key",
|
|
54
|
+
"AWS::SecretsManager::Secret", "AWS::Backup::BackupVault",
|
|
55
|
+
// Google Cloud
|
|
56
|
+
"google_sql_database_instance", "google_sql_database", "google_storage_bucket",
|
|
57
|
+
"google_bigquery_dataset", "google_bigquery_table", "google_spanner_instance",
|
|
58
|
+
"google_spanner_database", "google_compute_disk", "google_filestore_instance",
|
|
59
|
+
"google_redis_instance", "google_kms_crypto_key", "google_bigtable_instance",
|
|
60
|
+
// Azure
|
|
61
|
+
"azurerm_mssql_server", "azurerm_mssql_database", "azurerm_postgresql_flexible_server",
|
|
62
|
+
"azurerm_mysql_flexible_server", "azurerm_storage_account", "azurerm_cosmosdb_account",
|
|
63
|
+
"azurerm_managed_disk", "azurerm_redis_cache", "azurerm_key_vault",
|
|
64
|
+
];
|
|
65
|
+
// The generated scope types are narrowed to the project's own schema; these
|
|
66
|
+
// names are the gate schema's (`GATE_CEDAR_SCHEMA`).
|
|
67
|
+
const AGENT = GATE_AGENT_TYPE;
|
|
68
|
+
const PASS_GATE = PASS_GATE_ACTION;
|
|
69
|
+
const set = (values) => `[${values.map((v) => `"${escapeCedarString(v)}"`).join(", ")}]`;
|
|
70
|
+
function count(name, value, fallback) {
|
|
71
|
+
const n = value ?? fallback;
|
|
72
|
+
if (!Number.isInteger(n) || n < 0)
|
|
73
|
+
throw new Error(`${name} must be a whole number of at least 0, got ${n}`);
|
|
74
|
+
return n;
|
|
75
|
+
}
|
|
76
|
+
function nonEmpty(name, values) {
|
|
77
|
+
if (!values || values.length === 0 || values.some((v) => typeof v !== "string" || v === "")) {
|
|
78
|
+
throw new Error(`${name} needs at least one non-empty name`);
|
|
79
|
+
}
|
|
80
|
+
return values;
|
|
81
|
+
}
|
|
82
|
+
function principal(appliesTo, fallback) {
|
|
83
|
+
return (appliesTo ?? fallback) === "agents" ? { is: AGENT } : {};
|
|
84
|
+
}
|
|
85
|
+
/** A forbid on `PassGate` that applies when there is no plan summary, or when `condition` holds. `condition` reads `context.plan`. */
|
|
86
|
+
function forbid(id, appliesTo, condition, description) {
|
|
87
|
+
return new Policy({
|
|
88
|
+
effect: "forbid",
|
|
89
|
+
principal: principal(appliesTo, "everyone"),
|
|
90
|
+
action: { eq: PASS_GATE },
|
|
91
|
+
// Written as `unless`, so the validator sees `context has plan` guard the
|
|
92
|
+
// read of it (it tracks a `has` through `&&`): the forbid applies unless
|
|
93
|
+
// there is a plan and the condition does not hold.
|
|
94
|
+
unless: [`context has plan && !(${condition})`],
|
|
95
|
+
annotations: { id, description },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** Forbid a plan that deletes more than `max` resources. Default id `cap-deletes`. */
|
|
99
|
+
export function capDeletes(options = {}) {
|
|
100
|
+
const max = count("capDeletes: max", options.max, 0);
|
|
101
|
+
return forbid(options.id ?? "cap-deletes", options.appliesTo, `context.plan.deletes > ${max}`, `A plan may delete at most ${max} resources.`);
|
|
102
|
+
}
|
|
103
|
+
/** Forbid a plan that replaces more than `max` resources. Default id `cap-replacements`. */
|
|
104
|
+
export function capReplacements(options = {}) {
|
|
105
|
+
const max = count("capReplacements: max", options.max, 0);
|
|
106
|
+
return forbid(options.id ?? "cap-replacements", options.appliesTo, `context.plan.replaces > ${max}`, `A plan may replace at most ${max} resources.`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Forbid a plan that deletes or replaces a resource of a stateful type: an
|
|
110
|
+
* RDS instance, a DynamoDB table, a bucket. Default id `protect-stateful`.
|
|
111
|
+
*/
|
|
112
|
+
export function protectStateful(options = {}) {
|
|
113
|
+
const types = set(nonEmpty("protectStateful: types", options.types ?? STATEFUL_TYPES));
|
|
114
|
+
const deletes = options.deletes ?? true;
|
|
115
|
+
const replacements = options.replacements ?? true;
|
|
116
|
+
if (!deletes && !replacements)
|
|
117
|
+
throw new Error("protectStateful: deletes and replacements are both off, so the rule forbids nothing");
|
|
118
|
+
const conditions = [
|
|
119
|
+
...(deletes ? [`context.plan.deletedTypes.containsAny(${types})`] : []),
|
|
120
|
+
...(replacements ? [`context.plan.replacedTypes.containsAny(${types})`] : []),
|
|
121
|
+
];
|
|
122
|
+
return forbid(options.id ?? "protect-stateful", options.appliesTo, conditions.length === 1 ? conditions[0] : `(${conditions.join(" || ")})`, `A plan may not ${[deletes ? "delete" : "", replacements ? "replace" : ""].filter(Boolean).join(" or ")} a resource that holds state.`);
|
|
123
|
+
}
|
|
124
|
+
/** Forbid a plan that changes anything outside these regions. Default id `allow-regions`. */
|
|
125
|
+
export function allowRegions(options) {
|
|
126
|
+
const regions = nonEmpty("allowRegions: regions", options.regions);
|
|
127
|
+
return forbid(options.id ?? "allow-regions", options.appliesTo, `!${set(regions)}.containsAll(context.plan.regions)`, `A plan may change resources only in ${regions.join(", ")}.`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Forbid a plan whose tagged creates do not all carry `keys`. Reads
|
|
131
|
+
* `tags`, `tags_all` and `labels`. Default id `require-create-tags`.
|
|
132
|
+
*/
|
|
133
|
+
export function requireCreateTags(options) {
|
|
134
|
+
const keys = nonEmpty("requireCreateTags: keys", options.keys);
|
|
135
|
+
const missing = `(context.plan.taggedCreates > 0 && !context.plan.createTagKeys.containsAll(${set(keys)}))`;
|
|
136
|
+
return forbid(options.id ?? "require-create-tags", options.appliesTo, options.strict ? `(${missing} || context.plan.untaggedCreates > 0)` : missing, `Every created resource carries the tags ${keys.join(", ")}.`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Permit an agent to pass a gate on its own for a plan that deletes and
|
|
140
|
+
* replaces nothing, changes at most `maxChanges` resources, and planned
|
|
141
|
+
* every member in full (no failed member, no hole). In `enforce` mode that
|
|
142
|
+
* permit passes the gate; any forbid in the same set still wins.
|
|
143
|
+
*/
|
|
144
|
+
export function permitLowRiskAgents(options = {}) {
|
|
145
|
+
const max = count("permitLowRiskAgents: maxChanges", options.maxChanges, 10);
|
|
146
|
+
return new Policy({
|
|
147
|
+
effect: "permit",
|
|
148
|
+
principal: { is: AGENT },
|
|
149
|
+
action: { eq: PASS_GATE },
|
|
150
|
+
when: [
|
|
151
|
+
"context has plan && " +
|
|
152
|
+
`context.plan.deletes == 0 && context.plan.replaces == 0 && context.plan.changes <= ${max} && ` +
|
|
153
|
+
"context.plan.failed == 0 && context.plan.holes == 0",
|
|
154
|
+
],
|
|
155
|
+
annotations: {
|
|
156
|
+
id: options.id ?? "permit-low-risk-agents",
|
|
157
|
+
description: `An agent may pass a plan that deletes and replaces nothing and changes at most ${max} resources.`,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Permit an agent to pass a plan that only changes tags or labels. Default id `permit-tag-only-agents`. */
|
|
162
|
+
export function permitTagOnlyAgents(options = {}) {
|
|
163
|
+
return new Policy({
|
|
164
|
+
effect: "permit",
|
|
165
|
+
principal: { is: AGENT },
|
|
166
|
+
action: { eq: PASS_GATE },
|
|
167
|
+
when: ["context has plan && context.plan.tagOnly && context.plan.failed == 0 && context.plan.holes == 0"],
|
|
168
|
+
annotations: {
|
|
169
|
+
id: options.id ?? "permit-tag-only-agents",
|
|
170
|
+
description: "An agent may pass a plan that only changes tags or labels.",
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The pack as one record for `gatePolicy(name, starterGatePolicies(...))`:
|
|
176
|
+
* stateful types protected, deletes capped, regions and tags when given,
|
|
177
|
+
* and the two agent permits.
|
|
178
|
+
*/
|
|
179
|
+
export function starterGatePolicies(options = {}) {
|
|
180
|
+
return {
|
|
181
|
+
protectStateful: protectStateful(options.statefulTypes ? { types: options.statefulTypes } : {}),
|
|
182
|
+
capDeletes: capDeletes({ max: options.maxDeletes ?? 0 }),
|
|
183
|
+
...(options.maxReplacements !== undefined ? { capReplacements: capReplacements({ max: options.maxReplacements }) } : {}),
|
|
184
|
+
...(options.regions ? { allowRegions: allowRegions({ regions: options.regions }) } : {}),
|
|
185
|
+
...(options.requiredTags ? { requireCreateTags: requireCreateTags({ keys: options.requiredTags }) } : {}),
|
|
186
|
+
permitLowRiskAgents: permitLowRiskAgents(options.agentMaxChanges !== undefined ? { maxChanges: options.agentMaxChanges } : {}),
|
|
187
|
+
permitTagOnlyAgents: permitTagOnlyAgents(),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cedar as a gate's approval policy (chant#2508).
|
|
3
|
+
*
|
|
4
|
+
* Two halves, one per side of the build:
|
|
5
|
+
*
|
|
6
|
+
* - {@link gatePolicy} renders a set of cedar `Policy` declarables to Cedar
|
|
7
|
+
* text at authoring time and returns core's `GatePolicyRef`, plain data that
|
|
8
|
+
* lands in the Op's build output. A gate names it as `approval.policy`.
|
|
9
|
+
* - {@link evaluateGatePolicy} is what core's `loadGatePolicyEvaluator("cedar")`
|
|
10
|
+
* imports from `@intentius/chant-lexicon-cedar/gate-policy`. `chant approve`
|
|
11
|
+
* calls it once per approval with a `PassGate` request, through the same
|
|
12
|
+
* `cedar-wasm` the post-synth checks load (`./lint/post-synth/wasm-helpers.ts`).
|
|
13
|
+
*
|
|
14
|
+
* The request uses these entity types, so a policy is written against them:
|
|
15
|
+
*
|
|
16
|
+
* | Position | Entity | Attributes / parents |
|
|
17
|
+
* |-----------|------------------------------------------|----------------------|
|
|
18
|
+
* | principal | `Chant::Human::"<name>"` or `Chant::Agent::"<name>"` | parents: one `Chant::Role::"<role>"` per claimed role |
|
|
19
|
+
* | action | `Chant::Action::"PassGate"` | |
|
|
20
|
+
* | resource | `Chant::Gate::"<op>/<gate>"` | `op`, `gate` |
|
|
21
|
+
* | context | `planDigest` when the gate binds a plan, plus the gate's `approval.context` | |
|
|
22
|
+
*
|
|
23
|
+
* So `principal is Chant::Agent` picks out an agent, `principal in
|
|
24
|
+
* Chant::Role::"maintainer"` a role, and `context.risk == "low"` a declared
|
|
25
|
+
* plan attribute.
|
|
26
|
+
*/
|
|
27
|
+
import { gatePolicyVersion, } from "@intentius/chant/op";
|
|
28
|
+
import { getProps, resolvePolicyId } from "./policy-text.js";
|
|
29
|
+
import { CEDAR_POLICY_TYPE, renderPolicyText } from "./serializer.js";
|
|
30
|
+
import { describeError, loadWasm, wasmLoadError } from "./lint/post-synth/wasm-helpers.js";
|
|
31
|
+
export const GATE_HUMAN_TYPE = "Chant::Human";
|
|
32
|
+
export const GATE_AGENT_TYPE = "Chant::Agent";
|
|
33
|
+
export const GATE_ROLE_TYPE = "Chant::Role";
|
|
34
|
+
export const GATE_RESOURCE_TYPE = "Chant::Gate";
|
|
35
|
+
/** `action == Chant::Action::"PassGate"`, ready for a `Policy`'s `action` scope. */
|
|
36
|
+
export const PASS_GATE_ACTION = 'Chant::Action::"PassGate"';
|
|
37
|
+
function entries(name, policies) {
|
|
38
|
+
if (Array.isArray(policies)) {
|
|
39
|
+
return policies.map((p, i) => [`${name}-${i}`, p]);
|
|
40
|
+
}
|
|
41
|
+
if (isDeclarable(policies))
|
|
42
|
+
return [[name, policies]];
|
|
43
|
+
return Object.entries(policies);
|
|
44
|
+
}
|
|
45
|
+
function isDeclarable(value) {
|
|
46
|
+
return typeof value === "object" && value !== null && "entityType" in value;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A gate policy set from cedar `Policy` declarables. `name` is recorded with
|
|
50
|
+
* every decision; ids come from each policy's `annotations.id`, else its
|
|
51
|
+
* record key, else `<name>-<index>`.
|
|
52
|
+
*
|
|
53
|
+
* Throws when a member is not a cedar policy, or when the rendered set does
|
|
54
|
+
* not parse, so a broken policy fails where it is written and not at the
|
|
55
|
+
* first `chant approve`.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* export const shipPolicy = gatePolicy("ship", {
|
|
60
|
+
* agentLowRisk: new Policy({
|
|
61
|
+
* effect: "permit",
|
|
62
|
+
* principal: { is: GATE_AGENT_TYPE },
|
|
63
|
+
* action: { eq: PASS_GATE_ACTION },
|
|
64
|
+
* when: ['context.risk == "low"'],
|
|
65
|
+
* }),
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function gatePolicy(name, policies) {
|
|
70
|
+
const members = entries(name, policies);
|
|
71
|
+
if (members.length === 0)
|
|
72
|
+
throw new Error(`gatePolicy("${name}"): the policy set is empty`);
|
|
73
|
+
const texts = members.map(([logical, entity]) => {
|
|
74
|
+
if (entity.entityType !== CEDAR_POLICY_TYPE) {
|
|
75
|
+
throw new Error(`gatePolicy("${name}"): "${logical}" is a ${entity.entityType}, not a ${CEDAR_POLICY_TYPE}`);
|
|
76
|
+
}
|
|
77
|
+
const props = getProps(entity);
|
|
78
|
+
return renderPolicyText(resolvePolicyId(logical, props), props);
|
|
79
|
+
});
|
|
80
|
+
const text = texts.join("\n\n") + "\n";
|
|
81
|
+
const wasm = loadWasm();
|
|
82
|
+
if (wasm) {
|
|
83
|
+
const parsed = wasm.checkParsePolicySet({ staticPolicies: text });
|
|
84
|
+
if (parsed.type === "failure") {
|
|
85
|
+
throw new Error(`gatePolicy("${name}"): the policy set does not parse: ${parsed.errors.map(describeError).join("; ")}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { kind: "gate-policy", lexicon: "cedar", name, version: gatePolicyVersion(text), text };
|
|
89
|
+
}
|
|
90
|
+
/** A context value as Cedar JSON. Cedar has no floats or null, so those become strings or are dropped. */
|
|
91
|
+
function cedarValue(value) {
|
|
92
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
93
|
+
return value;
|
|
94
|
+
if (typeof value === "number")
|
|
95
|
+
return Number.isInteger(value) ? value : String(value);
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
return value.map(cedarValue).filter((v) => v !== undefined);
|
|
98
|
+
}
|
|
99
|
+
if (typeof value === "object" && value !== null) {
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const [k, v] of Object.entries(value)) {
|
|
102
|
+
const converted = cedarValue(v);
|
|
103
|
+
if (converted !== undefined)
|
|
104
|
+
out[k] = converted;
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The set as a record keyed by each policy's `@id`. Handed the text whole,
|
|
112
|
+
* cedar-wasm names policies by position (`policy0`), and a recorded decision
|
|
113
|
+
* would then name a rule nobody wrote. A policy with no `@id` keeps its
|
|
114
|
+
* positional name. Exported for `./gate-schema.ts`, so validation names rules the same way.
|
|
115
|
+
*/
|
|
116
|
+
export function keyedById(wasm, policy) {
|
|
117
|
+
const parts = wasm.policySetTextToParts(policy.text);
|
|
118
|
+
if (parts.type === "failure") {
|
|
119
|
+
throw new Error(`policy "${policy.name}" does not parse: ${parts.errors.map(describeError).join("; ")}`);
|
|
120
|
+
}
|
|
121
|
+
const keyed = {};
|
|
122
|
+
parts.policies.forEach((text, index) => {
|
|
123
|
+
const id = /@id\(\s*"((?:[^"\\]|\\.)*)"\s*\)/.exec(text)?.[1] ?? `policy${index}`;
|
|
124
|
+
if (id in keyed)
|
|
125
|
+
throw new Error(`policy "${policy.name}" has two policies with id "${id}"`);
|
|
126
|
+
keyed[id] = text;
|
|
127
|
+
});
|
|
128
|
+
return keyed;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Evaluate one `PassGate` request. Called by core through
|
|
132
|
+
* `loadGatePolicyEvaluator("cedar")`; see the module doc for the entity model.
|
|
133
|
+
*
|
|
134
|
+
* Throws when the wasm cannot be loaded or the request cannot be evaluated at
|
|
135
|
+
* all, so `chant approve` refuses rather than recording a decision nobody
|
|
136
|
+
* made. A policy that errors on this request is reported in `errors` and does
|
|
137
|
+
* not apply, which is Cedar's own rule.
|
|
138
|
+
*/
|
|
139
|
+
export function evaluateGatePolicy(policy, request) {
|
|
140
|
+
const wasm = loadWasm();
|
|
141
|
+
if (!wasm)
|
|
142
|
+
throw new Error(`@cedar-policy/cedar-wasm could not be loaded: ${wasmLoadError()}`);
|
|
143
|
+
const principalType = request.principal.kind === "agent" ? GATE_AGENT_TYPE : GATE_HUMAN_TYPE;
|
|
144
|
+
const roles = [...new Set(request.principal.roles)];
|
|
145
|
+
const principal = { type: principalType, id: request.principal.name };
|
|
146
|
+
const resource = { type: GATE_RESOURCE_TYPE, id: `${request.resource.op}/${request.resource.gate}` };
|
|
147
|
+
const answer = wasm.isAuthorized({
|
|
148
|
+
principal,
|
|
149
|
+
action: { type: "Chant::Action", id: request.action },
|
|
150
|
+
resource,
|
|
151
|
+
context: (cedarValue(request.context) ?? {}),
|
|
152
|
+
policies: { staticPolicies: keyedById(wasm, policy) },
|
|
153
|
+
entities: [
|
|
154
|
+
{ uid: principal, attrs: {}, parents: roles.map((id) => ({ type: GATE_ROLE_TYPE, id })) },
|
|
155
|
+
...roles.map((id) => ({ uid: { type: GATE_ROLE_TYPE, id }, attrs: {}, parents: [] })),
|
|
156
|
+
{ uid: resource, attrs: { op: request.resource.op, gate: request.resource.gate }, parents: [] },
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
if (answer.type === "failure") {
|
|
160
|
+
throw new Error(`policy "${policy.name}" could not be evaluated: ${answer.errors.map(describeError).join("; ")}`);
|
|
161
|
+
}
|
|
162
|
+
const { decision, diagnostics } = answer.response;
|
|
163
|
+
return {
|
|
164
|
+
decision,
|
|
165
|
+
determining: [...diagnostics.reason].sort(),
|
|
166
|
+
errors: diagnostics.errors.map((e) => `${e.policyId}: ${describeError(e.error)}`),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Cedar schema of a gate's `PassGate` request (chant#3182).
|
|
3
|
+
*
|
|
4
|
+
* {@link GATE_CEDAR_SCHEMA} declares the `Chant` namespace `evaluateGatePolicy`
|
|
5
|
+
* builds requests in (`./gate-policy.ts`), with the plan summary core puts in
|
|
6
|
+
* `context.plan` (`@intentius/chant/op`'s `GatePlanSummary`) as the common type
|
|
7
|
+
* `Chant::PlanSummary`. Two uses:
|
|
8
|
+
*
|
|
9
|
+
* - {@link validateGatePolicy} checks a gate policy set against it, so a rule
|
|
10
|
+
* that reads `context.plan.deleteCount` (a field that does not exist) fails
|
|
11
|
+
* where it is written rather than erroring on every request.
|
|
12
|
+
* - A project that writes gate policies with typed `Policy` scopes puts this
|
|
13
|
+
* text in its own `.cedarschema` (or hands {@link gateCedarSchema}'s output
|
|
14
|
+
* to `new Schema({ text })`), and the generated scope types then name
|
|
15
|
+
* `Chant::Agent` and `Chant::Action::"PassGate"` without a cast.
|
|
16
|
+
*
|
|
17
|
+
* A gate's own `approval.context` keys differ per gate, so the schema declares
|
|
18
|
+
* only `planDigest` and `plan` unless {@link gateCedarSchema} is told the rest.
|
|
19
|
+
* Every context attribute is optional: a gate with no plan, or a plan step
|
|
20
|
+
* that returned no change set, has neither.
|
|
21
|
+
*/
|
|
22
|
+
import { loadWasm, validatePolicySet, wasmLoadError } from "./lint/post-synth/wasm-helpers.js";
|
|
23
|
+
import { keyedById } from "./gate-policy.js";
|
|
24
|
+
/**
|
|
25
|
+
* Each `GatePlanSummary` field as a Cedar type. Typed as a record over the
|
|
26
|
+
* summary's keys, so a field added to the summary in core fails to compile
|
|
27
|
+
* here until the schema says what it is.
|
|
28
|
+
*/
|
|
29
|
+
export const GATE_PLAN_SUMMARY_CEDAR_TYPES = {
|
|
30
|
+
members: "Set<String>",
|
|
31
|
+
membersChanged: "Set<String>",
|
|
32
|
+
failedMembers: "Set<String>",
|
|
33
|
+
failed: "Long",
|
|
34
|
+
holes: "Long",
|
|
35
|
+
entries: "Long",
|
|
36
|
+
changes: "Long",
|
|
37
|
+
creates: "Long",
|
|
38
|
+
updates: "Long",
|
|
39
|
+
replaces: "Long",
|
|
40
|
+
deletes: "Long",
|
|
41
|
+
reads: "Long",
|
|
42
|
+
noOps: "Long",
|
|
43
|
+
forgets: "Long",
|
|
44
|
+
types: "Set<String>",
|
|
45
|
+
createdTypes: "Set<String>",
|
|
46
|
+
updatedTypes: "Set<String>",
|
|
47
|
+
replacedTypes: "Set<String>",
|
|
48
|
+
deletedTypes: "Set<String>",
|
|
49
|
+
deleted: "Set<String>",
|
|
50
|
+
replaced: "Set<String>",
|
|
51
|
+
regions: "Set<String>",
|
|
52
|
+
scopes: "Set<String>",
|
|
53
|
+
lexicons: "Set<String>",
|
|
54
|
+
taggedCreates: "Long",
|
|
55
|
+
untaggedCreates: "Long",
|
|
56
|
+
createTagKeys: "Set<String>",
|
|
57
|
+
tagOnly: "Bool",
|
|
58
|
+
truncated: "Bool",
|
|
59
|
+
};
|
|
60
|
+
const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
61
|
+
/** The schema text, with the gate's own context attributes when given. */
|
|
62
|
+
export function gateCedarSchema(options = {}) {
|
|
63
|
+
const extra = Object.entries(options.context ?? {});
|
|
64
|
+
for (const [key, type] of extra) {
|
|
65
|
+
if (!IDENT.test(key))
|
|
66
|
+
throw new Error(`gateCedarSchema: context attribute "${key}" is not a Cedar identifier`);
|
|
67
|
+
if (key === "plan" || key === "planDigest")
|
|
68
|
+
throw new Error(`gateCedarSchema: context attribute "${key}" is chant's own`);
|
|
69
|
+
if (type.trim() === "")
|
|
70
|
+
throw new Error(`gateCedarSchema: context attribute "${key}" has no type`);
|
|
71
|
+
}
|
|
72
|
+
const summary = Object.entries(GATE_PLAN_SUMMARY_CEDAR_TYPES)
|
|
73
|
+
.map(([k, t]) => ` "${k}": ${t},`)
|
|
74
|
+
.join("\n");
|
|
75
|
+
const context = [
|
|
76
|
+
` "planDigest"?: String,`,
|
|
77
|
+
` "plan"?: PlanSummary,`,
|
|
78
|
+
...extra.map(([k, t]) => ` "${k}"?: ${t},`),
|
|
79
|
+
].join("\n");
|
|
80
|
+
return `// The request chant builds for a gate approval (chant#2508, #3182).
|
|
81
|
+
namespace Chant {
|
|
82
|
+
// context.plan: the plan's change-set summary (GatePlanSummary).
|
|
83
|
+
type PlanSummary = {
|
|
84
|
+
${summary}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// A role a human or agent claimed when approving.
|
|
88
|
+
entity Role;
|
|
89
|
+
entity Human in [Role];
|
|
90
|
+
entity Agent in [Role];
|
|
91
|
+
// "<op>/<gate>".
|
|
92
|
+
entity Gate = {
|
|
93
|
+
"op": String,
|
|
94
|
+
"gate": String,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
action PassGate appliesTo {
|
|
98
|
+
principal: [Human, Agent],
|
|
99
|
+
resource: [Gate],
|
|
100
|
+
context: {
|
|
101
|
+
${context}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
/** The schema with no gate-specific context: `planDigest` and `plan` only. */
|
|
108
|
+
export const GATE_CEDAR_SCHEMA = gateCedarSchema();
|
|
109
|
+
/**
|
|
110
|
+
* Validate a gate policy set against the gate schema. Throws when the wasm
|
|
111
|
+
* cannot be loaded or validation cannot run at all (a schema that does not
|
|
112
|
+
* parse); findings come back sorted.
|
|
113
|
+
*/
|
|
114
|
+
export function validateGatePolicy(policy, options = {}) {
|
|
115
|
+
const wasm = loadWasm();
|
|
116
|
+
if (!wasm)
|
|
117
|
+
throw new Error(`@cedar-policy/cedar-wasm could not be loaded: ${wasmLoadError()}`);
|
|
118
|
+
const outcome = validatePolicySet(wasm, { staticPolicies: keyedById(wasm, policy) }, gateCedarSchema(options));
|
|
119
|
+
if (outcome.failure)
|
|
120
|
+
throw new Error(`policy "${policy.name}" could not be validated: ${outcome.failure}`);
|
|
121
|
+
return { errors: outcome.errors, warnings: outcome.warnings };
|
|
122
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createProperty, createResource } from "./runtime.js";
|
|
2
|
+
/**
|
|
3
|
+
* The policy resource. Declare one per policy:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* export const allowOwnerRead = new Policy({
|
|
7
|
+
* principal: { is: "App::User" },
|
|
8
|
+
* action: { eq: ReadAction },
|
|
9
|
+
* when: ['resource.owner == principal'],
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const Policy = createResource("Cedar::Policy", "cedar", {});
|
|
14
|
+
export const Application = createResource("App::Application", "cedar", {});
|
|
15
|
+
export const ApplicationAttributes = createProperty("App::Application.Attributes", "cedar");
|
|
16
|
+
export const Document = createResource("App::Document", "cedar", {});
|
|
17
|
+
export const DocumentAttributes = createProperty("App::Document.Attributes", "cedar");
|
|
18
|
+
export const Folder = createResource("App::Folder", "cedar", {});
|
|
19
|
+
export const FolderAttributes = createProperty("App::Folder.Attributes", "cedar");
|
|
20
|
+
export const Group = createResource("App::Group", "cedar", {});
|
|
21
|
+
export const GroupAttributes = createProperty("App::Group.Attributes", "cedar");
|
|
22
|
+
export const ServiceAccount = createResource("App::ServiceAccount", "cedar", {});
|
|
23
|
+
export const ServiceAccountAttributes = createProperty("App::ServiceAccount.Attributes", "cedar");
|
|
24
|
+
export const Team = createResource("App::Team", "cedar", {});
|
|
25
|
+
export const TeamAttributes = createProperty("App::Team.Attributes", "cedar");
|
|
26
|
+
export const User = createResource("App::User", "cedar", {});
|
|
27
|
+
export const UserAttributes = createProperty("App::User.Attributes", "cedar");
|
|
28
|
+
/**
|
|
29
|
+
* `App::Action::"admin"`.
|
|
30
|
+
* Principals: App::User.
|
|
31
|
+
* Resources: App::Application.
|
|
32
|
+
*/
|
|
33
|
+
export const AdminAction = "App::Action::\"admin\"";
|
|
34
|
+
export const AdminContext = createProperty("App::Action::\"admin\".Context", "cedar");
|
|
35
|
+
/**
|
|
36
|
+
* `App::Action::"approve"`.
|
|
37
|
+
* Principals: App::User.
|
|
38
|
+
* Resources: App::Document.
|
|
39
|
+
*/
|
|
40
|
+
export const ApproveAction = "App::Action::\"approve\"";
|
|
41
|
+
export const ApproveContext = createProperty("App::Action::\"approve\".Context", "cedar");
|
|
42
|
+
/**
|
|
43
|
+
* `App::Action::"comment"`.
|
|
44
|
+
* Principals: App::User.
|
|
45
|
+
* Resources: App::Document, App::Folder.
|
|
46
|
+
*/
|
|
47
|
+
export const CommentAction = "App::Action::\"comment\"";
|
|
48
|
+
export const CommentContext = createProperty("App::Action::\"comment\".Context", "cedar");
|
|
49
|
+
/**
|
|
50
|
+
* `App::Action::"create"`.
|
|
51
|
+
* Principals: App::User.
|
|
52
|
+
* Resources: App::Document, App::Folder.
|
|
53
|
+
*/
|
|
54
|
+
export const CreateAction = "App::Action::\"create\"";
|
|
55
|
+
export const CreateContext = createProperty("App::Action::\"create\".Context", "cedar");
|
|
56
|
+
/**
|
|
57
|
+
* `App::Action::"delete"`.
|
|
58
|
+
* Principals: App::User.
|
|
59
|
+
* Resources: App::Document.
|
|
60
|
+
*/
|
|
61
|
+
export const DeleteAction = "App::Action::\"delete\"";
|
|
62
|
+
export const DeleteContext = createProperty("App::Action::\"delete\".Context", "cedar");
|
|
63
|
+
/**
|
|
64
|
+
* `App::Action::"list"`.
|
|
65
|
+
* Principals: App::ServiceAccount, App::User.
|
|
66
|
+
* Resources: App::Document, App::Folder.
|
|
67
|
+
*/
|
|
68
|
+
export const ListAction = "App::Action::\"list\"";
|
|
69
|
+
export const ListContext = createProperty("App::Action::\"list\".Context", "cedar");
|
|
70
|
+
/**
|
|
71
|
+
* `App::Action::"read"`.
|
|
72
|
+
* Principals: App::ServiceAccount, App::User.
|
|
73
|
+
* Resources: App::Document, App::Folder.
|
|
74
|
+
*/
|
|
75
|
+
export const ReadAction = "App::Action::\"read\"";
|
|
76
|
+
export const ReadContext = createProperty("App::Action::\"read\".Context", "cedar");
|
|
77
|
+
/**
|
|
78
|
+
* `App::Action::"share"`.
|
|
79
|
+
* Principals: App::User.
|
|
80
|
+
* Resources: App::Document.
|
|
81
|
+
*/
|
|
82
|
+
export const ShareAction = "App::Action::\"share\"";
|
|
83
|
+
export const ShareContext = createProperty("App::Action::\"share\".Context", "cedar");
|
|
84
|
+
/**
|
|
85
|
+
* `App::Action::"write"`.
|
|
86
|
+
* Principals: App::User.
|
|
87
|
+
* Resources: App::Document, App::Folder.
|
|
88
|
+
*/
|
|
89
|
+
export const WriteAction = "App::Action::\"write\"";
|
|
90
|
+
export const WriteContext = createProperty("App::Action::\"write\".Context", "cedar");
|
|
91
|
+
/** Every generated action constant, for exhaustive iteration. */
|
|
92
|
+
export const ALL_ACTIONS = [AdminAction, ApproveAction, CommentAction, CreateAction, DeleteAction, ListAction, ReadAction, ShareAction, WriteAction];
|
|
93
|
+
/** Every entity type name the schema declares. */
|
|
94
|
+
export const ALL_ENTITY_TYPES = ["App::Application", "App::Document", "App::Folder", "App::Group", "App::ServiceAccount", "App::Team", "App::User"];
|