From dd5b2b910172c0df27f675a82b7af14a670bdcb0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 Jan 2021 18:33:38 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E5=8E=BB=E6=8E=89=20v2=20=E7=9A=84api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 13 - apps/jumpserver/views/swagger.py | 12 +- apps/terminal/api/terminal.py | 22 +- apps/terminal/api_v2/__init__.py | 3 - apps/terminal/api_v2/terminal.py | 44 ---- apps/terminal/serializers/terminal.py | 38 ++- apps/terminal/serializers_v2/__init__.py | 4 - apps/terminal/serializers_v2/terminal.py | 60 ----- apps/terminal/urls/api_urls.py | 1 + apps/terminal/urls/api_urls_v2.py | 26 -- apps/users/api/__init__.py | 1 + .../user.py => api/service_account.py} | 2 +- apps/users/api_v2/__init__.py | 4 - apps/users/serializers/__init__.py | 1 + apps/users/serializers/profile.py | 177 ++++++++++++++ apps/users/serializers/user.py | 222 ++++-------------- apps/users/serializers_v2/__init__.py | 3 - apps/users/serializers_v2/user.py | 48 ---- apps/users/urls/api_urls.py | 1 + apps/users/urls/api_urls_v2.py | 23 -- 20 files changed, 285 insertions(+), 420 deletions(-) delete mode 100644 apps/terminal/api_v2/__init__.py delete mode 100644 apps/terminal/api_v2/terminal.py delete mode 100644 apps/terminal/serializers_v2/__init__.py delete mode 100644 apps/terminal/serializers_v2/terminal.py delete mode 100644 apps/terminal/urls/api_urls_v2.py rename apps/users/{api_v2/user.py => api/service_account.py} (87%) delete mode 100644 apps/users/api_v2/__init__.py create mode 100644 apps/users/serializers/profile.py delete mode 100644 apps/users/serializers_v2/__init__.py delete mode 100644 apps/users/serializers_v2/user.py delete mode 100644 apps/users/urls/api_urls_v2.py diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 713db5791..306b1d9ea 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -4,8 +4,6 @@ from __future__ import unicode_literals from django.urls import path, include, re_path from django.conf import settings from django.conf.urls.static import static -from django.conf.urls.i18n import i18n_patterns -from django.http import HttpResponse from django.views.i18n import JavaScriptCatalog from . import views, api @@ -27,11 +25,6 @@ api_v1 = [ path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()) ] -api_v2 = [ - path('terminal/', include('terminal.urls.api_urls_v2', namespace='api-terminal-v2')), - path('users/', include('users.urls.api_urls_v2', namespace='api-users-v2')), -] - app_view_patterns = [ path('auth/', include('authentication.urls.view_urls'), name='auth'), path('ops/', include('ops.urls.view_urls'), name='ops'), @@ -53,7 +46,6 @@ apps = [ urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('api/v1/', include(api_v1)), - path('api/v2/', include(api_v2)), re_path('api/(?P\w+)/(?Pv\d)/.*', views.redirect_format_api), path('api/health/', views.HealthCheckView.as_view(), name="health"), # External apps url @@ -77,11 +69,6 @@ urlpatterns += [ views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), re_path('api/docs/?', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"), re_path('api/redoc/?', views.get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'), - - re_path('^api/v2/swagger(?P\.json|\.yaml)$', - views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), - path('api/docs/v2/', views.get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"), - path('api/redoc/v2/', views.get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'), ] diff --git a/apps/jumpserver/views/swagger.py b/apps/jumpserver/views/swagger.py index 34a5777c6..b42d283b6 100644 --- a/apps/jumpserver/views/swagger.py +++ b/apps/jumpserver/views/swagger.py @@ -60,20 +60,12 @@ api_info = openapi.Info( def get_swagger_view(version='v1'): - from ..urls import api_v1, api_v2 + from ..urls import api_v1 from django.urls import path, include api_v1_patterns = [ path('api/v1/', include(api_v1)) ] - - api_v2_patterns = [ - path('api/v2/', include(api_v2)) - ] - - if version == "v2": - patterns = api_v2_patterns - else: - patterns = api_v1_patterns + patterns = api_v1_patterns schema_view = get_schema_view( api_info, patterns=patterns, diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/terminal.py index 26b030ff8..444061035 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/terminal.py @@ -5,18 +5,22 @@ import uuid from django.core.cache import cache from django.shortcuts import get_object_or_404 -from rest_framework import viewsets +from rest_framework import viewsets, generics from rest_framework.views import APIView, Response +from rest_framework import status +from django.conf import settings + from common.drf.api import JMSBulkModelViewSet from common.utils import get_object_or_none -from common.permissions import IsAppUser, IsOrgAdminOrAppUser, IsSuperUser +from common.permissions import IsAppUser, IsOrgAdminOrAppUser, IsSuperUser, WithBootstrapToken from ..models import Terminal, Status, Session from .. import serializers from .. import exceptions __all__ = [ 'TerminalViewSet', 'StatusViewSet', 'TerminalConfig', + 'TerminalRegistrationApi', ] logger = logging.getLogger(__file__) @@ -112,4 +116,16 @@ class TerminalConfig(APIView): def get(self, request): config = request.user.terminal.config - return Response(config, status=200) \ No newline at end of file + return Response(config, status=200) + + +class TerminalRegistrationApi(generics.CreateAPIView): + serializer_class = serializers.TerminalRegistrationSerializer + permission_classes = [WithBootstrapToken] + http_method_names = ['post'] + + def create(self, request, *args, **kwargs): + if not settings.SECURITY_SERVICE_ACCOUNT_REGISTRATION: + data = {"error": "service account registration disabled"} + return Response(data=data, status=status.HTTP_400_BAD_REQUEST) + return super().create(request, *args, **kwargs) diff --git a/apps/terminal/api_v2/__init__.py b/apps/terminal/api_v2/__init__.py deleted file mode 100644 index 0dfa92ec1..000000000 --- a/apps/terminal/api_v2/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .terminal import * diff --git a/apps/terminal/api_v2/terminal.py b/apps/terminal/api_v2/terminal.py deleted file mode 100644 index 57b8ab3e3..000000000 --- a/apps/terminal/api_v2/terminal.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import viewsets, generics -from rest_framework import status -from rest_framework.response import Response -from django.conf import settings - -from common.permissions import IsSuperUser, WithBootstrapToken - - -from ..models import Terminal -from .. import serializers_v2 as serializers - -__all__ = ['TerminalViewSet', 'TerminalRegistrationApi'] - - -class TerminalViewSet(viewsets.ModelViewSet): - queryset = Terminal.objects.filter(is_deleted=False) - serializer_class = serializers.TerminalSerializer - permission_classes = [IsSuperUser] - http_method_names = [ - 'get', 'put', 'patch', 'delete', 'head', 'options', 'trace' - ] - - -class TerminalRegistrationApi(generics.CreateAPIView): - serializer_class = serializers.TerminalRegistrationSerializer - permission_classes = [WithBootstrapToken] - http_method_names = ['post'] - - def create(self, request, *args, **kwargs): - data = {k: v for k, v in request.data.items()} - serializer = serializers.TerminalSerializer( - data=data, context={'request': request} - ) - if not settings.SECURITY_SERVICE_ACCOUNT_REGISTRATION: - data = {"error": "service account registration disabled"} - return Response(data=data, status=status.HTTP_400_BAD_REQUEST) - serializer.is_valid(raise_exception=True) - terminal = serializer.save() - sa_serializer = serializer.sa_serializer_class(instance=terminal.user) - data['service_account'] = sa_serializer.data - return Response(data, status=status.HTTP_201_CREATED) - diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index 33d46ada1..caffba522 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -3,6 +3,9 @@ from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer, AdaptedBulkListSerializer from common.utils import is_uuid +from users.serializers import ServiceAccountSerializer +from common.utils import get_request_ip + from ..models import ( Terminal, Status, Session, Task, CommandStorage, ReplayStorage ) @@ -63,9 +66,42 @@ class StatusSerializer(serializers.ModelSerializer): class TaskSerializer(BulkModelSerializer): - class Meta: fields = '__all__' model = Task list_serializer_class = AdaptedBulkListSerializer ref_name = 'TerminalTaskSerializer' + + +class TerminalRegistrationSerializer(serializers.ModelSerializer): + service_account = ServiceAccountSerializer(read_only=True) + + class Meta: + model = Terminal + fields = ['name', 'type', 'comment', 'service_account', 'remote_addr'] + extra_fields = { + 'remote_addr': {'readonly': True} + } + + def is_valid(self, raise_exception=False): + valid = super().is_valid(raise_exception=raise_exception) + if not valid: + return valid + data = {'name': self.validated_data.get('name')} + kwargs = {'data': data} + if self.instance and self.instance.user: + kwargs['instance'] = self.instance.user + self.service_account = ServiceAccountSerializer(**kwargs) + valid = self.service_account.is_valid(raise_exception=True) + return valid + + def save(self, **kwargs): + instance = super().save(**kwargs) + request = self.context.get('request') + instance.is_accepted = True + if request: + instance.remote_addr = get_request_ip(request) + sa = self.service_account.save() + instance.user = sa + instance.save() + return instance diff --git a/apps/terminal/serializers_v2/__init__.py b/apps/terminal/serializers_v2/__init__.py deleted file mode 100644 index 9161be085..000000000 --- a/apps/terminal/serializers_v2/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from .terminal import * diff --git a/apps/terminal/serializers_v2/terminal.py b/apps/terminal/serializers_v2/terminal.py deleted file mode 100644 index 8121410e6..000000000 --- a/apps/terminal/serializers_v2/terminal.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.conf import settings -from rest_framework import serializers - -from common.utils import get_request_ip -from users.serializers_v2 import ServiceAccountSerializer -from ..models import Terminal - - -__all__ = ['TerminalSerializer', 'TerminalRegistrationSerializer'] - - -class TerminalSerializer(serializers.ModelSerializer): - sa_serializer_class = ServiceAccountSerializer - sa_serializer = None - - class Meta: - model = Terminal - fields = [ - 'id', 'name', 'type', 'remote_addr', 'command_storage', - 'replay_storage', 'user', 'is_accepted', 'is_deleted', - 'date_created', 'comment' - ] - read_only_fields = ['remote_addr', 'user', 'date_created'] - - def is_valid(self, raise_exception=False): - valid = super().is_valid(raise_exception=raise_exception) - if not valid: - return valid - data = {'name': self.validated_data.get('name')} - kwargs = {'data': data} - if self.instance and self.instance.user: - kwargs['instance'] = self.instance.user - self.sa_serializer = ServiceAccountSerializer(**kwargs) - valid = self.sa_serializer.is_valid(raise_exception=True) - return valid - - def save(self, **kwargs): - instance = super().save(**kwargs) - sa = self.sa_serializer.save() - instance.user = sa - instance.save() - return instance - - def create(self, validated_data): - request = self.context.get('request') - instance = super().create(validated_data) - instance.is_accepted = True - if request: - instance.remote_addr = get_request_ip(request) - instance.save() - return instance - - -class TerminalRegistrationSerializer(serializers.Serializer): - name = serializers.CharField(max_length=128) - comment = serializers.CharField(max_length=128) - type = serializers.CharField(max_length=64) - service_account = ServiceAccountSerializer(read_only=True) diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 077494d01..38b6df976 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -22,6 +22,7 @@ router.register(r'replay-storages', api.ReplayStorageViewSet, 'replay-storage') router.register(r'command-storages', api.CommandStorageViewSet, 'command-storage') urlpatterns = [ + path('terminal-registrations/', api.TerminalRegistrationApi.as_view(), name='terminal-registration'), path('sessions/join/validate/', api.SessionJoinValidateAPI.as_view(), name='join-session-validate'), path('sessions//replay/', api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), diff --git a/apps/terminal/urls/api_urls_v2.py b/apps/terminal/urls/api_urls_v2.py deleted file mode 100644 index 42c9ffdb5..000000000 --- a/apps/terminal/urls/api_urls_v2.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -from django.urls import path, re_path -from rest_framework_bulk.routes import BulkRouter - -from common import api as capi -from .. import api_v2 as api - -app_name = 'terminal' - -router = BulkRouter() -router.register(r'terminals', api.TerminalViewSet, 'terminal') - - -urlpatterns = [ - path('terminal-registrations/', api.TerminalRegistrationApi.as_view(), - name='terminal-registration') -] - -old_version_urlpatterns = [ - re_path('(?Pterminal)/.*', capi.redirect_plural_name_api) -] - -urlpatterns += router.urls diff --git a/apps/users/api/__init__.py b/apps/users/api/__init__.py index df750c107..d5db61888 100644 --- a/apps/users/api/__init__.py +++ b/apps/users/api/__init__.py @@ -4,4 +4,5 @@ from .user import * from .group import * from .profile import * +from .service_account import * from .relation import * diff --git a/apps/users/api_v2/user.py b/apps/users/api/service_account.py similarity index 87% rename from apps/users/api_v2/user.py rename to apps/users/api/service_account.py index fe097fa3f..783ba9162 100644 --- a/apps/users/api_v2/user.py +++ b/apps/users/api/service_account.py @@ -3,7 +3,7 @@ from rest_framework import viewsets from common.permissions import WithBootstrapToken -from .. import serializers_v2 as serializers +from .. import serializers class ServiceAccountRegistrationViewSet(viewsets.ModelViewSet): diff --git a/apps/users/api_v2/__init__.py b/apps/users/api_v2/__init__.py deleted file mode 100644 index 3c486a98c..000000000 --- a/apps/users/api_v2/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from .user import * diff --git a/apps/users/serializers/__init__.py b/apps/users/serializers/__init__.py index 41812a5c8..3dc95be36 100644 --- a/apps/users/serializers/__init__.py +++ b/apps/users/serializers/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # from .user import * +from .profile import * from .group import * from .realtion import * diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py new file mode 100644 index 000000000..b1611307a --- /dev/null +++ b/apps/users/serializers/profile.py @@ -0,0 +1,177 @@ +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from common.utils import validate_ssh_public_key +from ..models import User + +from .user import UserSerializer + + +class UserOrgSerializer(serializers.Serializer): + id = serializers.CharField() + name = serializers.CharField() + + +class UserOrgLabelSerializer(serializers.Serializer): + value = serializers.CharField(source='id') + label = serializers.CharField(source='name') + + +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 = ['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 + + +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) + user_all_orgs = UserOrgLabelSerializer(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 + ) + MFA_LEVEL_CHOICES = ( + (0, _('Disable')), + (1, _('Enable')), + ) + mfa_level = serializers.ChoiceField(choices=MFA_LEVEL_CHOICES, label=_('MFA'), required=False) + guide_url = serializers.SerializerMethodField() + + class Meta(UserSerializer.Meta): + fields = UserSerializer.Meta.fields + [ + 'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles', + 'guide_url', 'user_all_orgs' + ] + read_only_fields = [ + 'date_joined', 'last_login', 'created_by', 'source' + ] + 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}, + 'is_first_login': {'label': _('Is first login'), 'read_only': False}, + '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) + + @staticmethod + def get_guide_url(obj): + return settings.USER_GUIDE_URL + + def validate_mfa_level(self, mfa_level): + if self.instance and self.instance.mfa_force_enabled: + return 2 + return mfa_level + + def validate_public_key(self, public_key): + if self.instance and self.instance.can_update_ssh_key(): + if not validate_ssh_public_key(public_key): + raise serializers.ValidationError(_('Not a valid ssh public key')) + return public_key + return None + + +class UserPKUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'public_key'] + + @staticmethod + def validate_public_key(value): + if not validate_ssh_public_key(value): + raise serializers.ValidationError(_('Not a valid ssh public key')) + return value + + +class ChangeUserPasswordSerializer(serializers.ModelSerializer): + + class Meta: + model = User + fields = ['password'] + +class ResetOTPSerializer(serializers.Serializer): + msg = serializers.CharField(read_only=True) + + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + pass diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index b44ac050f..f1af1bfe9 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- # from django.core.cache import cache -from django.conf import settings 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 CommonBulkSerializerMixin from common.permissions import CanUpdateDeleteUser from orgs.models import ROLE as ORG_ROLE @@ -13,24 +11,11 @@ from ..models import User __all__ = [ - 'UserSerializer', 'UserPKUpdateSerializer', - 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', - 'UserProfileSerializer', 'UserOrgSerializer', - 'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer', - 'UserRetrieveSerializer', 'MiniUserSerializer', 'InviteSerializer' + 'UserSerializer', 'UserRetrieveSerializer', 'MiniUserSerializer', + 'InviteSerializer', 'ServiceAccountSerializer', ] -class UserOrgSerializer(serializers.Serializer): - id = serializers.CharField() - name = serializers.CharField() - - -class UserOrgLabelSerializer(serializers.Serializer): - value = serializers.CharField(source='id') - label = serializers.CharField(source='name') - - class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') CUSTOM_PASSWORD = _('Set password') @@ -181,166 +166,6 @@ class UserRetrieveSerializer(UserSerializer): fields = UserSerializer.Meta.fields + ['login_confirm_settings'] -class UserPKUpdateSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ['id', 'public_key'] - - @staticmethod - def validate_public_key(value): - if not validate_ssh_public_key(value): - raise serializers.ValidationError(_('Not a valid ssh public key')) - return value - - -class ChangeUserPasswordSerializer(serializers.ModelSerializer): - - class Meta: - model = User - fields = ['password'] - - -class ResetOTPSerializer(serializers.Serializer): - msg = serializers.CharField(read_only=True) - - def create(self, validated_data): - pass - - def update(self, instance, validated_data): - pass - - -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) - user_all_orgs = UserOrgLabelSerializer(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 - ) - MFA_LEVEL_CHOICES = ( - (0, _('Disable')), - (1, _('Enable')), - ) - mfa_level = serializers.ChoiceField(choices=MFA_LEVEL_CHOICES, label=_('MFA'), required=False) - guide_url = serializers.SerializerMethodField() - - class Meta(UserSerializer.Meta): - fields = UserSerializer.Meta.fields + [ - 'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles', - 'guide_url', 'user_all_orgs' - ] - read_only_fields = [ - 'date_joined', 'last_login', 'created_by', 'source' - ] - 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}, - 'is_first_login': {'label': _('Is first login'), 'read_only': False}, - '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) - - @staticmethod - def get_guide_url(obj): - return settings.USER_GUIDE_URL - - def validate_mfa_level(self, mfa_level): - if self.instance and self.instance.mfa_force_enabled: - return 2 - return mfa_level - - def validate_public_key(self, public_key): - if self.instance and self.instance.can_update_ssh_key(): - if not validate_ssh_public_key(public_key): - raise serializers.ValidationError(_('Not a valid ssh public key')) - return public_key - return 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 = ['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 - - class MiniUserSerializer(serializers.ModelSerializer): class Meta: model = User @@ -352,3 +177,46 @@ class InviteSerializer(serializers.Serializer): queryset=User.objects.exclude(role=User.ROLE.APP) ) role = serializers.ChoiceField(choices=ORG_ROLE.choices) + + +class ServiceAccountSerializer(serializers.ModelSerializer): + + class Meta: + model = User + fields = ['id', 'name', 'access_key'] + read_only_fields = ['access_key'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + from authentication.serializers import AccessKeySerializer + self.fields['access_key'] = AccessKeySerializer(read_only=True) + + def get_username(self): + return self.initial_data.get('name') + + def get_email(self): + name = self.initial_data.get('name') + return '{}@serviceaccount.local'.format(name) + + def validate_name(self, name): + email = self.get_email() + username = self.get_username() + if self.instance: + users = User.objects.exclude(id=self.instance.id) + else: + users = User.objects.all() + if users.filter(email=email) or \ + users.filter(username=username): + raise serializers.ValidationError(_('name not unique'), code='unique') + return name + + def save(self, **kwargs): + self.validated_data['email'] = self.get_email() + self.validated_data['username'] = self.get_username() + self.validated_data['role'] = User.ROLE.APP + return super().save(**kwargs) + + def create(self, validated_data): + instance = super().create(validated_data) + instance.create_access_key() + return instance diff --git a/apps/users/serializers_v2/__init__.py b/apps/users/serializers_v2/__init__.py deleted file mode 100644 index c2dce9535..000000000 --- a/apps/users/serializers_v2/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .user import * diff --git a/apps/users/serializers_v2/user.py b/apps/users/serializers_v2/user.py deleted file mode 100644 index d2b9cfa1c..000000000 --- a/apps/users/serializers_v2/user.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.utils.translation import ugettext as _ -from rest_framework import serializers -from ..models import User - -from authentication.serializers import AccessKeySerializer - -__all__ = ['ServiceAccountSerializer'] - - -class ServiceAccountSerializer(serializers.ModelSerializer): - access_key = AccessKeySerializer(read_only=True) - - class Meta: - model = User - fields = ['id', 'name', 'access_key'] - read_only_fields = ['access_key'] - - def get_username(self): - return self.initial_data.get('name') - - def get_email(self): - name = self.initial_data.get('name') - return '{}@serviceaccount.local'.format(name) - - def validate_name(self, name): - email = self.get_email() - username = self.get_username() - if self.instance: - users = User.objects.exclude(id=self.instance.id) - else: - users = User.objects.all() - if users.filter(email=email) or \ - users.filter(username=username): - raise serializers.ValidationError(_('name not unique'), code='unique') - return name - - def save(self, **kwargs): - self.validated_data['email'] = self.get_email() - self.validated_data['username'] = self.get_username() - self.validated_data['role'] = User.ROLE.APP - return super().save(**kwargs) - - def create(self, validated_data): - instance = super().create(validated_data) - instance.create_access_key() - return instance diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index ea1101eae..e6b6974c2 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -15,6 +15,7 @@ router = BulkRouter() router.register(r'users', api.UserViewSet, 'user') router.register(r'groups', api.UserGroupViewSet, 'user-group') router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation') +router.register(r'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration') urlpatterns = [ diff --git a/apps/users/urls/api_urls_v2.py b/apps/users/urls/api_urls_v2.py deleted file mode 100644 index dc3c6e249..000000000 --- a/apps/users/urls/api_urls_v2.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# ~*~ coding: utf-8 ~*~ -# -from __future__ import absolute_import - -from django.urls import path, include -from rest_framework_bulk.routes import BulkRouter -from .. import api_v2 as api - -app_name = 'users' - -router = BulkRouter() -router.register(r'service-account-registrations', - api.ServiceAccountRegistrationViewSet, - 'service-account-registration') - - -urlpatterns = [ - # path('token/', api.UserToken.as_view(), name='user-token'), -] -urlpatterns += router.urls - -