Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.io/library/rust:1.84.1-bookworm
FROM docker.io/library/rust:1.91.1-bookworm

RUN rustup component add rustfmt clippy

Expand Down
2 changes: 1 addition & 1 deletion bundler/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.io/library/rust:1.84.1-bookworm AS build
FROM docker.io/library/rust:1.91.1-bookworm AS build

ARG DATABASE_URL

Expand Down
8 changes: 6 additions & 2 deletions policy/diamond/policy/admin/admin.rego
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package diamond.policy.admin
import data.diamond.policy.token
import rego.v1

is_admin[subject] := "super_admin" in data.diamond.data.subjects[subject].permissions
default is_admin(_) := false

is_admin(subject) if {
"super_admin" in data.diamond.data.subjects[subject].permissions
}

beamline_admin_for_subject[subject_name] contains beamline if {
some subject_name, subject in data.diamond.data.subjects
Expand All @@ -13,7 +17,7 @@ beamline_admin_for_subject[subject_name] contains beamline if {
some beamline in role_beamlines
}

admin := is_admin[token.claims.fedid] # regal ignore:rule-name-repeats-package
admin := is_admin(token.claims.fedid) # regal ignore:rule-name-repeats-package

beamline_admin := input.beamline in object.get(beamline_admin_for_subject, token.claims.fedid, [])

Expand Down
6 changes: 3 additions & 3 deletions policy/diamond/policy/admin/admin_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ diamond_data := {
}

test_is_admin_for_admin if {
admin.is_admin.carol with data.diamond.data as diamond_data
admin.is_admin("carol") with data.diamond.data as diamond_data
}

test_beamline_admin_for_subject_for_beamline_admin if {
Expand All @@ -45,11 +45,11 @@ test_beamlines_admin_for_subject_for_group_admin if {
}

test_is_admin_for_non_admin if {
not admin.is_admin.alice with data.diamond.data as diamond_data
not admin.is_admin("alice") with data.diamond.data as diamond_data
}

test_is_admin_for_beamline_admin_not_admin if {
not admin.is_admin.bob with data.diamond.data as diamond_data
not admin.is_admin("bob") with data.diamond.data as diamond_data
}

test_beamline_admin_for_subject_for_non_beamline_admin if {
Expand Down
2 changes: 1 addition & 1 deletion policy/diamond/policy/proposal/proposal.rego
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on_proposal(subject, proposal_number) if {
default access_proposal(_, _) := false

# Allow if subject has super_admin permission
access_proposal(subject, proposal_number) if admin.is_admin[subject] # regal ignore:external-reference
access_proposal(subject, proposal_number) if admin.is_admin(subject) # regal ignore:external-reference

# Allow if subject is on proposal
access_proposal(subject, proposal_number) if on_proposal(subject, proposal_number)
Expand Down
8 changes: 7 additions & 1 deletion policy/diamond/policy/session/session.rego
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ on_session(subject, proposal_number, visit_number) if {
default access_session(_, _, _) := false

# Allow if subject has super_admin permission
access_session(subject, proposal_number, visit_number) if admin.is_admin[subject] # regal ignore:external-reference
access_session(subject, proposal_number, visit_number) if admin.is_admin(subject) # regal ignore:external-reference

# Allow if subject is admin for beamline containing session
access_session(subject, proposal_number, visit_number) if {
Expand Down Expand Up @@ -55,3 +55,9 @@ write_to_beamline_visit if {
access
matches_beamline
}

user_sessions contains user_session if {
some session in data.diamond.data.sessions
access_session(token.claims.fedid, session.proposal_number, session.visit_number)
user_session := sprintf("%s", [session])
}
70 changes: 65 additions & 5 deletions policy/diamond/policy/session/session_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ diamond_data := {
"proposals": [],
"sessions": [],
},
"desmond": {
"permissions": [],
"proposals": [2],
"sessions": [13],
},
"edna": {
"permissions": [],
"proposals": [2],
"sessions": [13, 14],
},
"oscar": {
"permissions": [],
"proposals": [],
Expand All @@ -37,12 +47,28 @@ diamond_data := {
"proposal_number": 1,
"visit_number": 2,
},
"13": {
"beamline": "b07",
"proposal_number": 2,
"visit_number": 1,
},
"14": {
"beamline": "b07",
"proposal_number": 2,
"visit_number": 2,
},
},
"proposals": {
"1": {"sessions": {
"1": 11,
"2": 12,
}},
"2": {"sessions": {
"1": 13,
"2": 14,
}},
},
"proposals": {"1": {"sessions": {
"1": 11,
"2": 12,
}}},
"beamlines": {"i03": {"sessions": [11]}, "b07": {"sessions": [12]}},
"beamlines": {"i03": {"sessions": [11]}, "b07": {"sessions": [12, 13, 14]}},
"admin": {"b07_admin": ["b07"]},
}

Expand Down Expand Up @@ -181,3 +207,37 @@ test_session_beamline if {
with data.diamond.data as diamond_data
bl2 == "b07"
}

test_user_session_tags if {
session.user_sessions == set() with data.diamond.data as diamond_data
with data.diamond.policy.token.claims as {"fedid": "oscar"}
session.user_sessions == {
"{\"beamline\": \"b07\", \"proposal_number\": 1, \"visit_number\": 2}",
"{\"beamline\": \"i03\", \"proposal_number\": 1, \"visit_number\": 1}",
} with data.diamond.data as diamond_data
with data.diamond.policy.token.claims as {"fedid": "alice"}
session.user_sessions == {
"{\"beamline\": \"b07\", \"proposal_number\": 1, \"visit_number\": 2}",
"{\"beamline\": \"i03\", \"proposal_number\": 1, \"visit_number\": 1}",
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 1}",
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 2}",
} with data.diamond.data as diamond_data
with data.diamond.policy.token.claims as {"fedid": "bob"}
session.user_sessions == {
"{\"beamline\": \"b07\", \"proposal_number\": 1, \"visit_number\": 2}",
"{\"beamline\": \"i03\", \"proposal_number\": 1, \"visit_number\": 1}",
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 1}",
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 2}",
} with data.diamond.data as diamond_data
with data.diamond.policy.token.claims as {"fedid": "carol"}
session.user_sessions == {
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 1}",
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 2}",
} with data.diamond.data as diamond_data
with data.diamond.policy.token.claims as {"fedid": "desmond"}
session.user_sessions == {
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 1}",
"{\"beamline\": \"b07\", \"proposal_number\": 2, \"visit_number\": 2}",
} with data.diamond.data as diamond_data
with data.diamond.policy.token.claims as {"fedid": "edna"}
}
33 changes: 33 additions & 0 deletions policy/diamond/policy/tiled/tiled.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package diamond.policy.tiled

import data.diamond.policy.token

read_scopes := {
"read:metadata",
"read:data",
}

write_scopes := {
"write:metadata",
"write:data",
"create",
"register",
}

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

}

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)
}
Comment on lines +23 to +30
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>.


default scopes := set()

scopes := scopes_for(token.claims)
26 changes: 26 additions & 0 deletions policy/diamond/policy/tiled/tiled_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package diamond.policy.tiled_test

import data.diamond.policy.tiled
import data.diamond.policy.token
import rego.v1

test_default_no_scopes if {
tiled.scopes == set()
}

test_wrong_azp_read_scopes if {
tiled.scopes == tiled.read_scopes with token.claims as {}
tiled.scopes == tiled.read_scopes with token.claims as {"sub": "foo"}
tiled.scopes == tiled.read_scopes with token.claims as {"azp": "foo"}
}

test_blueapi_given_write_scopes if {
tiled.scopes == {
"read:metadata",
"read:data",
"write:metadata",
"write:data",
"create",
"register",
} with token.claims as {"azp": "foo-blueapi"}
}
Loading