未验证 提交 087ba9ae 编写于 作者: B BaiJiangJie 提交者: GitHub

Merge pull request #4059 from jumpserver/lina

Lina
......@@ -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
......
# ~*~ 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
......
# -*- 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'},
}
# -*- 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']
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
# 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)
......
# -*- 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
GET = 'GET'
POST = 'POST'
PUT = 'PUT'
PATCH = 'PATCH'
DELETE = 'DELETE'
OPTIONS = 'OPTIONS'
# -*- 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)
# -*- 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_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_assets'):
data.update({
'total_count_assets': self.get_total_count_assets(),
})
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 = ()
......
# 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
# -*- 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校验做准备)
......
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,)
......
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
# 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,)
......
# 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()
# 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
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)
# 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'
]
# 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):
......
# 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):
......
# 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'
]
from django.test import TestCase
from django.contrib.sessions.backends import file, db, cache
from django.contrib.auth.views import login
\ No newline at end of file
......@@ -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 *
# 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 *
# 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):
......
# 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(
......
# -*- 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
# -*- 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'),
......
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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册