refactor(rbac): Migrate legacy is_admin checks to role-based has_role()/set_role()
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Replace ~20 remaining is_admin references across backend, templates and scripts with proper SystemRole checks. Column is_admin stays as deprecated (synced by set_role()) until DB migration removes it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
85e14bb4bf
commit
c0d60481f0
5
app.py
5
app.py
@ -162,7 +162,8 @@ from database import (
|
||||
HourlyActivity,
|
||||
AuditLog,
|
||||
SecurityAlert,
|
||||
ZOPKNews
|
||||
ZOPKNews,
|
||||
SystemRole
|
||||
)
|
||||
|
||||
# Import services
|
||||
@ -291,7 +292,7 @@ def is_admin_exempt():
|
||||
"""Exempt logged-in admins from rate limiting."""
|
||||
from flask_login import current_user
|
||||
try:
|
||||
return current_user.is_authenticated and current_user.is_admin
|
||||
return current_user.is_authenticated and current_user.has_role(SystemRole.ADMIN)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@ -158,7 +158,7 @@ def admin_users():
|
||||
companies = db.query(Company).order_by(Company.name).all()
|
||||
|
||||
total_users = len(users)
|
||||
admin_count = sum(1 for u in users if u.is_admin)
|
||||
admin_count = sum(1 for u in users if u.has_role(SystemRole.ADMIN))
|
||||
verified_count = sum(1 for u in users if u.is_verified)
|
||||
unverified_count = total_users - verified_count
|
||||
|
||||
@ -203,12 +203,13 @@ def admin_user_add():
|
||||
password_hash=password_hash,
|
||||
name=data.get('name', '').strip() or None,
|
||||
company_id=data.get('company_id') or None,
|
||||
is_admin=data.get('is_admin', False),
|
||||
is_verified=data.get('is_verified', True),
|
||||
is_active=True
|
||||
)
|
||||
|
||||
db.add(new_user)
|
||||
if data.get('is_admin', False):
|
||||
new_user.set_role(SystemRole.ADMIN)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
||||
@ -243,15 +244,20 @@ def admin_user_toggle_admin(user_id):
|
||||
if not user:
|
||||
return jsonify({'success': False, 'error': 'Użytkownik nie znaleziony'}), 404
|
||||
|
||||
user.is_admin = not user.is_admin
|
||||
if user.has_role(SystemRole.ADMIN):
|
||||
user.set_role(SystemRole.MEMBER)
|
||||
else:
|
||||
user.set_role(SystemRole.ADMIN)
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Admin {current_user.email} {'granted' if user.is_admin else 'revoked'} admin for user {user.email}")
|
||||
is_now_admin = user.has_role(SystemRole.ADMIN)
|
||||
logger.info(f"Admin {current_user.email} {'granted' if is_now_admin else 'revoked'} admin for user {user.email}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'is_admin': user.is_admin,
|
||||
'message': f"{'Nadano' if user.is_admin else 'Odebrano'} uprawnienia admina"
|
||||
'is_admin': is_now_admin,
|
||||
'role': user.role,
|
||||
'message': f"{'Nadano' if is_now_admin else 'Odebrano'} uprawnienia admina"
|
||||
})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -504,7 +504,7 @@ def admin_company_users(company_id):
|
||||
'id': u.id,
|
||||
'name': u.name,
|
||||
'email': u.email,
|
||||
'is_admin': u.is_admin,
|
||||
'role': u.role,
|
||||
'is_verified': u.is_verified
|
||||
} for u in users]
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ def admin_security():
|
||||
User.totp_enabled == True
|
||||
).count()
|
||||
total_admins = db.query(User).filter(
|
||||
User.is_admin == True
|
||||
User.role == 'ADMIN'
|
||||
).count()
|
||||
|
||||
# Alert type breakdown
|
||||
|
||||
@ -307,7 +307,7 @@ def admin_status():
|
||||
|
||||
# Users statistics
|
||||
try:
|
||||
app_metrics['admins'] = db.query(User).filter(User.is_admin == True).count()
|
||||
app_metrics['admins'] = db.query(User).filter(User.role == 'ADMIN').count()
|
||||
app_metrics['users_with_2fa'] = db.query(User).filter(User.totp_enabled == True).count()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
|
||||
@ -48,7 +48,7 @@ INSTRUKCJE:
|
||||
- email (WYMAGANY - jeśli brak prawidłowego emaila, pomiń użytkownika)
|
||||
- imię i nazwisko (jeśli dostępne)
|
||||
- firma (dopasuj do listy dostępnych firm po nazwie, nawet częściowej)
|
||||
- rola: jeśli tekst zawiera słowa "admin", "administrator", "zarząd" przy danej osobie - ustaw is_admin na true
|
||||
- rola: jeśli tekst zawiera słowa "admin", "administrator", "zarząd" przy danej osobie - ustaw role na "ADMIN", w przeciwnym razie "MEMBER"
|
||||
3. Jeśli email jest niepoprawny (brak @), dodaj ostrzeżenie
|
||||
4. Jeśli firma nie pasuje do żadnej z listy, ustaw company_id na null
|
||||
|
||||
@ -61,7 +61,7 @@ ZWRÓĆ TYLKO CZYSTY JSON w dokładnie takim formacie (bez żadnego tekstu przed
|
||||
"name": "Imię Nazwisko lub null",
|
||||
"company_id": 123,
|
||||
"company_name": "Nazwa dopasowanej firmy lub null",
|
||||
"is_admin": false,
|
||||
"role": "MEMBER",
|
||||
"warnings": []
|
||||
}}
|
||||
]
|
||||
@ -95,7 +95,7 @@ ZWRÓĆ TYLKO CZYSTY JSON w dokładnie takim formacie (bez żadnego tekstu przed
|
||||
"name": "Imię Nazwisko lub null",
|
||||
"company_id": 123,
|
||||
"company_name": "Nazwa dopasowanej firmy lub null",
|
||||
"is_admin": false,
|
||||
"role": "MEMBER",
|
||||
"warnings": []
|
||||
}}
|
||||
]
|
||||
@ -267,11 +267,14 @@ def admin_users_bulk_create():
|
||||
password_hash=password_hash,
|
||||
name=user_data.get('name', '').strip() or None,
|
||||
company_id=company_id,
|
||||
is_admin=user_data.get('is_admin', False),
|
||||
is_verified=True,
|
||||
is_active=True
|
||||
)
|
||||
db.add(new_user)
|
||||
# Set role based on AI parse result (supports both old is_admin and new role field)
|
||||
ai_role = user_data.get('role', 'MEMBER')
|
||||
if ai_role == 'ADMIN' or user_data.get('is_admin', False):
|
||||
new_user.set_role(SystemRole.ADMIN)
|
||||
db.flush() # Get the ID
|
||||
|
||||
created.append({
|
||||
|
||||
@ -512,7 +512,7 @@ def api_enrich_company_ai(company_id):
|
||||
}), 404
|
||||
|
||||
# Check permissions: user with company edit rights
|
||||
logger.info(f"Permission check: user={current_user.email}, is_admin={current_user.is_admin}, user_company_id={current_user.company_id}, target_company_id={company.id}")
|
||||
logger.info(f"Permission check: user={current_user.email}, role={current_user.role}, user_company_id={current_user.company_id}, target_company_id={company.id}")
|
||||
if not current_user.can_edit_company(company.id):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
|
||||
@ -1066,7 +1066,7 @@ def report_content():
|
||||
|
||||
# Notify admins about the report
|
||||
try:
|
||||
admin_users = db.query(User).filter(User.is_admin == True, User.is_active == True).all()
|
||||
admin_users = db.query(User).filter(User.role == 'ADMIN', User.is_active == True).all()
|
||||
admin_ids = [u.id for u in admin_users]
|
||||
reporter_name = current_user.name or current_user.email.split('@')[0]
|
||||
create_forum_report_notification(
|
||||
|
||||
@ -400,7 +400,7 @@ def accept_changes(app_id):
|
||||
application.proposed_changes_comment = None
|
||||
|
||||
# Create notification for admins
|
||||
admins = db.query(User).filter(User.is_admin == True).all()
|
||||
admins = db.query(User).filter(User.role == 'ADMIN').all()
|
||||
for admin in admins:
|
||||
notification = UserNotification(
|
||||
user_id=admin.id,
|
||||
@ -500,7 +500,7 @@ def reject_changes(app_id):
|
||||
application.updated_at = datetime.now()
|
||||
|
||||
# Create notification for admins
|
||||
admins = db.query(User).filter(User.is_admin == True).all()
|
||||
admins = db.query(User).filter(User.role == 'ADMIN').all()
|
||||
for admin in admins:
|
||||
notification = UserNotification(
|
||||
user_id=admin.id,
|
||||
|
||||
12
database.py
12
database.py
@ -1923,8 +1923,8 @@ class NordaEvent(Base):
|
||||
if not user or not user.is_authenticated:
|
||||
return False
|
||||
|
||||
# Admins can see everything
|
||||
if user.is_admin or user.has_role(SystemRole.OFFICE_MANAGER):
|
||||
# Admins and office managers can see everything
|
||||
if user.has_role(SystemRole.OFFICE_MANAGER):
|
||||
return True
|
||||
|
||||
access = self.access_level or 'members_only'
|
||||
@ -1948,8 +1948,8 @@ class NordaEvent(Base):
|
||||
if not user or not user.is_authenticated:
|
||||
return False
|
||||
|
||||
# Admins can attend everything
|
||||
if user.is_admin or user.has_role(SystemRole.OFFICE_MANAGER):
|
||||
# Admins and office managers can attend everything
|
||||
if user.has_role(SystemRole.OFFICE_MANAGER):
|
||||
return True
|
||||
|
||||
access = self.access_level or 'members_only'
|
||||
@ -1972,8 +1972,8 @@ class NordaEvent(Base):
|
||||
if not user or not user.is_authenticated:
|
||||
return False
|
||||
|
||||
# Admins can see attendees
|
||||
if user.is_admin or user.has_role(SystemRole.OFFICE_MANAGER):
|
||||
# Admins and office managers can see attendees
|
||||
if user.has_role(SystemRole.OFFICE_MANAGER):
|
||||
return True
|
||||
|
||||
access = self.access_level or 'members_only'
|
||||
|
||||
@ -52,7 +52,6 @@ def main():
|
||||
name=name,
|
||||
password_hash=generate_password_hash(temp_password),
|
||||
is_active=True,
|
||||
is_admin=False,
|
||||
company_id=company.id if company else None,
|
||||
created_at=datetime.now()
|
||||
)
|
||||
|
||||
@ -27,7 +27,7 @@ def main():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=12).first()
|
||||
admin_user = db.query(User).filter_by(is_admin=True).first()
|
||||
admin_user = db.query(User).filter_by(role='ADMIN').first()
|
||||
|
||||
if not company:
|
||||
print('Firma nie znaleziona')
|
||||
|
||||
@ -555,7 +555,7 @@
|
||||
<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
{% if company.status == 'archived' and current_user.is_admin %}
|
||||
{% if company.status == 'archived' and current_user.can_manage_users() %}
|
||||
<button class="btn-icon danger" onclick="hardDeleteCompany({{ company.id }}, '{{ company.name|e }}')" title="Trwale usuń">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
|
||||
@ -1122,7 +1122,7 @@
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr data-user-id="{{ user.id }}"
|
||||
data-is-admin="{{ 'true' if user.is_admin else 'false' }}"
|
||||
data-role="{{ user.role }}"
|
||||
data-is-verified="{{ 'true' if user.is_verified else 'false' }}">
|
||||
<td>{{ user.id }}</td>
|
||||
<td>
|
||||
@ -1158,7 +1158,7 @@
|
||||
{{ user.created_at.strftime('%d.%m.%Y %H:%M') }}
|
||||
</td>
|
||||
<td>
|
||||
{% if user.is_admin %}
|
||||
{% if user.can_manage_users() %}
|
||||
<span class="badge badge-admin">Admin</span>
|
||||
{% endif %}
|
||||
{% if user.is_rada_member %}
|
||||
@ -1182,9 +1182,9 @@
|
||||
</button>
|
||||
|
||||
<!-- Toggle Admin -->
|
||||
<button class="btn-icon admin-toggle {{ 'active' if user.is_admin else '' }}"
|
||||
<button class="btn-icon admin-toggle {{ 'active' if user.can_manage_users() else '' }}"
|
||||
onclick="toggleAdmin({{ user.id }})"
|
||||
title="{{ 'Odbierz uprawnienia admina' if user.is_admin else 'Nadaj uprawnienia admina' }}"
|
||||
title="{{ 'Odbierz uprawnienia admina' if user.can_manage_users() else 'Nadaj uprawnienia admina' }}"
|
||||
{% if user.id == current_user.id %}disabled{% endif %}>
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
||||
@ -1744,7 +1744,7 @@ Lub format CSV, Excel, lista emaili..."></textarea>
|
||||
|
||||
const filter = this.dataset.filter;
|
||||
document.querySelectorAll('[data-user-id]').forEach(row => {
|
||||
const isAdmin = row.dataset.isAdmin === 'true';
|
||||
const isAdmin = row.dataset.role === 'ADMIN';
|
||||
const isVerified = row.dataset.isVerified === 'true';
|
||||
|
||||
let show = false;
|
||||
@ -2434,7 +2434,7 @@ Lub format CSV, Excel, lista emaili..."></textarea>
|
||||
</td>
|
||||
<td>${user.name || '-'}</td>
|
||||
<td>${user.company_name || '-'}</td>
|
||||
<td>${user.is_admin ? 'Tak' : 'Nie'}</td>
|
||||
<td>${user.role === 'ADMIN' ? 'Tak' : 'Nie'}</td>
|
||||
<td>
|
||||
${hasWarnings ? `
|
||||
<div class="ai-user-warning">
|
||||
|
||||
@ -684,7 +684,7 @@
|
||||
<div class="info-box">
|
||||
<p>
|
||||
<strong>{{ reviewer.name if reviewer else 'Biuro Izby NORDA' }}</strong>
|
||||
{% if reviewer and reviewer.is_admin %}<span class="reviewer-role">Biuro Izby NORDA</span>{% endif %}
|
||||
{% if reviewer and reviewer.can_access_admin_panel() %}<span class="reviewer-role">Biuro Izby NORDA</span>{% endif %}
|
||||
zaproponował(a) aktualizację danych na podstawie oficjalnych danych z
|
||||
{% if application.registry_source == 'KRS' %}
|
||||
Krajowego Rejestru Sądowego (KRS).
|
||||
@ -730,7 +730,7 @@
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
|
||||
</svg>
|
||||
Komentarz od: <strong>{{ reviewer.name if reviewer else 'Biuro Izby NORDA' }}</strong>
|
||||
{% if reviewer and reviewer.is_admin %}<span class="reviewer-role">Biuro Izby NORDA</span>{% endif %}
|
||||
{% if reviewer and reviewer.can_access_admin_panel() %}<span class="reviewer-role">Biuro Izby NORDA</span>{% endif %}
|
||||
</div>
|
||||
<p>{{ application.proposed_changes_comment }}</p>
|
||||
</div>
|
||||
|
||||
@ -287,9 +287,9 @@ def admin_required(f):
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
# Use new role system, fallback to is_admin for backward compatibility
|
||||
# Use role system (is_admin fallback removed — role is source of truth)
|
||||
SystemRole = _get_system_role()
|
||||
if not (current_user.has_role(SystemRole.ADMIN) or current_user.is_admin):
|
||||
if not current_user.has_role(SystemRole.ADMIN):
|
||||
flash('Brak uprawnień administratora.', 'error')
|
||||
return redirect(url_for('public.index'))
|
||||
return f(*args, **kwargs)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user