diff --git a/policy/diamond/policy/beamline/beamline.rego b/policy/diamond/policy/beamline/beamline.rego new file mode 100644 index 00000000..c9c8bec1 --- /dev/null +++ b/policy/diamond/policy/beamline/beamline.rego @@ -0,0 +1,29 @@ +package diamond.policy.beamline + +import data.diamond.policy.admin +import data.diamond.policy.token +import rego.v1 + +subject := data.diamond.data.subjects[token.claims.fedid] + +# METADATA +# title: User Beamlines +# description: | +# Identifies all beamlines the subject is authorized to access +# based on their assigned permissions. +# entrypoint: true +user_beamlines contains beamline if { + token.claims.fedid + not admin.is_admin(token.claims.fedid) + some p in subject.permissions + some beamline in object.get(data.diamond.data.admin, p, []) +} + +user_beamlines contains beamline if { + admin.is_admin(token.claims.fedid) + some beamline in object.keys(data.diamond.data.beamlines) +} + +user_beamlines contains token.claims.beamline if { + token.claims.beamline in object.keys(data.diamond.data.beamlines) +} diff --git a/policy/diamond/policy/beamline/beamline_test.rego b/policy/diamond/policy/beamline/beamline_test.rego new file mode 100644 index 00000000..78cdc2ef --- /dev/null +++ b/policy/diamond/policy/beamline/beamline_test.rego @@ -0,0 +1,98 @@ +package diamond.policy.beamline_test + +import data.diamond.policy.beamline +import rego.v1 + +diamond_data := { + "subjects": { + "alice": { + "permissions": [], + "proposals": [1], + "sessions": [], + }, + "bob": { + "permissions": ["b07_admin"], + "proposals": [], + "sessions": [11], + }, + "carol": { + "permissions": ["super_admin"], + "proposals": [], + "sessions": [], + }, + "desmond": { + "permissions": [], + "proposals": [2], + "sessions": [13], + }, + "edna": { + "permissions": [], + "proposals": [2], + "sessions": [13, 14], + }, + "oscar": { + "permissions": [], + "proposals": [], + "sessions": [], + }, + }, + "sessions": { + "11": { + "beamline": "i03", + "proposal_number": 1, + "visit_number": 1, + }, + "12": { + "beamline": "b07", + "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, + }}, + }, + "beamlines": {"i03": {"sessions": [11]}, "b07": {"sessions": [12, 13, 14]}}, + "admin": {"b07_admin": ["b07"]}, +} + +test_user_beamlines_super_admin if { + beamline.user_beamlines == {"i03", "b07"} with data.diamond.policy.token.claims as {"fedid": "carol"} + with data.diamond.data as diamond_data +} + +test_user_beamlines_beamline_admin if { + beamline.user_beamlines == {"b07"} with data.diamond.policy.token.claims as {"fedid": "bob"} + with data.diamond.data as diamond_data +} + +test_user_beamlines_non_admin if { + beamline.user_beamlines == set() with data.diamond.policy.token.claims as {"fedid": "alice"} + with data.diamond.data as diamond_data +} + +test_user_beamlines_service_account if { + beamline.user_beamlines == {"b07"} with data.diamond.policy.token.claims as {"beamline": "b07"} + with data.diamond.data as diamond_data +} + +test_user_beamlines_service_account_bad_beamline_claim if { + beamline.user_beamlines == set() with data.diamond.policy.token.claims as {"beamline": "area-51-beamline"} + with data.diamond.data as diamond_data +} diff --git a/policy/diamond/policy/session/session.rego b/policy/diamond/policy/session/session.rego index b3a878f3..674a525c 100644 --- a/policy/diamond/policy/session/session.rego +++ b/policy/diamond/policy/session/session.rego @@ -1,6 +1,7 @@ package diamond.policy.session import data.diamond.policy.admin +import data.diamond.policy.beamline as beamline_policy import data.diamond.policy.proposal import data.diamond.policy.token import rego.v1 @@ -52,3 +53,52 @@ write_to_beamline_visit if { access input.beamline == beamline } + +subject := data.diamond.data.subjects[token.claims.fedid] + +# METADATA +# title: User Sessions +# description: | +# 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 +# entrypoint: false +# scope: document +user_sessions contains "*" if { + subject + admin.is_admin(token.claims.fedid) +} + +# Direct session membership +user_sessions contains format_int(session, 10) if { + subject + not admin.is_admin(token.claims.fedid) + some session in subject.sessions +} + +# Access via beamline permissions +user_sessions contains format_int(session, 10) if { + subject + not admin.is_admin(token.claims.fedid) + some _beamline in beamline_policy.user_beamlines + some session in data.diamond.data.beamlines[_beamline].sessions +} + +# Access via beamline permissions (service accounts) +user_sessions contains format_int(session, 10) if { + not subject + some _beamline in beamline_policy.user_beamlines + some session in data.diamond.data.beamlines[_beamline].sessions +} + +# Access via proposal permissions +user_sessions contains format_int(session, 10) if { + subject + not admin.is_admin(token.claims.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/session/session_test.rego b/policy/diamond/policy/session/session_test.rego index 27624320..a13ac94b 100644 --- a/policy/diamond/policy/session/session_test.rego +++ b/policy/diamond/policy/session/session_test.rego @@ -20,6 +20,16 @@ diamond_data := { "proposals": [], "sessions": [], }, + "desmond": { + "permissions": [], + "proposals": [2], + "sessions": [13], + }, + "edna": { + "permissions": [], + "proposals": [2], + "sessions": [13, 14], + }, "oscar": { "permissions": [], "proposals": [], @@ -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"]}, } @@ -175,3 +201,22 @@ test_session_beamline if { with data.diamond.data as diamond_data bl2 == "b07" } + +test_user_sessions if { + session.user_sessions == set() with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"fedid": "oscar"} + session.user_sessions == {"11", "12"} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"fedid": "alice"} + session.user_sessions == {"11", "12", "13", "14"} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"fedid": "bob"} + session.user_sessions == {"*"} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"fedid": "carol"} + session.user_sessions == {"13", "14"} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"fedid": "desmond"} + session.user_sessions == {"13", "14"} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"fedid": "edna"} + session.user_sessions == {"11"} with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"beamline": "i03"} + session.user_sessions == set() with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"beamline": "area-51-beamline"} +} diff --git a/policy/diamond/policy/token/token.rego b/policy/diamond/policy/token/token.rego index b4826a5e..de995287 100644 --- a/policy/diamond/policy/token/token.rego +++ b/policy/diamond/policy/token/token.rego @@ -35,4 +35,17 @@ verified := io.jwt.decode_verify(input.token, { "aud": input.audience, }) +# METADATA +# title: Valid Token +# description: | +# Returns whether or not input.token is a valid JWT for our identity provider +# Requires: +# - `input.token`, a JWT +# entrypoint: true +default valid_token := false + +valid_token if { + verified[0] +} + claims := verified[2] if verified[0] diff --git a/policy/diamond/policy/ulims/ulims.rego b/policy/diamond/policy/ulims/ulims.rego new file mode 100644 index 00000000..105be86b --- /dev/null +++ b/policy/diamond/policy/ulims/ulims.rego @@ -0,0 +1,67 @@ +package diamond.policy.ulims + +import data.diamond.policy.admin +import data.diamond.policy.beamline +import data.diamond.policy.session +import data.diamond.policy.token +import rego.v1 + +# METADATA +# title: Session Restrictions +# description: | +# Return the instrument sessions the current user is allowed to see, or null if the user is an admin +# Requires: +# - `input.token`, a JWT +# entrypoint: true +default session_restrictions := [] + +session_restrictions := null if { + admin.is_admin(token.claims.fedid) +} + +session_restrictions := [data.diamond.data.sessions[session_id] | some session_id in session.user_sessions] if { + not admin.is_admin(token.claims.fedid) +} + +session_restrictions := [data.diamond.data.sessions[session_id] | some session_id in session.user_sessions] if { + not token.claims.fedid +} + +# METADATA +# title: Filter sessions +# description: | +# Filter a provided list of instrument sessions, returning just those that the user has access to +# Requires: +# - `input.token`, a JWT +# - `input.instrument_sessions`, an array representing a list of instrument sessions, [(proposal, visit), ...] +# entrypoint: true +filter_sessions contains _session if { + "*" in session.user_sessions + some _session in input.instrument_sessions + proposal_number := format_int(_session[0], 10) + proposal_number in object.keys(data.diamond.data.proposals) + session_number := format_int(_session[1], 10) + session_number in object.keys(data.diamond.data.proposals[proposal_number].sessions) +} + +filter_sessions contains _session if { + not "*" in session.user_sessions + some _session in input.instrument_sessions + proposal_number := format_int(_session[0], 10) + session_number := format_int(_session[1], 10) + format_int(data.diamond.data.proposals[proposal_number].sessions[session_number], 10) in session.user_sessions +} + +# METADATA +# title: Filter instruments +# description: | +# Filter a provided list of instruments, returning just those that the user has access to +# Requires: +# - `input.token`, a JWT +# - `input.instruments`, an array of strings representing a list of instruments +# entrypoint: true +filter_instruments contains _beamline if { + some _beamline in input.instruments + _beamline in object.keys(data.diamond.data.beamlines) + _beamline in beamline.user_beamlines +} diff --git a/policy/diamond/policy/ulims/ulims_test.rego b/policy/diamond/policy/ulims/ulims_test.rego new file mode 100644 index 00000000..ba965d48 --- /dev/null +++ b/policy/diamond/policy/ulims/ulims_test.rego @@ -0,0 +1,174 @@ +package diamond.policy.ulims_test + +import data.diamond.policy.ulims +import rego.v1 + +diamond_data := { + "subjects": { + "alice": { + "permissions": [], + "proposals": [1], + "sessions": [], + }, + "bob": { + "permissions": ["b07_admin"], + "proposals": [], + "sessions": [], + }, + "carol": { + "permissions": ["super_admin"], + "proposals": [], + "sessions": [], + }, + "desmond": { + "permissions": [], + "proposals": [2], + "sessions": [13], + }, + "edna": { + "permissions": [], + "proposals": [2], + "sessions": [13, 14], + }, + "oscar": { + "permissions": [], + "proposals": [], + "sessions": [], + }, + }, + "sessions": { + "11": { + "beamline": "i03", + "proposal_number": 1, + "visit_number": 1, + }, + "12": { + "beamline": "b07", + "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, + }}, + }, + "beamlines": {"i03": {"sessions": [11]}, "b07": {"sessions": [12, 13, 14]}}, + "admin": {"b07_admin": ["b07"]}, +} + +test_session_restrictions_for_admin if { + ulims.session_restrictions == null with data.diamond.data as diamond_data + with data.diamond.policy.token as {"claims": {"fedid": "carol"}} +} + +test_session_restrictions_for_non_admin_1 if { + ulims.session_restrictions == [ + { + "beamline": "i03", + "proposal_number": 1, + "visit_number": 1, + }, + { + "beamline": "b07", + "proposal_number": 1, + "visit_number": 2, + }, + ] with data.diamond.data as diamond_data + with data.diamond.policy.token as {"claims": {"fedid": "alice"}} +} + +test_session_restrictions_for_non_admin_2 if { + ulims.session_restrictions == [] with data.diamond.data as diamond_data + with data.diamond.policy.token as {"claims": {"fedid": "oscar"}} +} + +test_session_restrictions_service_account if { + ulims.session_restrictions == [{ + "beamline": "i03", + "proposal_number": 1, + "visit_number": 1, + }] with data.diamond.data as diamond_data + with data.diamond.policy.token.claims as {"beamline": "i03"} +} + +test_filter_sessions_for_admin if { + ulims.filter_sessions == {[1, 1], [1, 2], [2, 1], [2, 2]} with data.diamond.data as diamond_data + with input.instrument_sessions as [[1, 1], [1, 2], [2, 1], [2, 2]] + with data.diamond.policy.token as {"claims": {"fedid": "carol"}} +} + +test_filter_sessions_beamline_admin if { + ulims.filter_sessions == {[1, 2], [2, 1], [2, 2]} with data.diamond.data as diamond_data + with input.instrument_sessions as [[1, 1], [1, 2], [2, 1], [2, 2]] + with data.diamond.policy.token as {"claims": {"fedid": "bob"}} +} + +test_filter_sessions_for_non_admin_1 if { + ulims.filter_sessions == {[1, 1], [1, 2]} with data.diamond.data as diamond_data + with input.instrument_sessions as [[1, 1], [1, 2], [2, 1], [2, 2]] + with data.diamond.policy.token as {"claims": {"fedid": "alice"}} +} + +test_filter_sessions_for_non_admin_2 if { + ulims.filter_sessions == set() with data.diamond.data as diamond_data + with input.instrument_sessions as [[1, 1], [1, 2], [2, 1], [2, 2]] + with data.diamond.policy.token as {"claims": {"fedid": "oscar"}} +} + +test_filter_sessions_service_account if { + ulims.filter_sessions == {[1, 1]} with data.diamond.data as diamond_data + with input.instrument_sessions as [[1, 1], [1, 2], [2, 1], [2, 2]] + with data.diamond.policy.token as {"claims": {"beamline": "i03"}} +} + +test_filter_sessions_non_existent_session if { + ulims.filter_sessions == set() with data.diamond.data as diamond_data + with input.instrument_sessions as [[999, 999]] + with data.diamond.policy.token as {"claims": {"fedid": "carol"}} +} + +test_filter_instruments_user if { + ulims.filter_instruments == set() with data.diamond.data as diamond_data + with input.instruments as ["i03", "b07"] + with data.diamond.policy.token as {"claims": {"fedid": "alice"}} +} + +test_filter_instruments_beamline_admin if { + ulims.filter_instruments == {"b07"} with data.diamond.data as diamond_data + with input.instruments as ["i03", "b07"] + with data.diamond.policy.token as {"claims": {"fedid": "bob"}} +} + +test_filter_instruments_super_admin if { + ulims.filter_instruments == {"i03", "b07"} with data.diamond.data as diamond_data + with input.instruments as ["i03", "b07"] + with data.diamond.policy.token as {"claims": {"fedid": "carol"}} +} + +test_filter_instruments_service_account if { + ulims.filter_instruments == {"i03"} with data.diamond.data as diamond_data + with input.instruments as ["i03", "b07"] + with data.diamond.policy.token as {"claims": {"beamline": "i03"}} +} + +test_filter_instruments_non_existent_instrument if { + ulims.filter_instruments == set() with data.diamond.data as diamond_data + with input.instruments as ["area-51-beamline"] + with data.diamond.policy.token as {"claims": {"fedid": "carol"}} +}