sprint12+13: zentrale rechte settings page + ABAC engine backend (model, migration 0055, service, routes)
This commit is contained in:
@@ -11,6 +11,7 @@ from app.models.contact_folder import ContactFolder
|
||||
from app.models.contact_folder_permission import ContactFolderPermission
|
||||
from app.models.contact_merge import ContactMergeHistory
|
||||
from app.models.entity_permission import EntityPermission
|
||||
from app.models.entity_policy import EntityPolicy
|
||||
from app.models.owned_mixin import OwnedMixin
|
||||
from app.models.entity_history import EntityHistory
|
||||
from app.models.currency import Currency
|
||||
@@ -51,6 +52,7 @@ __all__ = [
|
||||
"ContactFolderPermission",
|
||||
"ContactMergeHistory",
|
||||
"EntityPermission",
|
||||
"EntityPolicy",
|
||||
"OwnedMixin",
|
||||
"EntityHistory",
|
||||
"Currency",
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"""ABAC entity policy model — attribute-based access control policies.
|
||||
|
||||
Each policy defines a rule for a specific entity type:
|
||||
- allow policies: at least one must match for access
|
||||
- deny policies: if any matches, access is denied (deny takes precedence)
|
||||
|
||||
Conditions use JSONB with format:
|
||||
{
|
||||
"operator": "AND" | "OR",
|
||||
"rules": [
|
||||
{"field": "status", "op": "eq", "value": "active"},
|
||||
{"field": "amount", "op": "gte", "value": 1000},
|
||||
{"field": "tags", "op": "contains", "value": "vip"}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
CheckConstraint,
|
||||
DateTime,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.db import Base, TenantMixin
|
||||
|
||||
|
||||
class EntityPolicy(Base, TenantMixin):
|
||||
"""ABAC policy entry for any entity type in the system.
|
||||
|
||||
entity_type examples: 'contact', 'dms_file', 'mailbox', 'calendar_event',
|
||||
'task', 'workflow', 'contact_folder', etc.
|
||||
|
||||
principal_type: 'user', 'group', 'role'
|
||||
|
||||
effect: 'allow' | 'deny'
|
||||
- allow: grants access if conditions match
|
||||
- deny: blocks access if conditions match (deny takes precedence over allow)
|
||||
|
||||
conditions: JSONB with operator (AND/OR) and rules array
|
||||
priority: higher priority policies are evaluated first
|
||||
"""
|
||||
|
||||
__tablename__ = "entity_policies"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"principal_type IN ('user', 'group', 'role')",
|
||||
name="ck_epol_principal_type",
|
||||
),
|
||||
CheckConstraint(
|
||||
"effect IN ('allow', 'deny')",
|
||||
name="ck_epol_effect",
|
||||
),
|
||||
Index("ix_epol_entity_type", "entity_type"),
|
||||
Index("ix_epol_principal", "principal_type", "principal_id"),
|
||||
Index("ix_epol_tenant", "tenant_id"),
|
||||
Index("ix_epol_priority", "priority"),
|
||||
Index("ix_epol_enabled", "enabled"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
entity_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
principal_type: Mapped[str] = mapped_column(String(10), nullable=False)
|
||||
principal_id: Mapped[uuid.UUID] = mapped_column(
|
||||
PGUUID(as_uuid=True), nullable=False
|
||||
)
|
||||
effect: Mapped[str] = mapped_column(
|
||||
String(10), nullable=False, default="allow"
|
||||
)
|
||||
conditions: Mapped[dict | None] = mapped_column(
|
||||
JSONB, nullable=True, default=None
|
||||
)
|
||||
priority: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=0
|
||||
)
|
||||
enabled: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=True
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
Reference in New Issue
Block a user