Source code for flask_more_smorest.perms.models.role

"""Role and domain models for Flask-More-Smorest.

Provides BaseRoleEnum, Domain, and UserRole models for
multi-domain role-based access control.
"""

from __future__ import annotations

import enum
import os
import uuid
from typing import Any

import sqlalchemy as sa

from ...sqla import db
from .abstract_role import AbstractDomain, AbstractUserRole
from .base_roles import BaseRoleEnum

__all__ = ["BaseRoleEnum", "Domain", "UserRole"]


[docs] class Domain(AbstractDomain): """Distinct domains within the app for multi-domain support. This is a concrete implementation of AbstractDomain. For customization, subclass AbstractDomain instead of this class. """
[docs] @classmethod def get_default_domain_id(cls) -> uuid.UUID | None: """Get the default domain ID from environment or first available.""" domain: Domain | None if default_domain := os.getenv("DEFAULT_DOMAIN_NAME"): domain = cls.get_by(name=default_domain) if domain: return domain.id domain = db.session.execute(sa.select(cls).limit(1)).scalar_one_or_none() if domain: return domain.id return None
def _can_read(self, user: Any) -> bool: """Any user can read domains. Args: user: The current authenticated user, or None (ignored for Domain) """ return True
[docs] class UserRole(AbstractUserRole): """User roles with domain scoping for multi-domain applications. To use custom role enums, simply pass enum values when creating roles: class CustomRole(str, enum.Enum): SUPERADMIN = "superadmin" ADMIN = "admin" MANAGER = "manager" USER = "user" # Create roles with custom enum values role = UserRole(user=user, role=CustomRole.MANAGER) # The role property will return the string value, which can be # converted back to your custom enum as needed: manager_role = CustomRole(role.role) if hasattr(CustomRole, role.role) else role.role """ # Store role as string to support any enum # No default Role enum - accept any string/enum value @property def role(self) -> str: """Get role as string value. Returns: Role name as string """ return self._role @role.setter def role(self, value: str | enum.Enum) -> None: """Set role value from enum or string. Args: value: Role value (enum or string) """ # Normalize role to uppercase string for consistency # This handles both enum values (already uppercase) and string inputs if isinstance(value, enum.Enum): self._role = str(value.value).upper() else: self._role = str(value).upper() def __init__( self, domain_id: uuid.UUID | str | None = None, role: str | enum.Enum | None = None, **kwargs: object, ) -> None: """Initialize role with domain and role handling. Args: domain_id: Domain UUID or '*' for all domains role: Role value (enum or string) **kwargs: Additional field values """ if domain_id is None: domain_id = Domain.get_default_domain_id() # Force explicit use of '*' to set domain_id to None: elif domain_id == "*": domain_id = None if isinstance(domain_id, str): raise TypeError("Expected domain_id to be UUID, None or '*'") # Handle role parameter - normalize to uppercase if role is not None: if isinstance(role, enum.Enum): kwargs["_role"] = str(role.value).upper() else: kwargs["_role"] = str(role).upper() super().__init__(domain_id=domain_id, role=role, **kwargs) def _can_write(self, user: Any) -> bool: """Permission check for modifying roles. Supports custom role enums by checking for elevated role names ('superadmin' or 'admin' in the role string). Args: user: The current authenticated user, or None """ from ..user_context import ROLE_ADMIN, ROLE_SUPERADMIN try: if not user: return False # Superadmins can modify any role if user.has_role(ROLE_SUPERADMIN): return True # Admins can only modify non-admin roles # Check for elevated role names (uppercase) in stored role role_value = self._role.upper() is_elevated_role = ROLE_SUPERADMIN in role_value or ROLE_ADMIN in role_value return not is_elevated_role and user.has_role(ROLE_ADMIN) except Exception: return False def _can_create(self, user: Any) -> bool: """Permission check for creating roles. Uses same logic as _can_write(): only superadmins can create admin/superadmin roles, admins can create other roles. Args: user: The current authenticated user, or None """ return self._can_write(user) def _can_read(self, user: Any) -> bool: """Permission check for reading roles. Delegates to user.can_read() to properly apply admin bypass logic. Args: user: The current authenticated user, or None """ try: # Use can_read() instead of _can_read() to apply admin bypass logic return self.user.can_read(user) except Exception: return True