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
+20
-4
@@ -224,10 +224,12 @@ pub struct Responsible {
|
||||
pub contact: String,
|
||||
}
|
||||
|
||||
/// Whether `user` may view/submit `question`. `true` when the question
|
||||
/// has no `qualifies` requirement. Mirrors `chat::is_authorized_for_room`
|
||||
/// in cnats - same synchronous, I/O-free shape, same staleness tradeoff
|
||||
/// (group membership is fixed at login, not re-checked live).
|
||||
/// Whether to render `question`'s alternatives or its sign-in gate for
|
||||
/// `user`. A rendering hint for the browser, which has no policy: the
|
||||
/// server decides every submit, read, move and listing through
|
||||
/// `access::Policy`, and this must agree with the `open` rule there
|
||||
/// (a page is open to anyone unless `qualifies` names a group).
|
||||
/// Group membership is fixed at login, not re-checked live.
|
||||
pub fn is_qualified(user: Option<&crate::auth::User>, question: &Question) -> bool {
|
||||
match &question.qualifies {
|
||||
None => true,
|
||||
@@ -276,6 +278,20 @@ pub struct Alternative {
|
||||
/// checked against the stored item, not the lookup key.
|
||||
#[serde(default)]
|
||||
pub self_transition: Option<SelfTransition>,
|
||||
/// Submitting this alternative invites a person into a Kanidm
|
||||
/// group: the form's `name` and `email` become an account, the
|
||||
/// group membership, and a one-time link to set their own
|
||||
/// credentials (see `people.rs`). Only on a page gated by
|
||||
/// `qualifies`: the inviting group is that page's, the invited
|
||||
/// group is this. The link lands in the runtime's `portal_invites`
|
||||
/// bucket for the inviter's desk, and on `portal.people.invited`.
|
||||
#[serde(default)]
|
||||
pub invite: Option<Invite>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Invite {
|
||||
pub group: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user