From 0f35b3dd582b44bb6ba25cb684fe45da816b500d Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 25 Nov 2022 23:09:55 +0800 Subject: [PATCH] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20connect=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 3 -- apps/assets/models/asset/common.py | 4 -- apps/authentication/api/connection_token.py | 17 +++--- apps/authentication/api/perm_token.py | 0 apps/authentication/api/temp_token.py | 2 +- .../migrations/0015_auto_20221125_2240.py | 49 ++++++++++++++++++ .../authentication/models/connection_token.py | 7 +-- .../serializers/connection_token.py | 10 ++-- apps/perms/api/perm_token.py | 5 -- apps/static/img/logo_text_white.png | Bin 8320 -> 5027 bytes 10 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 apps/authentication/api/perm_token.py create mode 100644 apps/authentication/migrations/0015_auto_20221125_2240.py diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 9452a76f5..f7f788e72 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -68,9 +68,6 @@ class SerializeToTreeNodeMixin: 'data': { 'id': asset.id, 'name': asset.name, - 'address': asset.address, - 'protocols': asset.protocols_as_list, - 'platform': asset.platform.id, 'org_name': asset.org_name }, } diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index c9baf8818..c7012bc60 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -160,10 +160,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): return 0 return self.primary_protocol.port - @property - def protocols_as_list(self): - return [{'name': p.name, 'port': p.port} for p in self.protocols.all()] - @lazyproperty def type(self): return self.platform.type diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 1636c2cb1..59b0b7593 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -15,8 +15,8 @@ from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.http import is_true +from common.utils import random_string from orgs.mixins.api import RootOrgViewMixin -from orgs.utils import tmp_to_root_org from perms.models import ActionChoices from terminal.models import EndpointRule from ..models import ConnectionToken @@ -249,10 +249,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView serializer = self.get_serializer(instance=token) return Response(serializer.data, status=status.HTTP_200_OK) - def dispatch(self, request, *args, **kwargs): - with tmp_to_root_org(): - return super().dispatch(request, *args, **kwargs) - def get_queryset(self): return ConnectionToken.objects.filter(user=self.request.user) @@ -269,16 +265,17 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView data = serializer.validated_data user = self.get_user(serializer) asset = data.get('asset') - login = data.get('login') + account_name = data.get('account_name') data['org_id'] = asset.org_id data['user'] = user + data['value'] = random_string(16) util = PermAccountUtil() - permed_account = util.validate_permission(user, asset, login) + permed_account = util.validate_permission(user, asset, account_name) if not permed_account or not permed_account.actions: msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( - user, asset, login + user, asset, account_name ) raise PermissionDenied(msg) @@ -286,9 +283,9 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView raise PermissionDenied('Expired') if permed_account.has_secret: - data['secret'] = '' + data['input_secret'] = '' if permed_account.username != '@INPUT': - data['username'] = '' + data['input_username'] = '' return permed_account diff --git a/apps/authentication/api/perm_token.py b/apps/authentication/api/perm_token.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/api/temp_token.py b/apps/authentication/api/temp_token.py index 6e640edd6..2fa5791e3 100644 --- a/apps/authentication/api/temp_token.py +++ b/apps/authentication/api/temp_token.py @@ -2,10 +2,10 @@ from django.utils import timezone from rest_framework.response import Response from rest_framework.decorators import action +from rbac.permissions import RBACPermission from common.drf.api import JMSModelViewSet from ..models import TempToken from ..serializers import TempTokenSerializer -from rbac.permissions import RBACPermission class TempTokenViewSet(JMSModelViewSet): diff --git a/apps/authentication/migrations/0015_auto_20221125_2240.py b/apps/authentication/migrations/0015_auto_20221125_2240.py new file mode 100644 index 000000000..7b1c073e8 --- /dev/null +++ b/apps/authentication/migrations/0015_auto_20221125_2240.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.14 on 2022-11-25 14:40 + +import common.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0014_auto_20221122_2152'), + ] + + operations = [ + migrations.RenameField( + model_name='connectiontoken', + old_name='login', + new_name='account_name' + ), + migrations.RenameField( + model_name='connectiontoken', + old_name='secret', + new_name='value', + ), + migrations.RenameField( + model_name='connectiontoken', + old_name='username', + new_name='input_username', + ), + migrations.AddField( + model_name='connectiontoken', + name='input_secret', + field=common.db.fields.EncryptCharField(default='', max_length=128, verbose_name='Input Secret'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='account_name', + field=models.CharField(max_length=128, verbose_name='Account name'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='input_username', + field=models.CharField(default='', max_length=128, verbose_name='Input Username'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='value', + field=models.CharField(default='', max_length=64, verbose_name='Value'), + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 058d07581..d0a1d8478 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -19,6 +19,7 @@ def date_expired_default(): class ConnectionToken(OrgModelMixin, JMSBaseModel): + value = models.CharField(max_length=64, default='', verbose_name=_("Value")) user = models.ForeignKey( 'users.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('User') @@ -27,9 +28,9 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'assets.Asset', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('Asset'), ) - login = models.CharField(max_length=128, verbose_name=_("Login account")) - username = models.CharField(max_length=128, default='', verbose_name=_("Username")) - secret = EncryptCharField(max_length=64, default='', verbose_name=_("Secret")) + account_name = models.CharField(max_length=128, verbose_name=_("Account name")) # 登录账号Name + input_username = models.CharField(max_length=128, default='', verbose_name=_("Input Username")) + input_secret = EncryptCharField(max_length=64, default='', verbose_name=_("Input Secret")) protocol = models.CharField( choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 77981cd4a..db6b35963 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -15,15 +15,14 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): - username = serializers.CharField(max_length=128, label=_("Input username"), - allow_null=True, allow_blank=True) expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) class Meta: model = ConnectionToken - fields_mini = ['id'] + fields_mini = ['id', 'value'] fields_small = fields_mini + [ - 'protocol', 'login', 'secret', 'username', + 'protocol', 'account_name', + 'input_username', 'input_secret', 'actions', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', @@ -37,6 +36,9 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): 'user_display', 'asset_display', ] fields = fields_small + fields_fk + read_only_fields + extra_kwargs = { + 'value': {'read_only': True}, + } def get_request_user(self): request = self.context.get('request') diff --git a/apps/perms/api/perm_token.py b/apps/perms/api/perm_token.py index 63cf08062..e69de29bb 100644 --- a/apps/perms/api/perm_token.py +++ b/apps/perms/api/perm_token.py @@ -1,5 +0,0 @@ -from rest_framework.viewsets import ModelViewSet - - -class PermTokenViewSet(ModelViewSet): - pass diff --git a/apps/static/img/logo_text_white.png b/apps/static/img/logo_text_white.png index 39dea6778055a6d3bae745d3c749989dad7240a5..f791baa71a2599846091805fc5c7027fe43857bf 100644 GIT binary patch literal 5027 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 JtdqA4{|^CbEnWZs literal 8320 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 Qunb7$wT5D)yk+SB0^iX+pa1{>