One access policy from content; onboarding into Kanidm as a form
Who may do what to what was five checks in five files: `qualifies` on
a page, `requires_group`/`public` on a resource, a desk's transition
allow-list, a self_transition's email check, the automation token.
They are now compiled from content on every load into one Cedar
policy set (src/access.rs), and every server entry point asks it: open
and submit a page, read a resource, move a record, fire a self
transition, read a bucket as the automation service, extend an
invite. Each decision names the rules that granted it; refusals and
every mutation are published on portal.access.decided, and
portal.access.<site>.may answers the same question for other apps
over request-reply (proven live against uhhm's content). The policy
validates against a fixed schema, so a content shape the compiler
cannot express fails instead of granting nothing. Identity is still
Kanidm's; the browser keeps is_qualified as a rendering hint only.
`invite: {group}` on an alternative of a gated page onboards a person:
src/people.rs creates the account, records the email, adds the group
and mints a one-time credential link through Kanidm's REST API with a
service-account token (KANIDM_API_TOKEN, in idm_people_on_boarding).
The result is recorded with the answer into the runtime's
portal_invites bucket and announced on portal.people.invited. Validated:
an invite needs a gated page and required name and email fields.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
7408fa972a
commit
eeeb89b217
@@ -48,6 +48,14 @@ aggregates:
|
||||
transitions:
|
||||
announced: [awaiting_summary, summarized]
|
||||
awaiting_summary: [summarized]
|
||||
- bucket: portal_invites
|
||||
attended_by: the inviting group's own desk, or the link in the invite event
|
||||
initial: open
|
||||
states:
|
||||
open: { event: invited }
|
||||
done: { event: done }
|
||||
transitions:
|
||||
open: [done]
|
||||
";
|
||||
|
||||
/// Adds the runtime's own state graphs to a content repo's. Content
|
||||
@@ -145,6 +153,27 @@ pub fn validate_questions(
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
if let Some(invite) = &alternative.invite {
|
||||
if question.qualifies.is_none() {
|
||||
anyhow::bail!(
|
||||
"question {:?} alternative {:?}: an invite into {:?} must sit on a page gated by `qualifies` - that group is who may extend it",
|
||||
question.id, alternative.name, invite.group
|
||||
);
|
||||
}
|
||||
let has = |name: &str| {
|
||||
alternative
|
||||
.features
|
||||
.iter()
|
||||
.flat_map(|f| &f.requirements)
|
||||
.any(|r| r.name == name && !r.optional)
|
||||
};
|
||||
if !has("name") || !has("email") {
|
||||
anyhow::bail!(
|
||||
"question {:?} alternative {:?}: an invite must collect required `name` and `email` fields",
|
||||
question.id, alternative.name
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(st) = &alternative.self_transition {
|
||||
if let Some(schema) = aggregates.get(&st.bucket) {
|
||||
if !schema.has_state(&st.to) {
|
||||
|
||||
Reference in New Issue
Block a user