Skip to content

feat: update tiled policy to support layered node - #345

Open
ZohebShaikh wants to merge 4 commits into
mainfrom
tiled-policy-changes
Open

ZohebShaikh wants to merge 4 commits into
mainfrom
tiled-policy-changes

Conversation

@ZohebShaikh

@ZohebShaikh ZohebShaikh commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR changes the Tiled authorization policy so access can be granted at three levels: beamline → proposal → session. Before, it only worked with flat session IDs.

What changes

1. Hierarchical access tags replace bare session IDs
Nodes used to be tagged with a session ID such as "11". They now get a path-style tag that names every level above them:

  • beamline:i03
  • beamline:i03,proposal:1
  • beamline:i03,proposal:1,session:1-1

Each tag names its parents, so Tiled can check access at any level of the tree with one set-membership test.

2. user_session (create-node) can now create all three levels
Tiled's init_node doesn't pass a path or parent, so the policy works out the level from which input fields are present:

Input fields Node level Who's allowed
beamline only Beamline Users in beamline.user_beamlines
beamline + proposal Proposal Users with proposal access, or a service account on that beamline, and the proposal must run on that beamline
beamline + proposal + visit Session Users with session access, or a service account on that beamline, and the session must exist on that beamline

input.proposal is now a label such as "cm1234" and is reduced to 1234 by bare_proposal. Before, it was a plain integer.

3. user_sessions (list/filter) emits tags for all levels
For each session the caller can reach, it returns the session's tag plus its beamline and proposal tags, so the parent nodes stay visible in listings. Admins still get "*". The old local logic (direct, beamline-based and proposal-based sessions, plus service accounts) is removed. It now uses session.user_sessions from the shared session policy.

4. Service-account branches are stricter
The session-level and proposal-level service-account rules now require not token.claims.fedid. A token with both fedid and beamline claims is judged by the named user's own permissions. Before, a matching beamline claim could let such a token through. There's a new test for this.

5. Housekeeping

  • Added # METADATA doc blocks to each rule.
  • Changed imports: admin is replaced by beamline and proposal.
  • Reformatted with clauses in tiled_test.rego and ulims_test.rego, with no change in behaviour.
  • Added tests for creating beamline-level and proposal-level nodes, and updated the existing tests to the new tag format.

⚠️ Breaking changes

  • Tag format: existing Tiled nodes stamped with old IDs like "11" won't match the new tags in user_sessions, so they'll need a migration or re-tagging.
  • Input contract: callers must now send proposal as a label string instead of an int, and the Tiled side (dls.py) needs to change to match.

🤖 Generated with Claude Code

@fajinyuan

fajinyuan commented Oct 16, 2026 •

Copy link
Copy Markdown

could you provide a description for this ticket please?
PR description is missing; adding a summary of the behavioral change would help reviewers a lot

@ZohebShaikh

Copy link
Copy Markdown
Collaborator Author

Added as requested

# description: |
# Turns a proposal label into the plain number it is stored under,
# so "cm1234" becomes 1234.
bare_proposal(p) := to_number(trim_left(lower(p), "abcdefghijklmnopqrstuvwxyz"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This accepts any alphabetic prefix, not just cm. Can we tighten this so malformed proposal labels fail closed?

Comment on lines 109 to +119
}

# service account check
user_session := format_int(_session, 10) if {
# Session level, for a service account tied to the beamline.
user_session := session_tag(input.beamline, p, input.visit) if {
input.visit
not token.claims.fedid
input.beamline == token.claims.beamline
input.beamline == session.beamline_for(input.proposal, input.visit)
_session in data.diamond.data.beamlines[input.beamline].sessions
p := bare_proposal(input.proposal)
input.beamline == session.beamline_for(p, input.visit)
_session(p) in data.diamond.data.beamlines[input.beamline].sessions
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we confirm a token with both claims never falls back to the service-account branch if the named-user check fails?

Comment thread policy/diamond/policy/tiled/tiled.rego Outdated
Comment on lines +190 to +194

user_sessions contains beamline_tag(s.beamline) if {
not "*" in session.user_sessions
some id in session.user_sessions
s := data.diamond.data.sessions[id]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please confirm session.user_sessions is always complete and correct here; it now directly controls which Tiled nodes are visible.

ZohebShaikh and others added 3 commits October 17, 2026 12:24
user_sessions returned three tags for every session a caller could reach,
so a beamline admin's answer covered every session on the beamline (about
106k tags and ~4.7 s for a non-admin who can reach everything). Return
grants at the level they are held instead, and stamp each node with its
ancestors' grant tags so one grant matches the whole subtree:

- beamline admins and service accounts get the beamline tag
- proposal members get the proposal tag
- session members get the session tag

plus "visible:" tags for the containers above a grant, so they can be
listed without granting what else is under them. user_session now returns
the node's full tag list, own tag first.

The beamline claim in user_beamlines now only counts for service accounts:
a token that also carries a fedid was granted the whole beamline through
it, in tiled, session and ulims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calls tiled/user_session, tiled/user_sessions and tiled/scopes with the
inputs dls_tiled.policy's build_input sends, on a small i22 tree the
writer builds through user_session, and checks exactly which nodes each
kind of caller sees: service account, beamline admin, proposal member,
session member, super admin, nobody. One test shows that passing the
containers with grant tags instead of visible: tags would leak the whole
beamline. The header explains how tiled matches tags and why nodes carry
their ancestors' grants plus visible: tags on containers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI used the latest OPA and Regal, so a new release could change results
without any change here. Pin both, and the devcontainer's OPA, and make
the policy pass them: string interpolation where Regal now prefers it,
admin_test checks beamline_admin_for_subject by key (OPA 1.21's type
checker rejects `in` on it), and blueapi.rego drops an unused import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants