Files
dbstorage/dbapp/mainapp/permissions.py

258 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Система гранулярных прав доступа.
Определяет все доступные разрешения и функции для их проверки.
"""
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')