Permission fix

This commit is contained in:
Koncept Kit
2025-12-17 01:29:17 +07:00
parent ed5526e27b
commit 050cccae8f
3 changed files with 546 additions and 0 deletions

238
check_db_status.py Executable file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python3
"""
Database Migration Status Checker
Checks what migration steps have been completed and what's missing
"""
import sys
import os
from sqlalchemy import create_engine, text, inspect
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Get database URL from environment or use provided one
DATABASE_URL = os.getenv('DATABASE_URL')
if not DATABASE_URL:
print("ERROR: DATABASE_URL not found in environment")
sys.exit(1)
# Create database connection
engine = create_engine(DATABASE_URL)
Session = sessionmaker(bind=engine)
db = Session()
inspector = inspect(engine)
print("=" * 80)
print("DATABASE MIGRATION STATUS CHECKER")
print("=" * 80)
print(f"\nConnected to: {DATABASE_URL.split('@')[1] if '@' in DATABASE_URL else 'database'}")
print()
# Colors for output
class Colors:
GREEN = '\033[92m'
RED = '\033[91m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
END = '\033[0m'
def check_mark(exists):
return f"{Colors.GREEN}{Colors.END}" if exists else f"{Colors.RED}{Colors.END}"
def warning(exists):
return f"{Colors.YELLOW}{Colors.END}" if exists else f"{Colors.GREEN}{Colors.END}"
issues = []
warnings = []
# ============================================================
# Check 1: Does roles table exist?
# ============================================================
print(f"{Colors.BLUE}[1] Checking if 'roles' table exists...{Colors.END}")
roles_table_exists = 'roles' in inspector.get_table_names()
print(f" {check_mark(roles_table_exists)} roles table exists")
if not roles_table_exists:
issues.append("❌ MISSING: 'roles' table - run migration 006_add_dynamic_roles.sql")
print(f"\n{Colors.RED}ISSUE: roles table not found!{Colors.END}")
print(f" Action: Run 'psql $DATABASE_URL -f migrations/006_add_dynamic_roles.sql'")
# ============================================================
# Check 2: Does users table have role_id column?
# ============================================================
print(f"\n{Colors.BLUE}[2] Checking if 'users' table has 'role_id' column...{Colors.END}")
users_columns = [col['name'] for col in inspector.get_columns('users')]
users_has_role_id = 'role_id' in users_columns
print(f" {check_mark(users_has_role_id)} users.role_id column exists")
if not users_has_role_id:
issues.append("❌ MISSING: 'users.role_id' column - run migration 006_add_dynamic_roles.sql")
print(f"\n{Colors.RED}ISSUE: users.role_id column not found!{Colors.END}")
print(f" Action: Run 'psql $DATABASE_URL -f migrations/006_add_dynamic_roles.sql'")
# ============================================================
# Check 3: Does role_permissions table have role_id column?
# ============================================================
print(f"\n{Colors.BLUE}[3] Checking if 'role_permissions' table has 'role_id' column...{Colors.END}")
rp_columns = [col['name'] for col in inspector.get_columns('role_permissions')]
rp_has_role_id = 'role_id' in rp_columns
print(f" {check_mark(rp_has_role_id)} role_permissions.role_id column exists")
if not rp_has_role_id:
issues.append("❌ MISSING: 'role_permissions.role_id' column - run migration 006_add_dynamic_roles.sql")
print(f"\n{Colors.RED}ISSUE: role_permissions.role_id column not found!{Colors.END}")
print(f" Action: Run 'psql $DATABASE_URL -f migrations/006_add_dynamic_roles.sql'")
# ============================================================
# Check 4: Are system roles seeded?
# ============================================================
if roles_table_exists:
print(f"\n{Colors.BLUE}[4] Checking if system roles are seeded...{Colors.END}")
result = db.execute(text("SELECT COUNT(*) as count FROM roles WHERE is_system_role = true"))
system_roles_count = result.scalar()
print(f" System roles found: {system_roles_count}")
if system_roles_count == 0:
issues.append("❌ MISSING: System roles not seeded - run roles_seed.py")
print(f" {Colors.RED}✗ No system roles found!{Colors.END}")
print(f" Action: Run 'python3 roles_seed.py'")
elif system_roles_count < 5:
warnings.append(f"⚠️ WARNING: Expected 5 system roles, found {system_roles_count}")
print(f" {Colors.YELLOW}{Colors.END} Expected 5 roles, found {system_roles_count}")
print(f" Action: Run 'python3 roles_seed.py' to ensure all roles exist")
else:
print(f" {Colors.GREEN}{Colors.END} All system roles seeded")
# Show which roles exist
result = db.execute(text("SELECT code, name FROM roles WHERE is_system_role = true ORDER BY code"))
existing_roles = result.fetchall()
if existing_roles:
print(f"\n Existing roles:")
for role in existing_roles:
print(f" - {role[0]}: {role[1]}")
# ============================================================
# Check 5: Are users migrated to dynamic roles?
# ============================================================
if users_has_role_id and roles_table_exists:
print(f"\n{Colors.BLUE}[5] Checking if users are migrated to dynamic roles...{Colors.END}")
# Count total users
result = db.execute(text("SELECT COUNT(*) FROM users"))
total_users = result.scalar()
print(f" Total users: {total_users}")
# Count users with role_id set
result = db.execute(text("SELECT COUNT(*) FROM users WHERE role_id IS NOT NULL"))
migrated_users = result.scalar()
print(f" Migrated users (with role_id): {migrated_users}")
# Count users without role_id
unmigrated_users = total_users - migrated_users
if unmigrated_users > 0:
issues.append(f"❌ INCOMPLETE: {unmigrated_users} users not migrated to dynamic roles")
print(f" {Colors.RED}{unmigrated_users} users still need migration!{Colors.END}")
print(f" Action: Run 'python3 migrate_users_to_dynamic_roles.py'")
# Show sample unmigrated users
result = db.execute(text("""
SELECT email, role FROM users
WHERE role_id IS NULL
LIMIT 5
"""))
unmigrated = result.fetchall()
if unmigrated:
print(f"\n Sample unmigrated users:")
for user in unmigrated:
print(f" - {user[0]} (role: {user[1]})")
else:
print(f" {Colors.GREEN}{Colors.END} All users migrated to dynamic roles")
# ============================================================
# Check 6: Are role permissions migrated?
# ============================================================
if rp_has_role_id and roles_table_exists:
print(f"\n{Colors.BLUE}[6] Checking if role permissions are migrated...{Colors.END}")
# Count total role_permissions
result = db.execute(text("SELECT COUNT(*) FROM role_permissions"))
total_perms = result.scalar()
print(f" Total role_permissions: {total_perms}")
# Count permissions with role_id set
result = db.execute(text("SELECT COUNT(*) FROM role_permissions WHERE role_id IS NOT NULL"))
migrated_perms = result.scalar()
print(f" Migrated permissions (with role_id): {migrated_perms}")
unmigrated_perms = total_perms - migrated_perms
if unmigrated_perms > 0:
issues.append(f"❌ INCOMPLETE: {unmigrated_perms} permissions not migrated to dynamic roles")
print(f" {Colors.RED}{unmigrated_perms} permissions still need migration!{Colors.END}")
print(f" Action: Run 'python3 migrate_role_permissions_to_dynamic_roles.py'")
else:
print(f" {Colors.GREEN}{Colors.END} All permissions migrated to dynamic roles")
# ============================================================
# Check 7: Verify admin account
# ============================================================
print(f"\n{Colors.BLUE}[7] Checking admin account...{Colors.END}")
result = db.execute(text("""
SELECT email, role, role_id
FROM users
WHERE email LIKE '%admin%' OR role = 'admin' OR role = 'superadmin'
LIMIT 5
"""))
admin_users = result.fetchall()
if admin_users:
print(f" Found {len(admin_users)} admin/superadmin users:")
for user in admin_users:
role_id_status = "" if user[2] else ""
print(f" {role_id_status} {user[0]} (role: {user[1]}, role_id: {user[2] or 'NULL'})")
else:
warnings.append("⚠️ WARNING: No admin users found")
print(f" {Colors.YELLOW}{Colors.END} No admin users found in database")
# ============================================================
# Summary
# ============================================================
print("\n" + "=" * 80)
print("SUMMARY")
print("=" * 80)
if not issues and not warnings:
print(f"\n{Colors.GREEN}✓ All migration steps completed successfully!{Colors.END}")
print("\nNext steps:")
print(" 1. Deploy latest backend code")
print(" 2. Restart backend server")
print(" 3. Test /api/admin/users/export endpoint")
else:
if issues:
print(f"\n{Colors.RED}ISSUES FOUND ({len(issues)}):{Colors.END}")
for i, issue in enumerate(issues, 1):
print(f" {i}. {issue}")
if warnings:
print(f"\n{Colors.YELLOW}WARNINGS ({len(warnings)}):{Colors.END}")
for i, warning in enumerate(warnings, 1):
print(f" {i}. {warning}")
print(f"\n{Colors.BLUE}RECOMMENDED ACTIONS:{Colors.END}")
if not roles_table_exists or not users_has_role_id or not rp_has_role_id:
print(" 1. Run: psql $DATABASE_URL -f migrations/006_add_dynamic_roles.sql")
if roles_table_exists and system_roles_count == 0:
print(" 2. Run: python3 roles_seed.py")
if unmigrated_users > 0:
print(" 3. Run: python3 migrate_users_to_dynamic_roles.py")
if unmigrated_perms > 0:
print(" 4. Run: python3 migrate_role_permissions_to_dynamic_roles.py")
print(" 5. Deploy latest backend code and restart server")
print("\n" + "=" * 80)
db.close()

37
check_permissions.py Normal file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""
Check permissions table status
"""
import sys
import os
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
load_dotenv()
DATABASE_URL = os.getenv('DATABASE_URL')
engine = create_engine(DATABASE_URL)
Session = sessionmaker(bind=engine)
db = Session()
print("Checking permissions table...")
print("=" * 80)
# Check if permissions table exists
result = db.execute(text("SELECT COUNT(*) FROM permissions"))
count = result.scalar()
print(f"Total permissions in database: {count}")
if count > 0:
print("\nSample permissions:")
result = db.execute(text("SELECT code, name, module FROM permissions LIMIT 10"))
for perm in result.fetchall():
print(f" - {perm[0]}: {perm[1]} (module: {perm[2]})")
else:
print("\n⚠️ WARNING: Permissions table is EMPTY!")
print("\nThis will cause permission checks to fail.")
print("\nAction needed: Run 'python3 seed_permissions.py'")
db.close()

271
seed_permissions_rbac.py Executable file
View File

@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""
Permission Seeding Script for Dynamic RBAC System
This script populates the database with 56 granular permissions and assigns them
to the appropriate dynamic roles (not the old enum roles).
Usage:
python3 seed_permissions_rbac.py
Environment Variables:
DATABASE_URL - PostgreSQL connection string
"""
import os
import sys
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database import Base
from models import Permission, RolePermission, Role
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Database connection
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
print("Error: DATABASE_URL environment variable not set")
sys.exit(1)
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# ============================================================
# Permission Definitions (56 permissions across 9 modules)
# ============================================================
PERMISSIONS = [
# ========== USERS MODULE (10) ==========
{"code": "users.view", "name": "View Users", "description": "View user list and user profiles", "module": "users"},
{"code": "users.create", "name": "Create Users", "description": "Create new users and send invitations", "module": "users"},
{"code": "users.edit", "name": "Edit Users", "description": "Edit user profiles and information", "module": "users"},
{"code": "users.delete", "name": "Delete Users", "description": "Delete user accounts", "module": "users"},
{"code": "users.status", "name": "Change User Status", "description": "Change user status (active, inactive, etc.)", "module": "users"},
{"code": "users.approve", "name": "Approve/Validate Users", "description": "Approve or validate user applications", "module": "users"},
{"code": "users.export", "name": "Export Users", "description": "Export user data to CSV", "module": "users"},
{"code": "users.import", "name": "Import Users", "description": "Import users from CSV", "module": "users"},
{"code": "users.reset_password", "name": "Reset User Password", "description": "Reset user passwords via email", "module": "users"},
{"code": "users.resend_verification", "name": "Resend Verification Email", "description": "Resend email verification links", "module": "users"},
{"code": "users.invite", "name": "Invite Users", "description": "Send user invitations", "module": "users"},
# ========== EVENTS MODULE (8) ==========
{"code": "events.view", "name": "View Events", "description": "View event list and event details", "module": "events"},
{"code": "events.create", "name": "Create Events", "description": "Create new events", "module": "events"},
{"code": "events.edit", "name": "Edit Events", "description": "Edit existing events", "module": "events"},
{"code": "events.delete", "name": "Delete Events", "description": "Delete events", "module": "events"},
{"code": "events.publish", "name": "Publish Events", "description": "Publish or unpublish events", "module": "events"},
{"code": "events.attendance", "name": "Mark Event Attendance", "description": "Mark user attendance for events", "module": "events"},
{"code": "events.rsvps", "name": "View Event RSVPs", "description": "View and manage event RSVPs", "module": "events"},
{"code": "events.calendar_export", "name": "Export Event Calendar", "description": "Export events to iCal format", "module": "events"},
# ========== SUBSCRIPTIONS MODULE (6) ==========
{"code": "subscriptions.view", "name": "View Subscriptions", "description": "View subscription list and details", "module": "subscriptions"},
{"code": "subscriptions.create", "name": "Create Subscriptions", "description": "Create manual subscriptions for users", "module": "subscriptions"},
{"code": "subscriptions.edit", "name": "Edit Subscriptions", "description": "Edit subscription details", "module": "subscriptions"},
{"code": "subscriptions.cancel", "name": "Cancel Subscriptions", "description": "Cancel user subscriptions", "module": "subscriptions"},
{"code": "subscriptions.activate", "name": "Activate Subscriptions", "description": "Manually activate subscriptions", "module": "subscriptions"},
{"code": "subscriptions.plans", "name": "Manage Subscription Plans", "description": "Create and edit subscription plans", "module": "subscriptions"},
# ========== FINANCIALS MODULE (6) ==========
{"code": "financials.view", "name": "View Financial Reports", "description": "View financial reports and dashboards", "module": "financials"},
{"code": "financials.create", "name": "Create Financial Reports", "description": "Upload and create financial reports", "module": "financials"},
{"code": "financials.edit", "name": "Edit Financial Reports", "description": "Edit existing financial reports", "module": "financials"},
{"code": "financials.delete", "name": "Delete Financial Reports", "description": "Delete financial reports", "module": "financials"},
{"code": "financials.export", "name": "Export Financial Data", "description": "Export financial data to CSV/PDF", "module": "financials"},
{"code": "financials.payments", "name": "View Payment Details", "description": "View detailed payment information", "module": "financials"},
# ========== NEWSLETTERS MODULE (6) ==========
{"code": "newsletters.view", "name": "View Newsletters", "description": "View newsletter archives", "module": "newsletters"},
{"code": "newsletters.create", "name": "Create Newsletters", "description": "Upload and create newsletters", "module": "newsletters"},
{"code": "newsletters.edit", "name": "Edit Newsletters", "description": "Edit existing newsletters", "module": "newsletters"},
{"code": "newsletters.delete", "name": "Delete Newsletters", "description": "Delete newsletter archives", "module": "newsletters"},
{"code": "newsletters.send", "name": "Send Newsletters", "description": "Send newsletter emails to subscribers", "module": "newsletters"},
{"code": "newsletters.subscribers", "name": "Manage Newsletter Subscribers", "description": "View and manage newsletter subscribers", "module": "newsletters"},
# ========== BYLAWS MODULE (5) ==========
{"code": "bylaws.view", "name": "View Bylaws", "description": "View organization bylaws documents", "module": "bylaws"},
{"code": "bylaws.create", "name": "Create Bylaws", "description": "Upload new bylaws documents", "module": "bylaws"},
{"code": "bylaws.edit", "name": "Edit Bylaws", "description": "Edit existing bylaws documents", "module": "bylaws"},
{"code": "bylaws.delete", "name": "Delete Bylaws", "description": "Delete bylaws documents", "module": "bylaws"},
{"code": "bylaws.publish", "name": "Publish Bylaws", "description": "Mark bylaws as current/published version", "module": "bylaws"},
# ========== GALLERY MODULE (5) ==========
{"code": "gallery.view", "name": "View Event Gallery", "description": "View event gallery photos", "module": "gallery"},
{"code": "gallery.upload", "name": "Upload Photos", "description": "Upload photos to event galleries", "module": "gallery"},
{"code": "gallery.edit", "name": "Edit Photos", "description": "Edit photo captions and details", "module": "gallery"},
{"code": "gallery.delete", "name": "Delete Photos", "description": "Delete photos from galleries", "module": "gallery"},
{"code": "gallery.moderate", "name": "Moderate Gallery Content", "description": "Approve/reject uploaded photos", "module": "gallery"},
# ========== SETTINGS MODULE (6) ==========
{"code": "settings.view", "name": "View Settings", "description": "View application settings", "module": "settings"},
{"code": "settings.edit", "name": "Edit Settings", "description": "Edit application settings", "module": "settings"},
{"code": "settings.email_templates", "name": "Manage Email Templates", "description": "Edit email templates and notifications", "module": "settings"},
{"code": "settings.storage", "name": "Manage Storage", "description": "View and manage storage usage", "module": "settings"},
{"code": "settings.backup", "name": "Backup & Restore", "description": "Create and restore database backups", "module": "settings"},
{"code": "settings.logs", "name": "View System Logs", "description": "View application and audit logs", "module": "settings"},
# ========== PERMISSIONS MODULE (4) ==========
{"code": "permissions.view", "name": "View Permissions", "description": "View permission definitions and assignments", "module": "permissions"},
{"code": "permissions.assign", "name": "Assign Permissions", "description": "Assign permissions to roles", "module": "permissions"},
{"code": "permissions.manage_roles", "name": "Manage Roles", "description": "Create and manage user roles", "module": "permissions"},
{"code": "permissions.audit", "name": "View Permission Audit Log", "description": "View permission change audit logs", "module": "permissions"},
]
# Default permission assignments for dynamic roles
DEFAULT_ROLE_PERMISSIONS = {
"guest": [], # Guests have no permissions
"member": [
# Members can view public content
"events.view", "events.rsvps", "events.calendar_export",
"newsletters.view", "bylaws.view", "gallery.view",
],
"finance": [
# Finance role has financial permissions + some user viewing
"users.view", "financials.view", "financials.create", "financials.edit",
"financials.delete", "financials.export", "financials.payments",
"subscriptions.view", "subscriptions.create", "subscriptions.edit",
"subscriptions.cancel", "subscriptions.activate", "subscriptions.plans",
],
"admin": [
# Admins have most permissions except RBAC management
"users.view", "users.create", "users.edit", "users.status", "users.approve",
"users.export", "users.import", "users.reset_password", "users.resend_verification",
"users.invite",
"events.view", "events.create", "events.edit", "events.delete", "events.publish",
"events.attendance", "events.rsvps", "events.calendar_export",
"subscriptions.view", "subscriptions.create", "subscriptions.edit",
"subscriptions.cancel", "subscriptions.activate", "subscriptions.plans",
"financials.view", "financials.create", "financials.edit", "financials.delete",
"financials.export", "financials.payments",
"newsletters.view", "newsletters.create", "newsletters.edit", "newsletters.delete",
"newsletters.send", "newsletters.subscribers",
"bylaws.view", "bylaws.create", "bylaws.edit", "bylaws.delete", "bylaws.publish",
"gallery.view", "gallery.upload", "gallery.edit", "gallery.delete", "gallery.moderate",
"settings.view", "settings.edit", "settings.email_templates", "settings.storage",
"settings.logs",
],
"superadmin": [
# Superadmin gets ALL permissions
*[p["code"] for p in PERMISSIONS]
]
}
def seed_permissions():
"""Seed permissions and assign them to dynamic roles"""
db = SessionLocal()
try:
print("=" * 80)
print("🌱 PERMISSION SEEDING FOR DYNAMIC RBAC SYSTEM")
print("=" * 80)
# Step 1: Clear existing permissions and role_permissions
print("\n📦 Clearing existing permissions and role assignments...")
deleted_rp = db.query(RolePermission).delete()
deleted_p = db.query(Permission).delete()
db.commit()
print(f"✓ Cleared {deleted_rp} role-permission mappings")
print(f"✓ Cleared {deleted_p} permissions")
# Step 2: Create permissions
print(f"\n📝 Creating {len(PERMISSIONS)} permissions...")
permission_map = {} # Map code to permission object
for perm_data in PERMISSIONS:
permission = Permission(
code=perm_data["code"],
name=perm_data["name"],
description=perm_data["description"],
module=perm_data["module"]
)
db.add(permission)
permission_map[perm_data["code"]] = permission
db.commit()
print(f"✓ Created {len(PERMISSIONS)} permissions")
# Step 3: Get all roles from database
print("\n🔍 Fetching dynamic roles...")
roles = db.query(Role).all()
role_map = {role.code: role for role in roles}
print(f"✓ Found {len(roles)} roles: {', '.join(role_map.keys())}")
# Step 4: Assign permissions to roles
print("\n🔐 Assigning permissions to roles...")
from models import UserRole # Import for enum mapping
# Enum mapping for backward compatibility
role_enum_map = {
'guest': UserRole.guest,
'member': UserRole.member,
'admin': UserRole.admin,
'superadmin': UserRole.superadmin,
'finance': UserRole.admin # Finance uses admin enum for now
}
total_assigned = 0
for role_code, permission_codes in DEFAULT_ROLE_PERMISSIONS.items():
if role_code not in role_map:
print(f" ⚠️ Warning: Role '{role_code}' not found in database, skipping")
continue
role = role_map[role_code]
role_enum = role_enum_map.get(role_code, UserRole.guest)
for perm_code in permission_codes:
if perm_code not in permission_map:
print(f" ⚠️ Warning: Permission '{perm_code}' not found")
continue
role_permission = RolePermission(
role=role_enum, # Legacy enum for backward compatibility
role_id=role.id, # New dynamic role system
permission_id=permission_map[perm_code].id
)
db.add(role_permission)
total_assigned += 1
db.commit()
print(f"{role.name}: Assigned {len(permission_codes)} permissions")
# Step 5: Summary
print("\n" + "=" * 80)
print("📊 SEEDING SUMMARY")
print("=" * 80)
# Count permissions by module
modules = {}
for perm in PERMISSIONS:
module = perm["module"]
modules[module] = modules.get(module, 0) + 1
print("\nPermissions by module:")
for module, count in sorted(modules.items()):
print(f"{module.capitalize()}: {count} permissions")
print(f"\nTotal permissions created: {len(PERMISSIONS)}")
print(f"Total role-permission mappings: {total_assigned}")
print("\n✅ Permission seeding completed successfully!")
print("\nNext step: Restart backend server")
print("=" * 80)
except Exception as e:
db.rollback()
print(f"\n❌ Error seeding permissions: {str(e)}")
import traceback
traceback.print_exc()
raise
finally:
db.close()
if __name__ == "__main__":
seed_permissions()