modkit_db/secure/entity_traits.rs
1use sea_orm::EntityTrait;
2
3/// Defines the contract for entities that can be scoped by tenant, resource, owner, and type.
4///
5/// Each entity implementing this trait must explicitly declare all four scope dimensions:
6/// - `tenant_col()`: Column for tenant-based isolation (multi-tenancy)
7/// - `resource_col()`: Column for resource-level access (typically the primary key)
8/// - `owner_col()`: Column for owner-based filtering
9/// - `type_col()`: Column for type-based filtering
10///
11/// **Important**: No implicit defaults are allowed. Every scope dimension must be explicitly
12/// specified as `Some(Column::...)` or `None` to enforce compile-time safety in secure systems.
13///
14/// # Example (Manual Implementation)
15/// ```rust,ignore
16/// impl ScopableEntity for user::Entity {
17/// fn tenant_col() -> Option<Self::Column> {
18/// Some(user::Column::TenantId)
19/// }
20/// fn resource_col() -> Option<Self::Column> {
21/// Some(user::Column::Id)
22/// }
23/// fn owner_col() -> Option<Self::Column> {
24/// None
25/// }
26/// fn type_col() -> Option<Self::Column> {
27/// None
28/// }
29/// fn resolve_property(property: &str) -> Option<Self::Column> {
30/// match property {
31/// "owner_tenant_id" => Self::tenant_col(),
32/// "id" => Self::resource_col(),
33/// "owner_id" => Self::owner_col(),
34/// _ => None,
35/// }
36/// }
37/// }
38/// ```
39///
40/// # Example (Using Derive Macro)
41/// ```rust,ignore
42/// use modkit_db::secure::Scopable;
43///
44/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Scopable)]
45/// #[sea_orm(table_name = "users")]
46/// #[secure(
47/// tenant_col = "tenant_id",
48/// resource_col = "id",
49/// no_owner,
50/// no_type
51/// )]
52/// pub struct Model {
53/// #[sea_orm(primary_key)]
54/// pub id: Uuid,
55/// pub tenant_id: Uuid,
56/// pub email: String,
57/// }
58/// // Macro auto-generates resolve_property:
59/// // "owner_tenant_id" => Some(Column::TenantId) (from tenant_col)
60/// // "id" => Some(Column::Id) (from resource_col)
61/// // _ => None
62/// ```
63///
64/// # Custom PEP Properties
65/// ```rust,ignore
66/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Scopable)]
67/// #[sea_orm(table_name = "resources")]
68/// #[secure(
69/// tenant_col = "tenant_id",
70/// resource_col = "id",
71/// no_owner,
72/// no_type,
73/// pep_prop(department_id = "department_id"),
74/// )]
75/// pub struct Model {
76/// #[sea_orm(primary_key)]
77/// pub id: Uuid,
78/// pub tenant_id: Uuid,
79/// pub department_id: Uuid,
80/// }
81/// // Macro auto-generates resolve_property:
82/// // "owner_tenant_id" => Some(Column::TenantId) (from tenant_col)
83/// // "id" => Some(Column::Id) (from resource_col)
84/// // "department_id" => Some(Column::DepartmentId) (from pep_prop)
85/// // _ => None
86/// ```
87///
88/// # Unrestricted Entities
89/// ```rust,ignore
90/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Scopable)]
91/// #[sea_orm(table_name = "system_config")]
92/// #[secure(unrestricted)]
93/// pub struct Model {
94/// #[sea_orm(primary_key)]
95/// pub id: Uuid,
96/// pub config_key: String,
97/// }
98/// ```
99pub trait ScopableEntity: EntityTrait {
100 /// Indicates whether this entity is explicitly marked as unrestricted.
101 ///
102 /// This is a compile-time flag set via `#[secure(unrestricted)]` that documents
103 /// the entity's global nature (e.g., system configuration, lookup tables).
104 ///
105 /// When `IS_UNRESTRICTED` is true, all column methods return `None`.
106 ///
107 /// Default: `false` (entity participates in scoping logic)
108 const IS_UNRESTRICTED: bool = false;
109
110 /// Returns the column that stores the tenant identifier.
111 ///
112 /// - Multi-tenant entities: `Some(Column::TenantId)`
113 /// - Global/system entities: `None`
114 ///
115 /// Must be explicitly specified via `tenant_col = "..."` or `no_tenant`.
116 fn tenant_col() -> Option<Self::Column>;
117
118 /// Returns the column that stores the primary resource identifier.
119 ///
120 /// Typically the primary key column (e.g., `Column::Id`).
121 ///
122 /// Must be explicitly specified via `resource_col = "..."` or `no_resource`.
123 fn resource_col() -> Option<Self::Column>;
124
125 /// Returns the column that stores the resource owner identifier.
126 ///
127 /// Used for owner-based access control policies.
128 ///
129 /// Must be explicitly specified via `owner_col = "..."` or `no_owner`.
130 fn owner_col() -> Option<Self::Column>;
131
132 /// Returns the column that stores the resource type identifier.
133 ///
134 /// Used for type-based filtering in polymorphic scenarios.
135 ///
136 /// Must be explicitly specified via `type_col = "..."` or `no_type`.
137 fn type_col() -> Option<Self::Column>;
138
139 /// Resolve an authorization property name to a database column.
140 ///
141 /// Maps PEP property names (e.g. `"owner_tenant_id"`) to `SeaORM` columns
142 /// so the scope condition builder can translate `AccessScope` constraints
143 /// into SQL `WHERE` clauses.
144 ///
145 /// When using `#[derive(Scopable)]`, this method is auto-generated from
146 /// dimension columns and `pep_prop(...)` entries:
147 /// - `tenant_col` → `"owner_tenant_id"`
148 /// - `resource_col` → `"id"`
149 /// - `owner_col` → `"owner_id"`
150 /// - `pep_prop(custom = "column")` → `"custom"`
151 ///
152 /// Manual implementors must provide all property arms explicitly.
153 #[must_use]
154 fn resolve_property(property: &str) -> Option<Self::Column>;
155}