mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-22 11:58:29 +00:00
perf: 修改 connection token
This commit is contained in:
@@ -6,6 +6,7 @@ import urllib.parse
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
@@ -15,6 +16,7 @@ from rest_framework.response import Response
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from common.http import is_true
|
||||
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
|
||||
@@ -257,6 +259,10 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
|
||||
'get_client_protocol_url': 'authentication.add_connectiontoken',
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -264,22 +270,36 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
|
||||
return self.request.user
|
||||
|
||||
def perform_create(self, serializer):
|
||||
user = self.get_user(serializer)
|
||||
asset = serializer.validated_data.get('asset')
|
||||
account_username = serializer.validated_data.get('account_username')
|
||||
self.validate_asset_permission(user, asset, account_username)
|
||||
return super(ConnectionTokenViewSet, self).perform_create(serializer)
|
||||
self.validate_serializer(serializer)
|
||||
return super().perform_create(serializer)
|
||||
|
||||
@staticmethod
|
||||
def validate_asset_permission(user, asset, account_username):
|
||||
def validate_serializer(self, serializer):
|
||||
from perms.utils.account import PermAccountUtil
|
||||
actions, expire_at = PermAccountUtil().validate_permission(user, asset, account_username)
|
||||
if not actions:
|
||||
error = 'No actions'
|
||||
raise PermissionDenied(error)
|
||||
if expire_at < time.time():
|
||||
error = 'Expired'
|
||||
raise PermissionDenied(error)
|
||||
|
||||
data = serializer.validated_data
|
||||
user = self.get_user(serializer)
|
||||
asset = data.get('asset')
|
||||
login = data.get('login')
|
||||
data['org_id'] = asset.org_id
|
||||
data['user'] = user
|
||||
|
||||
util = PermAccountUtil()
|
||||
permed_account = util.validate_permission(user, asset, login)
|
||||
|
||||
if not permed_account or not permed_account.actions:
|
||||
msg = 'user `{}` not has asset `{}` permission for login `{}`'.format(
|
||||
user, asset, login
|
||||
)
|
||||
raise PermissionDenied(msg)
|
||||
|
||||
if permed_account.date_expired < timezone.now():
|
||||
raise PermissionDenied('Expired')
|
||||
|
||||
if permed_account.has_secret:
|
||||
serializer.validated_data['secret'] = ''
|
||||
if permed_account.username != '@INPUT':
|
||||
serializer.validated_data['username'] = ''
|
||||
return permed_account
|
||||
|
||||
|
||||
class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
||||
|
@@ -16,7 +16,7 @@ def migrate_system_user_to_account(apps, schema_editor):
|
||||
count += len(connection_tokens)
|
||||
updated = []
|
||||
for connection_token in connection_tokens:
|
||||
connection_token.account_username = connection_token.system_user.username
|
||||
connection_token.account = connection_token.system_user.username
|
||||
updated.append(connection_token)
|
||||
connection_token_model.objects.bulk_update(updated, ['account_username'])
|
||||
|
||||
|
29
apps/authentication/migrations/0014_auto_20221122_2152.py
Normal file
29
apps/authentication/migrations/0014_auto_20221122_2152.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.14 on 2022-11-22 13:52
|
||||
|
||||
import common.db.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0013_connectiontoken_protocol'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='connectiontoken',
|
||||
old_name='account_username',
|
||||
new_name='login'
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='connectiontoken',
|
||||
name='username',
|
||||
field=models.CharField(default='', max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='connectiontoken',
|
||||
name='secret',
|
||||
field=common.db.fields.EncryptCharField(default='', max_length=128, verbose_name='Secret'),
|
||||
),
|
||||
]
|
@@ -2,13 +2,15 @@ import time
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
from django.db import models
|
||||
from common.utils import lazyproperty
|
||||
from django.conf import settings
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.utils import lazyproperty, pretty_string
|
||||
from common.utils.timezone import as_current_tz
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.db.fields import EncryptCharField
|
||||
from assets.const import Protocol
|
||||
|
||||
|
||||
@@ -25,13 +27,14 @@ 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"))
|
||||
protocol = models.CharField(
|
||||
choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")
|
||||
)
|
||||
user_display = models.CharField(max_length=128, default='', verbose_name=_("User display"))
|
||||
asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display"))
|
||||
account_username = models.CharField(max_length=128, default='', verbose_name=_("Account"))
|
||||
secret = models.CharField(max_length=64, default='', verbose_name=_("Secret"))
|
||||
date_expired = models.DateTimeField(
|
||||
default=date_expired_default, verbose_name=_("Date expired")
|
||||
)
|
||||
@@ -59,9 +62,10 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel):
|
||||
seconds = 0
|
||||
return int(seconds)
|
||||
|
||||
@classmethod
|
||||
def get_default_date_expired(cls):
|
||||
return date_expired_default()
|
||||
def save(self, *args, **kwargs):
|
||||
self.asset_display = pretty_string(self.asset, max_length=128)
|
||||
self.user_display = pretty_string(self.user, max_length=128)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def expire(self):
|
||||
self.date_expired = timezone.now()
|
||||
@@ -69,7 +73,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel):
|
||||
|
||||
def renewal(self):
|
||||
""" 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """
|
||||
self.date_expired = self.get_default_date_expired()
|
||||
self.date_expired = date_expired_default()
|
||||
self.save()
|
||||
|
||||
# actions 和 expired_at 在 check_valid() 中赋值
|
||||
@@ -89,28 +93,52 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel):
|
||||
is_valid = False
|
||||
error = _('No asset or inactive asset')
|
||||
return is_valid, error
|
||||
if not self.account_username:
|
||||
if not self.login:
|
||||
is_valid = False
|
||||
error = _('No account')
|
||||
return is_valid, error
|
||||
actions, expire_at = PermAccountUtil().validate_permission(
|
||||
self.user, self.asset, self.account_username
|
||||
|
||||
permed_account = PermAccountUtil().validate_permission(
|
||||
self.user, self.asset, self.login
|
||||
)
|
||||
if not actions or expire_at < time.time():
|
||||
is_valid = False
|
||||
error = _('User has no permission to access asset or permission expired')
|
||||
return is_valid, error
|
||||
self.actions = actions
|
||||
self.expire_at = expire_at
|
||||
if not permed_account or not permed_account.actions:
|
||||
msg = 'user `{}` not has asset `{}` permission for login `{}`'.format(
|
||||
self.user, self.asset, self.login
|
||||
)
|
||||
raise PermissionDenied(msg)
|
||||
|
||||
if permed_account.date_expired < timezone.now():
|
||||
raise PermissionDenied('Expired')
|
||||
|
||||
is_valid, error = True, ''
|
||||
return is_valid, error
|
||||
|
||||
@lazyproperty
|
||||
def account(self):
|
||||
def platform(self):
|
||||
return self.asset.platform
|
||||
|
||||
@lazyproperty
|
||||
def accounts(self):
|
||||
if not self.asset:
|
||||
return None
|
||||
account = self.asset.accounts.filter(username=self.account_username).first()
|
||||
return account
|
||||
|
||||
data = []
|
||||
if self.login == '@INPUT':
|
||||
data.append({
|
||||
'name': self.login,
|
||||
'username': self.username,
|
||||
'secret_type': 'password',
|
||||
'secret': self.secret
|
||||
})
|
||||
else:
|
||||
accounts = self.asset.accounts.filter(username=self.login)
|
||||
for account in accounts:
|
||||
data.append({
|
||||
'username': account.uesrname,
|
||||
'secret_type': account.secret_type,
|
||||
'secret': account.secret if account.secret else self.secret
|
||||
})
|
||||
return data
|
||||
|
||||
@lazyproperty
|
||||
def domain(self):
|
||||
|
@@ -2,9 +2,8 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account, Platform
|
||||
from assets.serializers import PlatformSerializer
|
||||
from authentication.models import ConnectionToken
|
||||
from common.utils import pretty_string
|
||||
from common.utils.random import random_string
|
||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||
from perms.serializers.permission import ActionChoicesField
|
||||
from users.models import User
|
||||
@@ -16,6 +15,8 @@ __all__ = [
|
||||
|
||||
|
||||
class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
||||
username = serializers.CharField(max_length=128, label=_("Input username"),
|
||||
allow_null=True, allow_blank=True)
|
||||
is_valid = serializers.BooleanField(read_only=True, label=_('Validity'))
|
||||
expire_time = serializers.IntegerField(read_only=True, label=_('Expired time'))
|
||||
|
||||
@@ -23,9 +24,10 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
||||
model = ConnectionToken
|
||||
fields_mini = ['id']
|
||||
fields_small = fields_mini + [
|
||||
'secret', 'account_username', 'date_expired',
|
||||
'date_created', 'date_updated',
|
||||
'created_by', 'updated_by', 'org_id', 'org_name',
|
||||
'protocol', 'login', 'secret', 'username',
|
||||
'date_expired', 'date_created',
|
||||
'date_updated', 'created_by',
|
||||
'updated_by', 'org_id', 'org_name',
|
||||
]
|
||||
fields_fk = [
|
||||
'user', 'asset',
|
||||
@@ -45,32 +47,6 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
||||
def get_user(self, attrs):
|
||||
return self.get_request_user()
|
||||
|
||||
def validate(self, attrs):
|
||||
fields_attrs = self.construct_internal_fields_attrs(attrs)
|
||||
attrs.update(fields_attrs)
|
||||
return attrs
|
||||
|
||||
def construct_internal_fields_attrs(self, attrs):
|
||||
asset = attrs.get('asset') or ''
|
||||
asset_display = pretty_string(str(asset), max_length=128)
|
||||
user = self.get_user(attrs)
|
||||
user_display = pretty_string(str(user), max_length=128)
|
||||
secret = attrs.get('secret') or random_string(16)
|
||||
date_expired = attrs.get('date_expired') or ConnectionToken.get_default_date_expired()
|
||||
org_id = asset.org_id
|
||||
if not isinstance(asset, Asset):
|
||||
error = ''
|
||||
raise serializers.ValidationError(error)
|
||||
attrs = {
|
||||
'user': user,
|
||||
'secret': secret,
|
||||
'user_display': user_display,
|
||||
'asset_display': asset_display,
|
||||
'date_expired': date_expired,
|
||||
'org_id': org_id,
|
||||
}
|
||||
return attrs
|
||||
|
||||
|
||||
class ConnectionTokenDisplaySerializer(ConnectionTokenSerializer):
|
||||
class Meta(ConnectionTokenSerializer.Meta):
|
||||
@@ -122,7 +98,7 @@ class ConnectionTokenAccountSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = [
|
||||
'id', 'name', 'username', 'secret_type', 'secret', 'version'
|
||||
'username', 'secret_type', 'secret',
|
||||
]
|
||||
|
||||
|
||||
@@ -154,26 +130,31 @@ class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ConnectionTokenPlatform(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
class ConnectionTokenPlatform(PlatformSerializer):
|
||||
class Meta(PlatformSerializer.Meta):
|
||||
model = Platform
|
||||
fields = ['id', 'name', 'org_id']
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
names = super().get_field_names(declared_fields, info)
|
||||
names = [n for n in names if n not in ['automation']]
|
||||
return names
|
||||
|
||||
|
||||
class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
||||
user = ConnectionTokenUserSerializer(read_only=True)
|
||||
asset = ConnectionTokenAssetSerializer(read_only=True)
|
||||
platform = ConnectionTokenPlatform(read_only=True)
|
||||
account = ConnectionTokenAccountSerializer(read_only=True)
|
||||
accounts = ConnectionTokenAccountSerializer(read_only=True, many=True)
|
||||
gateway = ConnectionTokenGatewaySerializer(read_only=True)
|
||||
cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True)
|
||||
# cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True)
|
||||
actions = ActionChoicesField()
|
||||
expire_at = serializers.IntegerField()
|
||||
|
||||
class Meta:
|
||||
model = ConnectionToken
|
||||
fields = [
|
||||
'id', 'secret', 'user', 'asset', 'account_username',
|
||||
'account', 'protocol', 'domain', 'gateway',
|
||||
'cmd_filter_rules', 'actions', 'expire_at',
|
||||
'id', 'secret', 'user', 'asset', 'login',
|
||||
'accounts', 'protocol', 'domain', 'gateway',
|
||||
'actions', 'expire_at',
|
||||
'platform',
|
||||
]
|
||||
|
Reference in New Issue
Block a user