This commit is contained in:
2025-07-17 16:17:52 +03:00
parent e37cbf9fee
commit 67dfb671c4
4 changed files with 119 additions and 20 deletions

View File

@@ -0,0 +1,93 @@
# Ske_Signage Role-Based Access Control Implementation
## Summary of Changes
This document outlines the modifications made to implement proper role-based access control in the Ske_Signage project, ensuring that both 'admin' and 'sadmin' roles have appropriate access to user management functions while maintaining the 'sadmin' role as the highest privilege level.
## Modified Files
### 1. `/app/routes/admin.py`
**Changes made:**
- **create_user function (lines ~75-85)**:
- Changed restriction from completely preventing sadmin creation to allowing only sadmin users to create other sadmin users
- Regular admins can now create admin and user roles, but not sadmin
- **edit_user function (lines ~460-470)**:
- Changed from completely preventing sadmin editing to allowing sadmin users to edit other sadmin users
- Regular admins cannot modify sadmin users or assign sadmin role
- **delete_user function (lines ~108-130)**:
- Changed decorator from `@super_admin_required` to `@admin_required`
- Added protection so only sadmin users can delete other sadmin users
- Regular admins can delete admin and user roles, but not sadmin
### 2. `/app/templates/admin/index.html`
**Changes made:**
- **User row clickability (line ~164)**:
- Modified logic to allow sadmin users to edit other sadmin users
- `user.role != 'sadmin' or current_user.is_super_admin`
- **Edit action visibility (lines ~190-196)**:
- Updated to show edit option for sadmin users when current user is also sadmin
- Shows "Protected" badge only for sadmin users when current user is not sadmin
- **Edit form expandable rows (line ~202)**:
- Modified condition to allow sadmin editing by other sadmin users
- **Role selection in edit form (lines ~224-230)**:
- Added sadmin option only visible to super admin users
- `{% if current_user.is_super_admin %}<option value="sadmin">{% endif %}`
- **Delete user section (lines ~252-267)**:
- Changed from sadmin-only to admin-accessible with restrictions
- Regular admins can delete non-sadmin users
- Only sadmin can delete other sadmin users
- **Create user modal (lines ~578-586)**:
- Added sadmin option only visible to super admin users
### 3. `/app/templates/base.html`
**Changes made:**
- **Navigation menu (line ~83)**:
- Changed from `current_user.is_admin` to `current_user.has_admin_access`
- Now both admin and sadmin users can see admin navigation options
## Role Hierarchy
### Super Admin (sadmin)
- **Full access**: Can create, edit, and delete all users including other sadmin users
- **Exclusive privileges**: Only role that can assign sadmin role to others
- **Ultimate control**: Has access to all admin functions
### Admin
- **User management**: Can create, edit, and delete regular users and other admins
- **Restrictions**: Cannot create, edit, or delete sadmin users
- **Cannot**: Assign sadmin role to any user
### User
- **Limited access**: Cannot access admin functions
- **Standard user**: Can only access regular user features
## Security Features
1. **Privilege Escalation Prevention**: Regular admins cannot create or promote users to sadmin
2. **Self-Protection**: Users cannot edit or delete their own accounts
3. **Role Protection**: Sadmin users are protected from modification by regular admins
4. **Hierarchical Deletion**: Admins can only delete users at their level or below (except sadmin)
## Template Logic
The templates now use conditional logic to show/hide features based on user roles:
- `current_user.is_super_admin`: Only for sadmin users
- `current_user.has_admin_access`: For both admin and sadmin users
- `current_user.is_admin`: For admin users only
## Validation
All backend routes now include proper validation to:
- Prevent unauthorized role assignments
- Protect sadmin users from unauthorized modifications
- Ensure proper access control at the API level
- Provide meaningful error messages for restricted actions
This implementation ensures that the admin role has proper access to create, edit, and delete users while maintaining the sadmin role as the supreme administrator with full control over all aspects of user management.

View File

@@ -78,9 +78,9 @@ def create_user():
flash('Invalid role specified.', 'danger')
return redirect(url_for('admin.index'))
# Prevent creating sadmin users - sadmin only exists from deployment
if role == 'sadmin':
flash('Super admin users cannot be created through the interface.', 'danger')
# Prevent regular admins from creating sadmin users - only sadmin can create sadmin
if role == 'sadmin' and not current_user.is_super_admin:
flash('Only super admin users can create other super admin users.', 'danger')
return redirect(url_for('admin.index'))
# Check if user already exists
@@ -106,7 +106,7 @@ def create_user():
@bp.route('/delete_user', methods=['POST'])
@login_required
@super_admin_required
@admin_required
def delete_user():
"""Delete a user using POST form data"""
user_id = request.form.get('user_id')
@@ -122,9 +122,9 @@ def delete_user():
user = User.query.get_or_404(user_id)
username = user.username
# Prevent deletion of sadmin users - they are permanent
if user.role == 'sadmin':
flash('Super admin users cannot be deleted.', 'danger')
# Prevent deletion of sadmin users by regular admins - only sadmin can delete sadmin
if user.role == 'sadmin' and not current_user.is_super_admin:
flash('Only super admin users can delete other super admin users.', 'danger')
return redirect(url_for('admin.index'))
try:
@@ -457,14 +457,14 @@ def edit_user():
flash('Invalid role specified.', 'danger')
return redirect(url_for('admin.index'))
# Prevent changing sadmin users - they are permanent
if user.role == 'sadmin':
flash('Super admin users cannot be modified.', 'danger')
# Prevent regular admins from modifying sadmin users - only sadmin can modify sadmin
if user.role == 'sadmin' and not current_user.is_super_admin:
flash('Only super admin users can modify other super admin users.', 'danger')
return redirect(url_for('admin.index'))
# Prevent assigning sadmin role - sadmin only exists from deployment
if role == 'sadmin':
flash('Super admin role cannot be assigned through the interface.', 'danger')
# Prevent regular admins from assigning sadmin role - only sadmin can assign sadmin
if role == 'sadmin' and not current_user.is_super_admin:
flash('Only super admin users can assign super admin role.', 'danger')
return redirect(url_for('admin.index'))
# Check if username is taken by another user

View File

@@ -161,7 +161,7 @@
<tbody>
{% for user in users %}
<!-- Main user row (clickable) -->
<tr class="user-row {% if user.username != current_user.username and user.role != 'sadmin' %}clickable-row{% endif %}" data-user-id="{{ user.id }}" {% if user.username != current_user.username and user.role != 'sadmin' %}style="cursor: pointer;"{% endif %}>
<tr class="user-row {% if user.username != current_user.username and (user.role != 'sadmin' or current_user.is_super_admin) %}clickable-row{% endif %}" data-user-id="{{ user.id }}" {% if user.username != current_user.username and (user.role != 'sadmin' or current_user.is_super_admin) %}style="cursor: pointer;"{% endif %}>
<td>
<strong>{{ user.username }}</strong>
{% if user.username == current_user.username %}
@@ -187,10 +187,10 @@
<td>{{ user.created_at.strftime('%Y-%m-%d') if user.created_at else 'N/A' }}</td>
<td>{{ user.last_login.strftime('%Y-%m-%d %H:%M') if user.last_login else 'Never' }}</td>
<td>
{% if user.username != current_user.username and user.role != 'sadmin' %}
{% if user.username != current_user.username and (user.role != 'sadmin' or current_user.is_super_admin) %}
<i class="bi bi-chevron-down expand-icon"></i>
<small class="text-muted">Click to edit</small>
{% elif user.role == 'sadmin' %}
{% elif user.role == 'sadmin' and not current_user.is_super_admin %}
<span class="badge bg-danger">Protected</span>
{% else %}
<span class="badge bg-secondary">Current User</span>
@@ -199,7 +199,7 @@
</tr>
<!-- Expandable edit row (hidden by default) -->
{% if user.username != current_user.username and user.role != 'sadmin' %}
{% if user.username != current_user.username and (user.role != 'sadmin' or current_user.is_super_admin) %}
<tr class="edit-row" id="edit-row-{{ user.id }}" style="display: none;">
<td colspan="6" class="bg-light">
<div class="row p-3">
@@ -223,6 +223,9 @@
<select name="role" class="form-select form-select-sm">
<option value="user" {% if user.role == 'user' %}selected{% endif %}>User</option>
<option value="admin" {% if user.role == 'admin' %}selected{% endif %}>Admin</option>
{% if current_user.is_super_admin %}
<option value="sadmin" {% if user.role == 'sadmin' %}selected{% endif %}>Super Admin</option>
{% endif %}
</select>
</div>
@@ -246,7 +249,7 @@
</div>
<div class="col-md-4">
{% if current_user.is_super_admin %}
{% if current_user.has_admin_access and (user.role != 'sadmin' or current_user.is_super_admin) %}
<h6 class="text-danger"><i class="bi bi-trash"></i> Delete User</h6>
<p class="small text-muted">This action cannot be undone.</p>
<form method="POST" action="{{ url_for('admin.delete_user') }}" onsubmit="return confirm('Are you sure you want to delete user {{ user.username }}? This action cannot be undone!');">
@@ -257,7 +260,7 @@
</form>
{% else %}
<h6 class="text-muted"><i class="bi bi-shield-lock"></i> Delete User</h6>
<p class="small text-muted">Super admin access required.</p>
<p class="small text-muted">{% if user.role == 'sadmin' %}Super admin users cannot be deleted{% else %}Admin access required{% endif %}.</p>
<button type="button" class="btn btn-outline-secondary btn-sm" disabled>
<i class="bi bi-shield-lock"></i> Restricted
</button>
@@ -577,6 +580,9 @@
<select class="form-select" id="role" name="role">
<option value="user">User</option>
<option value="admin">Admin</option>
{% if current_user.is_super_admin %}
<option value="sadmin">Super Admin</option>
{% endif %}
</select>
</div>
</div>

View File

@@ -80,7 +80,7 @@
<i class="bi bi-house"></i> Dashboard
</a>
</li>
{% if current_user.is_admin %}
{% if current_user.has_admin_access %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('content.upload') }}">
<i class="bi bi-cloud-upload"></i> Upload Content