mirror of https://github.com/jumpserver/jumpserver
commit
087ba9ae95
|
@ -15,7 +15,7 @@ __all__ = [
|
|||
|
||||
class RemoteAppViewSet(OrgBulkModelViewSet):
|
||||
model = RemoteApp
|
||||
filter_fields = ('name',)
|
||||
filter_fields = ('name', 'type', 'comment')
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = RemoteAppSerializer
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
|
@ -49,7 +36,7 @@ class AdminUserViewSet(OrgBulkModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(_assets_amount=Count('assets'))
|
||||
queryset = queryset.annotate(assets_amount=Count('assets'))
|
||||
return queryset
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
|
|
|
@ -33,7 +33,7 @@ class AssetViewSet(OrgBulkModelViewSet):
|
|||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
model = Asset
|
||||
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
|
||||
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id", "platform__base")
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
serializer_classes = {
|
||||
|
|
|
@ -84,12 +84,15 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
|
|||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
if pk is None:
|
||||
return
|
||||
queryset = self.get_queryset()
|
||||
obj = queryset.get(id=pk)
|
||||
return obj
|
||||
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
logger.error(e, exc_info=True)
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from django.db.models import Prefetch, F
|
||||
from django.db.models import Prefetch, F, Count
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -73,21 +73,35 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
class Meta:
|
||||
model = Asset
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'ip', 'hostname', 'protocol', 'port',
|
||||
'protocols', 'platform', 'is_active', 'public_ip', 'domain',
|
||||
'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
|
||||
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
|
||||
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
|
||||
'hostname_raw', 'comment', 'created_by', 'date_created',
|
||||
'hardware_info',
|
||||
fields_mini = ['id', 'hostname', 'ip']
|
||||
fields_small = fields_mini + [
|
||||
'protocol', 'port', 'protocols', 'is_active', 'public_ip',
|
||||
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw', 'comment',
|
||||
'created_by', 'date_created', 'hardware_info',
|
||||
]
|
||||
read_only_fields = (
|
||||
fields_fk = [
|
||||
'admin_user', 'domain', 'platform'
|
||||
]
|
||||
fk_only_fields = {
|
||||
'platform': ['name']
|
||||
}
|
||||
fields_m2m = [
|
||||
'nodes', 'labels',
|
||||
]
|
||||
annotates_fields = {
|
||||
# 'admin_user_display': 'admin_user__name'
|
||||
}
|
||||
fields_as = list(annotates_fields.keys())
|
||||
fields = fields_small + fields_fk + fields_m2m + fields_as
|
||||
read_only_fields = [
|
||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||
'created_by', 'date_created',
|
||||
)
|
||||
] + fields_as
|
||||
|
||||
extra_kwargs = {
|
||||
'protocol': {'write_only': True},
|
||||
'port': {'write_only': True},
|
||||
|
@ -98,11 +112,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related(
|
||||
Prefetch('nodes', queryset=Node.objects.all().only('id')),
|
||||
Prefetch('labels', queryset=Label.objects.all().only('id')),
|
||||
).select_related('admin_user', 'domain', 'platform') \
|
||||
.annotate(platform_base=F('platform__base'))
|
||||
queryset = queryset.select_related('admin_user', 'domain', 'platform')
|
||||
return queryset
|
||||
|
||||
def compatible_with_old_protocol(self, validated_data):
|
||||
|
|
|
@ -16,7 +16,7 @@ class GatheredUserSerializer(OrgResourceModelSerializerMixin):
|
|||
'present', 'date_created', 'date_updated'
|
||||
]
|
||||
read_only_fields = fields
|
||||
labels = {
|
||||
'hostname': _("Hostname"),
|
||||
'ip': "IP"
|
||||
extra_kwargs = {
|
||||
'hostname': {'label': _("Hostname")},
|
||||
'ip': {'label': 'IP'},
|
||||
}
|
||||
|
|
|
@ -1,14 +1,97 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from rest_framework.mixins import ListModelMixin
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
|
||||
from orgs.mixins.api import OrgModelViewSet
|
||||
from .models import FTPLog
|
||||
from .serializers import FTPLogSerializer
|
||||
from common.mixins.api import CommonApiMixin
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin
|
||||
from common.drf.filters import DatetimeRangeFilter, current_user_filter
|
||||
from common.api import CommonGenericViewSet
|
||||
from orgs.mixins.api import OrgGenericViewSet
|
||||
from orgs.utils import current_org
|
||||
from ops.models import CommandExecution
|
||||
from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog
|
||||
from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer
|
||||
from .serializers import OperateLogSerializer, PasswordChangeLogSerializer
|
||||
from .filters import CurrentOrgMembersFilter
|
||||
|
||||
|
||||
class FTPLogViewSet(OrgModelViewSet):
|
||||
class FTPLogViewSet(ListModelMixin, OrgGenericViewSet):
|
||||
model = FTPLog
|
||||
serializer_class = FTPLogSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
|
||||
http_method_names = ['get', 'post', 'head', 'options']
|
||||
extra_filter_backends = [DatetimeRangeFilter]
|
||||
date_range_filter_fields = [
|
||||
('date_start', ('date_from', 'date_to'))
|
||||
]
|
||||
filterset_fields = ['user', 'asset', 'system_user']
|
||||
search_fields = ['filename']
|
||||
|
||||
|
||||
class UserLoginLogViewSet(ListModelMixin,
|
||||
CommonGenericViewSet):
|
||||
queryset = UserLoginLog.objects.all()
|
||||
permission_classes = [IsOrgAdmin | IsOrgAuditor]
|
||||
serializer_class = UserLoginLogSerializer
|
||||
extra_filter_backends = [DatetimeRangeFilter]
|
||||
date_range_filter_fields = [
|
||||
('datetime', ('date_from', 'date_to'))
|
||||
]
|
||||
filterset_fields = ['username']
|
||||
search_fields = ['ip', 'city', 'username']
|
||||
|
||||
@staticmethod
|
||||
def get_org_members():
|
||||
users = current_org.get_org_members().values_list('username', flat=True)
|
||||
return users
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not current_org.is_default():
|
||||
users = self.get_org_members()
|
||||
queryset = queryset.filter(username__in=users)
|
||||
return queryset
|
||||
|
||||
|
||||
class OperateLogViewSet(ListModelMixin, OrgGenericViewSet):
|
||||
model = OperateLog
|
||||
serializer_class = OperateLogSerializer
|
||||
permission_classes = [IsOrgAdmin | IsOrgAuditor]
|
||||
extra_filter_backends = [DatetimeRangeFilter]
|
||||
date_range_filter_fields = [
|
||||
('datetime', ('date_from', 'date_to'))
|
||||
]
|
||||
filterset_fields = ['user', 'action', 'resource_type']
|
||||
search_fields = ['filename']
|
||||
ordering_fields = ['-datetime']
|
||||
|
||||
|
||||
class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
|
||||
queryset = PasswordChangeLog.objects.all()
|
||||
permission_classes = [IsOrgAdmin | IsOrgAuditor]
|
||||
serializer_class = PasswordChangeLogSerializer
|
||||
extra_filter_backends = [DatetimeRangeFilter]
|
||||
date_range_filter_fields = [
|
||||
('datetime', ('date_from', 'date_to'))
|
||||
]
|
||||
filterset_fields = ['user']
|
||||
ordering_fields = ['-datetime']
|
||||
|
||||
def get_queryset(self):
|
||||
users = current_org.get_org_members()
|
||||
queryset = super().get_queryset().filter(
|
||||
user__in=[user.__str__() for user in users]
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
|
||||
model = CommandExecution
|
||||
serializer_class = CommandExecutionSerializer
|
||||
permission_classes = [IsOrgAdmin | IsOrgAuditor]
|
||||
extra_filter_backends = [DatetimeRangeFilter, current_user_filter(), CurrentOrgMembersFilter]
|
||||
date_range_filter_fields = [
|
||||
('date_start', ('date_from', 'date_to'))
|
||||
]
|
||||
search_fields = ['command']
|
||||
ordering_fields = ['-date_created']
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
from rest_framework import filters
|
||||
from rest_framework.compat import coreapi, coreschema
|
||||
|
||||
from orgs.utils import current_org
|
||||
|
||||
|
||||
__all__ = ['CurrentOrgMembersFilter']
|
||||
|
||||
|
||||
class CurrentOrgMembersFilter(filters.BaseFilterBackend):
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
coreapi.Field(
|
||||
name='user', location='query', required=False, type='string',
|
||||
schema=coreschema.String(
|
||||
title='user',
|
||||
description='user'
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
def _get_user_list(self):
|
||||
users = current_org.get_org_members(exclude=('Auditor',))
|
||||
return users
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
user_id = request.GET.get('user')
|
||||
if user_id:
|
||||
queryset = queryset.filter(user=user_id)
|
||||
else:
|
||||
queryset = queryset.filter(user__in=self._get_user_list())
|
||||
return queryset
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 2.2.10 on 2020-05-08 13:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('audits', '0007_auto_20191202_1010'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ftplog',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date start'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='operatelog',
|
||||
name='datetime',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Datetime'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='passwordchangelog',
|
||||
name='datetime',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Datetime'),
|
||||
),
|
||||
]
|
|
@ -22,7 +22,7 @@ class FTPLog(OrgModelMixin):
|
|||
operate = models.CharField(max_length=16, verbose_name=_("Operate"))
|
||||
filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
|
||||
is_success = models.BooleanField(default=True, verbose_name=_("Success"))
|
||||
date_start = models.DateTimeField(auto_now_add=True)
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
|
||||
|
||||
|
||||
class OperateLog(OrgModelMixin):
|
||||
|
@ -40,7 +40,7 @@ class OperateLog(OrgModelMixin):
|
|||
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
|
||||
resource = models.CharField(max_length=128, verbose_name=_("Resource"))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
datetime = models.DateTimeField(auto_now=True)
|
||||
datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime'))
|
||||
|
||||
def __str__(self):
|
||||
return "<{}> {} <{}>".format(self.user, self.action, self.resource)
|
||||
|
@ -51,7 +51,7 @@ class PasswordChangeLog(models.Model):
|
|||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
change_by = models.CharField(max_length=128, verbose_name=_("Change by"))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
datetime = models.DateTimeField(auto_now=True)
|
||||
datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime'))
|
||||
|
||||
def __str__(self):
|
||||
return "{} change {}'s password".format(self.change_by, self.user)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from terminal.models import Session
|
||||
from ops.models import CommandExecution
|
||||
from . import models
|
||||
|
||||
|
||||
|
@ -11,25 +12,40 @@ class FTPLogSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = models.FTPLog
|
||||
fields = '__all__'
|
||||
fields = (
|
||||
'user', 'remote_addr', 'asset', 'system_user',
|
||||
'operate', 'filename', 'is_success', 'date_start'
|
||||
)
|
||||
|
||||
|
||||
class LoginLogSerializer(serializers.ModelSerializer):
|
||||
class UserLoginLogSerializer(serializers.ModelSerializer):
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display')
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display')
|
||||
mfa_display = serializers.ReadOnlyField(source='get_mfa_display')
|
||||
|
||||
class Meta:
|
||||
model = models.UserLoginLog
|
||||
fields = '__all__'
|
||||
fields = (
|
||||
'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
|
||||
'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display'
|
||||
)
|
||||
|
||||
|
||||
class OperateLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.OperateLog
|
||||
fields = '__all__'
|
||||
fields = (
|
||||
'user', 'action', 'resource_type', 'resource',
|
||||
'remote_addr', 'datetime'
|
||||
)
|
||||
|
||||
|
||||
class PasswordChangeLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.PasswordChangeLog
|
||||
fields = '__all__'
|
||||
fields = (
|
||||
'user', 'change_by', 'remote_addr', 'datetime'
|
||||
)
|
||||
|
||||
|
||||
class SessionAuditSerializer(serializers.ModelSerializer):
|
||||
|
@ -37,3 +53,18 @@ class SessionAuditSerializer(serializers.ModelSerializer):
|
|||
model = Session
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CommandExecutionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CommandExecution
|
||||
fields = (
|
||||
'hosts', 'run_as', 'command', 'user', 'is_finished',
|
||||
'date_start', 'result', 'is_success'
|
||||
)
|
||||
extra_kwargs = {
|
||||
'result': {'label': _('Result')}, # model 上的方法,只能在这修改
|
||||
'is_success': {'label': _('Is success')},
|
||||
'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
|
||||
'run_as': {'label': _('Run as')},
|
||||
'user': {'label': _('User')},
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ app_name = "audits"
|
|||
|
||||
router = DefaultRouter()
|
||||
router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log')
|
||||
router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log')
|
||||
router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log')
|
||||
router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log')
|
||||
router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log')
|
||||
|
||||
urlpatterns = [
|
||||
]
|
||||
|
|
|
@ -106,6 +106,9 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
raise exceptions.AuthenticationFailed(_('User disabled.'))
|
||||
return access_key.user, None
|
||||
|
||||
def authenticate_header(self, request):
|
||||
return 'Sign access_key_id:Signature'
|
||||
|
||||
|
||||
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||
keyword = 'Bearer'
|
||||
|
@ -143,6 +146,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
|||
raise exceptions.AuthenticationFailed(msg)
|
||||
return user, None
|
||||
|
||||
def authenticate_header(self, request):
|
||||
return self.keyword
|
||||
|
||||
|
||||
class PrivateTokenAuthentication(authentication.TokenAuthentication):
|
||||
model = PrivateToken
|
||||
|
|
|
@ -18,4 +18,5 @@ urlpatterns = [
|
|||
# openid
|
||||
path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')),
|
||||
path('openid/', include(('jms_oidc_rp.urls', 'authentication'), namespace='openid')),
|
||||
path('captcha/', include('captcha.urls')),
|
||||
]
|
||||
|
|
|
@ -9,10 +9,12 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import generics, serializers
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from .http import HttpResponseTemporaryRedirect
|
||||
from .const import KEY_CACHE_RESOURCES_ID
|
||||
from .utils import get_logger
|
||||
from .mixins import CommonApiMixin
|
||||
|
||||
__all__ = [
|
||||
'LogTailApi', 'ResourcesIDCacheApi',
|
||||
|
@ -100,3 +102,7 @@ def redirect_plural_name_api(request, *args, **kwargs):
|
|||
full_path = org_full_path.replace(resource, resource+"s", 1)
|
||||
logger.debug("Redirect {} => {}".format(org_full_path, full_path))
|
||||
return HttpResponseTemporaryRedirect(full_path)
|
||||
|
||||
|
||||
class CommonGenericViewSet(CommonApiMixin, GenericViewSet):
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
GET = 'GET'
|
||||
POST = 'POST'
|
||||
PUT = 'PUT'
|
||||
PATCH = 'PATCH'
|
||||
DELETE = 'DELETE'
|
||||
OPTIONS = 'OPTIONS'
|
|
@ -1,27 +1,61 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import coreapi
|
||||
from rest_framework import filters
|
||||
from rest_framework.fields import DateTimeField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.compat import coreapi, coreschema
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
import logging
|
||||
|
||||
from common import const
|
||||
|
||||
__all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"]
|
||||
__all__ = ["DatetimeRangeFilter", "IDSpmFilter", 'IDInFilter', "CustomFilter"]
|
||||
|
||||
|
||||
class DatetimeRangeFilter(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
def get_schema_fields(self, view):
|
||||
ret = []
|
||||
fields = self._get_date_range_filter_fields(view)
|
||||
|
||||
for attr, date_range_keyword in fields.items():
|
||||
if len(date_range_keyword) != 2:
|
||||
continue
|
||||
for v in date_range_keyword:
|
||||
ret.append(
|
||||
coreapi.Field(
|
||||
name=v, location='query', required=False, type='string',
|
||||
schema=coreschema.String(
|
||||
title=v,
|
||||
description='%s %s' % (attr, v)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
def _get_date_range_filter_fields(self, view):
|
||||
if not hasattr(view, 'date_range_filter_fields'):
|
||||
return queryset
|
||||
return {}
|
||||
try:
|
||||
fields = dict(view.date_range_filter_fields)
|
||||
return dict(view.date_range_filter_fields)
|
||||
except ValueError:
|
||||
msg = "View {} datetime_filter_fields set is error".format(view.name)
|
||||
msg = """
|
||||
View {} `date_range_filter_fields` set is improperly.
|
||||
For example:
|
||||
```
|
||||
class ExampleView:
|
||||
date_range_filter_fields = [
|
||||
('db column', ('query param date from', 'query param date to'))
|
||||
]
|
||||
```
|
||||
""".format(view.name)
|
||||
logging.error(msg)
|
||||
return queryset
|
||||
raise ImproperlyConfigured(msg)
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
fields = self._get_date_range_filter_fields(view)
|
||||
|
||||
kwargs = {}
|
||||
for attr, date_range_keyword in fields.items():
|
||||
if len(date_range_keyword) != 2:
|
||||
|
@ -68,6 +102,25 @@ class IDSpmFilter(filters.BaseFilterBackend):
|
|||
return queryset
|
||||
|
||||
|
||||
class IDInFilter(filters.BaseFilterBackend):
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
coreapi.Field(
|
||||
name='ids', location='query', required=False,
|
||||
type='string', example='/api/v1/users/users?ids=1,2,3',
|
||||
description='Filter by id set'
|
||||
)
|
||||
]
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ids = request.query_params.get('ids')
|
||||
if not ids:
|
||||
return queryset
|
||||
id_list = [i.strip() for i in ids.split(',')]
|
||||
queryset = queryset.filter(id__in=id_list)
|
||||
return queryset
|
||||
|
||||
|
||||
class CustomFilter(filters.BaseFilterBackend):
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
|
@ -92,3 +145,10 @@ class CustomFilter(filters.BaseFilterBackend):
|
|||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
return queryset
|
||||
|
||||
|
||||
def current_user_filter(user_field='user'):
|
||||
class CurrentUserFilter(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
return queryset.filter(**{user_field: request.user})
|
||||
return CurrentUserFilter
|
||||
|
|
|
@ -21,7 +21,9 @@ class JMSCSVRender(BaseRenderer):
|
|||
|
||||
@staticmethod
|
||||
def _get_show_fields(fields, template):
|
||||
if template in ('import', 'update'):
|
||||
if template == 'import':
|
||||
return [v for k, v in fields.items() if not v.read_only and k != "org_id" and k != 'id']
|
||||
elif template == 'update':
|
||||
return [v for k, v in fields.items() if not v.read_only and k != "org_id"]
|
||||
else:
|
||||
return [v for k, v in fields.items() if not v.write_only and k != "org_id"]
|
||||
|
|
|
@ -3,18 +3,20 @@
|
|||
import time
|
||||
from hashlib import md5
|
||||
from threading import Thread
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.core.cache import cache
|
||||
from django.http import JsonResponse
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
from common.drf.filters import IDSpmFilter, CustomFilter
|
||||
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
||||
from ..utils import lazyproperty
|
||||
|
||||
__all__ = [
|
||||
"JSONResponseMixin", "CommonApiMixin",
|
||||
"IDSpmFilterMixin", 'AsyncApiMixin',
|
||||
'AsyncApiMixin', 'RelationMixin'
|
||||
]
|
||||
|
||||
|
||||
|
@ -25,19 +27,11 @@ class JSONResponseMixin(object):
|
|||
return JsonResponse(context)
|
||||
|
||||
|
||||
class IDSpmFilterMixin:
|
||||
def get_filter_backends(self):
|
||||
backends = super().get_filter_backends()
|
||||
backends.append(IDSpmFilter)
|
||||
return backends
|
||||
|
||||
|
||||
class SerializerMixin:
|
||||
def get_serializer_class(self):
|
||||
serializer_class = None
|
||||
if hasattr(self, 'serializer_classes') and \
|
||||
isinstance(self.serializer_classes, dict):
|
||||
if self.action == 'list' and self.request.query_params.get('draw'):
|
||||
if hasattr(self, 'serializer_classes') and isinstance(self.serializer_classes, dict):
|
||||
if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'):
|
||||
serializer_class = self.serializer_classes.get('display')
|
||||
if serializer_class is None:
|
||||
serializer_class = self.serializer_classes.get(
|
||||
|
@ -49,7 +43,10 @@ class SerializerMixin:
|
|||
|
||||
|
||||
class ExtraFilterFieldsMixin:
|
||||
default_added_filters = [CustomFilter, IDSpmFilter]
|
||||
"""
|
||||
额外的 api filter
|
||||
"""
|
||||
default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter]
|
||||
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
|
||||
extra_filter_fields = []
|
||||
extra_filter_backends = []
|
||||
|
@ -57,9 +54,10 @@ class ExtraFilterFieldsMixin:
|
|||
def get_filter_backends(self):
|
||||
if self.filter_backends != self.__class__.filter_backends:
|
||||
return self.filter_backends
|
||||
return list(self.filter_backends) + \
|
||||
self.default_added_filters + \
|
||||
list(self.extra_filter_backends)
|
||||
backends = list(self.filter_backends) + \
|
||||
list(self.default_added_filters) + \
|
||||
list(self.extra_filter_backends)
|
||||
return backends
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
for backend in self.get_filter_backends():
|
||||
|
@ -72,6 +70,9 @@ class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin):
|
|||
|
||||
|
||||
class InterceptMixin:
|
||||
"""
|
||||
Hack默认的dispatch, 让用户可以实现 self.do
|
||||
"""
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
@ -188,3 +189,47 @@ class AsyncApiMixin(InterceptMixin):
|
|||
data["error"] = str(e)
|
||||
data["status"] = "error"
|
||||
cache.set(key, data, 600)
|
||||
|
||||
|
||||
class RelationMixin:
|
||||
m2m_field = None
|
||||
from_field = None
|
||||
to_field = None
|
||||
to_model = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
assert self.m2m_field is not None, '''
|
||||
`m2m_field` should not be `None`
|
||||
'''
|
||||
|
||||
self.from_field = self.m2m_field.m2m_field_name()
|
||||
self.to_field = self.m2m_field.m2m_reverse_field_name()
|
||||
self.to_model = self.m2m_field.related_model
|
||||
self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.through.objects.all()
|
||||
return queryset
|
||||
|
||||
def send_post_add_signal(self, instances):
|
||||
if not isinstance(instances, list):
|
||||
instances = [instances]
|
||||
|
||||
from_to_mapper = defaultdict(list)
|
||||
|
||||
for i in instances:
|
||||
to_id = getattr(i, self.to_field).id
|
||||
from_obj = getattr(i, self.from_field)
|
||||
from_to_mapper[from_obj].append(to_id)
|
||||
|
||||
for from_obj, to_ids in from_to_mapper.items():
|
||||
m2m_changed.send(
|
||||
sender=self.through, instance=from_obj, action='post_add',
|
||||
reverse=False, model=self.to_model, pk_set=to_ids
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
instance = serializer.save()
|
||||
self.send_post_add_signal(instance)
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import Iterable
|
||||
|
||||
from django.db.models import Prefetch, F
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework.utils import html
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import SkipField, empty
|
||||
|
||||
__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin']
|
||||
__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin', 'CommonBulkSerializerMixin']
|
||||
|
||||
|
||||
class BulkSerializerMixin(object):
|
||||
|
@ -113,3 +115,126 @@ class BulkListSerializerMixin(object):
|
|||
raise ValidationError(errors)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class BaseDynamicFieldsPlugin:
|
||||
def __init__(self, serializer):
|
||||
self.serializer = serializer
|
||||
|
||||
def can_dynamic(self):
|
||||
try:
|
||||
request = self.serializer.context['request']
|
||||
method = request.method
|
||||
except (AttributeError, TypeError, KeyError):
|
||||
# The serializer was not initialized with request context.
|
||||
return False
|
||||
|
||||
if method != 'GET':
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_request(self):
|
||||
return self.serializer.context['request']
|
||||
|
||||
def get_query_params(self):
|
||||
request = self.get_request()
|
||||
try:
|
||||
query_params = request.query_params
|
||||
except AttributeError:
|
||||
# DRF 2
|
||||
query_params = getattr(request, 'QUERY_PARAMS', request.GET)
|
||||
return query_params
|
||||
|
||||
def get_exclude_field_names(self):
|
||||
return set()
|
||||
|
||||
|
||||
class QueryFieldsMixin(BaseDynamicFieldsPlugin):
|
||||
# https://github.com/wimglenn/djangorestframework-queryfields/
|
||||
|
||||
# If using Django filters in the API, these labels mustn't conflict with any model field names.
|
||||
include_arg_name = 'fields'
|
||||
exclude_arg_name = 'fields!'
|
||||
|
||||
# Split field names by this string. It doesn't necessarily have to be a single character.
|
||||
# Avoid RFC 1738 reserved characters i.e. ';', '/', '?', ':', '@', '=' and '&'
|
||||
delimiter = ','
|
||||
|
||||
def get_exclude_field_names(self):
|
||||
query_params = self.get_query_params()
|
||||
includes = query_params.getlist(self.include_arg_name)
|
||||
include_field_names = {name for names in includes for name in names.split(self.delimiter) if name}
|
||||
|
||||
excludes = query_params.getlist(self.exclude_arg_name)
|
||||
exclude_field_names = {name for names in excludes for name in names.split(self.delimiter) if name}
|
||||
|
||||
if not include_field_names and not exclude_field_names:
|
||||
# No user fields filtering was requested, we have nothing to do here.
|
||||
return []
|
||||
|
||||
serializer_field_names = set(self.serializer.fields)
|
||||
fields_to_drop = serializer_field_names & exclude_field_names
|
||||
|
||||
if include_field_names:
|
||||
fields_to_drop |= serializer_field_names - include_field_names
|
||||
return fields_to_drop
|
||||
|
||||
|
||||
class SizedModelFieldsMixin(BaseDynamicFieldsPlugin):
|
||||
arg_name = 'fields_size'
|
||||
|
||||
def can_dynamic(self):
|
||||
if not hasattr(self.serializer, 'Meta'):
|
||||
return False
|
||||
can = super().can_dynamic()
|
||||
return can
|
||||
|
||||
def get_exclude_field_names(self):
|
||||
query_params = self.get_query_params()
|
||||
size = query_params.get(self.arg_name)
|
||||
if not size:
|
||||
return []
|
||||
if size not in ['mini', 'small']:
|
||||
return []
|
||||
size_fields = getattr(self.serializer.Meta, 'fields_{}'.format(size), None)
|
||||
if not size_fields or not isinstance(size_fields, Iterable):
|
||||
return []
|
||||
serializer_field_names = set(self.serializer.fields)
|
||||
fields_to_drop = serializer_field_names - set(size_fields)
|
||||
return fields_to_drop
|
||||
|
||||
|
||||
class DynamicFieldsMixin:
|
||||
dynamic_fields_plugins = [QueryFieldsMixin, SizedModelFieldsMixin]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
exclude_field_names = set()
|
||||
for cls in self.dynamic_fields_plugins:
|
||||
plugin = cls(self)
|
||||
if not plugin.can_dynamic():
|
||||
continue
|
||||
exclude_field_names |= set(plugin.get_exclude_field_names())
|
||||
|
||||
for field in exclude_field_names or []:
|
||||
self.fields.pop(field, None)
|
||||
|
||||
|
||||
class EagerLoadQuerySetFields:
|
||||
def setup_eager_loading(self, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related(
|
||||
Prefetch('nodes'),
|
||||
Prefetch('labels'),
|
||||
).select_related('admin_user', 'domain', 'platform') \
|
||||
.annotate(platform_base=F('platform__base'))
|
||||
return queryset
|
||||
|
||||
|
||||
class CommonSerializerMixin(DynamicFieldsMixin):
|
||||
pass
|
||||
|
||||
|
||||
class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin):
|
||||
pass
|
||||
|
|
|
@ -36,5 +36,3 @@ class DatetimeSearchMixin:
|
|||
def get(self, request, *args, **kwargs):
|
||||
self.get_date_range()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -16,27 +16,36 @@ from common.utils import lazyproperty
|
|||
__all__ = ['IndexApi']
|
||||
|
||||
|
||||
class MonthLoginMetricMixin:
|
||||
class DatesLoginMetricMixin:
|
||||
@lazyproperty
|
||||
def days(self):
|
||||
query_params = self.request.query_params
|
||||
if query_params.get('monthly'):
|
||||
return 30
|
||||
return 7
|
||||
|
||||
@lazyproperty
|
||||
def session_month(self):
|
||||
month_ago = timezone.now() - timezone.timedelta(days=30)
|
||||
session_month = Session.objects.filter(date_start__gt=month_ago)
|
||||
return session_month
|
||||
def sessions_queryset(self):
|
||||
days = timezone.now() - timezone.timedelta(days=self.days)
|
||||
sessions_queryset = Session.objects.filter(date_start__gt=days)
|
||||
return sessions_queryset
|
||||
|
||||
@lazyproperty
|
||||
def session_month_dates(self):
|
||||
dates = self.session_month.dates('date_start', 'day')
|
||||
def session_dates_list(self):
|
||||
now = timezone.now()
|
||||
dates = [(now - timezone.timedelta(days=i)).date() for i in range(self.days)]
|
||||
dates.reverse()
|
||||
# dates = self.sessions_queryset.dates('date_start', 'day')
|
||||
return dates
|
||||
|
||||
def get_month_metrics_date(self):
|
||||
month_metrics_date = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||
return month_metrics_date
|
||||
def get_dates_metrics_date(self):
|
||||
dates_metrics_date = [d.strftime('%m-%d') for d in self.session_dates_list] or ['0']
|
||||
return dates_metrics_date
|
||||
|
||||
@staticmethod
|
||||
def get_cache_key(date, tp):
|
||||
date_str = date.strftime("%Y%m%d")
|
||||
key = "SESSION_MONTH_{}_{}_{}".format(current_org.id, tp, date_str)
|
||||
key = "SESSION_DATE_{}_{}_{}".format(current_org.id, tp, date_str)
|
||||
return key
|
||||
|
||||
def __get_data_from_cache(self, date, tp):
|
||||
|
@ -69,9 +78,9 @@ class MonthLoginMetricMixin:
|
|||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_metrics_total_count_login(self):
|
||||
def get_dates_metrics_total_count_login(self):
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
for d in self.session_dates_list:
|
||||
count = self.get_date_login_count(d)
|
||||
data.append(count)
|
||||
if len(data) == 0:
|
||||
|
@ -88,9 +97,9 @@ class MonthLoginMetricMixin:
|
|||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_metrics_total_count_active_users(self):
|
||||
def get_dates_metrics_total_count_active_users(self):
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
for d in self.session_dates_list:
|
||||
count = self.get_date_user_count(d)
|
||||
data.append(count)
|
||||
return data
|
||||
|
@ -105,90 +114,81 @@ class MonthLoginMetricMixin:
|
|||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_metrics_total_count_active_assets(self):
|
||||
def get_dates_metrics_total_count_active_assets(self):
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
for d in self.session_dates_list:
|
||||
count = self.get_date_asset_count(d)
|
||||
data.append(count)
|
||||
return data
|
||||
|
||||
@lazyproperty
|
||||
def month_total_count_active_users(self):
|
||||
count = len(set(self.session_month.values_list('user', flat=True)))
|
||||
def dates_total_count_active_users(self):
|
||||
count = len(set(self.sessions_queryset.values_list('user', flat=True)))
|
||||
return count
|
||||
|
||||
@lazyproperty
|
||||
def month_total_count_inactive_users(self):
|
||||
def dates_total_count_inactive_users(self):
|
||||
total = current_org.get_org_members().count()
|
||||
active = self.month_total_count_active_users
|
||||
active = self.dates_total_count_active_users
|
||||
count = total - active
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
@lazyproperty
|
||||
def month_total_count_disabled_users(self):
|
||||
def dates_total_count_disabled_users(self):
|
||||
return current_org.get_org_members().filter(is_active=False).count()
|
||||
|
||||
@lazyproperty
|
||||
def month_total_count_active_assets(self):
|
||||
return len(set(self.session_month.values_list('asset', flat=True)))
|
||||
def dates_total_count_active_assets(self):
|
||||
return len(set(self.sessions_queryset.values_list('asset', flat=True)))
|
||||
|
||||
@lazyproperty
|
||||
def month_total_count_inactive_assets(self):
|
||||
def dates_total_count_inactive_assets(self):
|
||||
total = Asset.objects.all().count()
|
||||
active = self.month_total_count_active_assets
|
||||
active = self.dates_total_count_active_assets
|
||||
count = total - active
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
@lazyproperty
|
||||
def month_total_count_disabled_assets(self):
|
||||
def dates_total_count_disabled_assets(self):
|
||||
return Asset.objects.filter(is_active=False).count()
|
||||
|
||||
|
||||
class WeekSessionMetricMixin:
|
||||
session_week = None
|
||||
|
||||
@lazyproperty
|
||||
def session_week(self):
|
||||
week_ago = timezone.now() - timezone.timedelta(weeks=1)
|
||||
session_week = Session.objects.filter(date_start__gt=week_ago)
|
||||
return session_week
|
||||
|
||||
def get_week_login_times_top5_users(self):
|
||||
users = self.session_week.values_list('user', flat=True)
|
||||
|
||||
# 以下是从week中而来
|
||||
def get_dates_login_times_top5_users(self):
|
||||
users = self.sessions_queryset.values_list('user', flat=True)
|
||||
users = [
|
||||
{'user': user, 'total': total}
|
||||
for user, total in Counter(users).most_common(5)
|
||||
]
|
||||
return users
|
||||
|
||||
def get_week_total_count_login_users(self):
|
||||
return len(set(self.session_week.values_list('user', flat=True)))
|
||||
def get_dates_total_count_login_users(self):
|
||||
return len(set(self.sessions_queryset.values_list('user', flat=True)))
|
||||
|
||||
def get_week_total_count_login_times(self):
|
||||
return self.session_week.count()
|
||||
def get_dates_total_count_login_times(self):
|
||||
return self.sessions_queryset.count()
|
||||
|
||||
def get_week_login_times_top10_assets(self):
|
||||
assets = self.session_week.values("asset")\
|
||||
.annotate(total=Count("asset"))\
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
def get_dates_login_times_top10_assets(self):
|
||||
assets = self.sessions_queryset.values("asset") \
|
||||
.annotate(total=Count("asset")) \
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
for asset in assets:
|
||||
asset['last'] = str(asset['last'])
|
||||
return list(assets)
|
||||
|
||||
def get_week_login_times_top10_users(self):
|
||||
users = self.session_week.values("user") \
|
||||
.annotate(total=Count("user")) \
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
def get_dates_login_times_top10_users(self):
|
||||
users = self.sessions_queryset.values("user") \
|
||||
.annotate(total=Count("user")) \
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
for user in users:
|
||||
user['last'] = str(user['last'])
|
||||
return list(users)
|
||||
|
||||
def get_week_login_record_top10_sessions(self):
|
||||
sessions = self.session_week.order_by('-date_start')[:10]
|
||||
def get_dates_login_record_top10_sessions(self):
|
||||
sessions = self.sessions_queryset.order_by('-date_start')[:10]
|
||||
for session in sessions:
|
||||
session.avatar_url = User.get_avatar_url("")
|
||||
sessions = [
|
||||
|
@ -223,7 +223,7 @@ class TotalCountMixin:
|
|||
return Session.objects.filter(is_finished=False).count()
|
||||
|
||||
|
||||
class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, APIView):
|
||||
class IndexApi(TotalCountMixin, DatesLoginMetricMixin, APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
http_method_names = ['get']
|
||||
|
||||
|
@ -234,60 +234,72 @@ class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, A
|
|||
|
||||
_all = query_params.get('all')
|
||||
|
||||
if _all or query_params.get('total_count'):
|
||||
if _all or query_params.get('total_count') or query_params.get('total_count_users'):
|
||||
data.update({
|
||||
'total_count_users': self.get_total_count_users(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('total_count') or query_params.get('total_count_assets'):
|
||||
data.update({
|
||||
'total_count_assets': self.get_total_count_assets(),
|
||||
'total_count_users': self.get_total_count_users(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('total_count') or query_params.get('total_count_online_users'):
|
||||
data.update({
|
||||
'total_count_online_users': self.get_total_count_online_users(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('total_count') or query_params.get('total_count_online_sessions'):
|
||||
data.update({
|
||||
'total_count_online_sessions': self.get_total_count_online_sessions(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('month_metrics'):
|
||||
if _all or query_params.get('dates_metrics'):
|
||||
data.update({
|
||||
'month_metrics_date': self.get_month_metrics_date(),
|
||||
'month_metrics_total_count_login': self.get_month_metrics_total_count_login(),
|
||||
'month_metrics_total_count_active_users': self.get_month_metrics_total_count_active_users(),
|
||||
'month_metrics_total_count_active_assets': self.get_month_metrics_total_count_active_assets(),
|
||||
'dates_metrics_date': self.get_dates_metrics_date(),
|
||||
'dates_metrics_total_count_login': self.get_dates_metrics_total_count_login(),
|
||||
'dates_metrics_total_count_active_users': self.get_dates_metrics_total_count_active_users(),
|
||||
'dates_metrics_total_count_active_assets': self.get_dates_metrics_total_count_active_assets(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('month_total_count_users'):
|
||||
if _all or query_params.get('dates_total_count_users'):
|
||||
data.update({
|
||||
'month_total_count_active_users': self.month_total_count_active_users,
|
||||
'month_total_count_inactive_users': self.month_total_count_inactive_users,
|
||||
'month_total_count_disabled_users': self.month_total_count_disabled_users,
|
||||
'dates_total_count_active_users': self.dates_total_count_active_users,
|
||||
'dates_total_count_inactive_users': self.dates_total_count_inactive_users,
|
||||
'dates_total_count_disabled_users': self.dates_total_count_disabled_users,
|
||||
})
|
||||
|
||||
if _all or query_params.get('month_total_count_assets'):
|
||||
if _all or query_params.get('dates_total_count_assets'):
|
||||
data.update({
|
||||
'month_total_count_active_assets': self.month_total_count_active_assets,
|
||||
'month_total_count_inactive_assets': self.month_total_count_inactive_assets,
|
||||
'month_total_count_disabled_assets': self.month_total_count_disabled_assets,
|
||||
'dates_total_count_active_assets': self.dates_total_count_active_assets,
|
||||
'dates_total_count_inactive_assets': self.dates_total_count_inactive_assets,
|
||||
'dates_total_count_disabled_assets': self.dates_total_count_disabled_assets,
|
||||
})
|
||||
|
||||
if _all or query_params.get('week_total_count'):
|
||||
if _all or query_params.get('dates_total_count'):
|
||||
data.update({
|
||||
'week_total_count_login_users': self.get_week_total_count_login_users(),
|
||||
'week_total_count_login_times': self.get_week_total_count_login_times(),
|
||||
'dates_total_count_login_users': self.get_dates_total_count_login_users(),
|
||||
'dates_total_count_login_times': self.get_dates_total_count_login_times(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('week_login_times_top5_users'):
|
||||
if _all or query_params.get('dates_login_times_top5_users'):
|
||||
data.update({
|
||||
'week_login_times_top5_users': self.get_week_login_times_top5_users(),
|
||||
'dates_login_times_top5_users': self.get_dates_login_times_top5_users(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('week_login_times_top10_assets'):
|
||||
if _all or query_params.get('dates_login_times_top10_assets'):
|
||||
data.update({
|
||||
'week_login_times_top10_assets': self.get_week_login_times_top10_assets(),
|
||||
'dates_login_times_top10_assets': self.get_dates_login_times_top10_assets(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('week_login_times_top10_users'):
|
||||
if _all or query_params.get('dates_login_times_top10_users'):
|
||||
data.update({
|
||||
'week_login_times_top10_users': self.get_week_login_times_top10_users(),
|
||||
'dates_login_times_top10_users': self.get_dates_login_times_top10_users(),
|
||||
})
|
||||
|
||||
if _all or query_params.get('week_login_record_top10_sessions'):
|
||||
if _all or query_params.get('dates_login_record_top10_sessions'):
|
||||
data.update({
|
||||
'week_login_record_top10_sessions': self.get_week_login_record_top10_sessions()
|
||||
'dates_login_record_top10_sessions': self.get_dates_login_record_top10_sessions()
|
||||
})
|
||||
|
||||
return JsonResponse(data, status=200)
|
||||
|
|
|
@ -123,7 +123,7 @@ class Config(dict):
|
|||
# Django Config, Must set before start
|
||||
'SECRET_KEY': '',
|
||||
'BOOTSTRAP_TOKEN': '',
|
||||
'DEBUG': True,
|
||||
'DEBUG': False,
|
||||
'LOG_LEVEL': 'DEBUG',
|
||||
'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'),
|
||||
'DB_ENGINE': 'mysql',
|
||||
|
@ -256,7 +256,9 @@ class Config(dict):
|
|||
'FORCE_SCRIPT_NAME': '',
|
||||
'LOGIN_CONFIRM_ENABLE': False,
|
||||
'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False,
|
||||
'ORG_CHANGE_TO_URL': ''
|
||||
'ORG_CHANGE_TO_URL': '',
|
||||
'LANGUAGE_CODE': 'zh',
|
||||
'TIME_ZONE': 'Asia/Shanghai'
|
||||
}
|
||||
|
||||
def compatible_auth_openid_of_key(self):
|
||||
|
@ -435,6 +437,15 @@ class DynamicConfig:
|
|||
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
|
||||
return backends
|
||||
|
||||
def XPACK_LICENSE_IS_VALID(self):
|
||||
if not HAS_XPACK:
|
||||
return False
|
||||
try:
|
||||
from xpack.plugins.license.models import License
|
||||
return License.has_valid_license()
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_from_db(self, item):
|
||||
if self.db_setting is not None:
|
||||
value = self.db_setting.get(item)
|
||||
|
|
|
@ -15,7 +15,7 @@ class TimezoneMiddleware:
|
|||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
tzname = request.META.get('TZ')
|
||||
tzname = request.META.get('HTTP_X_TZ')
|
||||
if tzname:
|
||||
timezone.activate(pytz.timezone(tzname))
|
||||
else:
|
||||
|
|
|
@ -175,9 +175,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||
# LANGUAGE_CODE = 'en'
|
||||
LANGUAGE_CODE = 'zh'
|
||||
LANGUAGE_CODE = CONFIG.LANGUAGE_CODE
|
||||
|
||||
TIME_ZONE = 'Asia/Shanghai'
|
||||
TIME_ZONE = CONFIG.TIME_ZONE
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
|
|
|
@ -85,3 +85,7 @@ LOGIN_LOG_KEEP_DAYS = DYNAMIC.LOGIN_LOG_KEEP_DAYS
|
|||
TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS
|
||||
ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL
|
||||
WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
|
||||
|
||||
# XPACK
|
||||
XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID
|
||||
|
||||
|
|
|
@ -43,6 +43,11 @@ app_view_patterns = [
|
|||
path('applications/', include('applications.urls.views_urls', namespace='applications')),
|
||||
path('tickets/', include('tickets.urls.views_urls', namespace='tickets')),
|
||||
re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
|
||||
re_path('luna/.*', views.LunaView.as_view(), name='luna-view'),
|
||||
re_path('koko/.*', views.KokoView.as_view(), name='koko-view'),
|
||||
re_path('ws/.*', views.WsView.as_view(), name='ws-view'),
|
||||
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
|
||||
path('settings/', include('settings.urls.view_urls', namespace='settings')),
|
||||
]
|
||||
|
||||
|
||||
|
@ -59,32 +64,38 @@ js_i18n_patterns = i18n_patterns(
|
|||
)
|
||||
|
||||
|
||||
apps = [
|
||||
'users', 'assets', 'perms', 'terminal', 'ops', 'audits', 'orgs', 'auth',
|
||||
'applications', 'tickets', 'settings', 'xpack'
|
||||
'flower', 'luna', 'koko', 'ws', 'i18n', 'jsi18n', 'docs', 'redocs',
|
||||
'zh-hans'
|
||||
]
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.IndexView.as_view(), name='index'),
|
||||
path('api/v1/', include(api_v1)),
|
||||
path('api/v2/', include(api_v2)),
|
||||
re_path('api/(?P<app>\w+)/(?P<version>v\d)/.*', views.redirect_format_api),
|
||||
path('api/health/', views.HealthCheckView.as_view(), name="health"),
|
||||
re_path('luna/.*', views.LunaView.as_view(), name='luna-view'),
|
||||
re_path('koko/.*', views.KokoView.as_view(), name='koko-view'),
|
||||
re_path('ws/.*', views.WsView.as_view(), name='ws-view'),
|
||||
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
|
||||
path('settings/', include('settings.urls.view_urls', namespace='settings')),
|
||||
|
||||
# External apps url
|
||||
path('captcha/', include('captcha.urls')),
|
||||
path('core/auth/captcha/', include('captcha.urls')),
|
||||
path('core/', include(app_view_patterns)),
|
||||
]
|
||||
|
||||
urlpatterns += app_view_patterns
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += js_i18n_patterns
|
||||
|
||||
# 兼容之前的
|
||||
old_app_pattern = '|'.join(apps)
|
||||
urlpatterns += [re_path(old_app_pattern, views.redirect_old_apps_view)]
|
||||
|
||||
handler404 = 'jumpserver.views.handler404'
|
||||
handler500 = 'jumpserver.views.handler500'
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
app_view_patterns += [
|
||||
re_path('^swagger(?P<format>\.json|\.yaml)$',
|
||||
views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
|
||||
path('docs/', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"),
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import re
|
||||
import time
|
||||
|
||||
from django.http import HttpResponseRedirect, JsonResponse
|
||||
from django.http import HttpResponseRedirect, JsonResponse, Http404
|
||||
from django.conf import settings
|
||||
from django.views.generic import View
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -16,7 +16,7 @@ from common.http import HttpResponseTemporaryRedirect
|
|||
|
||||
__all__ = [
|
||||
'LunaView', 'I18NView', 'KokoView', 'WsView', 'HealthCheckView',
|
||||
'redirect_format_api'
|
||||
'redirect_format_api', 'redirect_old_apps_view'
|
||||
]
|
||||
|
||||
|
||||
|
@ -51,6 +51,14 @@ def redirect_format_api(request, *args, **kwargs):
|
|||
return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
||||
|
||||
|
||||
def redirect_old_apps_view(request, *args, **kwargs):
|
||||
path = request.get_full_path()
|
||||
if path.find('/core') != -1:
|
||||
raise Http404()
|
||||
new_path = '/core{}'.format(path)
|
||||
return HttpResponseTemporaryRedirect(new_path)
|
||||
|
||||
|
||||
class HealthCheckView(APIView):
|
||||
permission_classes = ()
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.2.10 on 2020-05-09 06:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ops', '0017_auto_20200306_1747'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='commandexecution',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='commandexecution',
|
||||
name='date_finished',
|
||||
field=models.DateTimeField(null=True, verbose_name='Date finished'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='commandexecution',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(null=True, verbose_name='Date start'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='commandexecution',
|
||||
name='is_finished',
|
||||
field=models.BooleanField(default=False, verbose_name='Is finished'),
|
||||
),
|
||||
]
|
|
@ -110,7 +110,7 @@ class PeriodTaskSerializerMixin(serializers.Serializer):
|
|||
max_length=128, allow_blank=True,
|
||||
allow_null=True, required=False, label=_('Regularly perform')
|
||||
)
|
||||
interval = serializers.IntegerField(allow_null=True, required=False)
|
||||
interval = serializers.IntegerField(allow_null=True, required=False, label=_('Interval'))
|
||||
|
||||
INTERVAL_MAX = 65535
|
||||
INTERVAL_MIN = 1
|
||||
|
|
|
@ -23,10 +23,10 @@ class CommandExecution(OrgModelMixin):
|
|||
command = models.TextField(verbose_name=_("Command"))
|
||||
_result = models.TextField(blank=True, null=True, verbose_name=_('Result'))
|
||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True)
|
||||
is_finished = models.BooleanField(default=False)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_start = models.DateTimeField(null=True)
|
||||
date_finished = models.DateTimeField(null=True)
|
||||
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'))
|
||||
date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished'))
|
||||
|
||||
def __str__(self):
|
||||
return self.command[:10]
|
||||
|
|
|
@ -10,7 +10,7 @@ from common.permissions import IsSuperUserOrAppUser
|
|||
from .models import Organization
|
||||
from .serializers import OrgSerializer, OrgReadSerializer, \
|
||||
OrgMembershipUserSerializer, OrgMembershipAdminSerializer, \
|
||||
OrgAllUserSerializer
|
||||
OrgAllUserSerializer, OrgRetrieveSerializer
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||
from perms.models import AssetPermission
|
||||
|
@ -28,10 +28,11 @@ class OrgViewSet(BulkModelViewSet):
|
|||
org = None
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
return OrgReadSerializer
|
||||
else:
|
||||
return super().get_serializer_class()
|
||||
mapper = {
|
||||
'list': OrgReadSerializer,
|
||||
'retrieve': OrgRetrieveSerializer
|
||||
}
|
||||
return mapper.get(self.action, super().get_serializer_class())
|
||||
|
||||
def get_data_from_model(self, model):
|
||||
if model == User:
|
||||
|
|
|
@ -34,8 +34,7 @@ class OrgMiddleware:
|
|||
def __call__(self, request):
|
||||
self.set_permed_org_if_need(request)
|
||||
org = get_org_from_request(request)
|
||||
if org is not None:
|
||||
request.current_org = org
|
||||
set_current_org(org)
|
||||
request.current_org = org
|
||||
set_current_org(org)
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from common.mixins import CommonApiMixin
|
||||
from common.mixins import CommonApiMixin, RelationMixin
|
||||
from orgs.utils import current_org
|
||||
|
||||
from ..utils import set_to_root_org, filter_org_queryset
|
||||
from ..models import Organization
|
||||
|
@ -44,6 +45,10 @@ class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet):
|
|||
pass
|
||||
|
||||
|
||||
class OrgGenericViewSet(CommonApiMixin, OrgQuerySetMixin, GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet):
|
||||
def allow_bulk_destroy(self, qs, filtered):
|
||||
qs_count = qs.count()
|
||||
|
@ -76,3 +81,12 @@ class OrgMembershipModelViewSetMixin:
|
|||
def get_queryset(self):
|
||||
queryset = self.membership_class.objects.filter(organization=self.org)
|
||||
return queryset
|
||||
|
||||
|
||||
class OrgRelationMixin(RelationMixin):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
org_id = current_org.org_id()
|
||||
if org_id is not None:
|
||||
queryset = queryset.filter(**{f'{self.from_field}__org_id': org_id})
|
||||
return queryset
|
||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework import serializers
|
|||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.mixins import BulkSerializerMixin, CommonSerializerMixin
|
||||
from ..utils import get_current_org_id_for_serializer
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class OrgResourceSerializerMixin(serializers.Serializer):
|
||||
class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer):
|
||||
"""
|
||||
通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id
|
||||
(同时为serializer.is_valid()对Model的unique_together校验做准备)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
import re
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework import serializers
|
||||
from users.models import User, UserGroup
|
||||
from users.models import UserGroup
|
||||
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||
from perms.models import AssetPermission
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
@ -92,3 +91,12 @@ class OrgAllUserSerializer(serializers.Serializer):
|
|||
@staticmethod
|
||||
def get_user_display(obj):
|
||||
return str(obj)
|
||||
|
||||
|
||||
class OrgRetrieveSerializer(OrgReadSerializer):
|
||||
admins = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
auditors = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
users = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
|
||||
class Meta(OrgReadSerializer.Meta):
|
||||
pass
|
||||
|
|
|
@ -24,7 +24,7 @@ def get_org_from_request(request):
|
|||
oid = Organization.DEFAULT_ID
|
||||
elif oid.lower() == "root":
|
||||
oid = Organization.ROOT_ID
|
||||
org = Organization.get_instance(oid)
|
||||
org = Organization.get_instance(oid, True)
|
||||
return org
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from .user_permission import *
|
|||
from .asset_permission_relation import *
|
||||
from .user_group_permission import *
|
||||
from .remote_app_permission import *
|
||||
from .remote_app_permission_relation import *
|
||||
from .user_remote_app_permission import *
|
||||
from .database_app_permission import *
|
||||
from .database_app_permission_relation import *
|
||||
|
|
|
@ -22,10 +22,7 @@ class AssetPermissionViewSet(OrgModelViewSet):
|
|||
资产授权列表的增删改查api
|
||||
"""
|
||||
model = AssetPermission
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetPermissionCreateUpdateSerializer,
|
||||
'display': serializers.AssetPermissionListSerializer
|
||||
}
|
||||
serializer_class = serializers.AssetPermissionSerializer
|
||||
filter_fields = ['name']
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from django.db.models import F
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins.api import OrgRelationMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RelationViewSet'
|
||||
]
|
||||
|
||||
|
||||
class RelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(**{f'{self.from_field}_display': F(f'{self.from_field}__name')})
|
||||
return queryset
|
|
@ -1,14 +1,12 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from rest_framework import generics
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.functions import Concat
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.utils import current_org
|
||||
from common.permissions import IsOrgAdmin
|
||||
from .base import RelationViewSet
|
||||
from .. import models, serializers
|
||||
|
||||
__all__ = [
|
||||
|
@ -21,19 +19,9 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class RelationMixin(OrgBulkModelViewSet):
|
||||
def get_queryset(self):
|
||||
queryset = self.model.objects.all()
|
||||
org_id = current_org.org_id()
|
||||
if org_id is not None:
|
||||
queryset = queryset.filter(databaseapppermission__org_id=org_id)
|
||||
queryset = queryset.annotate(databaseapppermission_display=F('databaseapppermission__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class DatabaseAppPermissionUserRelationViewSet(RelationMixin):
|
||||
class DatabaseAppPermissionUserRelationViewSet(RelationViewSet):
|
||||
serializer_class = serializers.DatabaseAppPermissionUserRelationSerializer
|
||||
model = models.DatabaseAppPermission.users.through
|
||||
m2m_field = models.DatabaseAppPermission.users.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', 'user', 'databaseapppermission'
|
||||
|
@ -46,9 +34,9 @@ class DatabaseAppPermissionUserRelationViewSet(RelationMixin):
|
|||
return queryset
|
||||
|
||||
|
||||
class DatabaseAppPermissionUserGroupRelationViewSet(RelationMixin):
|
||||
class DatabaseAppPermissionUserGroupRelationViewSet(RelationViewSet):
|
||||
serializer_class = serializers.DatabaseAppPermissionUserGroupRelationSerializer
|
||||
model = models.DatabaseAppPermission.user_groups.through
|
||||
m2m_field = models.DatabaseAppPermission.user_groups.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', "usergroup", "databaseapppermission"
|
||||
|
@ -77,9 +65,9 @@ class DatabaseAppPermissionAllUserListApi(generics.ListAPIView):
|
|||
return users
|
||||
|
||||
|
||||
class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationMixin):
|
||||
class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationViewSet):
|
||||
serializer_class = serializers.DatabaseAppPermissionDatabaseAppRelationSerializer
|
||||
model = models.DatabaseAppPermission.database_apps.through
|
||||
m2m_field = models.DatabaseAppPermission.database_apps.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', 'databaseapp', 'databaseapppermission',
|
||||
|
@ -110,9 +98,9 @@ class DatabaseAppPermissionAllDatabaseAppListApi(generics.ListAPIView):
|
|||
return database_apps
|
||||
|
||||
|
||||
class DatabaseAppPermissionSystemUserRelationViewSet(RelationMixin):
|
||||
class DatabaseAppPermissionSystemUserRelationViewSet(RelationViewSet):
|
||||
serializer_class = serializers.DatabaseAppPermissionSystemUserRelationSerializer
|
||||
model = models.DatabaseAppPermission.system_users.through
|
||||
m2m_field = models.DatabaseAppPermission.system_users.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', 'systemuser', 'databaseapppermission'
|
||||
|
|
|
@ -11,10 +11,8 @@ from ..serializers import (
|
|||
RemoteAppPermissionSerializer,
|
||||
RemoteAppPermissionUpdateUserSerializer,
|
||||
RemoteAppPermissionUpdateRemoteAppSerializer,
|
||||
RemoteAppPermissionListSerializer,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppPermissionViewSet',
|
||||
'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi',
|
||||
|
@ -26,10 +24,7 @@ class RemoteAppPermissionViewSet(OrgModelViewSet):
|
|||
model = RemoteAppPermission
|
||||
filter_fields = ('name', )
|
||||
search_fields = filter_fields
|
||||
serializer_classes = {
|
||||
'default': RemoteAppPermissionSerializer,
|
||||
'display': RemoteAppPermissionListSerializer,
|
||||
}
|
||||
serializer_class = RemoteAppPermissionSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
from perms.api.base import RelationViewSet
|
||||
from rest_framework import generics
|
||||
from django.db.models import F
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsOrgAdmin
|
||||
from .. import models, serializers
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppPermissionUserRelationViewSet',
|
||||
'RemoteAppPermissionRemoteAppRelationViewSet',
|
||||
'RemoteAppPermissionAllRemoteAppListApi',
|
||||
'RemoteAppPermissionAllUserListApi',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppPermissionAllUserListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.PermissionAllUserSerializer
|
||||
filter_fields = ("username", "name")
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
perm = get_object_or_404(models.RemoteAppPermission, pk=pk)
|
||||
users = perm.all_users.only(
|
||||
*self.serializer_class.Meta.only_fields
|
||||
)
|
||||
return users
|
||||
|
||||
|
||||
class RemoteAppPermissionUserRelationViewSet(RelationViewSet):
|
||||
serializer_class = serializers.RemoteAppPermissionUserRelationSerializer
|
||||
m2m_field = models.RemoteAppPermission.users.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', 'user', 'remoteapppermission'
|
||||
]
|
||||
search_fields = ('user__name', 'user__username', 'remoteapppermission__name')
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(user_display=F('user__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class RemoteAppPermissionRemoteAppRelationViewSet(RelationViewSet):
|
||||
serializer_class = serializers.RemoteAppPermissionRemoteAppRelationSerializer
|
||||
m2m_field = models.RemoteAppPermission.remote_apps.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', 'remoteapp', 'remoteapppermission',
|
||||
]
|
||||
search_fields = [
|
||||
"id", "remoteapp__name", "remoteapppermission__name"
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset \
|
||||
.annotate(remoteapp_display=F('remoteapp__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class RemoteAppPermissionAllRemoteAppListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.RemoteAppPermissionAllRemoteAppSerializer
|
||||
filter_fields = ("name",)
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
perm = get_object_or_404(models.RemoteAppPermission, pk=pk)
|
||||
remote_apps = perm.all_remote_apps.only(
|
||||
*self.serializer_class.Meta.only_fields
|
||||
)
|
||||
return remote_apps
|
|
@ -26,8 +26,8 @@ __all__ = [
|
|||
class UserGrantedDatabaseAppsApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = DatabaseAppSerializer
|
||||
filter_fields = ['id', 'name']
|
||||
search_fields = ['name']
|
||||
filter_fields = ['id', 'name', 'type', 'comment']
|
||||
search_fields = ['name', 'comment']
|
||||
|
||||
def get_object(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
|
|
|
@ -5,7 +5,7 @@ import uuid
|
|||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404, RetrieveAPIView
|
||||
ListAPIView, get_object_or_404, RetrieveAPIView, DestroyAPIView
|
||||
)
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
|
||||
|
@ -25,6 +25,7 @@ __all__ = [
|
|||
'UserGrantedAssetSystemUsersApi',
|
||||
'ValidateUserAssetPermissionApi',
|
||||
'GetUserAssetPermissionActionsApi',
|
||||
'UserAssetPermissionsCacheApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -117,3 +118,10 @@ class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
|
|||
system_user.actions = actions
|
||||
return system_users
|
||||
|
||||
|
||||
class UserAssetPermissionsCacheApi(UserAssetPermissionMixin, DestroyAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
self.util.expire_user_tree_cache()
|
||||
return Response(status=204)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
from common.utils import lazyproperty
|
||||
from common.tree import TreeNodeSerializer
|
||||
from django.db.models import QuerySet
|
||||
from ..mixin import UserPermissionMixin
|
||||
from ...utils import AssetPermissionUtil, ParserNode
|
||||
from ...hands import Node, Asset
|
||||
|
@ -32,7 +33,8 @@ class UserNodeTreeMixin:
|
|||
nodes_only_fields = ParserNode.nodes_only_fields
|
||||
|
||||
def parse_nodes_to_queryset(self, nodes):
|
||||
nodes = nodes.only(*self.nodes_only_fields)
|
||||
if isinstance(nodes, QuerySet):
|
||||
nodes = nodes.only(*self.nodes_only_fields)
|
||||
_queryset = []
|
||||
|
||||
for node in nodes:
|
||||
|
|
|
@ -26,8 +26,8 @@ __all__ = [
|
|||
class UserGrantedRemoteAppsApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = RemoteAppSerializer
|
||||
filter_fields = ['name', 'id']
|
||||
search_fields = ['name']
|
||||
filter_fields = ['name', 'id', 'type', 'comment']
|
||||
search_fields = ['name', 'comment']
|
||||
|
||||
def get_object(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
|
|
|
@ -3,9 +3,9 @@ import logging
|
|||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from orgs.models import Organization
|
||||
from orgs.utils import get_current_org
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
|
@ -87,6 +87,18 @@ class AssetPermission(BasePermission):
|
|||
verbose_name = _("Asset permission")
|
||||
ordering = ('name',)
|
||||
|
||||
@lazyproperty
|
||||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
@lazyproperty
|
||||
def nodes_amount(self):
|
||||
return self.nodes.count()
|
||||
|
||||
@lazyproperty
|
||||
def system_users_amount(self):
|
||||
return self.system_users.count()
|
||||
|
||||
@classmethod
|
||||
def get_queryset_with_prefetch(cls):
|
||||
return cls.objects.all().valid().prefetch_related(
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.db.models import Q
|
|||
from django.utils import timezone
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
from common.utils import date_expired_default
|
||||
from common.utils import date_expired_default, lazyproperty
|
||||
from orgs.mixins.models import OrgManager
|
||||
|
||||
|
||||
|
@ -79,6 +79,23 @@ class BasePermission(OrgModelMixin):
|
|||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def all_users(self):
|
||||
from users.models import User
|
||||
|
||||
users_query = self._meta.get_field('users').related_query_name()
|
||||
user_groups_query = self._meta.get_field('user_groups').related_query_name()
|
||||
|
||||
users_q = Q(**{
|
||||
f'{users_query}': self
|
||||
})
|
||||
|
||||
user_groups_q = Q(**{
|
||||
f'groups__{user_groups_query}': self
|
||||
})
|
||||
|
||||
return User.objects.filter(users_q | user_groups_q).distinct()
|
||||
|
||||
def get_all_users(self):
|
||||
from users.models import User
|
||||
users_id = self.users.all().values_list('id', flat=True)
|
||||
|
@ -87,3 +104,11 @@ class BasePermission(OrgModelMixin):
|
|||
Q(id__in=users_id) | Q(groups__id__in=groups_id)
|
||||
).distinct()
|
||||
return users
|
||||
|
||||
@lazyproperty
|
||||
def users_amount(self):
|
||||
return self.users.count()
|
||||
|
||||
@lazyproperty
|
||||
def user_groups_amount(self):
|
||||
return self.user_groups.count()
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from .base import BasePermission
|
||||
|
||||
__all__ = [
|
||||
|
@ -28,3 +29,11 @@ class DatabaseAppPermission(BasePermission):
|
|||
|
||||
def get_all_database_apps(self):
|
||||
return self.database_apps.all()
|
||||
|
||||
@lazyproperty
|
||||
def database_apps_amount(self):
|
||||
return self.database_apps.count()
|
||||
|
||||
@lazyproperty
|
||||
def system_users_amount(self):
|
||||
return self.system_users.count()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from .base import BasePermission
|
||||
|
||||
__all__ = [
|
||||
|
@ -22,3 +22,15 @@ class RemoteAppPermission(BasePermission):
|
|||
|
||||
def get_all_remote_apps(self):
|
||||
return set(self.remote_apps.all())
|
||||
|
||||
@property
|
||||
def all_remote_apps(self):
|
||||
return self.remote_apps.all()
|
||||
|
||||
@lazyproperty
|
||||
def remote_apps_amount(self):
|
||||
return self.remote_apps.count()
|
||||
|
||||
@lazyproperty
|
||||
def system_users_amount(self):
|
||||
return self.system_users.count()
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
from .asset_permission import *
|
||||
from .user_permission import *
|
||||
from .remote_app_permission import *
|
||||
from .remote_app_permission_relation import *
|
||||
from .asset_permission_relation import *
|
||||
from .database_app_permission import *
|
||||
from .database_app_permission_relation import *
|
||||
from .base import *
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import StringManyToManyField
|
||||
from django.db.models import Count
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from perms.models import AssetPermission, Action
|
||||
|
||||
__all__ = [
|
||||
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
|
||||
'AssetPermissionSerializer',
|
||||
'ActionsField',
|
||||
]
|
||||
|
||||
|
@ -34,27 +34,31 @@ class ActionsDisplayField(ActionsField):
|
|||
return [choices.get(i) for i in values]
|
||||
|
||||
|
||||
class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer):
|
||||
class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
|
||||
actions = ActionsField(required=False, allow_null=True)
|
||||
is_valid = serializers.BooleanField(read_only=True)
|
||||
is_expired = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
exclude = ('created_by', 'date_created')
|
||||
|
||||
|
||||
class AssetPermissionListSerializer(BulkOrgResourceModelSerializer):
|
||||
users = StringManyToManyField(many=True, read_only=True)
|
||||
user_groups = StringManyToManyField(many=True, read_only=True)
|
||||
assets = StringManyToManyField(many=True, read_only=True)
|
||||
nodes = StringManyToManyField(many=True, read_only=True)
|
||||
system_users = StringManyToManyField(many=True, read_only=True)
|
||||
actions = ActionsDisplayField()
|
||||
is_valid = serializers.BooleanField()
|
||||
is_expired = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
mini_fields = ['id', 'name']
|
||||
small_fields = mini_fields + [
|
||||
'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created',
|
||||
'date_expired', 'date_start', 'comment'
|
||||
]
|
||||
m2m_fields = [
|
||||
'users', 'user_groups', 'assets', 'nodes', 'system_users',
|
||||
'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', 'system_users_amount',
|
||||
]
|
||||
fields = small_fields + m2m_fields
|
||||
read_only_fields = ['created_by', 'date_created']
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.annotate(
|
||||
users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True),
|
||||
assets_amount=Count('assets', distinct=True), nodes_amount=Count('nodes', distinct=True),
|
||||
system_users_amount=Count('system_users', distinct=True)
|
||||
)
|
||||
return queryset
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class PermissionAllUserSerializer(serializers.Serializer):
|
||||
user = serializers.UUIDField(read_only=True, source='id')
|
||||
user_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
only_fields = ['id', 'username', 'name']
|
||||
|
||||
@staticmethod
|
||||
def get_user_display(obj):
|
||||
return str(obj)
|
|
@ -1,6 +1,6 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.db.models import Count
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import StringManyToManyField
|
||||
|
@ -13,27 +13,41 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer):
|
||||
class AmountMixin:
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.annotate(
|
||||
users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True),
|
||||
database_apps_amount=Count('database_apps', distinct=True),
|
||||
system_users_amount=Count('system_users', distinct=True)
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class DatabaseAppPermissionSerializer(AmountMixin, BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = models.DatabaseAppPermission
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'users', 'user_groups',
|
||||
'database_apps', 'system_users', 'comment', 'is_active',
|
||||
'date_start', 'date_expired', 'is_valid',
|
||||
'created_by', 'date_created'
|
||||
'id', 'name', 'users', 'user_groups', 'database_apps', 'system_users',
|
||||
'comment', 'is_active', 'date_start', 'date_expired', 'is_valid',
|
||||
'created_by', 'date_created', 'users_amount', 'user_groups_amount',
|
||||
'database_apps_amount', 'system_users_amount',
|
||||
]
|
||||
read_only_fields = [
|
||||
'created_by', 'date_created', 'users_amount', 'user_groups_amount',
|
||||
'database_apps_amount', 'system_users_amount',
|
||||
]
|
||||
read_only_fields = ['created_by', 'date_created']
|
||||
|
||||
|
||||
class DatabaseAppPermissionListSerializer(BulkOrgResourceModelSerializer):
|
||||
users = StringManyToManyField(many=True, read_only=True)
|
||||
user_groups = StringManyToManyField(many=True, read_only=True)
|
||||
database_apps = StringManyToManyField(many=True, read_only=True)
|
||||
system_users = StringManyToManyField(many=True, read_only=True)
|
||||
is_valid = serializers.BooleanField()
|
||||
class DatabaseAppPermissionListSerializer(AmountMixin, BulkOrgResourceModelSerializer):
|
||||
is_expired = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
model = models.DatabaseAppPermission
|
||||
fields = '__all__'
|
||||
fields = [
|
||||
'id', 'name', 'comment', 'is_active', 'users_amount', 'user_groups_amount',
|
||||
'date_start', 'date_expired', 'is_valid', 'database_apps_amount', 'system_users_amount',
|
||||
'created_by', 'date_created', 'is_expired'
|
||||
]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
from perms.serializers.base import PermissionAllUserSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
from applications.models import DatabaseApp
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
|
@ -50,16 +50,9 @@ class DatabaseAppPermissionUserGroupRelationSerializer(RelationMixin, serializer
|
|||
]
|
||||
|
||||
|
||||
class DatabaseAppPermissionAllUserSerializer(serializers.Serializer):
|
||||
user = serializers.UUIDField(read_only=True, source='id')
|
||||
user_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
only_fields = ['id', 'username', 'name']
|
||||
|
||||
@staticmethod
|
||||
def get_user_display(obj):
|
||||
return str(obj)
|
||||
class DatabaseAppPermissionAllUserSerializer(PermissionAllUserSerializer):
|
||||
class Meta(PermissionAllUserSerializer.Meta):
|
||||
pass
|
||||
|
||||
|
||||
class DatabaseAppPermissionDatabaseAppRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.db.models import Count
|
||||
|
||||
from common.fields import StringManyToManyField
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import RemoteAppPermission
|
||||
|
@ -13,7 +12,6 @@ __all__ = [
|
|||
'RemoteAppPermissionSerializer',
|
||||
'RemoteAppPermissionUpdateUserSerializer',
|
||||
'RemoteAppPermissionUpdateRemoteAppSerializer',
|
||||
'RemoteAppPermissionListSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -21,25 +19,27 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer):
|
|||
class Meta:
|
||||
model = RemoteAppPermission
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'users', 'user_groups', 'remote_apps', 'system_users',
|
||||
mini_fields = ['id', 'name']
|
||||
small_fields = mini_fields + [
|
||||
'comment', 'is_active', 'date_start', 'date_expired', 'is_valid',
|
||||
'created_by', 'date_created',
|
||||
'created_by', 'date_created'
|
||||
]
|
||||
m2m_fields = [
|
||||
'users', 'user_groups', 'remote_apps', 'system_users',
|
||||
'users_amount', 'user_groups_amount', 'remote_apps_amount',
|
||||
'system_users_amount'
|
||||
]
|
||||
fields = small_fields + m2m_fields
|
||||
read_only_fields = ['created_by', 'date_created']
|
||||
|
||||
|
||||
class RemoteAppPermissionListSerializer(BulkOrgResourceModelSerializer):
|
||||
users = StringManyToManyField(many=True, read_only=True)
|
||||
user_groups = StringManyToManyField(many=True, read_only=True)
|
||||
remote_apps = StringManyToManyField(many=True, read_only=True)
|
||||
system_users = StringManyToManyField(many=True, read_only=True)
|
||||
is_valid = serializers.BooleanField()
|
||||
is_expired = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteAppPermission
|
||||
fields = '__all__'
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.annotate(
|
||||
users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True),
|
||||
remote_apps_amount=Count('remote_apps', distinct=True), system_users_amount=Count('system_users', distinct=True)
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import RemoteAppPermission
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppPermissionRemoteAppRelationSerializer',
|
||||
'RemoteAppPermissionAllRemoteAppSerializer',
|
||||
'RemoteAppPermissionUserRelationSerializer',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppPermissionRemoteAppRelationSerializer(serializers.ModelSerializer):
|
||||
remoteapp_display = serializers.ReadOnlyField()
|
||||
remoteapppermission_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteAppPermission.remote_apps.through
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'remoteapp', 'remoteapp_display', 'remoteapppermission', 'remoteapppermission_display'
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppPermissionAllRemoteAppSerializer(serializers.Serializer):
|
||||
remoteapp = serializers.UUIDField(read_only=True, source='id')
|
||||
remoteapp_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
only_fields = ['id', 'name']
|
||||
|
||||
@staticmethod
|
||||
def get_remoteapp_display(obj):
|
||||
return str(obj)
|
||||
|
||||
|
||||
class RemoteAppPermissionUserRelationSerializer(serializers.ModelSerializer):
|
||||
user_display = serializers.ReadOnlyField()
|
||||
remoteapppermission_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteAppPermission.users.through
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'user', 'user_display', 'remoteapppermission', 'remoteapppermission_display'
|
||||
]
|
|
@ -1,4 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from django.contrib.sessions.backends import file, db, cache
|
||||
from django.contrib.auth.views import login
|
|
@ -51,6 +51,11 @@ user_permission_urlpatterns = [
|
|||
# Asset System users
|
||||
path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
|
||||
path('assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
|
||||
|
||||
# Expire user permission cache
|
||||
path('<uuid:pk>/asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(),
|
||||
name='user-asset-permission-cache'),
|
||||
path('asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), name='my-asset-permission-cache'),
|
||||
]
|
||||
|
||||
user_group_permission_urlpatterns = [
|
||||
|
|
|
@ -7,6 +7,9 @@ from .. import api
|
|||
|
||||
router = BulkRouter()
|
||||
router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission')
|
||||
router.register('remote-app-permissions-users-relations', api.RemoteAppPermissionUserRelationViewSet, 'remote-app-permissions-users-relation')
|
||||
router.register('remote-app-permissions-remote-apps-relations', api.RemoteAppPermissionRemoteAppRelationViewSet, 'remote-app-permissions-remote-apps-relation')
|
||||
|
||||
|
||||
remote_app_permission_urlpatterns = [
|
||||
# 查询用户授权的RemoteApp
|
||||
|
@ -32,7 +35,9 @@ remote_app_permission_urlpatterns = [
|
|||
path('remote-app-permissions/<uuid:pk>/users/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'),
|
||||
path('remote-app-permissions/<uuid:pk>/remote-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'),
|
||||
path('remote-app-permissions/<uuid:pk>/remote-apps/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
|
||||
|
||||
path('remote-app-permissions/<uuid:pk>/remote-apps/all/', api.RemoteAppPermissionAllRemoteAppListApi.as_view(), name='remote-app-permission-all-remote-apps'),
|
||||
path('remote-app-permissions/<uuid:pk>/users/all/', api.RemoteAppPermissionAllUserListApi.as_view(), name='remote-app-permission-all-users'),
|
||||
]
|
||||
|
||||
remote_app_permission_urlpatterns += router.urls
|
||||
|
||||
|
|
|
@ -290,11 +290,12 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin):
|
|||
def parse_user_tree_to_full_tree(self, user_tree):
|
||||
"""
|
||||
经过前面两个动作,用户授权的节点已放到树上,但是树不是完整的,
|
||||
这里要讲树构造成一个完整的树
|
||||
这里要将树构造成一个完整的树
|
||||
"""
|
||||
# 开始修正user_tree,保证父节点都在树上
|
||||
root_children = user_tree.children('')
|
||||
for child in root_children:
|
||||
# print("child: {}".format(child.identifier))
|
||||
if child.identifier.isdigit():
|
||||
continue
|
||||
if child.identifier.startswith('-'):
|
||||
|
@ -302,6 +303,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin):
|
|||
ancestors = self.full_tree.ancestors(
|
||||
child.identifier, with_self=False, deep=True,
|
||||
)
|
||||
# print("Get ancestors: {}".format(len(ancestors)))
|
||||
if not ancestors:
|
||||
continue
|
||||
user_tree.safe_add_ancestors(child, ancestors)
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
#
|
||||
|
||||
import json
|
||||
|
||||
from collections.abc import Iterable
|
||||
from smtplib import SMTPSenderRefused
|
||||
from rest_framework import generics
|
||||
from rest_framework.views import Response, APIView
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail, get_connection
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from .utils import (
|
||||
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
|
||||
LDAP_USE_CACHE_FLAGS, LDAPTestUtil,
|
||||
LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict
|
||||
)
|
||||
from .tasks import sync_ldap_user_task
|
||||
from common.permissions import IsOrgAdmin, IsSuperUser
|
||||
from common.utils import get_logger
|
||||
from .serializers import (
|
||||
MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer,
|
||||
PublicSettingSerializer, LDAPTestLoginSerializer,
|
||||
PublicSettingSerializer, LDAPTestLoginSerializer, SettingsSerializer
|
||||
)
|
||||
from users.models import User
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ class MailTestingAPI(APIView):
|
|||
use_tls=email_use_tls, use_ssl=email_use_ssl,
|
||||
)
|
||||
send_mail(
|
||||
subject, message, email_from, [email_recipient],
|
||||
subject, message, email_from, [email_recipient],
|
||||
connection=connection
|
||||
)
|
||||
except SMTPSenderRefused as e:
|
||||
|
@ -72,13 +72,13 @@ class MailTestingAPI(APIView):
|
|||
continue
|
||||
else:
|
||||
break
|
||||
return Response({"error": str(resp)}, status=401)
|
||||
return Response({"error": str(resp)}, status=400)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return Response({"error": str(e)}, status=401)
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return Response({"msg": self.success_message.format(email_recipient)})
|
||||
else:
|
||||
return Response({"error": str(serializer.errors)}, status=401)
|
||||
return Response({"error": str(serializer.errors)}, status=400)
|
||||
|
||||
|
||||
class LDAPTestingConfigAPI(APIView):
|
||||
|
@ -88,10 +88,10 @@ class LDAPTestingConfigAPI(APIView):
|
|||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response({"error": str(serializer.errors)}, status=401)
|
||||
return Response({"error": str(serializer.errors)}, status=400)
|
||||
config = self.get_ldap_config(serializer)
|
||||
ok, msg = LDAPTestUtil(config).test_config()
|
||||
status = 200 if ok else 401
|
||||
status = 200 if ok else 400
|
||||
return Response(msg, status=status)
|
||||
|
||||
@staticmethod
|
||||
|
@ -124,11 +124,11 @@ class LDAPTestingLoginAPI(APIView):
|
|||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response({"error": str(serializer.errors)}, status=401)
|
||||
return Response({"error": str(serializer.errors)}, status=400)
|
||||
username = serializer.validated_data['username']
|
||||
password = serializer.validated_data['password']
|
||||
ok, msg = LDAPTestUtil().test_login(username, password)
|
||||
status = 200 if ok else 401
|
||||
status = 200 if ok else 400
|
||||
return Response(msg, status=status)
|
||||
|
||||
|
||||
|
@ -236,14 +236,14 @@ class LDAPUserImportAPI(APIView):
|
|||
try:
|
||||
users = self.get_ldap_users()
|
||||
except Exception as e:
|
||||
return Response({'error': str(e)}, status=401)
|
||||
return Response({'error': str(e)}, status=400)
|
||||
|
||||
if users is None:
|
||||
return Response({'msg': _('Get ldap users is None')}, status=401)
|
||||
return Response({'msg': _('Get ldap users is None')}, status=400)
|
||||
|
||||
errors = LDAPImportUtil().perform_import(users)
|
||||
if errors:
|
||||
return Response({'errors': errors}, status=401)
|
||||
return Response({'errors': errors}, status=400)
|
||||
|
||||
count = users if users is None else len(users)
|
||||
return Response({'msg': _('Imported {} users successfully').format(count)})
|
||||
|
@ -270,8 +270,34 @@ class PublicSettingApi(generics.RetrieveAPIView):
|
|||
"data": {
|
||||
"WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD,
|
||||
"SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME,
|
||||
"XPACK_ENABLED": settings.XPACK_ENABLED,
|
||||
"XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID
|
||||
}
|
||||
}
|
||||
return instance
|
||||
|
||||
|
||||
class SettingsApi(generics.RetrieveUpdateAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = SettingsSerializer
|
||||
|
||||
def get_object(self):
|
||||
instance = {category: self._get_setting_fields_obj(list(category_serializer.get_fields()))
|
||||
for category, category_serializer in self.serializer_class().get_fields().items()
|
||||
if isinstance(category_serializer, serializers.Serializer)}
|
||||
return ObjectDict(instance)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save()
|
||||
|
||||
def _get_setting_fields_obj(self, category_fields):
|
||||
if isinstance(category_fields, Iterable):
|
||||
fields_data = {field_name: getattr(settings, field_name)
|
||||
for field_name in category_fields}
|
||||
return ObjectDict(fields_data)
|
||||
|
||||
if isinstance(category_fields, str):
|
||||
fields_data = {category_fields: getattr(settings, category_fields)}
|
||||
return ObjectDict(fields_data)
|
||||
|
||||
return ObjectDict()
|
||||
|
|
|
@ -10,7 +10,8 @@ from common.utils import signer
|
|||
|
||||
class SettingQuerySet(models.QuerySet):
|
||||
def __getattr__(self, item):
|
||||
instances = self.filter(name=item)
|
||||
queryset = list(self)
|
||||
instances = [i for i in queryset if i.name == item]
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
else:
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
from .email import *
|
||||
from .ldap import *
|
||||
from .public import *
|
||||
from .settings import *
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
# coding: utf-8
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from ..models import Setting
|
||||
|
||||
__all__ = ['SettingsSerializer']
|
||||
|
||||
|
||||
class BasicSettingSerializer(serializers.Serializer):
|
||||
SITE_URL = serializers.URLField(required=True)
|
||||
USER_GUIDE_URL = serializers.URLField(required=False, allow_blank=True, )
|
||||
EMAIL_SUBJECT_PREFIX = serializers.CharField(max_length=1024, required=True)
|
||||
|
||||
|
||||
class EmailSettingSerializer(serializers.Serializer):
|
||||
encrypt_fields = ["EMAIL_HOST_PASSWORD", ]
|
||||
|
||||
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
|
||||
EMAIL_PORT = serializers.CharField(max_length=5, required=True)
|
||||
EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True)
|
||||
EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, )
|
||||
EMAIL_FROM = serializers.CharField(max_length=128, allow_blank=True, required=False)
|
||||
EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank=True, required=False)
|
||||
EMAIL_USE_SSL = serializers.BooleanField(required=False)
|
||||
EMAIL_USE_TLS = serializers.BooleanField(required=False)
|
||||
|
||||
|
||||
class EmailContentSettingSerializer(serializers.Serializer):
|
||||
EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, allow_blank=True, required=False, )
|
||||
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, allow_blank=True, required=False, )
|
||||
EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, allow_blank=True, required=False)
|
||||
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, allow_blank=True, required=False)
|
||||
|
||||
|
||||
class LdapSettingSerializer(serializers.Serializer):
|
||||
encrypt_fields = ["AUTH_LDAP_BIND_PASSWORD", ]
|
||||
|
||||
AUTH_LDAP_SERVER_URI = serializers.CharField(required=True)
|
||||
AUTH_LDAP_BIND_DN = serializers.CharField(required=False)
|
||||
AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False)
|
||||
AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False)
|
||||
AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True)
|
||||
AUTH_LDAP_USER_ATTR_MAP = serializers.DictField(required=True)
|
||||
AUTH_LDAP = serializers.BooleanField(required=False)
|
||||
|
||||
|
||||
class TerminalSettingSerializer(serializers.Serializer):
|
||||
SORT_BY_CHOICES = (
|
||||
('hostname', _('Hostname')),
|
||||
('ip', _('IP'))
|
||||
)
|
||||
|
||||
PAGE_SIZE_CHOICES = (
|
||||
('all', _('All')),
|
||||
('auto', _('Auto')),
|
||||
(10, 10),
|
||||
(15, 15),
|
||||
(25, 25),
|
||||
(50, 50),
|
||||
)
|
||||
TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False)
|
||||
TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False)
|
||||
TERMINAL_HEARTBEAT_INTERVAL = serializers.IntegerField(min_value=5, max_value=99999, required=True)
|
||||
TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES, required=False)
|
||||
TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES, required=False)
|
||||
TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField(min_value=1, max_value=99999, required=True)
|
||||
TERMINAL_TELNET_REGEX = serializers.CharField(allow_blank=True, required=False)
|
||||
|
||||
|
||||
class SecuritySettingSerializer(serializers.Serializer):
|
||||
SECURITY_MFA_AUTH = serializers.BooleanField(required=False)
|
||||
SECURITY_COMMAND_EXECUTION = serializers.BooleanField(required=False)
|
||||
SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(required=True)
|
||||
SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(min_value=3, max_value=99999, required=True)
|
||||
SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=True)
|
||||
SECURITY_MAX_IDLE_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=False)
|
||||
SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=True)
|
||||
SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(min_value=6, max_value=30, required=True)
|
||||
SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField(required=False)
|
||||
SECURITY_PASSWORD_LOWER_CASE = serializers.BooleanField(required=False)
|
||||
SECURITY_PASSWORD_NUMBER = serializers.BooleanField(required=False)
|
||||
SECURITY_PASSWORD_SPECIAL_CHAR = serializers.BooleanField(required=False)
|
||||
|
||||
|
||||
class SettingsSerializer(serializers.Serializer):
|
||||
basic = BasicSettingSerializer(required=False)
|
||||
email = EmailSettingSerializer(required=False)
|
||||
email_content = EmailContentSettingSerializer(required=False)
|
||||
ldap = LdapSettingSerializer(required=False)
|
||||
terminal = TerminalSettingSerializer(required=False)
|
||||
security = SecuritySettingSerializer(required=False)
|
||||
|
||||
encrypt_fields = ["EMAIL_HOST_PASSWORD", "AUTH_LDAP_BIND_PASSWORD"]
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
for category, category_data in validated_data.items():
|
||||
if not category_data:
|
||||
continue
|
||||
self.update_validated_settings(category_data)
|
||||
for field_name, field_value in category_data.items():
|
||||
setattr(getattr(instance, category), field_name, field_value)
|
||||
|
||||
return instance
|
||||
|
||||
def update_validated_settings(self, validated_data, category='default'):
|
||||
if not validated_data:
|
||||
return
|
||||
with transaction.atomic():
|
||||
for field_name, field_value in validated_data.items():
|
||||
try:
|
||||
setting = Setting.objects.get(name=field_name)
|
||||
except Setting.DoesNotExist:
|
||||
setting = Setting()
|
||||
encrypted = True if field_name in self.encrypt_fields else False
|
||||
setting.name = field_name
|
||||
setting.category = category
|
||||
setting.encrypted = encrypted
|
||||
setting.cleaned_value = field_value
|
||||
setting.save()
|
|
@ -14,5 +14,6 @@ urlpatterns = [
|
|||
path('ldap/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'),
|
||||
path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'),
|
||||
|
||||
path('setting/', api.SettingsApi.as_view(), name='settings-setting'),
|
||||
path('public/', api.PublicSettingApi.as_view(), name='public-setting'),
|
||||
]
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
#
|
||||
|
||||
from .ldap import *
|
||||
from .common import *
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# coding: utf-8
|
||||
|
||||
|
||||
class ObjectDict(dict):
|
||||
def __getattr__(self, name):
|
||||
if name in self:
|
||||
return self[name]
|
||||
else:
|
||||
raise AttributeError("No such attribute: " + name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
if name in self:
|
||||
del self[name]
|
||||
else:
|
||||
raise AttributeError("No such attribute: " + name)
|
|
@ -138,11 +138,11 @@ function setAjaxCSRFToken() {
|
|||
}
|
||||
|
||||
function activeNav(prefix) {
|
||||
var path = document.location.pathname;
|
||||
if (prefix) {
|
||||
path = path.replace(prefix, '');
|
||||
console.log(path);
|
||||
if (!prefix) {
|
||||
prefix = '/core'
|
||||
}
|
||||
var path = document.location.pathname;
|
||||
path = path.replace(prefix, '');
|
||||
var urlArray = path.split("/");
|
||||
var app = urlArray[1];
|
||||
var resource = urlArray[2];
|
||||
|
|
|
@ -58,11 +58,11 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
|
||||
<small>{% trans 'In the past week, a total of ' %}<span class="text-info" id="week_total_count_login_users"></span>{% trans ' users have logged in ' %}<span class="text-success" id="week_total_count_login_times"></span>{% trans ' times asset.' %}</small>
|
||||
<ul class="list-group clear-list m-t" id="week_login_times_top5_users">
|
||||
<small>{% trans 'In the past week, a total of ' %}<span class="text-info" id="dates_total_count_login_users"></span>{% trans ' users have logged in ' %}<span class="text-success" id="dates_total_count_login_times"></span>{% trans ' times asset.' %}</small>
|
||||
<ul class="list-group clear-list m-t" id="dates_login_times_top5_users">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-7" id="month_metrics_echarts" style="margin-left: -15px;height: 346px;padding: 15px 0 15px 0;"></div>
|
||||
<div class="col-sm-7" id="dates_metrics_echarts" style="margin-left: -15px;height: 346px;padding: 15px 0 15px 0;"></div>
|
||||
<div class="col-sm-3 white-bg" id="top1" style="margin-left: -15px;height: 346px">
|
||||
<div class="statistic-box">
|
||||
<h4>
|
||||
|
@ -73,12 +73,12 @@
|
|||
</p>
|
||||
<div class="row text-center">
|
||||
<div class="col-sm-6">
|
||||
<div id="month_total_count_users_pie" style="width: 140px; height: 140px;">
|
||||
<div id="dates_total_count_users_pie" style="width: 140px; height: 140px;">
|
||||
</div>
|
||||
<h5>{% trans 'User' %}</h5>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div id="month_total_count_assets_pie" style="width: 140px; height: 140px;"></div>
|
||||
<div id="dates_total_count_assets_pie" style="width: 140px; height: 140px;"></div>
|
||||
<h5>{% trans 'Asset' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -112,7 +112,7 @@
|
|||
<h3><i class="fa fa-inbox"></i>{% trans 'Top 10 assets in a week'%}</h3>
|
||||
<small><i class="fa fa-map-marker"></i>{% trans 'Login frequency and last login record.' %}</small>
|
||||
</div>
|
||||
<div class="ibox-content inspinia-timeline" id="week_login_times_top10_assets">
|
||||
<div class="ibox-content inspinia-timeline" id="dates_login_times_top10_assets">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -130,7 +130,7 @@
|
|||
</div>
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<div class="feed-activity-list" id="week_login_record_top10_sessions">
|
||||
<div class="feed-activity-list" id="dates_login_record_top10_sessions">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -158,7 +158,7 @@
|
|||
<h3><i class="fa fa-user"></i>{% trans 'Top 10 users in a week' %}</h3>
|
||||
<small><i class="fa fa-map-marker"></i>{% trans 'User login frequency and last login record.' %}</small>
|
||||
</div>
|
||||
<div class="ibox-content inspinia-timeline" id="week_login_times_top10_users">
|
||||
<div class="ibox-content inspinia-timeline" id="dates_login_times_top10_users">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -178,7 +178,7 @@ function requireMonthMetricsECharts(data){
|
|||
'echarts/chart/line'
|
||||
],
|
||||
function (ec) {
|
||||
var monthMetricsECharts = ec.init(document.getElementById('month_metrics_echarts'));
|
||||
var monthMetricsECharts = ec.init(document.getElementById('dates_metrics_echarts'));
|
||||
var option = {
|
||||
title : {
|
||||
text: "{% trans 'Monthly data overview' %}",
|
||||
|
@ -204,7 +204,7 @@ function requireMonthMetricsECharts(data){
|
|||
{
|
||||
type : 'category',
|
||||
boundaryGap : false,
|
||||
data : data['month_metrics_date'],
|
||||
data : data['dates_metrics_date'],
|
||||
}
|
||||
],
|
||||
yAxis : [
|
||||
|
@ -218,21 +218,21 @@ function requireMonthMetricsECharts(data){
|
|||
type:'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: data['month_metrics_total_count_login']
|
||||
data: data['dates_metrics_total_count_login']
|
||||
},
|
||||
{
|
||||
name: "{% trans 'Active users' %}",
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: data['month_metrics_total_count_active_users']
|
||||
data: data['dates_metrics_total_count_active_users']
|
||||
},
|
||||
{
|
||||
name:"{% trans 'Active assets' %}",
|
||||
type:'line',
|
||||
smooth:true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: data['month_metrics_total_count_active_assets']
|
||||
data: data['dates_metrics_total_count_active_assets']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -249,7 +249,7 @@ function requireMonthTotalCountUsersPie(data){
|
|||
'echarts/chart/pie'
|
||||
],
|
||||
function (ec) {
|
||||
var monthTotalCountUsersPie = ec.init(document.getElementById('month_total_count_users_pie'));
|
||||
var monthTotalCountUsersPie = ec.init(document.getElementById('dates_total_count_users_pie'));
|
||||
var option = {
|
||||
tooltip : {
|
||||
trigger: 'item',
|
||||
|
@ -310,9 +310,9 @@ function requireMonthTotalCountUsersPie(data){
|
|||
}
|
||||
},
|
||||
data:[
|
||||
{value:data['month_total_count_active_users'], name:"{% trans 'Monthly active users' %}"},
|
||||
{value:data['month_total_count_disabled_users'], name:"{% trans 'Disable user' %}"},
|
||||
{value:data['month_total_count_inactive_users'], name:"{% trans 'Month not logged in user' %}"}
|
||||
{value:data['dates_total_count_active_users'], name:"{% trans 'Monthly active users' %}"},
|
||||
{value:data['dates_total_count_disabled_users'], name:"{% trans 'Disable user' %}"},
|
||||
{value:data['dates_total_count_inactive_users'], name:"{% trans 'Month not logged in user' %}"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -329,7 +329,7 @@ function requireMonthTotalCountAssetsPie(data){
|
|||
'echarts/chart/pie'
|
||||
],
|
||||
function (ec) {
|
||||
var monthTotalCountAssetsPie = ec.init(document.getElementById('month_total_count_assets_pie'));
|
||||
var monthTotalCountAssetsPie = ec.init(document.getElementById('dates_total_count_assets_pie'));
|
||||
var option = {
|
||||
tooltip : {
|
||||
trigger: 'item',
|
||||
|
@ -389,9 +389,9 @@ function requireMonthTotalCountAssetsPie(data){
|
|||
}
|
||||
},
|
||||
data:[
|
||||
{value:data['month_total_count_active_assets'], name:"{% trans 'Month is logged into the host' %}"},
|
||||
{value:data['month_total_count_disabled_assets'], name:"{% trans 'Disable host' %}"},
|
||||
{value:data['month_total_count_inactive_assets'], name:"{% trans 'Month not logged on host' %}"}
|
||||
{value:data['dates_total_count_active_assets'], name:"{% trans 'Month is logged into the host' %}"},
|
||||
{value:data['dates_total_count_disabled_assets'], name:"{% trans 'Disable host' %}"},
|
||||
{value:data['dates_total_count_inactive_assets'], name:"{% trans 'Month not logged on host' %}"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -431,14 +431,14 @@ function renderMonthMetricsECharts(){
|
|||
var success = function (data) {
|
||||
requireMonthMetricsECharts(data)
|
||||
};
|
||||
renderRequestApi('month_metrics=1', success)
|
||||
renderRequestApi('dates_metrics=1', success)
|
||||
}
|
||||
|
||||
function renderMonthTotalCountUsersPie(){
|
||||
var success = function (data) {
|
||||
requireMonthTotalCountUsersPie(data)
|
||||
};
|
||||
renderRequestApi('month_total_count_users=1', success)
|
||||
renderRequestApi('dates_total_count_users=1', success)
|
||||
|
||||
}
|
||||
|
||||
|
@ -446,15 +446,15 @@ function renderMonthTotalCountAssetsPie(){
|
|||
var success = function (data) {
|
||||
requireMonthTotalCountAssetsPie(data)
|
||||
};
|
||||
renderRequestApi('month_total_count_assets=1', success)
|
||||
renderRequestApi('dates_total_count_assets=1', success)
|
||||
}
|
||||
|
||||
function renderWeekTotalCount(){
|
||||
var success = function (data) {
|
||||
$('#week_total_count_login_users').html(data['week_total_count_login_users']);
|
||||
$('#week_total_count_login_times').html(data['week_total_count_login_times'])
|
||||
$('#dates_total_count_login_users').html(data['dates_total_count_login_users']);
|
||||
$('#dates_total_count_login_times').html(data['dates_total_count_login_times'])
|
||||
};
|
||||
renderRequestApi('week_total_count=1', success)
|
||||
renderRequestApi('dates_total_count=1', success)
|
||||
}
|
||||
|
||||
function renderWeekLoginTimesTop5Users(){
|
||||
|
@ -468,14 +468,14 @@ function renderWeekLoginTimesTop5Users(){
|
|||
"<span class=\"label \">{INDEX}</span> {USER}" +
|
||||
"</li>";
|
||||
|
||||
$.each(data['week_login_times_top5_users'], function(index, value){
|
||||
$.each(data['dates_login_times_top5_users'], function(index, value){
|
||||
html += html_cell.replace('{TOTAL}', value['total'])
|
||||
.replace('{USER}', value['user'])
|
||||
.replace('{INDEX}', index+1)
|
||||
});
|
||||
$('#week_login_times_top5_users').html(html)
|
||||
$('#dates_login_times_top5_users').html(html)
|
||||
};
|
||||
renderRequestApi('week_login_times_top5_users=1', success)
|
||||
renderRequestApi('dates_login_times_top5_users=1', success)
|
||||
}
|
||||
|
||||
function renderWeekLoginTimesTop10Assets(){
|
||||
|
@ -497,7 +497,7 @@ function renderWeekLoginTimesTop10Assets(){
|
|||
"</div>" +
|
||||
"</div>";
|
||||
|
||||
var assets = data['week_login_times_top10_assets'];
|
||||
var assets = data['dates_login_times_top10_assets'];
|
||||
if (assets.length !== 0){
|
||||
$.each(assets, function(index, value){
|
||||
html += html_cell
|
||||
|
@ -509,9 +509,9 @@ function renderWeekLoginTimesTop10Assets(){
|
|||
else{
|
||||
html += "<p class=\"text-center\">{% trans '(No)' %}</p>"
|
||||
}
|
||||
$('#week_login_times_top10_assets').html(html)
|
||||
$('#dates_login_times_top10_assets').html(html)
|
||||
};
|
||||
renderRequestApi('week_login_times_top10_assets=1', success)
|
||||
renderRequestApi('dates_login_times_top10_assets=1', success)
|
||||
}
|
||||
|
||||
function renderWeekLoginTimesTop10Users(){
|
||||
|
@ -533,7 +533,7 @@ function renderWeekLoginTimesTop10Users(){
|
|||
"</div>" +
|
||||
"</div>";
|
||||
|
||||
var users = data['week_login_times_top10_users'];
|
||||
var users = data['dates_login_times_top10_users'];
|
||||
if (users.length !== 0){
|
||||
$.each(users, function(index, value){
|
||||
html += html_cell.replaceAll('{USER}', value['user'])
|
||||
|
@ -544,9 +544,9 @@ function renderWeekLoginTimesTop10Users(){
|
|||
else{
|
||||
html += "<p class=\"text-center\">{% trans '(No)' %}</p>"
|
||||
}
|
||||
$('#week_login_times_top10_users').html(html)
|
||||
$('#dates_login_times_top10_users').html(html)
|
||||
};
|
||||
renderRequestApi('week_login_times_top10_users=1', success)
|
||||
renderRequestApi('dates_login_times_top10_users=1', success)
|
||||
}
|
||||
|
||||
function renderWeekLoginRecordTop10Sessions(){
|
||||
|
@ -564,7 +564,7 @@ function renderWeekLoginRecordTop10Sessions(){
|
|||
"</div>" +
|
||||
"</div>";
|
||||
|
||||
var users = data['week_login_record_top10_sessions'];
|
||||
var users = data['dates_login_record_top10_sessions'];
|
||||
if (users.length !== 0){
|
||||
$.each(users, function(index, value){
|
||||
console.log(value['is_finished'])
|
||||
|
@ -579,10 +579,10 @@ function renderWeekLoginRecordTop10Sessions(){
|
|||
else{
|
||||
html += "<p class=\"text-center\">{% trans '(No)' %}</p>"
|
||||
}
|
||||
$('#week_login_record_top10_sessions').html(html)
|
||||
$('#dates_login_record_top10_sessions').html(html)
|
||||
|
||||
};
|
||||
renderRequestApi('week_login_record_top10_sessions=1', success)
|
||||
renderRequestApi('dates_login_record_top10_sessions=1', success)
|
||||
}
|
||||
|
||||
function renderData(){
|
||||
|
|
|
@ -16,4 +16,3 @@ class UserGroupViewSet(OrgBulkModelViewSet):
|
|||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = UserGroupSerializer
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from .mixins import UserQuerysetMixin
|
|||
__all__ = [
|
||||
'UserResetPasswordApi', 'UserResetPKApi',
|
||||
'UserProfileApi', 'UserUpdatePKApi',
|
||||
'UserPasswordApi', 'UserPublicKeyApi'
|
||||
]
|
||||
|
||||
|
||||
|
@ -55,9 +56,9 @@ class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView):
|
|||
user.save()
|
||||
|
||||
|
||||
class UserProfileApi(generics.RetrieveAPIView):
|
||||
class UserProfileApi(generics.RetrieveUpdateAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = serializers.UserSerializer
|
||||
serializer_class = serializers.UserProfileSerializer
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
@ -66,3 +67,24 @@ class UserProfileApi(generics.RetrieveAPIView):
|
|||
age = request.session.get_expiry_age()
|
||||
request.session.set_expiry(age)
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
class UserPasswordApi(generics.RetrieveUpdateAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = serializers.UserUpdatePasswordSerializer
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
|
||||
class UserPublicKeyApi(generics.RetrieveUpdateAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = serializers.UserUpdatePublicKeySerializer
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
def perform_update(self, serializer):
|
||||
user = self.get_object()
|
||||
user.public_key = serializer.validated_data['public_key']
|
||||
user.save()
|
||||
|
|
|
@ -27,12 +27,9 @@ __all__ = [
|
|||
|
||||
|
||||
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
||||
filter_fields = ('username', 'email', 'name', 'id')
|
||||
filter_fields = ('username', 'email', 'name', 'id', 'source')
|
||||
search_fields = filter_fields
|
||||
serializer_classes = {
|
||||
'default': serializers.UserSerializer,
|
||||
'display': serializers.UserDisplaySerializer
|
||||
}
|
||||
serializer_class = serializers.UserSerializer
|
||||
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.10 on 2020-05-08 13:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0025_auto_20200206_1216'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='ldap', max_length=30, verbose_name='Source'),
|
||||
),
|
||||
]
|
|
@ -47,6 +47,10 @@ class AuthMixin:
|
|||
post_user_change_password.send(self.__class__, user=self)
|
||||
super().set_password(raw_password)
|
||||
|
||||
def set_public_key(self, public_key):
|
||||
self.public_key = public_key
|
||||
self.save()
|
||||
|
||||
def can_update_password(self):
|
||||
return self.is_local
|
||||
|
||||
|
@ -79,6 +83,14 @@ class AuthMixin:
|
|||
pass
|
||||
return PubKey()
|
||||
|
||||
def get_public_key_comment(self):
|
||||
return self.public_key_obj.comment
|
||||
|
||||
def get_public_key_hash_md5(self):
|
||||
if not callable(self.public_key_obj.hash_md5):
|
||||
return ''
|
||||
return self.public_key_obj.hash_md5()
|
||||
|
||||
def reset_password(self, new_password):
|
||||
self.set_password(new_password)
|
||||
self.save()
|
||||
|
@ -159,6 +171,16 @@ class RoleMixin:
|
|||
roles.append(str(_('User')))
|
||||
return " | ".join(roles)
|
||||
|
||||
def current_org_roles(self):
|
||||
roles = []
|
||||
if self.can_admin_current_org:
|
||||
roles.append('Admin')
|
||||
if self.can_audit_current_org:
|
||||
roles.append('Auditor')
|
||||
else:
|
||||
roles.append('User')
|
||||
return roles
|
||||
|
||||
@property
|
||||
def is_superuser(self):
|
||||
if self.role == 'Admin':
|
||||
|
@ -481,7 +503,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
max_length=30, default='', blank=True, verbose_name=_('Created by')
|
||||
)
|
||||
source = models.CharField(
|
||||
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
|
||||
max_length=30, default=SOURCE_LDAP, choices=SOURCE_CHOICES,
|
||||
verbose_name=_('Source')
|
||||
)
|
||||
date_password_last_updated = models.DateTimeField(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.db.models import Prefetch
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
@ -18,15 +18,18 @@ __all__ = [
|
|||
class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
||||
users = serializers.PrimaryKeyRelatedField(
|
||||
required=False, many=True, queryset=User.objects, label=_('User'),
|
||||
write_only=True
|
||||
# write_only=True, # group can return many to many on detail
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'users', 'users_amount', 'comment',
|
||||
'date_created', 'created_by',
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = fields_mini + [
|
||||
'comment', 'date_created', 'created_by'
|
||||
]
|
||||
fields = fields_mini + fields_small + [
|
||||
'users', 'users_amount',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'created_by': {'label': _('Created by'), 'read_only': True}
|
||||
|
@ -37,8 +40,9 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
|||
self.set_fields_queryset()
|
||||
|
||||
def set_fields_queryset(self):
|
||||
users_field = self.fields['users']
|
||||
users_field.child_relation.queryset = utils.get_current_org_members()
|
||||
users_field = self.fields.get('users')
|
||||
if users_field:
|
||||
users_field.child_relation.queryset = utils.get_current_org_members(exclude=('Auditor',))
|
||||
|
||||
def validate_users(self, users):
|
||||
for user in users:
|
||||
|
@ -50,5 +54,7 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
|||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.annotate(users_amount=Count('users'))
|
||||
queryset = queryset.prefetch_related(
|
||||
Prefetch('users', queryset=User.objects.only('id'))
|
||||
).annotate(users_amount=Count('users'))
|
||||
return queryset
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.utils import validate_ssh_public_key
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.mixins import CommonBulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.permissions import CanUpdateDeleteUser
|
||||
from ..models import User
|
||||
|
@ -13,7 +14,8 @@ from ..models import User
|
|||
__all__ = [
|
||||
'UserSerializer', 'UserPKUpdateSerializer',
|
||||
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
|
||||
'UserProfileSerializer', 'UserDisplaySerializer',
|
||||
'UserProfileSerializer', 'UserOrgSerializer',
|
||||
'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -22,20 +24,43 @@ class UserOrgSerializer(serializers.Serializer):
|
|||
name = serializers.CharField()
|
||||
|
||||
|
||||
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True)
|
||||
class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
||||
EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user')
|
||||
CUSTOM_PASSWORD = _('Set password')
|
||||
PASSWORD_STRATEGY_CHOICES = (
|
||||
(0, EMAIL_SET_PASSWORD),
|
||||
(1, CUSTOM_PASSWORD)
|
||||
)
|
||||
password_strategy = serializers.ChoiceField(
|
||||
choices=PASSWORD_STRATEGY_CHOICES, required=False, initial=0,
|
||||
label=_('Password strategy'), write_only=True
|
||||
)
|
||||
mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display')
|
||||
login_blocked = serializers.SerializerMethodField()
|
||||
can_update = serializers.SerializerMethodField()
|
||||
can_delete = serializers.SerializerMethodField()
|
||||
|
||||
key_prefix_block = "_LOGIN_BLOCK_{}"
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'username', 'password', 'email', 'public_key',
|
||||
'groups', 'role', 'wechat', 'phone', 'mfa_level',
|
||||
# mini 是指能识别对象的最小单元
|
||||
fields_mini = ['id', 'name', 'username']
|
||||
# small 指的是 不需要计算的直接能从一张表中获取到的数据
|
||||
fields_small = fields_mini + [
|
||||
'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', 'mfa_enabled',
|
||||
'mfa_level_display', 'mfa_force_enabled',
|
||||
'comment', 'source', 'is_valid', 'is_expired',
|
||||
'is_active', 'created_by', 'is_first_login',
|
||||
'date_password_last_updated', 'date_expired',
|
||||
'avatar_url', 'admin_or_audit_orgs',
|
||||
'password_strategy', 'date_password_last_updated', 'date_expired',
|
||||
'avatar_url', 'source_display', 'date_joined', 'last_login'
|
||||
]
|
||||
fields = fields_small + [
|
||||
'groups', 'role', 'groups_display', 'role_display',
|
||||
'can_update', 'can_delete', 'login_blocked',
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True},
|
||||
'public_key': {'write_only': True},
|
||||
|
@ -44,8 +69,25 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
'is_expired': {'label': _('Is expired')},
|
||||
'avatar_url': {'label': _('Avatar url')},
|
||||
'created_by': {'read_only': True, 'allow_blank': True},
|
||||
'can_update': {'read_only': True},
|
||||
'can_delete': {'read_only': True},
|
||||
'groups_display': {'label': _('Groups name')},
|
||||
'source_display': {'label': _('Source name')},
|
||||
'role_display': {'label': _('Role name')},
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_role_choices()
|
||||
|
||||
def set_role_choices(self):
|
||||
role = self.fields.get('role')
|
||||
if not role:
|
||||
return
|
||||
choices = role._choices
|
||||
choices.pop('App', None)
|
||||
role._choices = choices
|
||||
|
||||
def validate_role(self, value):
|
||||
request = self.context.get('request')
|
||||
if not request.user.is_superuser and value != User.ROLE_USER:
|
||||
|
@ -67,6 +109,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
return password
|
||||
|
||||
def validate_groups(self, groups):
|
||||
"""
|
||||
审计员不能加入到组中
|
||||
"""
|
||||
role = self.initial_data.get('role')
|
||||
if self.instance:
|
||||
role = role or self.instance.role
|
||||
|
@ -92,19 +137,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
def validate(self, attrs):
|
||||
attrs = self.change_password_to_raw(attrs)
|
||||
attrs = self.clean_auth_fields(attrs)
|
||||
attrs.pop('password_strategy', None)
|
||||
return attrs
|
||||
|
||||
|
||||
class UserDisplaySerializer(UserSerializer):
|
||||
can_update = serializers.SerializerMethodField()
|
||||
can_delete = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(UserSerializer.Meta):
|
||||
fields = UserSerializer.Meta.fields + [
|
||||
'groups_display', 'role_display', 'source_display',
|
||||
'can_update', 'can_delete',
|
||||
]
|
||||
|
||||
def get_can_update(self, obj):
|
||||
return CanUpdateDeleteUser.has_update_object_permission(
|
||||
self.context['request'], self.context['view'], obj
|
||||
|
@ -115,16 +150,10 @@ class UserDisplaySerializer(UserSerializer):
|
|||
self.context['request'], self.context['view'], obj
|
||||
)
|
||||
|
||||
def get_extra_kwargs(self):
|
||||
kwargs = super().get_extra_kwargs()
|
||||
kwargs.update({
|
||||
'can_update': {'read_only': True},
|
||||
'can_delete': {'read_only': True},
|
||||
'groups_display': {'label': _('Groups name')},
|
||||
'source_display': {'label': _('Source name')},
|
||||
'role_display': {'label': _('Role name')},
|
||||
})
|
||||
return kwargs
|
||||
def get_login_blocked(self, obj):
|
||||
key_block = self.key_prefix_block.format(obj.username)
|
||||
blocked = bool(cache.get(key_block))
|
||||
return blocked
|
||||
|
||||
|
||||
class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||
|
@ -156,9 +185,109 @@ class ResetOTPSerializer(serializers.Serializer):
|
|||
pass
|
||||
|
||||
|
||||
class UserProfileSerializer(serializers.ModelSerializer):
|
||||
class UserRoleSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=24)
|
||||
display = serializers.CharField(max_length=64)
|
||||
|
||||
|
||||
class UserProfileSerializer(UserSerializer):
|
||||
admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True)
|
||||
current_org_roles = serializers.ListField(read_only=True)
|
||||
public_key_comment = serializers.CharField(
|
||||
source='get_public_key_comment', required=False, read_only=True, max_length=128
|
||||
)
|
||||
public_key_hash_md5 = serializers.CharField(
|
||||
source='get_public_key_hash_md5', required=False, read_only=True, max_length=128
|
||||
)
|
||||
|
||||
class Meta(UserSerializer.Meta):
|
||||
fields = UserSerializer.Meta.fields + [
|
||||
'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles'
|
||||
]
|
||||
extra_kwargs = dict(UserSerializer.Meta.extra_kwargs)
|
||||
extra_kwargs.update({
|
||||
'name': {'read_only': True, 'max_length': 128},
|
||||
'username': {'read_only': True, 'max_length': 128},
|
||||
'email': {'read_only': True},
|
||||
'mfa_level': {'read_only': True},
|
||||
'source': {'read_only': True},
|
||||
'is_valid': {'read_only': True},
|
||||
'is_active': {'read_only': True},
|
||||
'groups': {'read_only': True},
|
||||
'roles': {'read_only': True},
|
||||
'password_strategy': {'read_only': True},
|
||||
'date_expired': {'read_only': True},
|
||||
'date_joined': {'read_only': True},
|
||||
'last_login': {'read_only': True},
|
||||
'role': {'read_only': True},
|
||||
})
|
||||
|
||||
if 'password' in fields:
|
||||
fields.remove('password')
|
||||
extra_kwargs.pop('password', None)
|
||||
|
||||
if 'public_key' in fields:
|
||||
fields.remove('public_key')
|
||||
extra_kwargs.pop('public_key', None)
|
||||
|
||||
|
||||
class UserUpdatePasswordSerializer(serializers.ModelSerializer):
|
||||
old_password = serializers.CharField(required=True, max_length=128, write_only=True)
|
||||
new_password = serializers.CharField(required=True, max_length=128, write_only=True)
|
||||
new_password_again = serializers.CharField(required=True, max_length=128, write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'id', 'username', 'name', 'role', 'email'
|
||||
]
|
||||
fields = ['old_password', 'new_password', 'new_password_again']
|
||||
|
||||
def validate_old_password(self, value):
|
||||
if not self.instance.check_password(value):
|
||||
msg = 'The old password is incorrect'
|
||||
raise serializers.ValidationError(msg)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def validate_new_password(value):
|
||||
from ..utils import check_password_rules
|
||||
if not check_password_rules(value):
|
||||
msg = _('Password does not match security rules')
|
||||
raise serializers.ValidationError(msg)
|
||||
return value
|
||||
|
||||
def validate_new_password_again(self, value):
|
||||
if value != self.initial_data.get('new_password', ''):
|
||||
msg = 'The newly set password is inconsistent'
|
||||
raise serializers.ValidationError(msg)
|
||||
return value
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
new_password = self.validated_data.get('new_password')
|
||||
instance.reset_password(new_password)
|
||||
return instance
|
||||
|
||||
|
||||
class UserUpdatePublicKeySerializer(serializers.ModelSerializer):
|
||||
public_key_comment = serializers.CharField(
|
||||
source='get_public_key_comment', required=False, read_only=True, max_length=128
|
||||
)
|
||||
public_key_hash_md5 = serializers.CharField(
|
||||
source='get_public_key_hash_md5', required=False, read_only=True, max_length=128
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['public_key_comment', 'public_key_hash_md5', 'public_key']
|
||||
extra_kwargs = {
|
||||
'public_key': {'required': True, 'write_only': True, 'max_length': 2048}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def validate_public_key(value):
|
||||
if not validate_ssh_public_key(value):
|
||||
raise serializers.ValidationError(_('Not a valid ssh public key'))
|
||||
return value
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
new_public_key = self.validated_data.get('public_key')
|
||||
instance.set_public_key(new_public_key)
|
||||
return instance
|
||||
|
|
|
@ -132,7 +132,7 @@ function initTable() {
|
|||
|
||||
$(document).ready(function(){
|
||||
usersTable = initTable();
|
||||
initCsvImportExport(usersTable, "{% trans 'User groups' %}")
|
||||
initCsvImportExport(usersTable, "{% trans 'User' %}")
|
||||
}).on('click', '#btn_bulk_update', function(){
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var id_list = usersTable.selected;
|
||||
|
|
|
@ -21,6 +21,8 @@ urlpatterns = [
|
|||
path('connection-token/', auth_api.UserConnectionTokenApi.as_view(),
|
||||
name='connection-token'),
|
||||
path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
|
||||
path('profile/password/', api.UserPasswordApi.as_view(), name='user-password'),
|
||||
path('profile/public-key/', api.UserPublicKeyApi.as_view(), name='user-public-key'),
|
||||
path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
|
||||
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
|
||||
path('users/<uuid:pk>/password/', api.UserChangePasswordApi.as_view(), name='change-user-password'),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
amqp==2.1.4
|
||||
ansible==2.8.2
|
||||
ansible==2.8.8
|
||||
asn1crypto==0.24.0
|
||||
bcrypt==3.1.4
|
||||
billiard==3.5.0.3
|
||||
|
@ -49,7 +49,7 @@ olefile==0.44
|
|||
openapi-codec==1.3.2
|
||||
paramiko==2.4.2
|
||||
passlib==1.7.1
|
||||
Pillow==6.2.0
|
||||
Pillow==6.2.2
|
||||
pyasn1==0.4.8
|
||||
pycparser==2.19
|
||||
pycrypto==2.6.1
|
||||
|
@ -89,7 +89,7 @@ flower==0.9.3
|
|||
channels-redis==2.4.0
|
||||
channels==2.3.0
|
||||
daphne==2.3.0
|
||||
psutil==5.6.5
|
||||
psutil==5.6.6
|
||||
django-cas-ng==4.0.1
|
||||
python-cas==1.5.0
|
||||
ipython
|
||||
|
|
Loading…
Reference in New Issue