Merge branch 'lina' of github.com:jumpserver/jumpserver into lina

pull/3960/head
ibuler 2020-04-30 16:53:21 +08:00
commit b92137afd9
11 changed files with 254 additions and 17 deletions

View File

@ -1,10 +1,16 @@
# -*- coding: utf-8 -*-
#
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from django.db.models import Q
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
from common.mixins.api import CommonApiMixin
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin
from common.drf.filters import DatetimeRangeFilter
from orgs.mixins.api import OrgModelViewSet
from .models import FTPLog
from .serializers import FTPLogSerializer
from orgs.utils import current_org
from .models import FTPLog, UserLoginLog
from .serializers import FTPLogSerializer, UserLoginLogSerializer
class FTPLogViewSet(OrgModelViewSet):
@ -12,3 +18,29 @@ class FTPLogViewSet(OrgModelViewSet):
serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
http_method_names = ['get', 'post', 'head', 'options']
class UserLoginLogViewSet(CommonApiMixin,
ListModelMixin,
GenericViewSet):
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

View File

@ -14,10 +14,13 @@ class FTPLogSerializer(serializers.ModelSerializer):
fields = '__all__'
class LoginLogSerializer(serializers.ModelSerializer):
class UserLoginLogSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserLoginLog
fields = '__all__'
fields = (
'username', 'type', 'ip', 'city', 'user_agent',
'mfa', 'reason', 'status', 'datetime'
)
class OperateLogSerializer(serializers.ModelSerializer):

View File

@ -12,6 +12,7 @@ app_name = "audits"
router = DefaultRouter()
router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log')
router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log')
urlpatterns = [
]

View File

@ -4,7 +4,9 @@ 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
@ -13,15 +15,48 @@ __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:

View File

@ -36,5 +36,3 @@ class DatetimeSearchMixin:
def get(self, request, *args, **kwargs):
self.get_date_range()
return super().get(request, *args, **kwargs)

View File

@ -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:
@ -275,3 +275,26 @@ class PublicSettingApi(generics.RetrieveAPIView):
return instance
class SettingsApi(generics.RetrieveUpdateAPIView):
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()

View File

@ -4,3 +4,4 @@
from .email import *
from .ldap import *
from .public import *
from .settings import *

View File

@ -0,0 +1,124 @@
# coding: utf-8
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from ..models import Setting
__all__ = ['SettingsSerializer']
class BasicSettingSerializer(serializers.Serializer):
SITE_URL = serializers.URLField(required=True)
USER_GUIDE_URL = serializers.URLField(required=False, allow_blank=True, )
EMAIL_SUBJECT_PREFIX = serializers.CharField(max_length=1024, required=True)
class EmailSettingSerializer(serializers.Serializer):
encrypt_fields = ["EMAIL_HOST_PASSWORD", ]
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
EMAIL_PORT = serializers.CharField(max_length=5, required=True)
EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True)
EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, )
EMAIL_FROM = serializers.CharField(max_length=128, allow_blank=True, required=False)
EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank=True, required=False)
EMAIL_USE_SSL = serializers.BooleanField(required=False)
EMAIL_USE_TLS = serializers.BooleanField(required=False)
class EmailContentSettingSerializer(serializers.Serializer):
EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, allow_blank=True, required=False, )
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, allow_blank=True, required=False, )
EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, allow_blank=True, required=False)
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, allow_blank=True, required=False)
class LdapSettingSerializer(serializers.Serializer):
encrypt_fields = ["AUTH_LDAP_BIND_PASSWORD", ]
AUTH_LDAP_SERVER_URI = serializers.CharField(required=True)
AUTH_LDAP_BIND_DN = serializers.CharField(required=False)
AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False)
AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False)
AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True)
AUTH_LDAP_USER_ATTR_MAP = serializers.CharField(max_length=1024, 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=5, 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()

View File

@ -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'),
]

View File

@ -2,3 +2,4 @@
#
from .ldap import *
from .common import *

View File

@ -0,0 +1,18 @@
# coding: utf-8
class ObjectDict(dict):
def __getattr__(self, name):
if name in self:
return self[name]
else:
raise AttributeError("No such attribute: " + name)
def __setattr__(self, name, value):
self[name] = value
def __delattr__(self, name):
if name in self:
del self[name]
else:
raise AttributeError("No such attribute: " + name)