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

This commit is contained in:
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 -*- # -*- 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 orgs.mixins.api import OrgModelViewSet
from .models import FTPLog from orgs.utils import current_org
from .serializers import FTPLogSerializer from .models import FTPLog, UserLoginLog
from .serializers import FTPLogSerializer, UserLoginLogSerializer
class FTPLogViewSet(OrgModelViewSet): class FTPLogViewSet(OrgModelViewSet):
@ -12,3 +18,29 @@ class FTPLogViewSet(OrgModelViewSet):
serializer_class = FTPLogSerializer serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
http_method_names = ['get', 'post', 'head', 'options'] 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__' fields = '__all__'
class LoginLogSerializer(serializers.ModelSerializer): class UserLoginLogSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.UserLoginLog model = models.UserLoginLog
fields = '__all__' fields = (
'username', 'type', 'ip', 'city', 'user_agent',
'mfa', 'reason', 'status', 'datetime'
)
class OperateLogSerializer(serializers.ModelSerializer): class OperateLogSerializer(serializers.ModelSerializer):

View File

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

View File

@ -4,7 +4,9 @@ import coreapi
from rest_framework import filters from rest_framework import filters
from rest_framework.fields import DateTimeField from rest_framework.fields import DateTimeField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.compat import coreapi, coreschema
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured
import logging import logging
from common import const from common import const
@ -13,15 +15,48 @@ __all__ = ["DatetimeRangeFilter", "IDSpmFilter", 'IDInFilter', "CustomFilter"]
class DatetimeRangeFilter(filters.BaseFilterBackend): 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'): if not hasattr(view, 'date_range_filter_fields'):
return queryset return {}
try: try:
fields = dict(view.date_range_filter_fields) return dict(view.date_range_filter_fields)
except ValueError: 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) logging.error(msg)
return queryset raise ImproperlyConfigured(msg)
def filter_queryset(self, request, queryset, view):
fields = self._get_date_range_filter_fields(view)
kwargs = {} kwargs = {}
for attr, date_range_keyword in fields.items(): for attr, date_range_keyword in fields.items():
if len(date_range_keyword) != 2: if len(date_range_keyword) != 2:

View File

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

View File

@ -2,28 +2,28 @@
# #
import json import json
from collections.abc import Iterable
from smtplib import SMTPSenderRefused from smtplib import SMTPSenderRefused
from rest_framework import generics from rest_framework import generics
from rest_framework.views import Response, APIView from rest_framework.views import Response, APIView
from django.conf import settings from django.conf import settings
from django.core.mail import send_mail, get_connection from django.core.mail import send_mail, get_connection
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from .utils import ( from .utils import (
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil, LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
LDAP_USE_CACHE_FLAGS, LDAPTestUtil, LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict
) )
from .tasks import sync_ldap_user_task from .tasks import sync_ldap_user_task
from common.permissions import IsOrgAdmin, IsSuperUser from common.permissions import IsOrgAdmin, IsSuperUser
from common.utils import get_logger from common.utils import get_logger
from .serializers import ( from .serializers import (
MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer, MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer,
PublicSettingSerializer, LDAPTestLoginSerializer, PublicSettingSerializer, LDAPTestLoginSerializer, SettingsSerializer
) )
from users.models import User from users.models import User
logger = get_logger(__file__) logger = get_logger(__file__)
@ -59,7 +59,7 @@ class MailTestingAPI(APIView):
use_tls=email_use_tls, use_ssl=email_use_ssl, use_tls=email_use_tls, use_ssl=email_use_ssl,
) )
send_mail( send_mail(
subject, message, email_from, [email_recipient], subject, message, email_from, [email_recipient],
connection=connection connection=connection
) )
except SMTPSenderRefused as e: except SMTPSenderRefused as e:
@ -275,3 +275,26 @@ class PublicSettingApi(generics.RetrieveAPIView):
return instance 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 .email import *
from .ldap import * from .ldap import *
from .public 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/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'),
path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'), 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'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'),
] ]

View File

@ -2,3 +2,4 @@
# #
from .ldap import * 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)