100 lines
2.9 KiB
Rego
100 lines
2.9 KiB
Rego
|
|
# METADATA
|
||
|
|
# title: Data source policy for ds:expense-db-app
|
||
|
|
# description: |
|
||
|
|
# Governs which agents may read which resources in whose name on the
|
||
|
|
# own DB-app data source. Requires an active user session for all
|
||
|
|
# current reads (requires_user_context=TRUE for every registered
|
||
|
|
# agent_data_source_scope).
|
||
|
|
# authors:
|
||
|
|
# - CTO Agent <cto@basicstack.de>
|
||
|
|
# related_resources:
|
||
|
|
# - ref: docs/architecture/v1.1.md#user-content-3b-data-source-registry
|
||
|
|
# - ref: docs/architecture/v1.1.md#user-content-3c-session-scope-tokens--delegation-agent-im-auftrag-von-benutzer
|
||
|
|
package proofedmesh.data_sources.expense_db_app
|
||
|
|
|
||
|
|
import rego.v1
|
||
|
|
|
||
|
|
default allow := false
|
||
|
|
|
||
|
|
allow if {
|
||
|
|
input.data_source == "ds:expense-db-app"
|
||
|
|
input.operation == "read"
|
||
|
|
input.user_sub != ""
|
||
|
|
input.session_id != ""
|
||
|
|
some rule in agent_scope_rules
|
||
|
|
rule.agent_id == input.agent_id
|
||
|
|
rule.resource == input.resource
|
||
|
|
filters_within_scope(rule)
|
||
|
|
user_bound_filters_ok(rule)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Rule table: who may read what, with which filter dimensions and user-binding mode.
|
||
|
|
agent_scope_rules := [
|
||
|
|
{
|
||
|
|
"agent_id": "agent-travel",
|
||
|
|
"resource": "project_assignments",
|
||
|
|
"allowed_filters": {"employee_id", "active_on"},
|
||
|
|
"required_filters": {"employee_id"},
|
||
|
|
"require_own_employee": true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"agent_id": "agent-sink-mapping",
|
||
|
|
"resource": "account_assignments",
|
||
|
|
"allowed_filters": {"project_code", "cost_center"},
|
||
|
|
"required_filters": {"project_code"},
|
||
|
|
"require_own_employee": false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"agent_id": "agent-sink-mapping",
|
||
|
|
"resource": "cost_centers",
|
||
|
|
"allowed_filters": {"active"},
|
||
|
|
"required_filters": set(),
|
||
|
|
"require_own_employee": false,
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
# Only the declared filter keys are allowed; required filters must be present.
|
||
|
|
filters_within_scope(rule) if {
|
||
|
|
provided := {k | some k; _ := input.filters[k]}
|
||
|
|
count(provided - rule.allowed_filters) == 0
|
||
|
|
count(rule.required_filters - provided) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
# When require_own_employee=true, the employee_id filter must map to input.user_sub.
|
||
|
|
# The mapping is loaded as external data: data.employee_mapping.by_sub[user_sub] -> "EMP-...".
|
||
|
|
user_bound_filters_ok(rule) if {
|
||
|
|
not rule.require_own_employee
|
||
|
|
}
|
||
|
|
|
||
|
|
user_bound_filters_ok(rule) if {
|
||
|
|
rule.require_own_employee
|
||
|
|
expected := data.employee_mapping.by_sub[input.user_sub]
|
||
|
|
input.filters.employee_id == expected
|
||
|
|
}
|
||
|
|
|
||
|
|
deny_reason contains msg if {
|
||
|
|
input.data_source != "ds:expense-db-app"
|
||
|
|
msg := sprintf("wrong data_source: %s", [input.data_source])
|
||
|
|
}
|
||
|
|
|
||
|
|
deny_reason contains msg if {
|
||
|
|
input.operation != "read"
|
||
|
|
msg := sprintf("operation %s not supported", [input.operation])
|
||
|
|
}
|
||
|
|
|
||
|
|
deny_reason contains msg if {
|
||
|
|
input.user_sub == ""
|
||
|
|
msg := "missing user_sub (requires_user_context=true for all registered scopes)"
|
||
|
|
}
|
||
|
|
|
||
|
|
deny_reason contains msg if {
|
||
|
|
not any_scope_rule_matches
|
||
|
|
msg := sprintf("no scope rule for agent=%s resource=%s", [input.agent_id, input.resource])
|
||
|
|
}
|
||
|
|
|
||
|
|
any_scope_rule_matches if {
|
||
|
|
some rule in agent_scope_rules
|
||
|
|
rule.agent_id == input.agent_id
|
||
|
|
rule.resource == input.resource
|
||
|
|
}
|