Skip to content

Conversation

@DiamondJoseph
Copy link
Collaborator

@DiamondJoseph DiamondJoseph commented Dec 3, 2025

I cannot modify the target for this PR to be into #291 as it is in Zoheb's fork, but the changes from that are required to get the CI passing again so they are included. #291 should be reviewed first.

Questions around: where should the Tiled specific policy live: a. the get-all-sessions for a user query, which is used for tiled but may find uses elsewhere, and given the time the query takes may benefit from a communal effort to improve and b. the Tiled specific scope query, which is Tiled specific (it may unilaterally change implementation but not return values) but is an authorization decision?


scopes_for(claims) := read_scopes | write_scopes if {
"azp" in object.keys(claims)
endswith(claims.azp, "-blueapi")
Copy link
Contributor

Choose a reason for hiding this comment

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

What is azp?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

"authorizing party"- the client-id that the token was minted for- i.e. this token has been one that a blueapi instance has set as headers to a request to tiled, not one that has been set in a tiled browser client or tiled login flow.

This of course then only works if your request originated with blueapi, meaning as soon as there's an experiment UI in front of that request it stops working. When we make use of token-exchange flow in keycloak from the incoming request token, I think we can request from keycloak a token with "aud": ["blueapi", "tiled"] and check that both audiences are present, then configure our services to never give both audiences otherwise? Or make use of some optional scope for the blueapi client - I can't see many other ways of checking that the request has come from a blueapi instance (which should be the only thing permitted to write to tiled) without checking something about the party sending the token.

https://www.keycloak.org/securing-apps/token-exchange

Comment on lines +22 to +29
scopes_for(claims) := read_scopes if {
"azp" in object.keys(claims)
not endswith(claims.azp, "-blueapi")
}

scopes_for(claims) := read_scopes if {
not "azp" in object.keys(claims)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these can be combined?

Suggested change
scopes_for(claims) := read_scopes if {
"azp" in object.keys(claims)
not endswith(claims.azp, "-blueapi")
}
scopes_for(claims) := read_scopes if {
not "azp" in object.keys(claims)
}
scopes_for(claims) := read_scopes if {
not "azp" in object.keys(claims)
not endswith(claims.azp, "-blueapi")
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  tiled_test.rego:12       | | Fail data.diamond.policy.tiled.scopes = data.diamond.policy.tiled.read_scopes with data.diamond.policy.token.claims as {}  

Looks like it fails the tests when "azp" not in token.claims? Presumably the first line is true, then it checks the second line and it isn't defined, so it returns no scopes?

There is already a token if we have made it here, it just doesn't have the azp claim- I believe it is optional, so an authenticated user could have make a request without it (I think right now we're only getting it because our audience is always account rather than <client-id>.

Copy link
Collaborator

@tpoliaw tpoliaw left a comment

Choose a reason for hiding this comment

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

I think tiled specific policy should be deployed alongside tiled (see application policy dev guide). The user_sessions policy could be in the core but would be better as a list of sessions instead of a list of JSON formatted strings.

Could we have some form of admin handling in tiled? Presumably for an admin, this list is every session that's ever happened at diamond?

The rust update should be its own PR.

some session in data.diamond.data.sessions
access_session(token.claims.fedid, session.proposal_number, session.visit_number)
user_session := sprintf(
`{"proposal": %d, "visit": %d, "beamline": "%s"}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we formatting JSON here? Doesn't OPA already return its data in JSON format?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OPA returns it in JSON but the inputs of the policy is proposal (and so we want tiled to store that key) but the key in the session object has proposal_number - same for visit[_number].

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could store proposal_number, visit_number and a json blob and have tiled do the mutation, but I think it should naively make an input from the access_blob https://github.com/bluesky/tiled/blob/397e6e6c149b9b718b5308a29c8a92692ba56d7e/tiled/access_control/dls.py#L64

We could keep it fairly self contained:

class DiamondAccessBlob(TypedDict):
    proposal: int = Field(serialization_alias="proposal_number")
    visit: int = Field(serialization_alias="visit_number")
    beamline: str

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

At some point we'd like to replace the access_blob with the session_id (either by Tiled swapping the blob it's given with proposal, visit, beamline or by blueapi querying OPA to get the session_id of the same) as it allows a faster comparison with an int

@DiamondJoseph
Copy link
Collaborator Author

I think tiled specific policy should be deployed alongside tiled (see application policy dev guide). The user_sessions policy could be in the core but would be better as a list of sessions instead of a list of JSON formatted strings.

That makes orchestrating changes in Tiled specific policies much simpler. We don't have an application in the DLS namespace to own the policies, could use Glazed or make its own one.

Could we have some form of admin handling in tiled? Presumably for an admin, this list is every session that's ever happened at diamond?

Yes, which is why it takes ~5s currently for any calls we make. Tiled shouldn't have any knowledge about who is an admin, but we can return an empty list for super_admin- Tiled does not filter any elements out if given an empty list?

@DiamondJoseph
Copy link
Collaborator Author

Tiled does not filter any elements out if given an empty list?

This is wrong, Tiled filters out all elements if given an empty list, but does not filter if returned None instead of a list. I think this is the opposite way than we would expect?

@tpoliaw
Copy link
Collaborator

tpoliaw commented Dec 8, 2025

I think that way makes sense? Only filter if you're given a list of filters. If you pass None, it doesn't do any filtering

@tpoliaw
Copy link
Collaborator

tpoliaw commented Dec 8, 2025

We don't have an application in the DLS namespace to own the policies, could use Glazed or make its own one.

It could be its own repo but deployed alongside our tiled deployment(s).

@ZohebShaikh
Copy link
Collaborator

Policy lint issues fixed in this #297

@keithralphs
Copy link

superseded by 297

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.

6 participants