ProofedMesh/policies/data_sources/expense_db_app.rego

99 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
}