diff --git a/policy/diamond/policy/tiled/tiled.rego b/policy/diamond/policy/tiled/tiled.rego index 3c94495..783e7e7 100644 --- a/policy/diamond/policy/tiled/tiled.rego +++ b/policy/diamond/policy/tiled/tiled.rego @@ -1,44 +1,117 @@ package diamond.policy.tiled +import data.diamond.policy.admin import data.diamond.policy.session import data.diamond.policy.token import rego.v1 -read_scopes := { +# Assign read & write scopes to clients with tiled-writer audience +# defaults to read-only scopes +default scopes := { "read:metadata", "read:data", } -write_scopes := { +scopes := { + "read:metadata", + "read:data", "write:metadata", "write:data", "create:node", "register", +} if { + "tiled-writer" in token.claims.aud +} + +_session := data.diamond.data.proposals[format_int(input.proposal, 10)].sessions[format_int(input.visit, 10)] + +# Returns the session ID if the subject has write permissions for the +# specific beamline, visit and proposal requested in the input. +user_session := to_number(_session) if { + session.write_to_beamline_visit + _session +} + +user_session := to_number(_session) if { + input.proposal in token.claims.subject.proposals +} + +user_session := to_number(_session) if { + _session in token.claims.subject.sessions +} + +user_session := to_number(_session) if { + input.beamline in beamlines + input.beamline == session.beamline_for(input.proposal, input.visit) + _session in data.diamond.data.beamlines[input.beamline].sessions +} + +default fedid := "" + +fedid := token.claims.fedid + +# Validates if the subject has permission to modify +# the specific session in the input. +default modify_session := false + +modify_session if session.access_session( + fedid, + data.diamond.data.sessions[input.session].proposal_number, + data.diamond.data.sessions[input.session].visit_number, +) + +modify_session if { + data.diamond.data.sessions[input.session].proposal_number in token.claims.subject.proposals +} + +modify_session if { + to_number(input.session) in token.claims.subject.sessions } -scopes_for(claims) := read_scopes | write_scopes if { - "azp" in object.keys(claims) - endswith(claims.azp, "-blueapi") +modify_session if { + session.beamline_for( + data.diamond.data.sessions[input.session].proposal_number, + data.diamond.data.sessions[input.session].visit_number, + ) in beamlines } -scopes_for(claims) := read_scopes if { - "azp" in object.keys(claims) - not endswith(claims.azp, "-blueapi") +subject := data.diamond.data.subjects[token.claims.fedid] if token.claims.fedid + +else := token.claims.subject if token.claims.subject + +# Identifies all beamlines the subject is authorized to access +# based on their assigned permissions. +beamlines contains beamline if { + not admin.is_admin(fedid) + some p in subject.permissions + some beamline in object.get(data.diamond.data.admin, p, []) } -scopes_for(claims) := read_scopes if { - not "azp" in object.keys(claims) +# Aggregates all session IDs the subject is authorized to view. +# Admins receive a wildcard "*" granting access to all sessions. + +# Regular users gain session access through three pathways: +# 1. Direct session membership +# 2. Access via beamline-level permissions +# 3. Access via proposal-level permissions +user_sessions contains "*" if { + admin.is_admin(fedid) } -default scopes := set() +user_sessions contains to_number(session) if { + not admin.is_admin(fedid) + some session in subject.sessions +} -scopes := scopes_for(token.claims) +user_sessions contains to_number(session) if { + not admin.is_admin(fedid) + some beamline in beamlines + some session in data.diamond.data.beamlines[beamline].sessions +} -user_sessions contains user_session if { - some i in data.diamond.data.sessions - session.access_session(token.claims.fedid, i.proposal_number, i.visit_number) - user_session := sprintf( - `{"proposal": %d, "visit": %d, "beamline": "%s"}`, - [i.proposal_number, i.visit_number, i.beamline], - ) +user_sessions contains to_number(session) if { + not admin.is_admin(fedid) + some p in subject.proposals + some i in data.diamond.data.proposals[format_int(p, 10)] + some session in i } diff --git a/policy/diamond/policy/tiled/tiled_test.rego b/policy/diamond/policy/tiled/tiled_test.rego index 14a4666..a16b30c 100644 --- a/policy/diamond/policy/tiled/tiled_test.rego +++ b/policy/diamond/policy/tiled/tiled_test.rego @@ -3,17 +3,14 @@ package diamond.policy.tiled_test import data.diamond.policy.tiled import rego.v1 -test_default_no_scopes if { - tiled.scopes == set() -} - -test_wrong_azp_read_scopes if { - tiled.scopes == tiled.read_scopes with data.diamond.policy.token.claims as {} - tiled.scopes == tiled.read_scopes with data.diamond.policy.token.claims as {"sub": "foo"} - tiled.scopes == tiled.read_scopes with data.diamond.policy.token.claims as {"azp": "foo"} +test_read_scopes if { + tiled.scopes == { + "read:metadata", + "read:data", + } with data.diamond.policy.token.claims as {} } -test_blueapi_given_write_scopes if { +test_tiled_writer_given_write_scopes if { tiled.scopes == { "read:metadata", "read:data", @@ -21,7 +18,7 @@ test_blueapi_given_write_scopes if { "write:data", "create:node", "register", - } with data.diamond.policy.token.claims as {"azp": "foo-blueapi"} + } with data.diamond.policy.token.claims as {"aud": ["tiled-writer"]} } diamond_data := { @@ -96,33 +93,110 @@ diamond_data := { test_user_session_tags if { tiled.user_sessions == set() with data.diamond.data as diamond_data with data.diamond.policy.token.claims as {"fedid": "oscar"} - tiled.user_sessions == { - `{"proposal": 1, "visit": 2, "beamline": "b07"}`, - `{"proposal": 1, "visit": 1, "beamline": "i03"}`, - } with data.diamond.data as diamond_data + tiled.user_sessions == {11, 12} with data.diamond.data as diamond_data with data.diamond.policy.token.claims as {"fedid": "alice"} - tiled.user_sessions == { - `{"proposal": 1, "visit": 2, "beamline": "b07"}`, - `{"proposal": 1, "visit": 1, "beamline": "i03"}`, - `{"proposal": 2, "visit": 1, "beamline": "b07"}`, - `{"proposal": 2, "visit": 2, "beamline": "b07"}`, - } with data.diamond.data as diamond_data + tiled.user_sessions == {11, 12, 13, 14} with data.diamond.data as diamond_data with data.diamond.policy.token.claims as {"fedid": "bob"} - tiled.user_sessions == { - `{"proposal": 1, "visit": 2, "beamline": "b07"}`, - `{"proposal": 1, "visit": 1, "beamline": "i03"}`, - `{"proposal": 2, "visit": 1, "beamline": "b07"}`, - `{"proposal": 2, "visit": 2, "beamline": "b07"}`, - } with data.diamond.data as diamond_data + tiled.user_sessions == {"*"} with data.diamond.data as diamond_data with data.diamond.policy.token.claims as {"fedid": "carol"} - tiled.user_sessions == { - `{"proposal": 2, "visit": 1, "beamline": "b07"}`, - `{"proposal": 2, "visit": 2, "beamline": "b07"}`, - } with data.diamond.data as diamond_data + tiled.user_sessions == {13, 14} with data.diamond.data as diamond_data with data.diamond.policy.token.claims as {"fedid": "desmond"} - tiled.user_sessions == { - `{"proposal": 2, "visit": 1, "beamline": "b07"}`, - `{"proposal": 2, "visit": 2, "beamline": "b07"}`, - } with data.diamond.data as diamond_data + tiled.user_sessions == {13, 14} with data.diamond.data as diamond_data with data.diamond.policy.token.claims as {"fedid": "edna"} } + +test_user_session_allow if { + tiled.user_session == 11 with data.diamond.data as diamond_data + with input as {"beamline": "i03", "proposal": 1, "visit": 1} + with data.diamond.policy.token.claims as {"fedid": "carol"} +} + +test_user_session_not_allowed if { + not tiled.user_session with data.diamond.data as diamond_data + with input as {"beamline": "i03", "proposal": 1, "visit": 1} + with data.diamond.policy.token.claims as {"fedid": "oscar"} +} + +test_not_modify_session if { + not tiled.modify_session with data.diamond.data as diamond_data + with input as {"session": "13"} + with data.diamond.policy.token.claims as {"fedid": "alice"} +} + +test_modify_session if { + tiled.modify_session with data.diamond.data as diamond_data + with input as {"session": "11"} + with data.diamond.policy.token.claims as {"fedid": "alice"} +} + +# Service account tests + +test_user_session_allow_service_account_on_proposal if { + tiled.user_session == 11 with data.diamond.data as diamond_data + with input as {"beamline": "i03", "proposal": 1, "visit": 1} + with data.diamond.policy.token.claims as {"subject": {"proposals": [1], "sessions": [], "permissions": []}} +} + +test_user_session_allow_service_account_on_session if { + tiled.user_session == 11 with data.diamond.data as diamond_data + with input as {"beamline": "i03", "proposal": 1, "visit": 1} + with data.diamond.policy.token.claims as {"subject": {"proposals": [], "sessions": [11], "permissions": []}} +} + +test_user_session_not_allow_service_account_wrong_beamline if { + not tiled.user_session with data.diamond.data as diamond_data + with input as {"beamline": "i03", "proposal": 1, "visit": 2} + with data.diamond.policy.token.claims as {"subject": {"proposals": [], "sessions": [], "permissions": ["b07_admin"]}} +} + +test_user_session_allow_service_account_with_beamline if { + tiled.user_session with data.diamond.data as diamond_data + with input as {"beamline": "b07", "proposal": 1, "visit": 2} + with data.diamond.policy.token.claims as { + "subject": {"proposals": [], "sessions": [], "permissions": ["b07_admin"]}, + "fedid": "", + } +} + +test_modify_session_on_proposal if { + tiled.modify_session with data.diamond.data as diamond_data + with input as {"session": "11"} + with data.diamond.policy.token.claims as {"subject": {"proposals": [1], "sessions": [], "permissions": []}} +} + +test_modify_session_on_session if { + tiled.modify_session with data.diamond.data as diamond_data + with input as {"session": "11"} + with data.diamond.policy.token.claims as {"subject": {"proposals": [], "sessions": [11], "permissions": []}} +} + +test_modify_session_on_permission if { + tiled.modify_session with data.diamond.data as diamond_data + with input as {"session": "12"} + with data.diamond.policy.token.claims as {"subject": { + "proposals": [], + "sessions": [], + "permissions": ["b07_admin"], + }} +} + +test_user_session_tags_service_account if { + tiled.user_sessions == {11} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"subject": { + "proposals": [], + "sessions": [11], + "permissions": [], + }} + tiled.user_sessions == {11, 12} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"subject": { + "proposals": [1], + "sessions": [], + "permissions": [], + }} + tiled.user_sessions == {12, 13, 14} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"subject": { + "proposals": [], + "sessions": [], + "permissions": ["b07_admin"], + }} +}