Fix RBAC role-based permissions for accounts endpoint

Fixed critical bugs preventing users from seeing accounts through their assigned roles:

1. **Fixed duplicate function definition** (crud.py)
   - Removed duplicate auto_assign_default_role() that only took 1 parameter
   - Kept correct version with proper signature and logging
   - Added get_all_user_roles() helper function

2. **Added role-based permissions to accounts endpoint** (views_api.py)
   - Previously only checked direct user permissions
   - Now retrieves and combines both direct AND role permissions
   - Auto-assigns default role to new users on first access

3. **Fixed permission inheritance logic** (views_api.py)
   - Inheritance check now uses combined permissions (direct + role)
   - Previously only checked direct user permissions for parents
   - Users can now inherit access to child accounts via role permissions

Changes enable proper RBAC functionality:
- Users with "Employee" role (or any role) now see permitted accounts
- Permission inheritance works correctly with role-based permissions
- Auto-assignment of default role on first Castle access

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-11-12 03:00:17 +01:00
parent c086916be8
commit 52c6c3f8f1
2 changed files with 134 additions and 35 deletions

85
crud.py
View file

@ -1502,6 +1502,31 @@ async def get_user_roles(user_id: str) -> list[UserRole]:
]
async def get_all_user_roles() -> list[UserRole]:
"""Get all active user role assignments"""
rows = await db.fetchall(
"""
SELECT * FROM user_roles
WHERE (expires_at IS NULL OR expires_at > :now)
ORDER BY user_id, granted_at DESC
""",
{"now": datetime.now()},
)
return [
UserRole(
id=row["id"],
user_id=row["user_id"],
role_id=row["role_id"],
granted_by=row["granted_by"],
granted_at=row["granted_at"],
expires_at=row["expires_at"],
notes=row["notes"],
)
for row in rows
]
async def get_role_users(role_id: str) -> list[UserRole]:
"""Get all users assigned to a role"""
rows = await db.fetchall(
@ -1550,6 +1575,41 @@ async def get_role_count_for_user(user_id: str) -> int:
return row["count"] if row else 0
async def auto_assign_default_role(user_id: str, assigned_by: str) -> UserRole | None:
"""
Auto-assign the default role to a user if they don't have any roles yet.
Returns the created UserRole if assigned, None if user already has roles or no default role exists.
"""
from loguru import logger
logger.info(f"[AUTO-ASSIGN] Checking auto-assignment for user {user_id}")
# Check if user already has any roles
user_role_count = await get_role_count_for_user(user_id)
logger.info(f"[AUTO-ASSIGN] User {user_id} has {user_role_count} roles")
if user_role_count > 0:
logger.info(f"[AUTO-ASSIGN] User {user_id} already has roles, skipping auto-assignment")
return None
# Find the default role
default_role = await get_default_role()
if not default_role:
logger.warning(f"[AUTO-ASSIGN] No default role found, cannot auto-assign for user {user_id}")
return None
logger.info(f"[AUTO-ASSIGN] Found default role: {default_role.name} (id: {default_role.id})")
# Assign the default role
data = AssignUserRole(
user_id=user_id,
role_id=default_role.id,
notes="Auto-assigned default role on first access",
)
result = await assign_user_role(data, assigned_by)
logger.info(f"[AUTO-ASSIGN] Successfully assigned role {default_role.name} to user {user_id}")
return result
async def get_user_count_for_role(role_id: str) -> int:
"""Get count of users assigned to a role"""
row = await db.fetchone(
@ -1601,28 +1661,3 @@ async def check_user_has_role_permission(
return True
return False
async def auto_assign_default_role(user_id: str) -> Optional[UserRole]:
"""
Auto-assign the default role to a new user.
Returns the UserRole if a default role exists and was assigned, None otherwise.
"""
default_role = await get_default_role()
if not default_role:
return None
# Check if user already has this role
user_roles = await get_user_roles(user_id)
if any(ur.role_id == default_role.id for ur in user_roles):
return None
# Assign the default role
return await assign_user_role(
AssignUserRole(
user_id=user_id,
role_id=default_role.id,
notes="Auto-assigned default role",
),
granted_by="system",
)