feat: Implement multi-tenancy support in middleware and security layers
- Enhanced TenantMiddleware to validate tenant information from JWT tokens. - Added LicenseValidationMiddleware to check tenant licenses before processing requests. - Updated security utilities to extract tenant information from tokens and validate company access. - Introduced CompanyStore to manage active company state and handle company switching in the frontend. - Modified API routes to include company_id in requests for better resource management. - Improved logging and error handling throughout the middleware and API layers. - Updated frontend components to reflect changes in company management and selection. - Added new API route for fetching user's companies with proper authentication handling.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
DTOs para gestión de relaciones usuario-tenant
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
@@ -8,6 +9,7 @@ from datetime import datetime
|
||||
|
||||
class AddUserToTenantRequestDTO(BaseModel):
|
||||
"""Request para agregar un usuario a un tenant"""
|
||||
|
||||
keycloak_user_id: str = Field(..., description="ID del usuario en Keycloak")
|
||||
tenant_id: int = Field(..., description="ID del tenant")
|
||||
role: Optional[str] = Field(None, description="Rol del usuario en el tenant")
|
||||
@@ -15,6 +17,7 @@ class AddUserToTenantRequestDTO(BaseModel):
|
||||
|
||||
class RemoveUserFromTenantRequestDTO(BaseModel):
|
||||
"""Request para eliminar un usuario de un tenant"""
|
||||
|
||||
keycloak_user_id: str = Field(..., description="ID del usuario en Keycloak")
|
||||
tenant_id: int = Field(..., description="ID del tenant")
|
||||
soft_delete: bool = Field(True, description="Si True, desactiva. Si False, elimina")
|
||||
@@ -22,6 +25,7 @@ class RemoveUserFromTenantRequestDTO(BaseModel):
|
||||
|
||||
class UpdateUserRoleRequestDTO(BaseModel):
|
||||
"""Request para actualizar el rol de un usuario en un tenant"""
|
||||
|
||||
keycloak_user_id: str = Field(..., description="ID del usuario en Keycloak")
|
||||
tenant_id: int = Field(..., description="ID del tenant")
|
||||
role: str = Field(..., description="Nuevo rol del usuario")
|
||||
@@ -29,6 +33,7 @@ class UpdateUserRoleRequestDTO(BaseModel):
|
||||
|
||||
class UserTenantResponseDTO(BaseModel):
|
||||
"""Response con información de relación usuario-tenant"""
|
||||
|
||||
id: int
|
||||
keycloak_user_id: str
|
||||
tenant_id: int
|
||||
@@ -36,24 +41,26 @@ class UserTenantResponseDTO(BaseModel):
|
||||
role: Optional[str]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TenantBasicInfoDTO(BaseModel):
|
||||
"""Información básica de un tenant"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
slug: str
|
||||
is_active: bool
|
||||
keycloak_realm: str
|
||||
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserTenantsResponseDTO(BaseModel):
|
||||
"""Response con los tenants de un usuario"""
|
||||
|
||||
keycloak_user_id: str
|
||||
tenants: list[TenantBasicInfoDTO]
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
"""
|
||||
Modelo de relación entre usuarios (Keycloak) y tenants
|
||||
"""
|
||||
from sqlalchemy import Integer, String, DateTime, Boolean, UniqueConstraint, ForeignKeyConstraint, ForeignKey
|
||||
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
DateTime,
|
||||
Boolean,
|
||||
UniqueConstraint,
|
||||
ForeignKeyConstraint,
|
||||
)
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from datetime import datetime
|
||||
@@ -11,42 +19,51 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.tenants.models import Tenant
|
||||
|
||||
|
||||
class UserTenant(Base):
|
||||
"""
|
||||
Relación muchos-a-muchos entre usuarios de Keycloak y tenants
|
||||
|
||||
|
||||
Un usuario puede pertenecer a múltiples tenants
|
||||
Un tenant puede tener múltiples usuarios
|
||||
"""
|
||||
|
||||
__tablename__ = "user_tenants"
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
UniqueConstraint('keycloak_user_id', 'tenant_id', name='uq_user_tenant'),
|
||||
{"schema": "a76"}
|
||||
ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
|
||||
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
|
||||
UniqueConstraint(
|
||||
"keycloak_user_id", "tenant_id", "company_id", name="uq_user_tenant"
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
# Primary Key
|
||||
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
||||
|
||||
|
||||
# ID del usuario en Keycloak (UUID string)
|
||||
keycloak_user_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
||||
|
||||
keycloak_user_id: Mapped[str] = mapped_column(
|
||||
String(255), nullable=False, index=True
|
||||
)
|
||||
|
||||
# ID del tenant
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
# ID de la empresa asociada
|
||||
company_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.companies.id"), nullable=False, index=True)
|
||||
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
# Estado de la relación
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
|
||||
|
||||
# Información adicional - Rol del usuario en este tenant (opcional)
|
||||
role: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
# Relación con Tenant
|
||||
tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="user_relations")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Rutas para gestión de relaciones usuario-tenant
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
@@ -14,31 +15,26 @@ from .dto import (
|
||||
UpdateUserRoleRequestDTO,
|
||||
UserTenantResponseDTO,
|
||||
UserTenantsResponseDTO,
|
||||
TenantBasicInfoDTO
|
||||
TenantBasicInfoDTO,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/user-tenants",
|
||||
tags=["User-Tenant Relations"]
|
||||
)
|
||||
router = APIRouter(prefix="/user-tenants", tags=["User-Tenant Relations"])
|
||||
|
||||
|
||||
@router.post("/add", response_model=UserTenantResponseDTO)
|
||||
def add_user_to_tenant(
|
||||
data: AddUserToTenantRequestDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Agrega un usuario a un tenant
|
||||
|
||||
|
||||
Requiere permisos de administrador
|
||||
"""
|
||||
service = UserTenantService(db)
|
||||
result = service.add_user_to_tenant(
|
||||
keycloak_user_id=data.keycloak_user_id,
|
||||
tenant_id=data.tenant_id,
|
||||
role=data.role
|
||||
keycloak_user_id=data.keycloak_user_id, tenant_id=data.tenant_id, role=data.role
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -47,18 +43,18 @@ def add_user_to_tenant(
|
||||
def remove_user_from_tenant(
|
||||
data: RemoveUserFromTenantRequestDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Elimina un usuario de un tenant
|
||||
|
||||
|
||||
Requiere permisos de administrador
|
||||
"""
|
||||
service = UserTenantService(db)
|
||||
service.remove_user_from_tenant(
|
||||
keycloak_user_id=data.keycloak_user_id,
|
||||
tenant_id=data.tenant_id,
|
||||
soft_delete=data.soft_delete
|
||||
soft_delete=data.soft_delete,
|
||||
)
|
||||
return {"message": "User removed from tenant successfully"}
|
||||
|
||||
@@ -67,18 +63,16 @@ def remove_user_from_tenant(
|
||||
def update_user_role(
|
||||
data: UpdateUserRoleRequestDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Actualiza el rol de un usuario en un tenant
|
||||
|
||||
|
||||
Requiere permisos de administrador
|
||||
"""
|
||||
service = UserTenantService(db)
|
||||
result = service.update_user_role_in_tenant(
|
||||
keycloak_user_id=data.keycloak_user_id,
|
||||
tenant_id=data.tenant_id,
|
||||
role=data.role
|
||||
keycloak_user_id=data.keycloak_user_id, tenant_id=data.tenant_id, role=data.role
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -87,27 +81,26 @@ def update_user_role(
|
||||
def get_user_tenants(
|
||||
keycloak_user_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Obtiene todos los tenants a los que tiene acceso un usuario
|
||||
|
||||
|
||||
Los usuarios solo pueden ver sus propios tenants, a menos que sean admin
|
||||
"""
|
||||
# Verificar que el usuario solo pueda ver sus propios tenants (excepto admin)
|
||||
if current_user.get("sub") != keycloak_user_id:
|
||||
# TODO: Verificar si es admin
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You can only view your own tenants"
|
||||
status_code=403, detail="You can only view your own tenants"
|
||||
)
|
||||
|
||||
|
||||
service = UserTenantService(db)
|
||||
tenants = service.get_user_tenants(keycloak_user_id)
|
||||
|
||||
|
||||
return UserTenantsResponseDTO(
|
||||
keycloak_user_id=keycloak_user_id,
|
||||
tenants=[TenantBasicInfoDTO.model_validate(t) for t in tenants]
|
||||
tenants=[TenantBasicInfoDTO.model_validate(t) for t in tenants],
|
||||
)
|
||||
|
||||
|
||||
@@ -115,11 +108,11 @@ def get_user_tenants(
|
||||
def get_tenant_users(
|
||||
tenant_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Obtiene todos los usuarios que tienen acceso a un tenant
|
||||
|
||||
|
||||
Requiere permisos de administrador del tenant
|
||||
"""
|
||||
service = UserTenantService(db)
|
||||
@@ -132,16 +125,16 @@ def check_user_access(
|
||||
keycloak_user_id: str,
|
||||
tenant_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Verifica si un usuario tiene acceso a un tenant
|
||||
"""
|
||||
service = UserTenantService(db)
|
||||
has_access = service.user_has_access_to_tenant(keycloak_user_id, tenant_id)
|
||||
|
||||
|
||||
return {
|
||||
"keycloak_user_id": keycloak_user_id,
|
||||
"tenant_id": tenant_id,
|
||||
"has_access": has_access
|
||||
"has_access": has_access,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Servicio para gestionar relaciones entre usuarios y tenants
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
from typing import List, Optional
|
||||
@@ -15,24 +16,21 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class UserTenantService:
|
||||
"""Servicio para gestionar acceso de usuarios a tenants"""
|
||||
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
|
||||
def add_user_to_tenant(
|
||||
self,
|
||||
keycloak_user_id: str,
|
||||
tenant_id: int,
|
||||
role: Optional[str] = None
|
||||
self, keycloak_user_id: str, tenant_id: int, role: Optional[str] = None
|
||||
) -> UserTenant:
|
||||
"""
|
||||
Agrega un usuario a un tenant
|
||||
|
||||
|
||||
Args:
|
||||
keycloak_user_id: ID del usuario en Keycloak
|
||||
tenant_id: ID del tenant
|
||||
role: Rol opcional del usuario en este tenant
|
||||
|
||||
|
||||
Returns:
|
||||
UserTenant creado
|
||||
"""
|
||||
@@ -40,15 +38,19 @@ class UserTenantService:
|
||||
tenant = self.db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="Tenant not found")
|
||||
|
||||
|
||||
# Verificar si la relación ya existe
|
||||
existing = self.db.query(UserTenant).filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id
|
||||
existing = (
|
||||
self.db.query(UserTenant)
|
||||
.filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing:
|
||||
# Si existe pero está inactiva, reactivarla
|
||||
if not existing.is_active:
|
||||
@@ -56,59 +58,60 @@ class UserTenantService:
|
||||
existing.role = role
|
||||
self.db.commit()
|
||||
self.db.refresh(existing)
|
||||
logger.info(f"Reactivated user {keycloak_user_id} in tenant {tenant_id}")
|
||||
logger.info(
|
||||
f"Reactivated user {keycloak_user_id} in tenant {tenant_id}"
|
||||
)
|
||||
return existing
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="User already has access to this tenant"
|
||||
status_code=409, detail="User already has access to this tenant"
|
||||
)
|
||||
|
||||
|
||||
# Crear nueva relación
|
||||
user_tenant = UserTenant(
|
||||
keycloak_user_id=keycloak_user_id,
|
||||
tenant_id=tenant_id,
|
||||
role=role,
|
||||
is_active=True
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
|
||||
self.db.add(user_tenant)
|
||||
self.db.commit()
|
||||
self.db.refresh(user_tenant)
|
||||
|
||||
|
||||
logger.info(f"Added user {keycloak_user_id} to tenant {tenant_id}")
|
||||
return user_tenant
|
||||
|
||||
|
||||
def remove_user_from_tenant(
|
||||
self,
|
||||
keycloak_user_id: str,
|
||||
tenant_id: int,
|
||||
soft_delete: bool = True
|
||||
self, keycloak_user_id: str, tenant_id: int, soft_delete: bool = True
|
||||
) -> bool:
|
||||
"""
|
||||
Elimina un usuario de un tenant
|
||||
|
||||
|
||||
Args:
|
||||
keycloak_user_id: ID del usuario en Keycloak
|
||||
tenant_id: ID del tenant
|
||||
soft_delete: Si True, solo marca como inactivo. Si False, elimina físicamente
|
||||
|
||||
|
||||
Returns:
|
||||
True si se eliminó correctamente
|
||||
"""
|
||||
user_tenant = self.db.query(UserTenant).filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id
|
||||
user_tenant = (
|
||||
self.db.query(UserTenant)
|
||||
.filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
.first()
|
||||
)
|
||||
|
||||
if not user_tenant:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="User-tenant relationship not found"
|
||||
status_code=404, detail="User-tenant relationship not found"
|
||||
)
|
||||
|
||||
|
||||
if soft_delete:
|
||||
user_tenant.is_active = False
|
||||
self.db.commit()
|
||||
@@ -117,112 +120,118 @@ class UserTenantService:
|
||||
self.db.delete(user_tenant)
|
||||
self.db.commit()
|
||||
logger.info(f"Deleted user {keycloak_user_id} from tenant {tenant_id}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_user_tenants(self, keycloak_user_id: str) -> List[Tenant]:
|
||||
"""
|
||||
Obtiene todos los tenants a los que tiene acceso un usuario
|
||||
|
||||
|
||||
Args:
|
||||
keycloak_user_id: ID del usuario en Keycloak
|
||||
|
||||
|
||||
Returns:
|
||||
Lista de tenants
|
||||
"""
|
||||
user_tenants = self.db.query(UserTenant).filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.is_active == True
|
||||
user_tenants = (
|
||||
self.db.query(UserTenant)
|
||||
.filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.is_active == True,
|
||||
)
|
||||
)
|
||||
).all()
|
||||
|
||||
.all()
|
||||
)
|
||||
|
||||
tenant_ids = [ut.tenant_id for ut in user_tenants]
|
||||
|
||||
tenants = self.db.query(Tenant).filter(
|
||||
and_(
|
||||
Tenant.id.in_(tenant_ids),
|
||||
Tenant.is_active == True
|
||||
)
|
||||
).all()
|
||||
|
||||
|
||||
tenants = (
|
||||
self.db.query(Tenant)
|
||||
.filter(and_(Tenant.id.in_(tenant_ids), Tenant.is_active == True))
|
||||
.all()
|
||||
)
|
||||
|
||||
return tenants
|
||||
|
||||
|
||||
def get_tenant_users(self, tenant_id: int) -> List[UserTenant]:
|
||||
"""
|
||||
Obtiene todos los usuarios que tienen acceso a un tenant
|
||||
|
||||
|
||||
Args:
|
||||
tenant_id: ID del tenant
|
||||
|
||||
|
||||
Returns:
|
||||
Lista de relaciones UserTenant
|
||||
"""
|
||||
return self.db.query(UserTenant).filter(
|
||||
and_(
|
||||
UserTenant.tenant_id == tenant_id,
|
||||
UserTenant.is_active == True
|
||||
return (
|
||||
self.db.query(UserTenant)
|
||||
.filter(
|
||||
and_(UserTenant.tenant_id == tenant_id, UserTenant.is_active == True)
|
||||
)
|
||||
).all()
|
||||
|
||||
def user_has_access_to_tenant(
|
||||
self,
|
||||
keycloak_user_id: str,
|
||||
tenant_id: int
|
||||
) -> bool:
|
||||
.all()
|
||||
)
|
||||
|
||||
def user_has_access_to_tenant(self, keycloak_user_id: str, tenant_id: int) -> bool:
|
||||
"""
|
||||
Verifica si un usuario tiene acceso a un tenant
|
||||
|
||||
|
||||
Args:
|
||||
keycloak_user_id: ID del usuario en Keycloak
|
||||
tenant_id: ID del tenant
|
||||
|
||||
|
||||
Returns:
|
||||
True si tiene acceso, False en caso contrario
|
||||
"""
|
||||
user_tenant = self.db.query(UserTenant).filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id,
|
||||
UserTenant.is_active == True
|
||||
user_tenant = (
|
||||
self.db.query(UserTenant)
|
||||
.filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id,
|
||||
UserTenant.is_active == True,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
.first()
|
||||
)
|
||||
|
||||
return user_tenant is not None
|
||||
|
||||
|
||||
def update_user_role_in_tenant(
|
||||
self,
|
||||
keycloak_user_id: str,
|
||||
tenant_id: int,
|
||||
role: str
|
||||
self, keycloak_user_id: str, tenant_id: int, role: str
|
||||
) -> UserTenant:
|
||||
"""
|
||||
Actualiza el rol de un usuario en un tenant
|
||||
|
||||
|
||||
Args:
|
||||
keycloak_user_id: ID del usuario en Keycloak
|
||||
tenant_id: ID del tenant
|
||||
role: Nuevo rol
|
||||
|
||||
|
||||
Returns:
|
||||
UserTenant actualizado
|
||||
"""
|
||||
user_tenant = self.db.query(UserTenant).filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id
|
||||
user_tenant = (
|
||||
self.db.query(UserTenant)
|
||||
.filter(
|
||||
and_(
|
||||
UserTenant.keycloak_user_id == keycloak_user_id,
|
||||
UserTenant.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
.first()
|
||||
)
|
||||
|
||||
if not user_tenant:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="User-tenant relationship not found"
|
||||
status_code=404, detail="User-tenant relationship not found"
|
||||
)
|
||||
|
||||
|
||||
user_tenant.role = role
|
||||
self.db.commit()
|
||||
self.db.refresh(user_tenant)
|
||||
|
||||
logger.info(f"Updated role for user {keycloak_user_id} in tenant {tenant_id} to {role}")
|
||||
|
||||
logger.info(
|
||||
f"Updated role for user {keycloak_user_id} in tenant {tenant_id} to {role}"
|
||||
)
|
||||
return user_tenant
|
||||
|
||||
Reference in New Issue
Block a user