forked from andika/membership-be
Merge pull request 'feat: Implement Option 3 - Proper RBAC with role-based staff invitations' (#22) from dev into loaf-prod
Reviewed-on: andika/membership-be#22
This commit is contained in:
95
server.py
95
server.py
@@ -5242,6 +5242,101 @@ async def get_all_roles(
|
|||||||
for role, count in roles_with_counts
|
for role, count in roles_with_counts
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@api_router.get("/admin/roles/assignable", response_model=List[RoleResponse])
|
||||||
|
async def get_assignable_roles(
|
||||||
|
current_user: User = Depends(require_permission("users.create")),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get roles that the current user can assign when inviting staff
|
||||||
|
|
||||||
|
- Superadmin: Can assign all roles
|
||||||
|
- Admin: Can assign admin, finance, and non-elevated custom roles
|
||||||
|
- Returns roles filtered by user's permission level
|
||||||
|
"""
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
# Query all roles with permission counts
|
||||||
|
roles_query = db.query(
|
||||||
|
Role,
|
||||||
|
func.count(RolePermission.id).label('permission_count')
|
||||||
|
).outerjoin(RolePermission, Role.id == RolePermission.role_id)\
|
||||||
|
.group_by(Role.id)\
|
||||||
|
.order_by(Role.is_system_role.desc(), Role.name)
|
||||||
|
|
||||||
|
all_roles = roles_query.all()
|
||||||
|
|
||||||
|
# Superadmin can assign any role
|
||||||
|
if current_user.role == UserRole.superadmin:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": str(role.id),
|
||||||
|
"code": role.code,
|
||||||
|
"name": role.name,
|
||||||
|
"description": role.description,
|
||||||
|
"is_system_role": role.is_system_role,
|
||||||
|
"created_at": role.created_at,
|
||||||
|
"updated_at": role.updated_at,
|
||||||
|
"permission_count": count
|
||||||
|
}
|
||||||
|
for role, count in all_roles
|
||||||
|
]
|
||||||
|
|
||||||
|
# Admin users can assign: admin, finance, and non-elevated custom roles
|
||||||
|
# Get admin role's permissions to check for elevation
|
||||||
|
admin_role = db.query(Role).filter(Role.code == "admin").first()
|
||||||
|
admin_permission_codes = set()
|
||||||
|
if admin_role:
|
||||||
|
admin_permissions = db.query(RolePermission).filter(
|
||||||
|
RolePermission.role_id == admin_role.id
|
||||||
|
).all()
|
||||||
|
admin_permission_codes = {rp.permission_id for rp in admin_permissions}
|
||||||
|
|
||||||
|
assignable_roles = []
|
||||||
|
for role, count in all_roles:
|
||||||
|
# Always exclude superadmin role
|
||||||
|
if role.code == "superadmin":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Include system roles: admin and finance
|
||||||
|
if role.is_system_role and role.code in ["admin", "finance"]:
|
||||||
|
assignable_roles.append({
|
||||||
|
"id": str(role.id),
|
||||||
|
"code": role.code,
|
||||||
|
"name": role.name,
|
||||||
|
"description": role.description,
|
||||||
|
"is_system_role": role.is_system_role,
|
||||||
|
"created_at": role.created_at,
|
||||||
|
"updated_at": role.updated_at,
|
||||||
|
"permission_count": count
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# For custom roles, check if they're elevated
|
||||||
|
if not role.is_system_role:
|
||||||
|
role_permissions = db.query(RolePermission).filter(
|
||||||
|
RolePermission.role_id == role.id
|
||||||
|
).all()
|
||||||
|
role_permission_ids = {rp.permission_id for rp in role_permissions}
|
||||||
|
|
||||||
|
# Check if custom role has permissions admin doesn't have (elevated)
|
||||||
|
has_elevated_permissions = bool(role_permission_ids - admin_permission_codes)
|
||||||
|
|
||||||
|
# Only include non-elevated custom roles
|
||||||
|
if not has_elevated_permissions:
|
||||||
|
assignable_roles.append({
|
||||||
|
"id": str(role.id),
|
||||||
|
"code": role.code,
|
||||||
|
"name": role.name,
|
||||||
|
"description": role.description,
|
||||||
|
"is_system_role": role.is_system_role,
|
||||||
|
"created_at": role.created_at,
|
||||||
|
"updated_at": role.updated_at,
|
||||||
|
"permission_count": count
|
||||||
|
})
|
||||||
|
|
||||||
|
return assignable_roles
|
||||||
|
|
||||||
@api_router.post("/admin/roles", response_model=RoleResponse)
|
@api_router.post("/admin/roles", response_model=RoleResponse)
|
||||||
async def create_role(
|
async def create_role(
|
||||||
request: CreateRoleRequest,
|
request: CreateRoleRequest,
|
||||||
|
|||||||
Reference in New Issue
Block a user