258 lines
12 KiB
Python
258 lines
12 KiB
Python
"""
|
||
Система гранулярных прав доступа.
|
||
|
||
Определяет все доступные разрешения и функции для их проверки.
|
||
"""
|
||
from functools import wraps
|
||
from django.http import JsonResponse, HttpResponseForbidden
|
||
from django.shortcuts import redirect
|
||
from django.contrib import messages
|
||
|
||
|
||
# Определение всех разрешений в системе
|
||
# Формат: (код, название, описание)
|
||
PERMISSIONS = [
|
||
# Source List Page - Toolbar buttons
|
||
('source_create', 'Создание источника', 'Кнопка "Создать" на странице списка источников'),
|
||
('source_edit', 'Редактирование источника', 'Кнопка редактирования источника'),
|
||
('source_delete', 'Удаление источников', 'Кнопка "Удалить" на странице списка источников'),
|
||
('source_import_excel', 'Импорт из Excel', 'Кнопка "Excel" для загрузки данных'),
|
||
('source_import_csv', 'Импорт из CSV', 'Кнопка "CSV" для загрузки данных'),
|
||
('source_averaging', 'Усреднение точек', 'Кнопка "Усреднение" на странице списка источников'),
|
||
('source_tech_analyze', 'Технический анализ', 'Кнопка "Тех. анализ" на странице списка источников'),
|
||
('source_merge', 'Объединение источников', 'Кнопка "Объединить" в offcanvas списка'),
|
||
|
||
# Source Requests
|
||
('request_create', 'Создание заявки', 'Создание новой заявки на источник'),
|
||
('request_edit', 'Редактирование заявки', 'Редактирование существующей заявки'),
|
||
('request_delete', 'Удаление заявки', 'Удаление заявки на источник'),
|
||
('request_import', 'Импорт заявок', 'Импорт заявок из файла'),
|
||
|
||
# ObjItem (Points)
|
||
('objitem_create', 'Создание точки', 'Создание новой точки ГЛ'),
|
||
('objitem_edit', 'Редактирование точки', 'Редактирование точки ГЛ'),
|
||
('objitem_delete', 'Удаление точки', 'Удаление точки ГЛ'),
|
||
|
||
# Satellites
|
||
('satellite_create', 'Создание спутника', 'Создание нового спутника'),
|
||
('satellite_edit', 'Редактирование спутника', 'Редактирование спутника'),
|
||
('satellite_delete', 'Удаление спутника', 'Удаление спутника'),
|
||
|
||
# Tech Analyze
|
||
('tech_analyze_create', 'Создание тех. анализа', 'Создание записи технического анализа'),
|
||
('tech_analyze_edit', 'Редактирование тех. анализа', 'Редактирование записи технического анализа'),
|
||
('tech_analyze_delete', 'Удаление тех. анализа', 'Удаление записи технического анализа'),
|
||
|
||
# Signal Marks
|
||
('mark_create', 'Создание отметки', 'Создание отметки о сигнале'),
|
||
('mark_edit', 'Редактирование отметки', 'Редактирование отметки о сигнале'),
|
||
|
||
# Statistics
|
||
('statistics_view', 'Просмотр статистики', 'Доступ к странице статистики'),
|
||
|
||
# Kubsat
|
||
('kubsat_view', 'Просмотр Кубсат', 'Доступ к странице Кубсат'),
|
||
('kubsat_edit', 'Редактирование Кубсат', 'Редактирование данных Кубсат'),
|
||
|
||
# LyngSat
|
||
('lyngsat_parse', 'Парсинг LyngSat', 'Запуск парсинга LyngSat'),
|
||
|
||
# Transponders
|
||
('transponder_create', 'Создание транспондера', 'Создание нового транспондера'),
|
||
('transponder_edit', 'Редактирование транспондера', 'Редактирование транспондера'),
|
||
('transponder_delete', 'Удаление транспондера', 'Удаление транспондера'),
|
||
('transponder_import_xml', 'Импорт транспондеров из XML', 'Загрузка транспондеров из XML файла'),
|
||
|
||
# Errors Report (Журнал ошибок)
|
||
('errors_report_create', 'Создание записи журнала ошибок', 'Создание новой записи в журнале ошибок'),
|
||
('errors_report_edit', 'Редактирование записи журнала ошибок', 'Редактирование записи в журнале ошибок'),
|
||
('errors_report_delete', 'Удаление записи журнала ошибок', 'Удаление записи из журнала ошибок'),
|
||
|
||
# Admin access
|
||
# ('admin_access', 'Доступ к админ-панели', 'Доступ к административной панели Django'),
|
||
]
|
||
|
||
# Словарь для быстрого доступа к разрешениям
|
||
PERMISSION_CHOICES = [(code, name) for code, name, _ in PERMISSIONS]
|
||
PERMISSION_DESCRIPTIONS = {code: desc for code, _, desc in PERMISSIONS}
|
||
|
||
# Права по умолчанию для ролей
|
||
DEFAULT_ROLE_PERMISSIONS = {
|
||
'admin': [code for code, _, _ in PERMISSIONS], # Все права
|
||
'moderator': [
|
||
'source_create', 'source_edit', 'source_import_excel', 'source_import_csv',
|
||
'source_averaging', 'source_tech_analyze', 'source_merge',
|
||
'request_create', 'request_edit', 'request_import',
|
||
'objitem_create', 'objitem_edit',
|
||
'satellite_create', 'satellite_edit',
|
||
'transponder_create', 'transponder_edit', 'transponder_import_xml',
|
||
'tech_analyze_create', 'tech_analyze_edit',
|
||
'mark_create', 'mark_edit',
|
||
'statistics_view',
|
||
'kubsat_view', 'kubsat_edit',
|
||
'errors_report_create', 'errors_report_edit',
|
||
],
|
||
'user': [
|
||
'statistics_view',
|
||
'kubsat_view',
|
||
],
|
||
}
|
||
|
||
|
||
def has_permission(user, permission_code):
|
||
"""
|
||
Проверяет, имеет ли пользователь указанное разрешение.
|
||
|
||
Args:
|
||
user: Объект User Django
|
||
permission_code: Код разрешения (строка)
|
||
|
||
Returns:
|
||
bool: True если пользователь имеет разрешение
|
||
"""
|
||
if not user or not user.is_authenticated:
|
||
return False
|
||
|
||
# Суперпользователь имеет все права
|
||
if user.is_superuser:
|
||
return True
|
||
|
||
# Получаем CustomUser
|
||
custom_user = getattr(user, 'customuser', None)
|
||
if not custom_user:
|
||
return False
|
||
|
||
# Проверяем, используются ли индивидуальные разрешения
|
||
if custom_user.use_custom_permissions:
|
||
# Используем индивидуальные разрешения
|
||
return custom_user.user_permissions.filter(code=permission_code).exists()
|
||
|
||
# Иначе используем права по умолчанию для роли
|
||
role = custom_user.role
|
||
default_perms = DEFAULT_ROLE_PERMISSIONS.get(role, [])
|
||
return permission_code in default_perms
|
||
|
||
|
||
def get_user_permissions(user):
|
||
"""
|
||
Возвращает список кодов разрешений пользователя.
|
||
|
||
Args:
|
||
user: Объект User Django
|
||
|
||
Returns:
|
||
list: Список кодов разрешений
|
||
"""
|
||
if not user or not user.is_authenticated:
|
||
return []
|
||
|
||
if user.is_superuser:
|
||
return [code for code, _, _ in PERMISSIONS]
|
||
|
||
custom_user = getattr(user, 'customuser', None)
|
||
if not custom_user:
|
||
return []
|
||
|
||
# Проверяем, используются ли индивидуальные разрешения
|
||
if custom_user.use_custom_permissions:
|
||
return list(custom_user.user_permissions.values_list('code', flat=True))
|
||
|
||
# Права по умолчанию для роли
|
||
return DEFAULT_ROLE_PERMISSIONS.get(custom_user.role, [])
|
||
|
||
|
||
def permission_required(permission_code, redirect_url=None, raise_exception=False):
|
||
"""
|
||
Декоратор для проверки разрешения на уровне view.
|
||
|
||
Args:
|
||
permission_code: Код разрешения
|
||
redirect_url: URL для редиректа при отсутствии прав (по умолчанию на предыдущую страницу)
|
||
raise_exception: Если True, возвращает 403 вместо редиректа
|
||
|
||
Usage:
|
||
@permission_required('source_create')
|
||
def my_view(request):
|
||
...
|
||
"""
|
||
def decorator(view_func):
|
||
@wraps(view_func)
|
||
def wrapper(request, *args, **kwargs):
|
||
if has_permission(request.user, permission_code):
|
||
return view_func(request, *args, **kwargs)
|
||
|
||
# Для AJAX запросов возвращаем JSON
|
||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'У вас нет прав для выполнения этого действия'
|
||
}, status=403)
|
||
|
||
if raise_exception:
|
||
return HttpResponseForbidden('У вас нет прав для выполнения этого действия')
|
||
|
||
messages.error(request, 'У вас нет прав для выполнения этого действия')
|
||
|
||
if redirect_url:
|
||
return redirect(redirect_url)
|
||
|
||
# Редирект на предыдущую страницу или на главную
|
||
referer = request.META.get('HTTP_REFERER')
|
||
if referer:
|
||
return redirect(referer)
|
||
return redirect('mainapp:source_list')
|
||
|
||
return wrapper
|
||
return decorator
|
||
|
||
|
||
class PermissionRequiredMixin:
|
||
"""
|
||
Миксин для class-based views для проверки разрешений.
|
||
|
||
Usage:
|
||
class MyView(PermissionRequiredMixin, View):
|
||
permission_required = 'source_create'
|
||
# или для нескольких разрешений (любое из них):
|
||
permission_required = ['source_create', 'source_edit']
|
||
"""
|
||
permission_required = None
|
||
permission_denied_message = 'У вас нет прав для выполнения этого действия'
|
||
|
||
def has_permission(self):
|
||
"""Проверяет наличие разрешения."""
|
||
perms = self.get_permission_required()
|
||
if isinstance(perms, str):
|
||
return has_permission(self.request.user, perms)
|
||
# Для списка - проверяем наличие хотя бы одного разрешения
|
||
return any(has_permission(self.request.user, perm) for perm in perms)
|
||
|
||
def get_permission_required(self):
|
||
"""Возвращает требуемое разрешение."""
|
||
if self.permission_required is None:
|
||
raise ValueError(
|
||
f'{self.__class__.__name__} is missing the permission_required attribute.'
|
||
)
|
||
return self.permission_required
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
if not self.has_permission():
|
||
return self.handle_no_permission()
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def handle_no_permission(self):
|
||
"""Обработка отсутствия разрешения."""
|
||
# Для AJAX запросов
|
||
if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': self.permission_denied_message
|
||
}, status=403)
|
||
|
||
messages.error(self.request, self.permission_denied_message)
|
||
|
||
referer = self.request.META.get('HTTP_REFERER')
|
||
if referer:
|
||
return redirect(referer)
|
||
return redirect('mainapp:source_list')
|