Use core auth and scope contracts
This commit is contained in:
29
README.md
29
README.md
@@ -6,10 +6,11 @@ administration.
|
|||||||
|
|
||||||
The repository contains the extracted access seed implementation under
|
The repository contains the extracted access seed implementation under
|
||||||
`src/govoplan_access/backend`. Session, API-key, and password helper services,
|
`src/govoplan_access/backend`. Session, API-key, and password helper services,
|
||||||
interactive auth routes, and administration routers are owned here. The public
|
interactive auth routes, and administration routers are owned here. Access
|
||||||
FastAPI request dependency API is exported from `govoplan_access.auth`; modules
|
still exports `govoplan_access.auth` for compatibility, but sibling modules
|
||||||
must not import the backend dependency module directly. Access-side admin
|
should import the core `govoplan_core.auth` facade so auth can become a
|
||||||
service helpers remain
|
provider-neutral capability. Modules must not import the backend dependency
|
||||||
|
module directly. Access-side admin service helpers remain
|
||||||
here for users, groups, roles, system accounts, sessions, API keys, tenant
|
here for users, groups, roles, system accounts, sessions, API keys, tenant
|
||||||
access enforcement, admin/audit lookup capabilities, tenant owner
|
access enforcement, admin/audit lookup capabilities, tenant owner
|
||||||
provisioning, and governance-template materialization into access-owned groups
|
provisioning, and governance-template materialization into access-owned groups
|
||||||
@@ -18,10 +19,12 @@ transitional administration WebUI route shell and
|
|||||||
access-owned panels live under `webui/src` as `@govoplan/access-webui`. Generic
|
access-owned panels live under `webui/src` as `@govoplan/access-webui`. Generic
|
||||||
system administration panels are contributed by `@govoplan/admin-webui` through
|
system administration panels are contributed by `@govoplan/admin-webui` through
|
||||||
core's `admin.sections` UI capability. Live access ORM models are defined here
|
core's `admin.sections` UI capability. Live access ORM models are defined here
|
||||||
with `access_*` table names; tenant records live in `govoplan-tenancy`,
|
with `access_*` table names. Access stores current scope identifiers in
|
||||||
governance templates in `govoplan-admin`, audit logs in `govoplan-audit`, and
|
`tenant_id` columns but does not hard-depend on the tenancy module package.
|
||||||
system settings in `govoplan-core`. The current access boundary is documented
|
Tenancy-specific tenant administration and tenant resolver behavior live in
|
||||||
in:
|
`govoplan-tenancy`; governance templates in `govoplan-admin`, audit logs in
|
||||||
|
`govoplan-audit`, and system settings in `govoplan-core`. The current access
|
||||||
|
boundary is documented in:
|
||||||
|
|
||||||
- `/mnt/DATA/git/govoplan-core/docs/ACCESS_RBAC_MODEL.md`
|
- `/mnt/DATA/git/govoplan-core/docs/ACCESS_RBAC_MODEL.md`
|
||||||
- `/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`
|
- `/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`
|
||||||
@@ -47,7 +50,8 @@ This module will own:
|
|||||||
- permission evaluation
|
- permission evaluation
|
||||||
- access administration backend routes
|
- access administration backend routes
|
||||||
- access administration WebUI route contributions
|
- access administration WebUI route contributions
|
||||||
- published FastAPI auth dependency API at `govoplan_access.auth`
|
- compatibility FastAPI auth dependency API at `govoplan_access.auth`
|
||||||
|
- provider-facing auth facade consumed by sibling modules at `govoplan_core.auth`
|
||||||
- access administration, tenant provisioning, and governance materializer
|
- access administration, tenant provisioning, and governance materializer
|
||||||
capabilities
|
capabilities
|
||||||
- access-owned migrations
|
- access-owned migrations
|
||||||
@@ -55,15 +59,16 @@ This module will own:
|
|||||||
The governance-template routes under `/admin/system/governance-templates` are
|
The governance-template routes under `/admin/system/governance-templates` are
|
||||||
contributed by `govoplan-admin`; access must not register those routes.
|
contributed by `govoplan-admin`; access must not register those routes.
|
||||||
|
|
||||||
`govoplan-access` depends on `govoplan-tenancy`; the registry loads tenancy
|
`govoplan-access` treats `govoplan-tenancy` as optional. Access can run in the
|
||||||
before access for authenticated platform composition.
|
single-scope compatibility mode used by the core/access baseline, and tenancy
|
||||||
|
adds tenant administration plus tenant resolver behavior when installed.
|
||||||
|
|
||||||
## Principal Context
|
## Principal Context
|
||||||
|
|
||||||
The stable principal DTO is `govoplan_core.core.access.PrincipalRef`. Access
|
The stable principal DTO is `govoplan_core.core.access.PrincipalRef`. Access
|
||||||
resolves sessions, API keys, and future service accounts into that DTO and
|
resolves sessions, API keys, and future service accounts into that DTO and
|
||||||
serializes it as `principal` in auth API responses. Feature modules should use
|
serializes it as `principal` in auth API responses. Feature modules should use
|
||||||
that DTO, primitive IDs, or the published `govoplan_access.auth` dependency API
|
that DTO, primitive IDs, or the core `govoplan_core.auth` dependency facade
|
||||||
instead of importing access ORM models or backend dependency internals.
|
instead of importing access ORM models or backend dependency internals.
|
||||||
|
|
||||||
The detailed module boundary and serialization fields are documented in
|
The detailed module boundary and serialization fields are documented in
|
||||||
|
|||||||
@@ -53,9 +53,15 @@ importing access internals:
|
|||||||
- generic security helpers that are not access-state semantics, such as
|
- generic security helpers that are not access-state semantics, such as
|
||||||
secret encryption and UTC time helpers
|
secret encryption and UTC time helpers
|
||||||
|
|
||||||
Feature modules should depend on these kernel contracts or the published
|
Feature modules should depend on these kernel contracts or the core
|
||||||
`govoplan_access.auth` request dependency API, not on access ORM models or
|
`govoplan_core.auth` request dependency facade, not on access ORM models or
|
||||||
`govoplan_access.backend.*` implementation internals.
|
`govoplan_access.backend.*` implementation internals. The access package still
|
||||||
|
exports `govoplan_access.auth` for compatibility, but new routers should use
|
||||||
|
the core facade so auth can move behind provider-neutral capabilities.
|
||||||
|
|
||||||
|
Access declares tenancy as an optional module integration. It uses the
|
||||||
|
core-owned `core_scopes` table as the scope table, but it must not import
|
||||||
|
`govoplan_tenancy` or require the tenancy package to start.
|
||||||
|
|
||||||
## Principal Context Contract
|
## Principal Context Contract
|
||||||
|
|
||||||
@@ -115,7 +121,7 @@ selection are still follow-up work on top of these routes.
|
|||||||
## Removed Compatibility Paths
|
## Removed Compatibility Paths
|
||||||
|
|
||||||
These legacy imports were removed from core. Use access-owned modules, the
|
These legacy imports were removed from core. Use access-owned modules, the
|
||||||
public `govoplan_access.auth` request dependency API, or kernel capabilities
|
core `govoplan_core.auth` request dependency facade, or kernel capabilities
|
||||||
instead:
|
instead:
|
||||||
|
|
||||||
- `govoplan_core.security.api_keys`
|
- `govoplan_core.security.api_keys`
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from sqlalchemy.orm import Session
|
|||||||
from govoplan_core.core.access import (
|
from govoplan_core.core.access import (
|
||||||
CAPABILITY_ACCESS_PERMISSION_EVALUATOR,
|
CAPABILITY_ACCESS_PERMISSION_EVALUATOR,
|
||||||
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
|
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
|
||||||
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||||
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||||
PermissionEvaluator,
|
PermissionEvaluator,
|
||||||
PrincipalRef,
|
PrincipalRef,
|
||||||
PrincipalResolver,
|
PrincipalResolver,
|
||||||
@@ -16,7 +18,7 @@ from govoplan_core.core.modules import AccessDecision
|
|||||||
from govoplan_core.core.registry import PlatformRegistry
|
from govoplan_core.core.registry import PlatformRegistry
|
||||||
from govoplan_core.core.maintenance import MAINTENANCE_ACCESS_SCOPE, maintenance_response_detail, saved_maintenance_mode
|
from govoplan_core.core.maintenance import MAINTENANCE_ACCESS_SCOPE, maintenance_response_detail, saved_maintenance_mode
|
||||||
from govoplan_core.db.session import get_database, get_session
|
from govoplan_core.db.session import get_database, get_session
|
||||||
from govoplan_access.backend.db.models import Account, ApiKey, AuthSession, User
|
from govoplan_access.backend.db.models import Account, ApiKey, AuthSession, Tenant, User
|
||||||
from govoplan_access.backend.semantic import collect_function_assignment_ids, collect_function_delegation_ids, identity_id_for_account
|
from govoplan_access.backend.semantic import collect_function_assignment_ids, collect_function_delegation_ids, identity_id_for_account
|
||||||
from govoplan_access.backend.security.api_keys import authenticate_api_key
|
from govoplan_access.backend.security.api_keys import authenticate_api_key
|
||||||
from govoplan_access.backend.security.sessions import (
|
from govoplan_access.backend.security.sessions import (
|
||||||
@@ -26,7 +28,6 @@ from govoplan_access.backend.security.sessions import (
|
|||||||
collect_user_scopes,
|
collect_user_scopes,
|
||||||
verify_auth_session_csrf,
|
verify_auth_session_csrf,
|
||||||
)
|
)
|
||||||
from govoplan_tenancy.backend.db.models import Tenant
|
|
||||||
from govoplan_core.security.module_permissions import scopes_grant_compatible
|
from govoplan_core.security.module_permissions import scopes_grant_compatible
|
||||||
from govoplan_core.security.permissions import intersect_api_key_scopes
|
from govoplan_core.security.permissions import intersect_api_key_scopes
|
||||||
from govoplan_core.settings import settings
|
from govoplan_core.settings import settings
|
||||||
@@ -278,21 +279,35 @@ def _registry_from_request(request: Request) -> PlatformRegistry | None:
|
|||||||
|
|
||||||
def _principal_resolver_from_request(request: Request) -> PrincipalResolver | None:
|
def _principal_resolver_from_request(request: Request) -> PrincipalResolver | None:
|
||||||
registry = _registry_from_request(request)
|
registry = _registry_from_request(request)
|
||||||
if registry is None or not registry.has_capability(CAPABILITY_ACCESS_PRINCIPAL_RESOLVER):
|
if registry is None:
|
||||||
return None
|
return None
|
||||||
capability = registry.require_capability(CAPABILITY_ACCESS_PRINCIPAL_RESOLVER)
|
capability_name = (
|
||||||
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER
|
||||||
|
if registry.has_capability(CAPABILITY_AUTH_PRINCIPAL_RESOLVER)
|
||||||
|
else CAPABILITY_ACCESS_PRINCIPAL_RESOLVER
|
||||||
|
)
|
||||||
|
if not registry.has_capability(capability_name):
|
||||||
|
return None
|
||||||
|
capability = registry.require_capability(capability_name)
|
||||||
if not isinstance(capability, PrincipalResolver):
|
if not isinstance(capability, PrincipalResolver):
|
||||||
raise HTTPException(status_code=500, detail=f"Invalid capability: {CAPABILITY_ACCESS_PRINCIPAL_RESOLVER}")
|
raise HTTPException(status_code=500, detail=f"Invalid capability: {capability_name}")
|
||||||
return capability
|
return capability
|
||||||
|
|
||||||
|
|
||||||
def _permission_evaluator_from_request(request: Request) -> PermissionEvaluator | None:
|
def _permission_evaluator_from_request(request: Request) -> PermissionEvaluator | None:
|
||||||
registry = _registry_from_request(request)
|
registry = _registry_from_request(request)
|
||||||
if registry is None or not registry.has_capability(CAPABILITY_ACCESS_PERMISSION_EVALUATOR):
|
if registry is None:
|
||||||
return None
|
return None
|
||||||
capability = registry.require_capability(CAPABILITY_ACCESS_PERMISSION_EVALUATOR)
|
capability_name = (
|
||||||
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR
|
||||||
|
if registry.has_capability(CAPABILITY_AUTH_PERMISSION_EVALUATOR)
|
||||||
|
else CAPABILITY_ACCESS_PERMISSION_EVALUATOR
|
||||||
|
)
|
||||||
|
if not registry.has_capability(capability_name):
|
||||||
|
return None
|
||||||
|
capability = registry.require_capability(capability_name)
|
||||||
if not isinstance(capability, PermissionEvaluator):
|
if not isinstance(capability, PermissionEvaluator):
|
||||||
raise HTTPException(status_code=500, detail=f"Invalid capability: {CAPABILITY_ACCESS_PERMISSION_EVALUATOR}")
|
raise HTTPException(status_code=500, detail=f"Invalid capability: {capability_name}")
|
||||||
return capability
|
return capability
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from sqlalchemy import Boolean, DateTime, ForeignKey, Index, String, Text, Uniqu
|
|||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from govoplan_access.backend.db.base import AccessBase, TimestampMixin
|
from govoplan_access.backend.db.base import AccessBase, TimestampMixin
|
||||||
from govoplan_tenancy.backend.db.models import Tenant
|
from govoplan_core.tenancy.scope import Tenant
|
||||||
|
|
||||||
|
|
||||||
def new_uuid() -> str:
|
def new_uuid() -> str:
|
||||||
@@ -93,7 +93,7 @@ class User(AccessBase, TimestampMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
email: Mapped[str] = mapped_column(String(320), nullable=False, index=True)
|
email: Mapped[str] = mapped_column(String(320), nullable=False, index=True)
|
||||||
display_name: Mapped[str | None] = mapped_column(String(255))
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
||||||
@@ -105,7 +105,6 @@ class User(AccessBase, TimestampMixin):
|
|||||||
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
||||||
mail_profile_policy: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
mail_profile_policy: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
||||||
|
|
||||||
tenant: Mapped[Tenant] = relationship(back_populates="users")
|
|
||||||
account: Mapped[Account] = relationship(back_populates="memberships")
|
account: Mapped[Account] = relationship(back_populates="memberships")
|
||||||
api_keys: Mapped[list[ApiKey]] = relationship(back_populates="user", cascade="all, delete-orphan")
|
api_keys: Mapped[list[ApiKey]] = relationship(back_populates="user", cascade="all, delete-orphan")
|
||||||
auth_sessions: Mapped[list[AuthSession]] = relationship(back_populates="user", cascade="all, delete-orphan")
|
auth_sessions: Mapped[list[AuthSession]] = relationship(back_populates="user", cascade="all, delete-orphan")
|
||||||
@@ -116,7 +115,7 @@ class Group(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_groups_tenant_slug"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_groups_tenant_slug"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
description: Mapped[str | None] = mapped_column(Text)
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
@@ -141,7 +140,7 @@ class Role(AccessBase, TimestampMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str | None] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=True, index=True)
|
tenant_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
|
||||||
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
description: Mapped[str | None] = mapped_column(Text)
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
@@ -157,7 +156,7 @@ class OrganizationUnit(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_organization_units_tenant_slug"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_organization_units_tenant_slug"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
parent_id: Mapped[str | None] = mapped_column(ForeignKey("access_organization_units.id", ondelete="SET NULL"), nullable=True, index=True)
|
parent_id: Mapped[str | None] = mapped_column(ForeignKey("access_organization_units.id", ondelete="SET NULL"), nullable=True, index=True)
|
||||||
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
@@ -171,7 +170,7 @@ class Function(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "organization_unit_id", "slug", name="uq_functions_tenant_ou_slug"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "organization_unit_id", "slug", name="uq_functions_tenant_ou_slug"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
organization_unit_id: Mapped[str] = mapped_column(ForeignKey("access_organization_units.id", ondelete="CASCADE"), nullable=False, index=True)
|
organization_unit_id: Mapped[str] = mapped_column(ForeignKey("access_organization_units.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
@@ -187,7 +186,7 @@ class FunctionRoleAssignment(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "function_id", "role_id", name="uq_function_role_assignments"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "function_id", "role_id", name="uq_function_role_assignments"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
function_id: Mapped[str] = mapped_column(ForeignKey("access_functions.id", ondelete="CASCADE"), nullable=False, index=True)
|
function_id: Mapped[str] = mapped_column(ForeignKey("access_functions.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
|
|
||||||
@@ -205,7 +204,7 @@ class FunctionAssignment(AccessBase, TimestampMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
identity_id: Mapped[str | None] = mapped_column(ForeignKey("access_identities.id", ondelete="SET NULL"), nullable=True, index=True)
|
identity_id: Mapped[str | None] = mapped_column(ForeignKey("access_identities.id", ondelete="SET NULL"), nullable=True, index=True)
|
||||||
function_id: Mapped[str] = mapped_column(ForeignKey("access_functions.id", ondelete="CASCADE"), nullable=False, index=True)
|
function_id: Mapped[str] = mapped_column(ForeignKey("access_functions.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
@@ -233,7 +232,7 @@ class FunctionDelegation(AccessBase, TimestampMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
function_assignment_id: Mapped[str] = mapped_column(ForeignKey("access_function_assignments.id", ondelete="CASCADE"), nullable=False, index=True)
|
function_assignment_id: Mapped[str] = mapped_column(ForeignKey("access_function_assignments.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
delegator_account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
delegator_account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
delegate_account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
delegate_account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
@@ -263,7 +262,7 @@ class UserGroupMembership(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "user_id", "group_id", name="uq_user_group_memberships"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "user_id", "group_id", name="uq_user_group_memberships"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
|
|
||||||
@@ -273,7 +272,7 @@ class UserRoleAssignment(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "user_id", "role_id", name="uq_user_role_assignments"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "user_id", "role_id", name="uq_user_role_assignments"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
|
|
||||||
@@ -283,7 +282,7 @@ class GroupRoleAssignment(AccessBase, TimestampMixin):
|
|||||||
__table_args__ = (UniqueConstraint("tenant_id", "group_id", "role_id", name="uq_group_role_assignments"),)
|
__table_args__ = (UniqueConstraint("tenant_id", "group_id", "role_id", name="uq_group_role_assignments"),)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
|
|
||||||
@@ -292,7 +291,7 @@ class ApiKey(AccessBase, TimestampMixin):
|
|||||||
__tablename__ = "access_api_keys"
|
__tablename__ = "access_api_keys"
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
prefix: Mapped[str] = mapped_column(String(16), nullable=False, index=True)
|
prefix: Mapped[str] = mapped_column(String(16), nullable=False, index=True)
|
||||||
@@ -309,7 +308,7 @@ class AuthSession(AccessBase, TimestampMixin):
|
|||||||
__tablename__ = "access_auth_sessions"
|
__tablename__ = "access_auth_sessions"
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
||||||
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||||
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||||
token_hash: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
|
token_hash: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ from govoplan_core.core.access import (
|
|||||||
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
|
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
|
||||||
CAPABILITY_ACCESS_TENANT_PROVISIONER,
|
CAPABILITY_ACCESS_TENANT_PROVISIONER,
|
||||||
CAPABILITY_ACCESS_SEMANTIC_DIRECTORY,
|
CAPABILITY_ACCESS_SEMANTIC_DIRECTORY,
|
||||||
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||||
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
||||||
from govoplan_core.core.modules import (
|
from govoplan_core.core.modules import (
|
||||||
@@ -402,8 +404,7 @@ manifest = ModuleManifest(
|
|||||||
id="access",
|
id="access",
|
||||||
name="Access",
|
name="Access",
|
||||||
version="0.1.6",
|
version="0.1.6",
|
||||||
dependencies=("tenancy",),
|
optional_dependencies=("identity", "organizations", "tenancy"),
|
||||||
optional_dependencies=("identity", "organizations"),
|
|
||||||
permissions=ACCESS_PERMISSIONS,
|
permissions=ACCESS_PERMISSIONS,
|
||||||
role_templates=ACCESS_ROLE_TEMPLATES,
|
role_templates=ACCESS_ROLE_TEMPLATES,
|
||||||
route_factory=_route_factory,
|
route_factory=_route_factory,
|
||||||
@@ -442,6 +443,8 @@ manifest = ModuleManifest(
|
|||||||
nav_items=(NavItem(path="/admin", label="Admin", icon="admin", required_any=ADMIN_READ_SCOPES, order=900),),
|
nav_items=(NavItem(path="/admin", label="Admin", icon="admin", required_any=ADMIN_READ_SCOPES, order=900),),
|
||||||
),
|
),
|
||||||
capability_factories={
|
capability_factories={
|
||||||
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER: _legacy_principal_resolver,
|
||||||
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR: _legacy_permission_evaluator,
|
||||||
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER: _legacy_principal_resolver,
|
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER: _legacy_principal_resolver,
|
||||||
CAPABILITY_ACCESS_PERMISSION_EVALUATOR: _legacy_permission_evaluator,
|
CAPABILITY_ACCESS_PERMISSION_EVALUATOR: _legacy_permission_evaluator,
|
||||||
CAPABILITY_ACCESS_DIRECTORY: _access_directory,
|
CAPABILITY_ACCESS_DIRECTORY: _access_directory,
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ def _tables() -> set[str]:
|
|||||||
return set(sa.inspect(op.get_bind()).get_table_names())
|
return set(sa.inspect(op.get_bind()).get_table_names())
|
||||||
|
|
||||||
|
|
||||||
|
def _scope_fk_target() -> str:
|
||||||
|
tables = _tables()
|
||||||
|
return "core_scopes.id" if "core_scopes" in tables else "tenancy_tenants.id"
|
||||||
|
|
||||||
|
|
||||||
def _indexes(table_name: str) -> set[str]:
|
def _indexes(table_name: str) -> set[str]:
|
||||||
return {item["name"] for item in sa.inspect(op.get_bind()).get_indexes(table_name)}
|
return {item["name"] for item in sa.inspect(op.get_bind()).get_indexes(table_name)}
|
||||||
|
|
||||||
@@ -36,6 +41,7 @@ def _drop_index_if_exists(name: str, table_name: str) -> None:
|
|||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
tables = _tables()
|
tables = _tables()
|
||||||
|
scope_fk_target = _scope_fk_target()
|
||||||
|
|
||||||
if "access_identities" not in tables:
|
if "access_identities" not in tables:
|
||||||
op.create_table(
|
op.create_table(
|
||||||
@@ -119,8 +125,8 @@ def upgrade() -> None:
|
|||||||
),
|
),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
["tenant_id"],
|
["tenant_id"],
|
||||||
["tenancy_tenants.id"],
|
[scope_fk_target],
|
||||||
name=op.f("fk_access_organization_units_tenant_id_tenancy_tenants"),
|
name=op.f("fk_access_organization_units_tenant_id_scopes"),
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
),
|
),
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_organization_units")),
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_organization_units")),
|
||||||
@@ -153,8 +159,8 @@ def upgrade() -> None:
|
|||||||
),
|
),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
["tenant_id"],
|
["tenant_id"],
|
||||||
["tenancy_tenants.id"],
|
[scope_fk_target],
|
||||||
name=op.f("fk_access_functions_tenant_id_tenancy_tenants"),
|
name=op.f("fk_access_functions_tenant_id_scopes"),
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
),
|
),
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_functions")),
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_functions")),
|
||||||
@@ -187,8 +193,8 @@ def upgrade() -> None:
|
|||||||
),
|
),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
["tenant_id"],
|
["tenant_id"],
|
||||||
["tenancy_tenants.id"],
|
[scope_fk_target],
|
||||||
name=op.f("fk_access_function_role_assignments_tenant_id_tenancy_tenants"),
|
name=op.f("fk_access_function_role_assignments_tenant_id_scopes"),
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
),
|
),
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_function_role_assignments")),
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_function_role_assignments")),
|
||||||
@@ -256,8 +262,8 @@ def upgrade() -> None:
|
|||||||
),
|
),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
["tenant_id"],
|
["tenant_id"],
|
||||||
["tenancy_tenants.id"],
|
[scope_fk_target],
|
||||||
name=op.f("fk_access_function_assignments_tenant_id_tenancy_tenants"),
|
name=op.f("fk_access_function_assignments_tenant_id_scopes"),
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
),
|
),
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_function_assignments")),
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_function_assignments")),
|
||||||
@@ -309,8 +315,8 @@ def upgrade() -> None:
|
|||||||
),
|
),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
["tenant_id"],
|
["tenant_id"],
|
||||||
["tenancy_tenants.id"],
|
[scope_fk_target],
|
||||||
name=op.f("fk_access_function_delegations_tenant_id_tenancy_tenants"),
|
name=op.f("fk_access_function_delegations_tenant_id_scopes"),
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
),
|
),
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_function_delegations")),
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_access_function_delegations")),
|
||||||
|
|||||||
Reference in New Issue
Block a user