From ae8b7d62531e7e43561008a81fd67cbfa3dee99c Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Fri, 4 Oct 2024 09:59:28 +0100 Subject: [PATCH] Add policy rules for session and proposal access * session.access: user can access session * session.named_user: user is a named member of the visit * session.matches_beamline: visit is on the given beamline * session.session_beamline: beamline for the given visit * proposal.access: user can access proposal * proposal.named_user: user is a named user on the proposal * admin.admin: user is super admin * admin.beamline_admin: user is admin for the given beamline Rules are only defined if the required fields are included in the input. `admin.beamline_admin` refers to the beamline passed as `input.beamline` not as the beamline for the session defined by `proposal`+`visit`. Previous function `session.beamline` has been renamed to `beamline_for` to distinguish it from the `session.beamline` rule. --- policy/diamond/policy/admin/admin.rego | 15 +-- policy/diamond/policy/admin/admin_test.rego | 89 ++++++++++++--- policy/diamond/policy/proposal/proposal.rego | 10 +- .../policy/proposal/proposal_test.rego | 53 +++++++++ policy/diamond/policy/session/session.rego | 21 +++- .../diamond/policy/session/session_test.rego | 101 ++++++++++++++++++ 6 files changed, 265 insertions(+), 24 deletions(-) diff --git a/policy/diamond/policy/admin/admin.rego b/policy/diamond/policy/admin/admin.rego index f4946d3..d8732ee 100644 --- a/policy/diamond/policy/admin/admin.rego +++ b/policy/diamond/policy/admin/admin.rego @@ -2,11 +2,14 @@ package diamond.policy.admin import rego.v1 -is_admin(subject) if { - "super_admin" in data.diamond.data.subjects[subject].permissions # regal ignore:external-reference -} +is_admin[subject] := "super_admin" in data.diamond.data.subjects[subject].permissions -is_beamline_admin(subject, beamline) if { - some admin in data.diamond.data.subjects[subject].permissions - beamline in data.diamond.data.admin[admin] # regal ignore:external-reference +beamline_admin_for_subject[subject] contains beamline if { + some subject + some role in data.diamond.data.subjects[subject].permissions + some beamline in data.diamond.data.admin[role] } + +admin := is_admin[input.user] # regal ignore:rule-name-repeats-package + +beamline_admin := input.beamline in object.get(beamline_admin_for_subject, input.user, []) diff --git a/policy/diamond/policy/admin/admin_test.rego b/policy/diamond/policy/admin/admin_test.rego index e9d36f7..544faf0 100644 --- a/policy/diamond/policy/admin/admin_test.rego +++ b/policy/diamond/policy/admin/admin_test.rego @@ -32,30 +32,91 @@ diamond_data := { "admin": {"b07_admin": ["b07"], "group_admin": ["b07", "i07"]}, } -test_super_admin_subject if { - admin.is_admin("carol") with data.diamond.data as diamond_data +test_is_admin_for_admin if { + admin.is_admin.carol with data.diamond.data as diamond_data } -test_beamline_admin_subject_beamline if { - admin.is_beamline_admin("bob", "b07") with data.diamond.data as diamond_data +test_beamline_admin_for_subject_for_beamline_admin if { + admin.beamline_admin_for_subject.bob == {"b07"} with data.diamond.data as diamond_data } -test_group_admin_subject_beamline if { - admin.is_beamline_admin("oscar", "b07") with data.diamond.data as diamond_data +test_beamlines_admin_for_subject_for_group_admin if { + admin.beamline_admin_for_subject.oscar == {"b07", "i07"} with data.diamond.data as diamond_data } -test_non_admin if { - not admin.is_admin("alice") with data.diamond.data as diamond_data +test_is_admin_for_non_admin if { + not admin.is_admin.alice with data.diamond.data as diamond_data } -test_beamline_admin_not_admin if { - not admin.is_admin("bob") 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 } -test_non_beamline_admin if { - not admin.is_beamline_admin("alice", "b07") with data.diamond.data as diamond_data +test_beamline_admin_for_subject_for_non_beamline_admin if { + not "alice" in admin.beamline_admin_for_subject with data.diamond.data as diamond_data } -test_super_admin_not_beamline_admin if { - not admin.is_beamline_admin("carol", "b07") with data.diamond.data as diamond_data +test_beamline_admin_for_subject_for_admin if { + not "carol" in admin.beamline_admin_for_subject with data.diamond.data as diamond_data } + +test_admin_rule_for_admin if { + admin.admin with input as {"user": "carol"} + with data.diamond.data as diamond_data +} + +test_admin_rule_for_non_admin if { + not admin.admin with input as {"user": "bob"} + with data.diamond.data as diamond_data +} + +# If no user is passed as input, the rule should be undefined +test_admin_rule_for_no_user := false if { + local_admin := admin.admin with input as {} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_beamline_admin_rule_for_beamline_admin if { + admin.beamline_admin with input as {"user": "bob", "beamline": "b07"} + with data.diamond.data as diamond_data +} + +# super_admin can access anything but they still aren't automatically beamline admins +test_beamline_admin_rule_for_super_admin if { + not admin.beamline_admin with input as {"user": "carol", "beamline": "b07"} + with data.diamond.data as diamond_data +} + +test_beamline_admin_rule_for_non_beamline_admin if { + not admin.beamline_admin with input as {"user": "alice", "beamline": "b07"} + with data.diamond.data as diamond_data +} + +test_beamline_admin_rule_for_wrong_beamline_admin if { + # bob is only beamline admin for b07 + not admin.beamline_admin with input as {"user": "bob", "beamline": "i07"} + with data.diamond.data as diamond_data +} + +test_beamline_admin_rule_for_no_user := false if { + local_admin := admin.beamline_admin with input as {"beamline": "i07"} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_beamline_admin_rule_for_no_beamline := false if { + local_admin := admin.beamline_admin with input as {"user": "bob"} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_beamline_admin_rule_for_no_input := false if { + local_admin := admin.beamline_admin with input as {} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else diff --git a/policy/diamond/policy/proposal/proposal.rego b/policy/diamond/policy/proposal/proposal.rego index 727f561..f61f539 100644 --- a/policy/diamond/policy/proposal/proposal.rego +++ b/policy/diamond/policy/proposal/proposal.rego @@ -3,12 +3,20 @@ package diamond.policy.proposal import data.diamond.policy.admin import rego.v1 +default on_proposal(_, _) := false + on_proposal(subject, proposal_number) if { proposal_number in data.diamond.data.subjects[subject].proposals # regal ignore:external-reference } +default access_proposal(_, _) := false + # Allow if subject has super_admin permission -access_proposal(subject, proposal_number) if admin.is_admin(subject) +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) + +access := access_proposal(input.user, input.proposal) + +named_user := on_proposal(input.user, input.proposal) diff --git a/policy/diamond/policy/proposal/proposal_test.rego b/policy/diamond/policy/proposal/proposal_test.rego index d2d2a82..f830978 100644 --- a/policy/diamond/policy/proposal/proposal_test.rego +++ b/policy/diamond/policy/proposal/proposal_test.rego @@ -43,3 +43,56 @@ test_member_on_proposal if { test_admin_not_on_proposal if { not proposal.on_proposal("carol", 1) with data.diamond.data as diamond_data } + +test_named_user_rule_for_named_user if { + proposal.named_user with input as {"user": "alice", "proposal": 1} + with data.diamond.data as diamond_data +} + +test_named_user_rule_for_unnamed_user if { + not proposal.named_user with input as {"user": "carol", "proposal": 1} + with data.diamond.data as diamond_data +} + +test_named_user_rule_for_no_user := false if { + named := proposal.named_user with input as {"proposal": 1} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_named_user_rule_for_no_proposal := false if { + named := proposal.named_user with input as {"user": "carol"} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_access_rule_for_super_admin if { + proposal.access with input as {"user": "carol", "proposal": 1} + with data.diamond.data as diamond_data +} + +test_access_rule_for_named_user if { + proposal.access with input as {"user": "alice", "proposal": 1} + with data.diamond.data as diamond_data +} + +test_access_rule_for_unnamed_user if { + not proposal.access with input as {"user": "oscar", "proposal": 1} + with data.diamond.data as diamond_data +} + +test_access_rule_for_no_user := false if { + access := proposal.access with input as {"proposal": 1} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_access_rule_for_no_proposal := false if { + access := proposal.access with input as {"user": "alice"} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else diff --git a/policy/diamond/policy/session/session.rego b/policy/diamond/policy/session/session.rego index b8a9b69..6bbd98e 100644 --- a/policy/diamond/policy/session/session.rego +++ b/policy/diamond/policy/session/session.rego @@ -4,13 +4,15 @@ import data.diamond.policy.admin import data.diamond.policy.proposal import rego.v1 -beamline(proposal_number, visit_number) := beamline if { +beamline_for(proposal_number, visit_number) := beamline if { proposal := data.diamond.data.proposals[format_int(proposal_number, 10)] # regal ignore:external-reference session_id := proposal.sessions[format_int(visit_number, 10)] session := data.diamond.data.sessions[format_int(session_id, 10)] # regal ignore:external-reference beamline := session.beamline } +default on_session(_, _, _) := false + on_session(subject, proposal_number, visit_number) if { some session_id in data.diamond.data.subjects[subject].sessions # regal ignore:external-reference subject_session := data.diamond.data.sessions[format_int(session_id, 10)] # regal ignore:external-reference @@ -18,12 +20,15 @@ on_session(subject, proposal_number, visit_number) if { subject_session.visit_number == visit_number } +default access_session(_, _, _) := false + # Allow if subject has super_admin permission -access_session(subject, proposal_number, visit_number) if admin.is_admin(subject) +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 { - admin.is_beamline_admin(subject, beamline(proposal_number, visit_number)) + # regal ignore:external-reference + beamline_for(proposal_number, visit_number) in admin.beamline_admin_for_subject[subject] } # Allow if subject on proposal which contains session @@ -31,3 +36,13 @@ access_session(subject, proposal_number, visit_number) if proposal.on_proposal(s # Allow if subject directly on session access_session(subject, proposal_number, visit_number) if on_session(subject, proposal_number, visit_number) + +# Rules depending on input data + +access := access_session(input.user, input.proposal, input.visit) + +named_user := on_session(input.user, input.proposal, input.visit) + +matches_beamline := input.beamline == beamline_for(input.proposal, input.visit) # regal ignore:boolean-assignment + +beamline := beamline_for(input.proposal, input.visit) diff --git a/policy/diamond/policy/session/session_test.rego b/policy/diamond/policy/session/session_test.rego index 583b634..dc808ef 100644 --- a/policy/diamond/policy/session/session_test.rego +++ b/policy/diamond/policy/session/session_test.rego @@ -69,3 +69,104 @@ test_non_member_denied if { test_admin_not_on_session if { not session.on_session("carol", 1, 1) with data.diamond.data as diamond_data } + +test_access_rule_for_named_user if { + session.access with input as {"user": "alice", "proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +test_access_rule_for_beamline_admin if { + session.access with input as {"user": "bob", "proposal": 1, "visit": 2} + with data.diamond.data as diamond_data +} + +test_access_rule_for_super_admin if { + session.access with input as {"user": "carol", "proposal": 1, "visit": 2} + with data.diamond.data as diamond_data +} + +test_access_rule_for_non_user if { + not session.access with input as {"user": "oscar", "proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +test_access_rule_for_no_user := false if { + access := session.access with input as {"proposal": 1, "visit": 2} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_access_rule_for_no_proposal := false if { + access := session.access with input as {"user": "bob", "visit": 2} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_access_rule_for_no_visit := false if { + access := session.access with input as {"user": "bob", "proposal": 2} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_named_user_rule_for_named_user if { + session.named_user with input as {"user": "bob", "proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +test_named_user_rule_for_unnamed_user if { + not session.named_user with input as {"user": "oscar", "proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +test_named_user_rule_for_super_admin if { + not session.named_user with input as {"user": "alice", "proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +test_named_user_rule_for_beamline_admin if { + not session.named_user with input as {"user": "bob", "proposal": 1, "visit": 2} + with data.diamond.data as diamond_data +} + +test_named_user_rule_for_named_proposal if { + # Users on the proposal can access the session but aren't named on it + not session.named_user with input as {"user": "alice", "proposal": 1, "visit": 2} + with data.diamond.data as diamond_data +} + +test_matches_beamline_rule_for_match if { + session.matches_beamline with input as {"beamline": "b07", "proposal": 1, "visit": 2} + with data.diamond.data as diamond_data +} + +test_matches_beamline_rule_for_non_match if { + not session.matches_beamline with input as {"beamline": "b07", "proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +test_matches_beamline_rule_for_no_beamline := false if { + match := session.matches_beamline with input as {"proposal": 1, "visit": 1} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_matches_beamline_rule_for_no_visit := false if { + match := session.matches_beamline with input as {"beamline": "b07"} + with data.diamond.data as diamond_data +} + +else := true # regal ignore:default-over-else + +test_session_beamline if { + bl1 := session.beamline with input as {"proposal": 1, "visit": 1} + with data.diamond.data as diamond_data + bl2 := session.beamline with input as {"proposal": 1, "visit": 2} + with data.diamond.data as diamond_data + + bl1 == "i03" + bl2 == "b07" +}