loko/loko/projects/permissions.py

235 lines
No EOL
8.6 KiB
Python

from django.shortcuts import get_object_or_404
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from functools import wraps
from projects.models import Project, ProjectTeamAccess, ProjectUserAccess
from common.models import UserConfig, UserThematics
def _has_explicit_view_access(user, project, user_config):
"""Vérifie uniquement les accès explicites (équipe ou utilisateur direct)."""
from contracts.models import CompanyMember
team_ids = CompanyMember.objects.filter(
user=user
).values_list('teams__id', flat=True)
if team_ids and ProjectTeamAccess.objects.filter(
project=project, team__in=team_ids, can_view=True
).exists():
return True
return ProjectUserAccess.objects.filter(project=project, user=user, can_view=True).exists()
def can_view_project(user, project):
try:
user_config = UserConfig.objects.get(user=user)
except UserConfig.DoesNotExist:
return False
# Les admins et top_managers voient tout, y compris les projets en accès strict
if user_config.roles.filter(name__in=['admin', 'top_manager']).exists():
return True
# En accès strict : seuls les accès explicites (équipe/utilisateur) sont pris en compte
if project.strict_access:
return _has_explicit_view_access(user, project, user_config)
# Vérifie si l'utilisateur est interne ou viewer et a accès à la thématique (permission projet dédiée)
thematics = project.thematics.all() if project.thematics.exists() else None
if thematics and (user_config.is_intern or user_config.has_role('viewer')):
if UserThematics.objects.filter(
user_config=user_config,
thematic__in=thematics,
can_view_projects=True
).exists():
return True
# Accès explicites (équipe ou utilisateur)
return _has_explicit_view_access(user, project, user_config)
def project_permission_required(view_func):
@wraps(view_func)
def _wrapped_view(request, project_id, *args, **kwargs):
project = get_object_or_404(Project, pk=project_id)
if not can_view_project(request.user, project):
raise PermissionDenied(_("Vous n'avez pas la permission de voir ce projet."))
return view_func(request, project_id, *args, **kwargs)
return _wrapped_view
def can_add_or_edit_project(user, project=None, ignore_status_lock=False):
try:
user_config = UserConfig.objects.get(user=user)
except UserConfig.DoesNotExist:
return False
# Verrouille l'édition d'attributs quand le projet est comptabilisé/clos/annulé,
# sauf si l'appel demande explicitement d'ignorer ce verrou (ex: changement de statut).
if project and not ignore_status_lock and project.status in ['invoiced', 'closed', 'canceled']:
return False
# Les admins peuvent tout modifier
if user_config.roles.filter(name__in=['admin']).exists():
return True
# Les externes ne peuvent pas modifier les propriétés d'un projet
if not user_config.is_intern:
return False
# Vérifie les accès explicites en édition (équipe ou utilisateur direct)
if project:
from contracts.models import CompanyMember
team_ids = CompanyMember.objects.filter(
user=user
).values_list('teams__id', flat=True)
if team_ids and ProjectTeamAccess.objects.filter(
project=project, team__in=team_ids, can_edit=True
).exists():
return True
if ProjectUserAccess.objects.filter(project=project, user=user, can_edit=True).exists():
return True
# En accès strict : les permissions thématiques ne s'appliquent pas
if project and project.strict_access:
return False
# Vérifie si l'utilisateur est interne, a le rôle manager et a accès à la thématique du projet
if project and project.thematics and user_config.is_intern and user_config.roles.filter(name='manager').exists():
if UserThematics.objects.filter(
user_config=user_config,
thematic__in=project.thematics.all(),
can_edit_projects=True
).exists():
return True
if not project and user_config.is_intern and user_config.roles.filter(name='manager').exists():
return True
return False
def can_add_project_documents(user, project):
"""Autorise l'ajout de documents : édition requise pour les internes, vue seule suffit pour les externes."""
if not can_view_project(user, project):
return False
try:
user_config = UserConfig.objects.get(user=user)
except UserConfig.DoesNotExist:
return False
if not user_config.is_intern:
return True
return can_add_or_edit_project(user, project, ignore_status_lock=True)
def can_manage_project_orders(user, project=None):
"""Admins ou éditeurs du projet uniquement."""
try:
user_config = UserConfig.objects.get(user=user)
except UserConfig.DoesNotExist:
return False
if not user_config.roles.filter(name__in=['admin', 'manager']).exists():
return False
if project:
return can_add_or_edit_project(user, project)
return True
def can_manage_project_members(user, project):
"""Seul un utilisateur ayant des droits d'édition sur le projet peut gérer les accès."""
if not can_view_project(user, project):
return False
return can_add_or_edit_project(user, project)
def project_manage_members_permission_required(view_func):
@wraps(view_func)
def _wrapped_view(request, project_id, *args, **kwargs):
project = get_object_or_404(Project, pk=project_id)
if not can_manage_project_members(request.user, project):
raise PermissionDenied(_("Seul le responsable du projet ou un manager peut gérer les membres."))
return view_func(request, project_id, *args, **kwargs)
return _wrapped_view
def project_add_or_edit_permission_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
project = None
# Vérifie si 'project_id' est fourni dans les paramètres de la vue
project_id = kwargs.get('project_id')
if project_id is not None:
project = get_object_or_404(Project, pk=project_id)
if not can_add_or_edit_project(request.user, project):
raise PermissionDenied(_("Vous n'avez pas la permission d'ajouter ou de modifier ce projet."))
return view_func(request, *args, **kwargs)
return _wrapped_view
def filter_viewable_projects_for_user(user):
"""
Renvoie un queryset des projets visibles par l'utilisateur
selon les règles de can_view_project().
"""
try:
user_config = UserConfig.objects.select_related('user').prefetch_related('roles', 'thematics').get(user=user)
except UserConfig.DoesNotExist:
return Project.objects.none()
projects = Project.objects.all()
# Rôles admin ou top_manager → accès global
if user_config.roles.filter(name__in=['admin', 'top_manager']).exists():
return projects
from contracts.models import CompanyMember
team_ids = list(CompanyMember.objects.filter(
user=user
).values_list('teams__id', flat=True))
# Accès explicites (équipe ou utilisateur) — valables même en accès strict
q_explicit = Q(user_accesses__user=user, user_accesses__can_view=True)
if team_ids:
q_explicit |= Q(team_accesses__team__in=team_ids, team_accesses__can_view=True)
# Accès via thématique (uniquement pour les projets sans accès strict)
q_thematic = Q()
if user_config.is_intern or user_config.has_role('viewer'):
thematic_ids = list(UserThematics.objects.filter(
user_config=user_config,
can_view_projects=True
).values_list('thematic__id', flat=True))
if thematic_ids:
q_thematic = Q(strict_access=False, thematics__id__in=thematic_ids)
return projects.filter(q_explicit | q_thematic).distinct()
# =============================================================================
# Guard functions for private file access
# =============================================================================
def guard_project_document_file(obj, user):
"""
Guard function for ProjectDocument.file and ProjectDocument.thumbnail fields.
Checks if user has access to the project.
"""
from .models import ProjectDocument
if not isinstance(obj, ProjectDocument):
return False
if not user.is_authenticated:
return False
return can_view_project(user, obj.project)