diff --git a/apps/audits/api.py b/apps/audits/api.py index daf111ed8051bc3cf5ef10198841104004ffed73..626749b2977b60d66854ff4a54e002882764668c 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -3,7 +3,7 @@ from rest_framework import viewsets -from common.permissions import IsOrgAdminOrAppUser, IsAuditor +from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor from .models import FTPLog from .serializers import FTPLogSerializer @@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer class FTPLogViewSet(viewsets.ModelViewSet): queryset = FTPLog.objects.all() serializer_class = FTPLogSerializer - permission_classes = (IsOrgAdminOrAppUser | IsAuditor,) + permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) diff --git a/apps/audits/views.py b/apps/audits/views.py index 8c9b6467dd9bf8b5e3aa47fc6657910cbf078708..13b0031d0325d6f0a87d05d6bb5faa526d07ae69 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -18,8 +18,9 @@ from django.db.models import Q from audits.utils import get_excel_response, write_content_to_excel from common.mixins import DatetimeSearchMixin -from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser - +from common.permissions import ( + PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor +) from orgs.utils import current_org from ops.views import CommandExecutionListView as UserCommandExecutionListView from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog @@ -47,7 +48,7 @@ class FTPLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): paginate_by = settings.DISPLAY_PER_PAGE user = asset = system_user = filename = '' date_from = date_to = None - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] def get_queryset(self): self.queryset = super().get_queryset() @@ -96,7 +97,7 @@ class OperateLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): user = action = resource_type = '' date_from = date_to = None actions_dict = dict(OperateLog.ACTION_CHOICES) - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] def get_queryset(self): self.queryset = super().get_queryset() @@ -119,7 +120,7 @@ class OperateLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): def get_context_data(self, **kwargs): context = { - 'user_list': current_org.get_org_users(), + 'user_list': current_org.get_org_members(), 'actions': self.actions_dict, 'resource_type_list': get_resource_type_list(), 'date_from': self.date_from, @@ -139,10 +140,10 @@ class PasswordChangeLogList(PermissionsMixin, DatetimeSearchMixin, ListView): paginate_by = settings.DISPLAY_PER_PAGE user = '' date_from = date_to = None - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] def get_queryset(self): - users = current_org.get_org_users() + users = current_org.get_org_members() self.queryset = super().get_queryset().filter( user__in=[user.__str__() for user in users] ) @@ -159,7 +160,7 @@ class PasswordChangeLogList(PermissionsMixin, DatetimeSearchMixin, ListView): def get_context_data(self, **kwargs): context = { - 'user_list': current_org.get_org_users(), + 'user_list': current_org.get_org_members(), 'date_from': self.date_from, 'date_to': self.date_to, 'user': self.user, @@ -176,18 +177,18 @@ class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): paginate_by = settings.DISPLAY_PER_PAGE user = keyword = "" date_to = date_from = None - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] @staticmethod - def get_org_users(): - users = current_org.get_org_users().values_list('username', flat=True) + def get_org_members(): + users = current_org.get_org_members().values_list('username', flat=True) return users def get_queryset(self): if current_org.is_default(): queryset = super().get_queryset() else: - users = self.get_org_users() + users = self.get_org_members() queryset = super().get_queryset().filter(username__in=users) self.user = self.request.GET.get('user', '') @@ -214,7 +215,7 @@ class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): 'date_to': self.date_to, 'user': self.user, 'keyword': self.keyword, - 'user_list': self.get_org_users(), + 'user_list': self.get_org_members(), } kwargs.update(context) return super().get_context_data(**kwargs) @@ -223,6 +224,10 @@ class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): class CommandExecutionListView(UserCommandExecutionListView): user_id = None + def get_user_list(self): + users = current_org.get_org_members(exclude=('Auditor',)) + return users + def get_queryset(self): queryset = self._get_queryset() self.user_id = self.request.GET.get('user') @@ -233,10 +238,6 @@ class CommandExecutionListView(UserCommandExecutionListView): queryset = queryset.filter(user__in=org_users) return queryset - def get_user_list(self): - users = current_org.get_org_users() - return users - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update({ diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 64868916538df036cb898e720d27a541e3218d83..08350d89bd26c06113702f3fa191cf7fb45e6f8c 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -4,8 +4,6 @@ import time from rest_framework import permissions from django.contrib.auth.mixins import UserPassesTestMixin -from django.shortcuts import redirect -from django.http.response import HttpResponseForbidden from django.conf import settings from orgs.utils import current_org @@ -27,12 +25,6 @@ class IsAppUser(IsValidUser): and request.user.is_app -class IsAuditor(IsValidUser): - def has_permission(self, request, view): - return super(IsAuditor, self).has_permission(request, view) \ - and request.user.is_auditor - - class IsSuperUser(IsValidUser): def has_permission(self, request, view): return super(IsSuperUser, self).has_permission(request, view) \ @@ -45,6 +37,20 @@ class IsSuperUserOrAppUser(IsSuperUser): or request.user.is_app +class IsSuperAuditor(IsValidUser): + def has_permission(self, request, view): + return super(IsSuperAuditor, self).has_permission(request, view) \ + and request.user.is_super_auditor + + +class IsOrgAuditor(IsValidUser): + def has_permission(self, request, view): + if not current_org: + return False + return super(IsOrgAuditor, self).has_permission(request, view) \ + and current_org.can_audit_by(request.user) + + class IsOrgAdmin(IsValidUser): """Allows access only to superuser""" @@ -81,43 +87,6 @@ class IsCurrentUserOrReadOnly(permissions.BasePermission): return obj == request.user -class LoginRequiredMixin(UserPassesTestMixin): - def test_func(self): - if self.request.user.is_authenticated: - return True - else: - return False - - -class AdminUserRequiredMixin(UserPassesTestMixin): - def test_func(self): - if not self.request.user.is_authenticated: - return False - elif not current_org.can_admin_by(self.request.user): - self.raise_exception = True - return False - return True - - def dispatch(self, request, *args, **kwargs): - if not request.user.is_authenticated: - return super().dispatch(request, *args, **kwargs) - - if not current_org: - return redirect('orgs:switch-a-org') - - if not current_org.can_admin_by(request.user): - if request.user.is_org_admin: - return redirect('orgs:switch-a-org') - return HttpResponseForbidden() - return super().dispatch(request, *args, **kwargs) - - -class SuperUserRequiredMixin(UserPassesTestMixin): - def test_func(self): - if self.request.user.is_authenticated and self.request.user.is_superuser: - return True - - class WithBootstrapToken(permissions.BasePermission): def has_permission(self, request, view): authorization = request.META.get('HTTP_AUTHORIZATION', '') @@ -159,14 +128,61 @@ class NeedMFAVerify(permissions.BasePermission): return False -class CanUpdateDeleteSuperUser(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - if request.method in ['GET', 'OPTIONS']: +class CanUpdateDeleteUser(permissions.BasePermission): + + @staticmethod + def has_delete_object_permission(request, view, obj): + if not request.user.can_admin_current_org: + return False + # 超级管ç†å‘˜ / 组织管ç†å‘˜ + if str(request.user.id) == str(obj.id): + return False + # 超级管ç†å‘˜ + if request.user.is_superuser: + if obj.is_superuser and obj.username in ['admin']: + return False return True - elif request.method == 'DELETE' and str(request.user.id) == str(obj.id): + # 组织管ç†å‘˜ + if obj.is_superuser: + return False + if obj.is_super_auditor: + return False + if obj.is_org_admin: + return False + if len(obj.audit_orgs) > 1: return False - elif request.user.is_superuser: + if len(obj.user_orgs) > 1: + return False + return True + + @staticmethod + def has_update_object_permission(request, view, obj): + if not request.user.can_admin_current_org: + return False + # 超级管ç†å‘˜ / 组织管ç†å‘˜ + if str(request.user.id) == str(obj.id): return True - if hasattr(obj, 'is_superuser') and obj.is_superuser: + # 超级管ç†å‘˜ + if request.user.is_superuser: + return True + # 组织管ç†å‘˜ + if obj.is_superuser: + return False + if obj.is_super_auditor: + return False + if obj.is_org_admin: + return False + if len(obj.audit_orgs) > 1: + return False + if len(obj.user_orgs) > 1: + return False + return True + + def has_object_permission(self, request, view, obj): + if not request.user.can_admin_current_org: return False + if request.method in ['DELETE']: + return self.has_delete_object_permission(request, view, obj) + if request.method in ['PUT', 'PATCH']: + return self.has_update_object_permission(request, view, obj) return True diff --git a/apps/jumpserver/views.py b/apps/jumpserver/views.py index 2afd4d420963878763d2600b0cb55f76e7565120..e9c064b660691d836335809283518ab2d7916b33 100644 --- a/apps/jumpserver/views.py +++ b/apps/jumpserver/views.py @@ -34,17 +34,13 @@ class IndexView(PermissionsMixin, TemplateView): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() - if request.user.is_auditor: - return super(IndexView, self).dispatch(request, *args, **kwargs) - if not request.user.is_org_admin: + if request.user.is_common_user: return redirect('assets:user-asset-list') - if not current_org or not current_org.can_admin_by(request.user): - return redirect('orgs:switch-a-org') return super(IndexView, self).dispatch(request, *args, **kwargs) @staticmethod def get_user_count(): - return current_org.get_org_users().count() + return current_org.get_org_members().count() @staticmethod def get_asset_count(): @@ -99,7 +95,7 @@ class IndexView(PermissionsMixin, TemplateView): return self.session_month.values('user').distinct().count() def get_month_inactive_user_total(self): - count = current_org.get_org_users().count() - self.get_month_active_user_total() + count = current_org.get_org_members().count() - self.get_month_active_user_total() if count < 0: count = 0 return count @@ -115,7 +111,7 @@ class IndexView(PermissionsMixin, TemplateView): @staticmethod def get_user_disabled_total(): - return current_org.get_org_users().filter(is_active=False).count() + return current_org.get_org_members().filter(is_active=False).count() @staticmethod def get_asset_disabled_total(): diff --git a/apps/ops/views/celery.py b/apps/ops/views/celery.py index f2f0124bd6a81dddc2aec4b951e4ef35c4e9c2aa..98ceada9283d8b26e69481c36351dde77f12e9e0 100644 --- a/apps/ops/views/celery.py +++ b/apps/ops/views/celery.py @@ -2,7 +2,7 @@ # from django.views.generic import TemplateView -from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor +from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor __all__ = ['CeleryTaskLogView'] @@ -10,7 +10,7 @@ __all__ = ['CeleryTaskLogView'] class CeleryTaskLogView(PermissionsMixin, TemplateView): template_name = 'ops/celery_task_log.html' - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/apps/ops/views/command.py b/apps/ops/views/command.py index 9c8df208615c3742df8c6996c336fe88954f4cf8..1d5420c9f89282ece0e6184207748677dbca5de1 100644 --- a/apps/ops/views/command.py +++ b/apps/ops/views/command.py @@ -6,7 +6,7 @@ from django.conf import settings from django.views.generic import ListView, TemplateView from common.permissions import ( - PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser + PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor ) from common.mixins import DatetimeSearchMixin from ..models import CommandExecution @@ -25,7 +25,7 @@ class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView): ordering = ('-date_created',) context_object_name = 'task_list' keyword = '' - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] def _get_queryset(self): self.keyword = self.request.GET.get('keyword', '') diff --git a/apps/orgs/api.py b/apps/orgs/api.py index ab13102198acd10ac4a6687aa51a3bc3bbe552da..ea4a4895559987ee1c64e1a58d3455f0d8e518c6 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -33,7 +33,7 @@ class OrgViewSet(BulkModelViewSet): def get_data_from_model(self, model): if model == User: - data = model.objects.filter(orgs__id=self.org.id) + data = model.objects.filter(related_user_orgs__id=self.org.id) else: data = model.objects.filter(org_id=self.org.id) return data diff --git a/apps/orgs/context_processor.py b/apps/orgs/context_processor.py index 493031bc0b63dfc33390eb28afc1729e3c2770f3..c78ed834aec9eb440d8c6a5538f01b2784be9c31 100644 --- a/apps/orgs/context_processor.py +++ b/apps/orgs/context_processor.py @@ -7,9 +7,11 @@ from .models import Organization def org_processor(request): context = { - 'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user), + # 'ADMIN_ORGS': request.user.admin_orgs, + # 'AUDIT_ORGS': request.user.audit_orgs, + 'ADMIN_OR_AUDIT_ORGS': Organization.get_user_admin_or_audit_orgs(request.user), 'CURRENT_ORG': get_org_from_request(request), - 'HAS_ORG_PERM': current_org.can_admin_by(request.user), + # 'HAS_ORG_PERM': current_org.can_admin_by(request.user), } return context diff --git a/apps/orgs/middleware.py b/apps/orgs/middleware.py index 24a2b99fee8c1e1461db53e3687dbea7c11d0e87..3e491d3d284b6f8e8426575f7f90774e9b448f8f 100644 --- a/apps/orgs/middleware.py +++ b/apps/orgs/middleware.py @@ -13,14 +13,23 @@ class OrgMiddleware: def set_permed_org_if_need(request): if request.path.startswith('/api'): return - if not (request.user.is_authenticated and request.user.is_org_admin): + if not request.user.is_authenticated: + return + if request.user.is_common_user: return org = get_org_from_request(request) if org.can_admin_by(request.user): return - admin_orgs = Organization.get_user_admin_orgs(request.user) + if org.can_audit_by(request.user): + return + admin_orgs = request.user.admin_orgs if admin_orgs: request.session['oid'] = str(admin_orgs[0].id) + return + audit_orgs = request.user.audit_orgs + if audit_orgs: + request.session['oid'] = str(audit_orgs[0].id) + return def __call__(self, request): self.set_permed_org_if_need(request) diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 03b46e9bc25f9ab5c7d27a3c739bda08720fa0ed..59e28b1d1d045df8edeba47798c8e474d2aac867 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -9,8 +9,9 @@ from common.utils import is_uuid class Organization(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) - users = models.ManyToManyField('users.User', related_name='orgs', blank=True) - admins = models.ManyToManyField('users.User', related_name='admin_orgs', blank=True) + users = models.ManyToManyField('users.User', related_name='related_user_orgs', blank=True) + admins = models.ManyToManyField('users.User', related_name='related_admin_orgs', blank=True) + auditors = models.ManyToManyField('users.User', related_name='related_audit_orgs', blank=True) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) @@ -70,25 +71,46 @@ class Organization(models.Model): org = cls.default() if default else None return org - def get_org_users(self, include_app=False): + def get_org_users(self): from users.models import User if self.is_real(): - users = self.users.all() - else: - users = User.objects.all() - if not include_app: - users = users.exclude(role=User.ROLE_APP) - return users + return self.users.all() + return User.objects.filter(role=User.ROLE_USER) def get_org_admins(self): + from users.models import User if self.is_real(): return self.admins.all() - return [] + return User.objects.filter(role=User.ROLE_ADMIN) + + def get_org_auditors(self): + from users.models import User + if self.is_real(): + return self.auditors.all() + return User.objects.filter(role=User.ROLE_AUDITOR) + + def get_org_members(self, exclude=()): + from users.models import User + members = User.objects.none() + if 'Admin' not in exclude: + members |= self.get_org_admins() + if 'User' not in exclude: + members |= self.get_org_users() + if 'Auditor' not in exclude: + members |= self.get_org_auditors() + return members.exclude(role=User.ROLE_APP).distinct() def can_admin_by(self, user): if user.is_superuser: return True - if user in list(self.get_org_admins()): + if self.get_org_admins().filter(id=user.id): + return True + return False + + def can_audit_by(self, user): + if user.is_super_auditor: + return True + if self.get_org_auditors().filter(id=user.id): return True return False @@ -100,13 +122,40 @@ class Organization(models.Model): admin_orgs = [] if user.is_anonymous: return admin_orgs - elif user.is_superuser or user.is_auditor: + elif user.is_superuser: admin_orgs = list(cls.objects.all()) admin_orgs.append(cls.default()) elif user.is_org_admin: - admin_orgs = user.admin_orgs.all() + admin_orgs = user.related_admin_orgs.all() return admin_orgs + @classmethod + def get_user_user_orgs(self, user): + user_orgs = [] + if user.is_anonymous: + return user_orgs + user_orgs = user.related_user_orgs.all() + return user_orgs + + @classmethod + def get_user_audit_orgs(cls, user): + audit_orgs = [] + if user.is_anonymous: + return audit_orgs + elif user.is_super_auditor: + audit_orgs = list(cls.objects.all()) + audit_orgs.append(cls.default()) + elif user.is_org_auditor: + audit_orgs = user.related_audit_orgs.all() + return audit_orgs + + @classmethod + def get_user_admin_or_audit_orgs(self, user): + admin_orgs = self.get_user_admin_orgs(user) + audit_orgs = self.get_user_audit_orgs(user) + orgs = set(admin_orgs) | set(audit_orgs) + return orgs + @classmethod def default(cls): return cls(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 7f9b7c10679368480ec062b9f696ad96ac821520..222598c883cf3253d2f625012ed5043643a82424 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -21,6 +21,7 @@ class OrgSerializer(ModelSerializer): class OrgReadSerializer(ModelSerializer): admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) + auditors = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) user_groups = serializers.SerializerMethodField() assets = serializers.SerializerMethodField() diff --git a/apps/orgs/views.py b/apps/orgs/views.py index 757129a7f508edc5b023a85019849d5e756ac76b..c4b215fe26b391c3c0f60d14d1572e6cebcb3200 100644 --- a/apps/orgs/views.py +++ b/apps/orgs/views.py @@ -5,6 +5,7 @@ from django.views.generic import DetailView, View from .models import Organization from common.utils import UUID_PATTERN +from orgs.utils import current_org class SwitchOrgView(DetailView): @@ -22,17 +23,28 @@ class SwitchOrgView(DetailView): return redirect(reverse('index')) if UUID_PATTERN.search(referer): return redirect(reverse('index')) + # 组织管ç†å‘˜åˆ‡æ¢åˆ°ç»„织审计员时(403) + if not self.object.get_org_admins().filter(id=request.user.id): + return redirect(reverse('index')) return redirect(referer) class SwitchToAOrgView(View): def get(self, request, *args, **kwargs): - admin_orgs = Organization.get_user_admin_orgs(request.user) - if not admin_orgs: + if request.user.is_common_user: return HttpResponseForbidden() + admin_orgs = request.user.admin_orgs + audit_orgs = request.user.audit_orgs default_org = Organization.default() - if default_org in admin_orgs: - redirect_org = default_org - else: - redirect_org = admin_orgs[0] - return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) + if admin_orgs: + if default_org in admin_orgs: + redirect_org = default_org + else: + redirect_org = admin_orgs[0] + return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) + if audit_orgs: + if default_org in audit_orgs: + redirect_org = default_org + else: + redirect_org = audit_orgs[0] + return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) diff --git a/apps/perms/forms/asset_permission.py b/apps/perms/forms/asset_permission.py index 6fdaee30bf2dcdd4eb6a9146562569a8086bb53b..b11269b85114853eb9aecb290b8f6840110a7bd5 100644 --- a/apps/perms/forms/asset_permission.py +++ b/apps/perms/forms/asset_permission.py @@ -39,7 +39,7 @@ class AssetPermissionForm(OrgModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) users_field = self.fields.get('users') - users_field.queryset = current_org.get_org_users() + users_field.queryset = current_org.get_org_members(exclude=('Auditor',)) if self.data: return diff --git a/apps/perms/forms/remote_app_permission.py b/apps/perms/forms/remote_app_permission.py index d08066ba452cc708567c51b86996f6699899a317..5d38b283c9092139f1926589cf32480cb2e69880 100644 --- a/apps/perms/forms/remote_app_permission.py +++ b/apps/perms/forms/remote_app_permission.py @@ -19,7 +19,7 @@ class RemoteAppPermissionCreateUpdateForm(OrgModelForm): super().__init__(*args, **kwargs) users_field = self.fields.get('users') if hasattr(users_field, 'queryset'): - users_field.queryset = current_org.get_org_users() + users_field.queryset = current_org.get_org_members(exclude=('Auditor',)) class Meta: model = RemoteAppPermission diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py index 94c7c8c621732418128399e4e4302cf8d044bfa6..6130ad2e3c75c75b7e645a87a3221a26e1c72c86 100644 --- a/apps/perms/views/asset_permission.py +++ b/apps/perms/views/asset_permission.py @@ -131,16 +131,15 @@ class AssetPermissionUserView(PermissionsMixin, return queryset def get_context_data(self, **kwargs): - + user_remain = current_org.get_org_members(exclude=('Auditor',)).exclude( + assetpermission=self.object) + user_groups_remain = UserGroup.objects.exclude( + assetpermission=self.object) context = { 'app': _('Perms'), 'action': _('Asset permission user list'), - 'users_remain': current_org.get_org_users().exclude( - assetpermission=self.object - ), - 'user_groups_remain': UserGroup.objects.exclude( - assetpermission=self.object - ) + 'users_remain': user_remain, + 'user_groups_remain': user_groups_remain, } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py index 1e8b30c6da5440c666ac8b9966fcdbfbd338f487..742a5fd850223ed0fd994de31c0dfcd822258115 100644 --- a/apps/perms/views/remote_app_permission.py +++ b/apps/perms/views/remote_app_permission.py @@ -107,15 +107,15 @@ class RemoteAppPermissionUserView(PermissionsMixin, return queryset def get_context_data(self, **kwargs): + user_remain = current_org.get_org_members(exclude=('Auditor',)).exclude( + remoteapppermission=self.object) + user_groups_remain = UserGroup.objects.exclude( + remoteapppermission=self.object) context = { 'app': _('Perms'), 'action': _('RemoteApp permission user list'), - 'users_remain': current_org.get_org_users().exclude( - remoteapppermission=self.object - ), - 'user_groups_remain': UserGroup.objects.exclude( - remoteapppermission=self.object - ) + 'users_remain': user_remain, + 'user_groups_remain': user_groups_remain, } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/templates/_header_bar.html b/apps/templates/_header_bar.html index d6a0da1c34265c1dde3c2c835802b7d8ebef0d52..63ee1bbfc24e596f8a917511da3992a8d2b1e43f 100644 --- a/apps/templates/_header_bar.html +++ b/apps/templates/_header_bar.html @@ -68,7 +68,7 @@ </a> <ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown"> <li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li> - {% if request.user.is_org_admin %} + {% if request.user.can_admin_or_audit_current_org %} {% if request.COOKIES.IN_ADMIN_PAGE == 'No' %} <li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li> {% else %} @@ -107,3 +107,23 @@ <div class="col-sm-2"> </div> </div> +<script> +$(document).ready(function () { +}) +.on('click', '#switch_admin', function () { + var cookieName = "IN_ADMIN_PAGE"; + setTimeout(function () { + delCookie(cookieName); + setCookie(cookieName, "Yes"); + window.location = "/" + }, 100) +}) +.on('click', '#switch_user', function () { + var cookieName = "IN_ADMIN_PAGE"; + setTimeout(function () { + delCookie(cookieName); + setCookie(cookieName, "No"); + window.location = "{% url 'assets:user-asset-list' %}" + }, 100); +}) +</script> diff --git a/apps/templates/_left_side_bar.html b/apps/templates/_left_side_bar.html index 3c38d6d12df9baefca3e8c3f75e22dd07828f842..572375ed81d15ba1767e19fe4591e835e623548b 100644 --- a/apps/templates/_left_side_bar.html +++ b/apps/templates/_left_side_bar.html @@ -2,12 +2,10 @@ <div class="sidebar-collapse"> <ul class="nav" id="side-menu"> {% include '_user_profile.html' %} - {% if request.user.is_org_admin and request.COOKIES.IN_ADMIN_PAGE != "No" %} - {% include '_nav.html' %} - {% elif request.user.is_auditor %} - {% include '_nav_audits.html' %} - {% else %} + {% if request.user.is_common_user or request.COOKIES.IN_ADMIN_PAGE == 'No' %} {% include '_nav_user.html' %} + {% else %} + {% include '_nav.html' %} {% endif %} </ul> </div> diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 1acb752c9ac08edafcb283696e616173349dc530..67f6140d8e90d7e704d89e51e021d2b57827e37c 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -1,127 +1,172 @@ {% load i18n %} -<li id="index"> - <a href="{% url 'index' %}"> - <i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span> - <span class="label label-info pull-right"></span> - </a> -</li> -<li id="users"> - <a href="#"> - <i class="fa fa-group" style="width: 14px"></i> <span class="nav-label">{% trans 'Users' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level active"> - <li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li> - <li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'User group' %}</a></li> - </ul> -</li> -<li id="assets"> - <a> - <i class="fa fa-inbox" style="width: 14px"></i> <span class="nav-label">{% trans 'Assets' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="asset"><a href="{% url 'assets:asset-list' %}">{% trans 'Asset list' %}</a></li> - <li id="domain"><a href="{% url 'assets:domain-list' %}">{% trans 'Domain list' %}</a></li> - <li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li> - <li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li> - <li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li> - <li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li> - </ul> -</li> -{% if LICENSE_VALID %} -<li id="applications"> - <a> - <i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'Applications' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="remote-app"><a href="{% url 'applications:remote-app-list' %}">{% trans 'RemoteApp' %}</a></li> - </ul> -</li> + +{# Index #} +{% if request.user.can_admin_or_audit_current_org %} + <li id="index"> + <a href="{% url 'index' %}"> + <i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span> + <span class="label label-info pull-right"></span> + </a> + </li> +{% endif %} + +{# Users #} +{% if request.user.can_admin_current_org %} + <li id="users"> + <a href="#"> + <i class="fa fa-group" style="width: 14px"></i> <span class="nav-label">{% trans 'Users' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level active"> + <li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li> + <li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'User group' %}</a></li> + </ul> + </li> +{% endif %} + +{# User info #} +{% if not request.user.can_admin_current_org and request.user.can_audit_current_org %} + <li id="users"> + <a href="{% url 'users:user-profile' %}"> + <i class="fa fa-user" style="width: 14px"></i> <span class="nav-label">{% trans 'Profile' %}</span><span class="label label-info pull-right"></span> + </a> + </li> +{% endif %} + +{# Assets #} +{% if request.user.can_admin_current_org %} + <li id="assets"> + <a> + <i class="fa fa-inbox" style="width: 14px"></i> <span class="nav-label">{% trans 'Assets' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level"> + <li id="asset"><a href="{% url 'assets:asset-list' %}">{% trans 'Asset list' %}</a></li> + <li id="domain"><a href="{% url 'assets:domain-list' %}">{% trans 'Domain list' %}</a></li> + <li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li> + <li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li> + <li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li> + <li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li> + </ul> + </li> {% endif %} -<li id="perms"> - <a href="#"><i class="fa fa-edit" style="width: 14px"></i> <span class="nav-label">{% trans 'Perms' %}</span><span class="fa arrow"></span></a> - <ul class="nav nav-second-level"> - <li id="asset-permission"> - <a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a> - </li> - {% if LICENSE_VALID %} - <li id="remote-app-permission"> - <a href="{% url 'perms:remote-app-permission-list' %}">{% trans 'RemoteApp' %}</a> - </li> - {% endif %} - </ul> -</li> -<li id="terminal"> - <a> - <i class="fa fa-rocket" style="width: 14px"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li> - <li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li> - <li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li> - <li> - <a href="{% url 'terminal:web-terminal' %}" target="_blank"> - <span class="nav-label">{% trans 'Web terminal' %}</span> - </a> - </li> - <li> - <a href="{% url 'terminal:web-sftp' %}" target="_blank"> - <span class="nav-label">{% trans 'File manager' %}</span> - </a> - </li> - {% if request.user.is_superuser %} - <li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li> - {% endif %} - </ul> -</li> -<li id="ops"> - <a> - <i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li> - <li id="command-execution"><a href="{% url 'ops:command-execution-start' %}">{% trans 'Batch command' %}</a></li> - </ul> -</li> -<li id="audits"> - <a> - <i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="login-log"><a href="{% url 'audits:login-log-list' %}">{% trans 'Login log' %}</a></li> - <li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li> - <li id="operate-log"><a href="{% url 'audits:operate-log-list' %}">{% trans 'Operate log' %}</a></li> - <li id="password-change-log"><a href="{% url 'audits:password-change-log-list' %}">{% trans 'Password change log' %}</a></li> - <li id="command-execution-log"><a href="{% url 'audits:command-execution-log-list' %}">{% trans 'Batch command' %}</a></li> - </ul> -</li> -{% if XPACK_PLUGINS %} -<li id="xpack"> - <a> - <i class="fa fa-sitemap" style="width: 14px"></i> <span class="nav-label">{% trans 'XPack' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - {% for plugin in XPACK_PLUGINS %} - {% ifequal plugin.name 'cloud'%} - <li id="{{ plugin.name }}"> - <a href="#"><span class="nav-label">{% trans plugin.verbose_name %}</span><span class="fa arrow"></span></a> - <ul class="nav nav-third-level"> - <li id="account"><a href="{% url 'xpack:cloud:account-list' %}">{% trans 'Account list' %}</a></li> - <li id="sync-instance-task"><a href="{% url 'xpack:cloud:sync-instance-task-list' %}">{% trans 'Sync instance' %}</a></li> - </ul> - </li> - {% else %} - <li id="{{ plugin.name }}"><a href="{{ plugin.endpoint }}">{% trans plugin.verbose_name %}</a></li> - {% endifequal %} - {% endfor %} - </ul> -</li> + + +{# Applications #} +{% if request.user.can_admin_current_org and LICENSE_VALID %} + <li id="applications"> + <a> + <i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'Applications' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level"> + <li id="remote-app"><a href="{% url 'applications:remote-app-list' %}">{% trans 'RemoteApp' %}</a></li> + </ul> + </li> {% endif %} + + +{# Perms #} +{% if request.user.can_admin_current_org %} + <li id="perms"> + <a href="#"><i class="fa fa-edit" style="width: 14px"></i> <span class="nav-label">{% trans 'Perms' %}</span><span class="fa arrow"></span></a> + <ul class="nav nav-second-level"> + <li id="asset-permission"> + <a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a> + </li> + {% if LICENSE_VALID %} + <li id="remote-app-permission"> + <a href="{% url 'perms:remote-app-permission-list' %}">{% trans 'RemoteApp' %}</a> + </li> + {% endif %} + </ul> + </li> +{% endif %} + + +{# Terminal #} +{% if request.user.can_admin_or_audit_current_org %} + <li id="terminal"> + <a> + <i class="fa fa-rocket" style="width: 14px"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level"> + <li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li> + <li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li> + <li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li> + + {% if request.user.can_admin_current_org %} + <li><a href="{% url 'terminal:web-terminal' %}" target="_blank"><span class="nav-label">{% trans 'Web terminal' %}</span></a></li> + <li><a href="{% url 'terminal:web-sftp' %}" target="_blank"><span class="nav-label">{% trans 'File manager' %}</span></a></li> + {% endif %} + + {% if request.user.is_superuser %} + <li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li> + {% endif %} + </ul> + </li> +{% endif %} + + +{# Ops #} +{% if request.user.can_admin_current_org %} + <li id="ops"> + <a> + <i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level"> + <li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li> + <li id="command-execution"><a href="{% url 'ops:command-execution-start' %}">{% trans 'Batch command' %}</a></li> + </ul> + </li> +{% endif %} + + +{# Audits #} +{% if request.user.can_admin_or_audit_current_org %} + <li id="audits"> + <a> + <i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level"> + <li id="login-log"><a href="{% url 'audits:login-log-list' %}">{% trans 'Login log' %}</a></li> + <li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li> + <li id="operate-log"><a href="{% url 'audits:operate-log-list' %}">{% trans 'Operate log' %}</a></li> + <li id="password-change-log"><a href="{% url 'audits:password-change-log-list' %}">{% trans 'Password change log' %}</a></li> + <li id="command-execution-log"><a href="{% url 'audits:command-execution-log-list' %}">{% trans 'Batch command' %}</a></li> + </ul> + </li> +{% endif %} + + +{# X-Pack #} +{% if request.user.can_admin_current_org and XPACK_PLUGINS %} + <li id="xpack"> + <a> + <i class="fa fa-sitemap" style="width: 14px"></i> <span class="nav-label">{% trans 'XPack' %}</span><span class="fa arrow"></span> + </a> + <ul class="nav nav-second-level"> + {% for plugin in XPACK_PLUGINS %} + {% ifequal plugin.name 'cloud'%} + <li id="{{ plugin.name }}"> + <a href="#"><span class="nav-label">{% trans plugin.verbose_name %}</span><span class="fa arrow"></span></a> + <ul class="nav nav-third-level"> + <li id="account"><a href="{% url 'xpack:cloud:account-list' %}">{% trans 'Account list' %}</a></li> + <li id="sync-instance-task"><a href="{% url 'xpack:cloud:sync-instance-task-list' %}">{% trans 'Sync instance' %}</a></li> + </ul> + </li> + {% else %} + <li id="{{ plugin.name }}"><a href="{{ plugin.endpoint }}">{% trans plugin.verbose_name %}</a></li> + {% endifequal %} + {% endfor %} + </ul> + </li> +{% endif %} + +{# Settings #} {% if request.user.is_superuser %} -<li id="settings"> - <a href="{% url 'settings:basic-setting' %}"> - <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span> - </a> -</li> + <li id="settings"> + <a href="{% url 'settings:basic-setting' %}"> + <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span> + </a> + </li> {% endif %} <script> diff --git a/apps/templates/_nav_audits.html b/apps/templates/_nav_audits.html deleted file mode 100644 index 853bb24b85b695ee4ed2063ab004d37827e7ebb7..0000000000000000000000000000000000000000 --- a/apps/templates/_nav_audits.html +++ /dev/null @@ -1,31 +0,0 @@ -{% load i18n %} -<li id="index"> - <a href="{% url 'index' %}"> - <i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span> - <span class="label label-info pull-right"></span> - </a> -</li> - -<li id="terminal"> - <a> - <i class="fa fa-rocket" style="width: 14px"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li> - <li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li> - <li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li> - </ul> -</li> - -<li id="audits"> - <a> - <i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span> - </a> - <ul class="nav nav-second-level"> - <li id="login-log"><a href="{% url 'audits:login-log-list' %}">{% trans 'Login log' %}</a></li> - <li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li> - <li id="operate-log"><a href="{% url 'audits:operate-log-list' %}">{% trans 'Operate log' %}</a></li> - <li id="password-change-log"><a href="{% url 'audits:password-change-log-list' %}">{% trans 'Password change log' %}</a></li> - <li id="command-execution-log"><a href="{% url 'audits:command-execution-log-list' %}">{% trans 'Batch command' %}</a></li> - </ul> -</li> \ No newline at end of file diff --git a/apps/templates/_user_profile.html b/apps/templates/_user_profile.html index e9f30da32c1f76924a1b515e6370829f498eef6f..248aa1d5ab96ce17635c97ec85e9bcf4dc3d0e4c 100644 --- a/apps/templates/_user_profile.html +++ b/apps/templates/_user_profile.html @@ -9,49 +9,29 @@ <div class="logo-element"> <img alt="image" height="40" src="{{ LOGO_URL }}"/> </div> - {% if ADMIN_ORGS and request.COOKIES.IN_ADMIN_PAGE != 'No' %} - {% if ADMIN_ORGS|length > 1 or not CURRENT_ORG.is_default %} - <div> - <a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" style="display: block; background-color: transparent; color: #8095a8; padding: 14px 20px 14px 25px"> - <i class="fa fa-bookmark" style="width: 14px; "></i> - <span class="nav-label" style="padding-left: 7px"> - {{ CURRENT_ORG.name }} - </span> - <span class="fa fa-sort-desc pull-right"></span> - </a> - <ul class="dropdown-menu" style="min-width: 220px"> - {% for org in ADMIN_ORGS %} - <li> - <a class="org-dropdown" href="{% url 'orgs:org-switch' pk=org.id %}" data-id="{{ org.id }}"> - {{ org.name }} - {% if org.id == CURRENT_ORG.id %} - <span class="fa fa-circle" style="padding-top: 5px; color: #1ab394"></span> - {% endif %} - </a> - </li> - {% endfor %} - </ul> - </div> + {% if ADMIN_OR_AUDIT_ORGS and request.COOKIES.IN_ADMIN_PAGE != 'No' %} + {% if ADMIN_OR_AUDIT_ORGS|length > 1 or not CURRENT_ORG.is_default %} + <div> + <a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" style="display: block; background-color: transparent; color: #8095a8; padding: 14px 20px 14px 25px"> + <i class="fa fa-bookmark" style="width: 14px; "></i> + <span class="nav-label" style="padding-left: 7px"> + {{ CURRENT_ORG.name }} + </span> + <span class="fa fa-sort-desc pull-right"></span> + </a> + <ul class="dropdown-menu" style="min-width: 220px"> + {% for org in ADMIN_OR_AUDIT_ORGS %} + <li> + <a class="org-dropdown" href="{% url 'orgs:org-switch' pk=org.id %}" data-id="{{ org.id }}"> + {{ org.name }} + {% if org.id == CURRENT_ORG.id %} + <span class="fa fa-circle" style="padding-top: 5px; color: #1ab394"></span> + {% endif %} + </a> + </li> + {% endfor %} + </ul> + </div> {% endif %} {% endif %} </li> -<script> -$(document).ready(function () { -}) -.on('click', '#switch_admin', function () { - var cookieName = "IN_ADMIN_PAGE"; - setTimeout(function () { - delCookie(cookieName); - setCookie(cookieName, "Yes"); - window.location = "/" - }, 100) -}) -.on('click', '#switch_user', function () { - var cookieName = "IN_ADMIN_PAGE"; - setTimeout(function () { - delCookie(cookieName); - setCookie(cookieName, "No"); - window.location = "{% url 'assets:user-asset-list' %}" - }, 100); -}) -</script> diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py index 588f059b62e97abb8143aaf9b87be29050c65cc2..c18de010fd5e279516c72085c39b6474468042f5 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/command.py @@ -9,7 +9,7 @@ from rest_framework.response import Response from django.template import loader -from common.permissions import IsOrgAdminOrAppUser, IsAuditor +from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor from common.utils import get_logger from ..backends import ( get_command_storage, get_multi_command_storage, @@ -22,7 +22,7 @@ __all__ = ['CommandViewSet', 'CommandExportApi'] class CommandQueryMixin: command_store = get_command_storage() - permission_classes = [IsOrgAdminOrAppUser | IsAuditor] + permission_classes = [IsOrgAdminOrAppUser | IsOrgAuditor] filter_fields = [ "asset", "system_user", "user", "session", ] diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 7d26c13edfb2b197ac516a2293c9feaa51b8736c..e7da10125f716e9ff757b66187036ec42c3f3cdf 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -12,7 +12,7 @@ from rest_framework.generics import GenericAPIView import jms_storage from common.utils import is_uuid, get_logger -from common.permissions import IsOrgAdminOrAppUser, IsAuditor +from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor from common.filters import DatetimeRangeFilter from orgs.mixins.api import OrgBulkModelViewSet from ..hands import SystemUser @@ -27,7 +27,7 @@ logger = get_logger(__name__) class SessionViewSet(OrgBulkModelViewSet): queryset = Session.objects.all() serializer_class = serializers.SessionSerializer - permission_classes = (IsOrgAdminOrAppUser | IsAuditor, ) + permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor, ) filter_fields = [ "user", "asset", "system_user", "remote_addr", "protocol", "terminal", "is_finished", @@ -62,7 +62,7 @@ class SessionViewSet(OrgBulkModelViewSet): class SessionReplayViewSet(viewsets.ViewSet): serializer_class = serializers.ReplaySerializer - permission_classes = (IsOrgAdminOrAppUser | IsAuditor,) + permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) session = None def create(self, request, *args, **kwargs): diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index b1b4b98a560281fc297a10bb334ce6debe4950d9..3ac31eed5b8508be30f982261771e04744366992 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -5,14 +5,14 @@ from django.views.generic import TemplateView from django.utils.translation import ugettext as _ from django.utils import timezone -from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor +from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor __all__ = ['CommandListView'] class CommandListView(PermissionsMixin, TemplateView): template_name = "terminal/command_list.html" - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] default_days_ago = 5 def get_context_data(self, **kwargs): diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index af401996a9c2b1a357e31806f5e8e7528f4d324e..38558f2928217e8f76f2ed16b3fa48a187b1ef37 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _ from django.utils import timezone from django.conf import settings -from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor +from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor from common.mixins import DatetimeSearchMixin from ..models import Session, Command, Terminal from ..backends import get_multi_command_storage @@ -24,7 +24,7 @@ class SessionListView(PermissionsMixin, TemplateView): model = Session template_name = 'terminal/session_list.html' date_from = date_to = None - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] default_days_ago = 5 def get_context_data(self, **kwargs): @@ -63,7 +63,7 @@ class SessionDetailView(SingleObjectMixin, PermissionsMixin, ListView): template_name = 'terminal/session_detail.html' model = Session object = None - permission_classes = [IsOrgAdmin | IsAuditor] + permission_classes = [IsOrgAdmin | IsOrgAuditor] def get(self, request, *args, **kwargs): self.object = self.get_object(queryset=self.model.objects.all()) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index dce57145e11663668cd248e3fe9ef0f640a9209d..ac661e45754e36f720cc0b0ab9077b4abedcf5e8 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -8,12 +8,11 @@ from django.utils.translation import ugettext as _ from rest_framework import generics from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -from rest_framework.serializers import ValidationError from rest_framework_bulk import BulkModelViewSet from common.permissions import ( IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser, - CanUpdateDeleteSuperUser, + CanUpdateDeleteUser, ) from common.mixins import IDInCacheFilterMixin from common.utils import get_logger @@ -36,7 +35,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): search_fields = filter_fields queryset = User.objects.exclude(role=User.ROLE_APP) serializer_class = serializers.UserSerializer - permission_classes = (IsOrgAdmin, CanUpdateDeleteSuperUser) + permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) def send_created_signal(self, users): if not isinstance(users, list): @@ -53,7 +52,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): self.send_created_signal(users) def get_queryset(self): - queryset = current_org.get_org_users().prefetch_related('groups') + queryset = current_org.get_org_members().prefetch_related('groups') return queryset def get_permissions(self): @@ -61,32 +60,17 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): self.permission_classes = (IsOrgAdminOrAppUser,) return super().get_permissions() - def _deny_permission(self, instance): - """ - check current user has permission to handle instance - (update, destroy, bulk_update, bulk destroy) - """ - if instance.is_superuser and not self.request.user.is_superuser: - return True - return False - - def _bulk_deny_permission(self, instances): - deny_instances = [i for i in instances if self._deny_permission(i)] - if len(deny_instances) > 0: - return True - else: - return False - def allow_bulk_destroy(self, qs, filtered): return False def perform_bulk_update(self, serializer): - users_ids = [d.get("id") or d.get("pk") for d in serializer.validated_data] - users = User.objects.filter(id__in=users_ids) - deny_instances = [str(i.id) for i in users if self._deny_permission(i)] - if deny_instances: - msg = "{} can't be update".format(deny_instances) - raise ValidationError({"id": msg}) + # TODO: éœ€è¦æµ‹è¯• + users_ids = [ + d.get("id") or d.get("pk") for d in serializer.validated_data + ] + users = current_org.get_org_members().filter(id__in=users_ids) + for user in users: + self.check_object_permissions(self.request, user) return super().perform_bulk_update(serializer) diff --git a/apps/users/forms.py b/apps/users/forms.py index c22438d6d061b091fcf67b0186c429401559d917..0ca3c3568dfbf6329b4b5ea881ffc17e11cb4922 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -335,7 +335,7 @@ class UserGroupForm(OrgModelForm): return users_field = self.fields.get('users') if hasattr(users_field, 'queryset'): - users_field.queryset = current_org.get_org_users() + users_field.queryset = current_org.get_org_members(exclude=('Auditor',)) def save(self, commit=True): group = super().save(commit=commit) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 11e5529e2880b7b3ce29deb8ed85b5fbc710c47e..f58422fcda040ed63f9fe8cd625bd17728f7cd7b 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -5,7 +5,6 @@ import uuid import base64 import string import random -from collections import OrderedDict from django.conf import settings from django.contrib.auth.hashers import make_password @@ -16,6 +15,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse +from orgs.utils import current_org from common.utils import get_signer, date_expired_default, get_logger from common import fields @@ -132,7 +132,16 @@ class RoleMixin: @property def role_display(self): - return self.get_role_display() + if not current_org.is_real(): + return self.get_role_display() + roles = [] + if self in current_org.get_org_admins(): + roles.append(str(_('Org admin'))) + if self in current_org.get_org_auditors(): + roles.append(str(_('Org auditor'))) + if self in current_org.get_org_users(): + roles.append(str(_('User'))) + return " | ".join(roles) @property def is_superuser(self): @@ -148,35 +157,69 @@ class RoleMixin: else: self.role = 'User' + @property + def is_super_auditor(self): + return self.role == 'Auditor' + + @property + def is_common_user(self): + if self.is_org_admin: + return False + if self.is_org_auditor: + return False + if self.is_app: + return False + return True + + @property + def is_app(self): + return self.role == 'App' + + @property + def user_orgs(self): + from orgs.models import Organization + return Organization.get_user_user_orgs(self) + @property def admin_orgs(self): from orgs.models import Organization return Organization.get_user_admin_orgs(self) + @property + def audit_orgs(self): + from orgs.models import Organization + return Organization.get_user_audit_orgs(self) + + @property + def admin_or_audit_orgs(self): + from orgs.models import Organization + return Organization.get_user_admin_or_audit_orgs(self) + @property def is_org_admin(self): - if self.is_superuser or self.admin_orgs.exists(): + if self.is_superuser or self.related_admin_orgs.exists(): return True else: return False @property - def is_auditor(self): - return self.role == 'Auditor' + def is_org_auditor(self): + if self.is_super_auditor or self.related_audit_orgs.exists(): + return True + else: + return False @property - def is_common_user(self): - if self.is_org_admin: - return False - if self.is_auditor: - return False - if self.is_app: - return False - return True + def can_admin_current_org(self): + return current_org.can_admin_by(self) @property - def is_app(self): - return self.role == 'App' + def can_audit_current_org(self): + return current_org.can_audit_by(self) + + @property + def can_admin_or_audit_current_org(self): + return self.can_admin_current_org or self.can_audit_current_org @property def is_staff(self): diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index ac4ac03a70afa608fe7d6921cc02c7b2b9289e34..12e41b8f6f95c681efe2bf97122c359d5c1505b6 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import copy from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers @@ -8,6 +9,7 @@ from common.utils import validate_ssh_public_key from common.mixins import BulkSerializerMixin from common.fields import StringManyToManyField from common.serializers import AdaptedBulkListSerializer +from common.permissions import CanUpdateDeleteUser from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import User, UserGroup @@ -22,6 +24,9 @@ __all__ = [ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): + can_update = serializers.SerializerMethodField() + can_delete = serializers.SerializerMethodField() + class Meta: model = User list_serializer_class = AdaptedBulkListSerializer @@ -32,6 +37,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'comment', 'source', 'source_display', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', 'date_password_last_updated', 'date_expired', 'avatar_url', + 'can_update', 'can_delete', ] extra_kwargs = { 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True}, @@ -43,10 +49,22 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'is_valid': {'label': _('Is valid')}, 'is_expired': {'label': _('Is expired')}, 'avatar_url': {'label': _('Avatar url')}, - 'created_by': {'read_only': True, 'allow_blank': True}, 'source': {'read_only': True}, + 'created_by': {'read_only': True, 'allow_blank': True}, + 'can_update': {'read_only': True}, + 'can_delete': {'read_only': True}, } + def get_can_update(self, obj): + return CanUpdateDeleteUser.has_update_object_permission( + self.context['request'], self.context['view'], obj + ) + + def get_can_delete(self, obj): + return CanUpdateDeleteUser.has_delete_object_permission( + self.context['request'], self.context['view'], obj + ) + def validate_role(self, value): request = self.context.get('request') if not request.user.is_superuser and value != User.ROLE_USER: @@ -67,20 +85,24 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): raise serializers.ValidationError(msg) return password + def validate_groups(self, groups): + role = self.initial_data.get('role') + if self.instance: + role = role or self.instance.role + if role == User.ROLE_AUDITOR: + return [] + return groups + @staticmethod - def change_password_to_raw(validated_data): - password = validated_data.pop('password', None) + def change_password_to_raw(attrs): + password = attrs.pop('password', None) if password: - validated_data['password_raw'] = password - return validated_data + attrs['password_raw'] = password + return attrs - def create(self, validated_data): - validated_data = self.change_password_to_raw(validated_data) - return super().create(validated_data) - - def update(self, instance, validated_data): - validated_data = self.change_password_to_raw(validated_data) - return super().update(instance, validated_data) + def validate(self, attrs): + attrs = self.change_password_to_raw(attrs) + return attrs class UserPKUpdateSerializer(serializers.ModelSerializer): @@ -119,6 +141,13 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): 'created_by': {'label': _('Created by'), 'read_only': True} } + def validate_users(self, users): + for user in users: + if user.is_super_auditor: + msg = _('Auditors cannot be join in the group') + raise serializers.ValidationError(msg) + return users + class UserGroupListSerializer(UserGroupSerializer): users = StringManyToManyField(many=True, read_only=True) @@ -140,4 +169,4 @@ class ChangeUserPasswordSerializer(serializers.ModelSerializer): class ResetOTPSerializer(serializers.Serializer): - msg = serializers.CharField(read_only=True) \ No newline at end of file + msg = serializers.CharField(read_only=True) diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html index 0e8764f6a59fd5e3b31669ed6b13d3e52cb8261d..192dbfb703494dada81916983662eed796a2fdca 100644 --- a/apps/users/templates/users/_user.html +++ b/apps/users/templates/users/_user.html @@ -61,6 +61,17 @@ <link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} /> <script> + var role_id = '#' + '{{ form.role.id_for_label }}'; + var groups_id = '#' + '{{ form.groups.id_for_label }}'; + function fieldDisplay(){ + var role = $(role_id).val(); + if (role === 'Auditor'){ + $(groups_id).closest('.form-group').addClass('hidden'); + } + else { + $(groups_id).closest('.form-group').removeClass('hidden'); + }} + var dateOptions = { singleDatePicker: true, showDropdowns: true, @@ -76,7 +87,10 @@ $('#id_date_expired').daterangepicker(dateOptions); var mfa_radio = $('#id_otp_level'); mfa_radio.addClass("form-inline"); - mfa_radio.children().css("margin-right","15px") + mfa_radio.children().css("margin-right","15px"); + fieldDisplay() + }).on('change', role_id, function(){ + fieldDisplay(); }) </script> {% endblock %} diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index b39671a73467d83083798a1e7f405f2ba20d4dd2..8e049938c7b2a2b0d73e2cb539188bdba0238eb6 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -22,11 +22,11 @@ <a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a> </li> <li class="pull-right"> - <a class="btn btn-outline {% if user_object.is_superuser and not request.user.is_superuser %} disabled {% else %} btn-default {% endif %}" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a> + <a class="btn btn-outline {% if can_update %} btn-default {% else %} disabled {% endif %}" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a> </li> <li class="pull-right"> - <a class="btn btn-outline {% if request.user == user_object or user_object.username == "admin" or user_object.is_superuser and not request.user.is_superuser %} disabled {% else %} btn-danger btn-delete-user {% endif %}"> + <a class="btn btn-outline {% if can_delete %} btn-danger btn-delete-user {% else %} disabled {% endif %}"> <i class="fa fa-trash-o"></i>{% trans 'Delete' %} </a> </li> @@ -85,7 +85,7 @@ {% endif %} <tr> <td>{% trans 'Role' %}:</td> - <td><b>{{ user_object.get_role_display }}</b></td> + <td><b>{{ user_object.role_display }}</b></td> </tr> <tr> <td>{% trans 'MFA certification' %}:</td> @@ -212,44 +212,46 @@ </div> </div> - <div class="panel panel-info"> - <div class="panel-heading"> - <i class="fa fa-info-circle"></i> {% trans 'User group' %} - </div> - <div class="panel-body"> - <table class="table group_edit"> - <tbody> - <form> - <tr> - <td colspan="2" class="no-borders"> - <select data-placeholder="{% trans 'Join user groups' %}" id="groups_selected" class="select2" style="width: 100%" multiple="" tabindex="4"> - {% for group in groups %} - <option value="{{ group.id }}" id="opt_{{ group.id }}" >{{ group.name }}</option> - {% endfor %} - </select> - </td> - </tr> - <tr> - <td colspan="2" class="no-borders"> - <button type="button" class="btn btn-info btn-small" id="btn_join_group">{% trans 'Join' %}</button> - </td> - </tr> - </form> + {% if user_object.is_current_org_admin %} + <div class="panel panel-info"> + <div class="panel-heading"> + <i class="fa fa-info-circle"></i> {% trans 'User group' %} + </div> + <div class="panel-body"> + <table class="table group_edit"> + <tbody> + <form> + <tr> + <td colspan="2" class="no-borders"> + <select data-placeholder="{% trans 'Join user groups' %}" id="groups_selected" class="select2" style="width: 100%" multiple="" tabindex="4"> + {% for group in groups %} + <option value="{{ group.id }}" id="opt_{{ group.id }}" >{{ group.name }}</option> + {% endfor %} + </select> + </td> + </tr> + <tr> + <td colspan="2" class="no-borders"> + <button type="button" class="btn btn-info btn-small" id="btn_join_group">{% trans 'Join' %}</button> + </td> + </tr> + </form> - {% for group in user_object.groups.all %} - <tr> - <td > - <b class="bdg_group" data-gid={{ group.id }}>{{ group.name }}</b> - </td> - <td> - <button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button> - </td> - </tr> - {% endfor %} - </tbody> - </table> + {% for group in user_object.groups.all %} + <tr> + <td > + <b class="bdg_group" data-gid={{ group.id }}>{{ group.name }}</b> + </td> + <td> + <button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> </div> - </div> + {% endif %} </div> </div> </div> diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index a2bfde461cb16708b963b44ae6a5487eabbe3f3b..046a77822e80a1dfed0a73d2bdea14d52f812c7d 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -99,7 +99,7 @@ function initTable() { {targets: 7, createdCell: function (td, cellData, rowData) { var name = htmlEscape(rowData.name); var update_btn = ""; - if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) { + if (rowData.can_update === false){ update_btn = '<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>'; } else{ @@ -107,7 +107,7 @@ function initTable() { } var del_btn = ""; - if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) { + if (rowData.can_delete === false){ del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>' .replace('{{ DEFAULT_PK }}', cellData) .replace('99991938', name); diff --git a/apps/users/views/group.py b/apps/users/views/group.py index 2f19a805552d8c3b20ed89ea9b9c59860c350e3a..ed9976eab14be3179e4fec318941df1a8d37e5d5 100644 --- a/apps/users/views/group.py +++ b/apps/users/views/group.py @@ -76,7 +76,8 @@ class UserGroupDetailView(PermissionsMixin, DetailView): permission_classes = [IsOrgAdmin] def get_context_data(self, **kwargs): - users = current_org.get_org_users().exclude(id__in=self.object.users.all()) + users = current_org.get_org_members(exclude=('Auditor',)).exclude( + groups=self.object) context = { 'app': _('Users'), 'action': _('User group detail'), diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 1a8c25f2e3e03c0ca20e037ae2356cdd35088c57..36922f47e565576fa97f7e59983d19a34c859622 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -27,6 +27,7 @@ from common.utils import get_logger, ssh_key_gen from common.permissions import ( PermissionsMixin, IsOrgAdmin, IsValidUser, UserCanUpdatePassword, UserCanUpdateSSHKey, + CanUpdateDeleteUser, ) from orgs.utils import current_org from .. import forms @@ -86,7 +87,7 @@ class UserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): user.created_by = self.request.user.username or 'System' user.save() if current_org and current_org.is_real(): - user.orgs.add(current_org.id) + user.related_user_orgs.add(current_org.id) post_user_create.send(self.__class__, user=user) return super().form_valid(form) @@ -189,13 +190,19 @@ class UserDetailView(PermissionsMixin, DetailView): 'action': _('User detail'), 'groups': groups, 'unblock': is_need_unblock(key_block), + 'can_update': CanUpdateDeleteUser.has_update_object_permission( + self.request, self, user + ), + 'can_delete': CanUpdateDeleteUser.has_delete_object_permission( + self.request, self, user + ), } kwargs.update(context) return super().get_context_data(**kwargs) def get_queryset(self): queryset = super().get_queryset() - org_users = current_org.get_org_users().values_list('id', flat=True) + org_users = current_org.get_org_members().values_list('id', flat=True) queryset = queryset.filter(id__in=org_users) return queryset