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

pull/3986/head
ibuler 2020-05-09 14:51:47 +08:00
commit 0bdc425c55
16 changed files with 853 additions and 1104 deletions

View File

@ -2,27 +2,34 @@
#
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from django.db.models import Q
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 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 .models import FTPLog, UserLoginLog
from .serializers import FTPLogSerializer, UserLoginLogSerializer
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(CommonApiMixin,
ListModelMixin,
GenericViewSet):
class UserLoginLogViewSet(ListModelMixin,
CommonGenericViewSet):
queryset = UserLoginLog.objects.all()
permission_classes = [IsOrgAdmin | IsOrgAuditor]
serializer_class = UserLoginLogSerializer
@ -44,3 +51,47 @@ class UserLoginLogViewSet(CommonApiMixin,
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']

32
apps/audits/filters.py Normal file
View File

@ -0,0 +1,32 @@
from rest_framework import filters
from rest_framework.compat import coreapi, coreschema
from orgs.utils import current_org
__all__ = ['CurrentOrgMembersFilter']
class CurrentOrgMembersFilter(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='user', location='query', required=False, type='string',
schema=coreschema.String(
title='user',
description='user'
)
)
]
def _get_user_list(self):
users = current_org.get_org_members(exclude=('Auditor',))
return users
def filter_queryset(self, request, queryset, view):
user_id = request.GET.get('user')
if user_id:
queryset = queryset.filter(user=user_id)
else:
queryset = queryset.filter(user__in=self._get_user_list())
return queryset

View File

@ -0,0 +1,28 @@
# Generated by Django 2.2.10 on 2020-05-08 13:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audits', '0007_auto_20191202_1010'),
]
operations = [
migrations.AlterField(
model_name='ftplog',
name='date_start',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date start'),
),
migrations.AlterField(
model_name='operatelog',
name='datetime',
field=models.DateTimeField(auto_now=True, verbose_name='Datetime'),
),
migrations.AlterField(
model_name='passwordchangelog',
name='datetime',
field=models.DateTimeField(auto_now=True, verbose_name='Datetime'),
),
]

View File

@ -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)

View File

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from terminal.models import Session
from ops.models import CommandExecution
from . import models
@ -11,31 +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 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 = (
'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
'mfa', 'reason', 'status', 'status_display', 'datetime'
'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):
@ -43,3 +53,15 @@ 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')},
'is_success': {'label': _('Is success')},
}

View File

@ -13,6 +13,9 @@ 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 = [
]

View File

@ -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

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
import coreapi
from rest_framework import filters
from rest_framework.fields import DateTimeField
from rest_framework.serializers import ValidationError
@ -146,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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
# Generated by Django 2.2.10 on 2020-05-08 13:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
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='hosts',
field=models.ManyToManyField(to='assets.Asset', verbose_name='Hosts'),
),
migrations.AlterField(
model_name='commandexecution',
name='is_finished',
field=models.BooleanField(default=False, verbose_name='Is finished'),
),
migrations.AlterField(
model_name='commandexecution',
name='run_as',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='Run as'),
),
migrations.AlterField(
model_name='commandexecution',
name='user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@ -18,15 +18,15 @@ from ..inventory import JMSInventory
class CommandExecution(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
hosts = models.ManyToManyField('assets.Asset')
run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE)
hosts = models.ManyToManyField('assets.Asset', verbose_name=_('Hosts'))
run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, verbose_name=_('Run as'))
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)
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True, verbose_name=_('User'))
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]

View File

@ -1,7 +1,7 @@
# -*- 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
@ -44,6 +44,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()

View File

@ -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)})
@ -276,6 +276,7 @@ class PublicSettingApi(generics.RetrieveAPIView):
class SettingsApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
serializer_class = SettingsSerializer
def get_object(self):

View File

@ -42,7 +42,7 @@ class LdapSettingSerializer(serializers.Serializer):
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_USER_ATTR_MAP = serializers.DictField(required=True)
AUTH_LDAP = serializers.BooleanField(required=False)
@ -75,7 +75,7 @@ class SecuritySettingSerializer(serializers.Serializer):
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_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)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.10 on 2020-05-08 13:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0025_auto_20200206_1216'),
]
operations = [
migrations.AlterField(
model_name='user',
name='source',
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='ldap', max_length=30, verbose_name='Source'),
),
]