mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-26 06:52:53 +00:00
perf: connect token 允许复用
This commit is contained in:
parent
926550bf26
commit
2aa03d5b79
@ -12,14 +12,12 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ValidationError
|
|
||||||
|
|
||||||
from assets.const import CloudTypes
|
|
||||||
from common.api import JMSModelViewSet
|
from common.api import JMSModelViewSet
|
||||||
from common.exceptions import JMSException
|
from common.exceptions import JMSException
|
||||||
from common.utils import random_string, get_logger
|
from common.utils import random_string, get_logger
|
||||||
from common.utils.django import get_request_os
|
from common.utils.django import get_request_os
|
||||||
from common.utils.http import is_true
|
from common.utils.http import is_true, is_false
|
||||||
from orgs.mixins.api import RootOrgViewMixin
|
from orgs.mixins.api import RootOrgViewMixin
|
||||||
from perms.models import ActionChoices
|
from perms.models import ActionChoices
|
||||||
from terminal.connect_methods import NativeClient, ConnectMethodUtil
|
from terminal.connect_methods import NativeClient, ConnectMethodUtil
|
||||||
@ -27,7 +25,7 @@ from terminal.models import EndpointRule
|
|||||||
from ..models import ConnectionToken, date_expired_default
|
from ..models import ConnectionToken, date_expired_default
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
|
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
|
||||||
SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer
|
SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer, ConnectionTokenUpdateSerializer
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
|
__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
|
||||||
@ -230,10 +228,14 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
|
|||||||
search_fields = filterset_fields
|
search_fields = filterset_fields
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': ConnectionTokenSerializer,
|
'default': ConnectionTokenSerializer,
|
||||||
|
'update': ConnectionTokenUpdateSerializer,
|
||||||
|
'partial_update': ConnectionTokenUpdateSerializer,
|
||||||
}
|
}
|
||||||
|
http_method_names = ['get', 'post', 'patch', 'head', 'options', 'trace']
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'list': 'authentication.view_connectiontoken',
|
'list': 'authentication.view_connectiontoken',
|
||||||
'retrieve': 'authentication.view_connectiontoken',
|
'retrieve': 'authentication.view_connectiontoken',
|
||||||
|
'update': 'authentication.change_connectiontoken',
|
||||||
'create': 'authentication.add_connectiontoken',
|
'create': 'authentication.add_connectiontoken',
|
||||||
'exchange': 'authentication.add_connectiontoken',
|
'exchange': 'authentication.add_connectiontoken',
|
||||||
'expire': 'authentication.change_connectiontoken',
|
'expire': 'authentication.change_connectiontoken',
|
||||||
@ -370,19 +372,27 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
|||||||
|
|
||||||
token_id = request.data.get('id') or ''
|
token_id = request.data.get('id') or ''
|
||||||
token = get_object_or_404(ConnectionToken, pk=token_id)
|
token = get_object_or_404(ConnectionToken, pk=token_id)
|
||||||
if token.is_expired:
|
|
||||||
raise ValidationError({'id': 'Token is expired'})
|
|
||||||
|
|
||||||
token.is_valid()
|
token.is_valid()
|
||||||
serializer = self.get_serializer(instance=token)
|
serializer = self.get_serializer(instance=token)
|
||||||
expire_now = request.data.get('expire_now', True)
|
|
||||||
|
|
||||||
|
expire_now = request.data.get('expire_now', None)
|
||||||
|
asset_type = token.asset.type
|
||||||
|
asset_category = token.asset.category
|
||||||
|
# 设置默认值
|
||||||
|
if expire_now is None:
|
||||||
# TODO 暂时特殊处理 k8s 不过期
|
# TODO 暂时特殊处理 k8s 不过期
|
||||||
if token.asset.type == CloudTypes.K8S:
|
if asset_type in ['k8s', 'kubernetes']:
|
||||||
expire_now = False
|
expire_now = False
|
||||||
|
elif asset_category in ['database', 'db']:
|
||||||
|
expire_now = False
|
||||||
|
else:
|
||||||
|
expire_now = True
|
||||||
|
|
||||||
if expire_now:
|
if is_false(expire_now) or token.is_reusable:
|
||||||
|
logger.debug('Token is reusable or specify, not expire')
|
||||||
|
else:
|
||||||
token.expire()
|
token.expire()
|
||||||
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@action(methods=['POST'], detail=False, url_path='applet-option')
|
@action(methods=['POST'], detail=False, url_path='applet-option')
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-05-08 07:34
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0018_alter_connectiontoken_input_secret'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='connectiontoken',
|
||||||
|
name='is_reusable',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Reusable'),
|
||||||
|
),
|
||||||
|
]
|
@ -40,6 +40,7 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||||||
connect_method = models.CharField(max_length=32, verbose_name=_("Connect method"))
|
connect_method = models.CharField(max_length=32, verbose_name=_("Connect method"))
|
||||||
user_display = models.CharField(max_length=128, default='', verbose_name=_("User display"))
|
user_display = models.CharField(max_length=128, default='', verbose_name=_("User display"))
|
||||||
asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display"))
|
asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display"))
|
||||||
|
is_reusable = models.BooleanField(default=False, verbose_name=_("Reusable"))
|
||||||
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired"))
|
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired"))
|
||||||
from_ticket = models.OneToOneField(
|
from_ticket = models.OneToOneField(
|
||||||
'tickets.ApplyLoginAssetTicket', related_name='connection_token',
|
'tickets.ApplyLoginAssetTicket', related_name='connection_token',
|
||||||
@ -74,7 +75,7 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||||||
|
|
||||||
def expire(self):
|
def expire(self):
|
||||||
self.date_expired = timezone.now()
|
self.date_expired = timezone.now()
|
||||||
self.save()
|
self.save(update_fields=['date_expired'])
|
||||||
|
|
||||||
def renewal(self):
|
def renewal(self):
|
||||||
""" 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """
|
""" 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """
|
||||||
@ -108,9 +109,8 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||||||
error = _('No user or invalid user')
|
error = _('No user or invalid user')
|
||||||
raise PermissionDenied(error)
|
raise PermissionDenied(error)
|
||||||
if not self.asset or not self.asset.is_active:
|
if not self.asset or not self.asset.is_active:
|
||||||
is_valid = False
|
|
||||||
error = _('No asset or inactive asset')
|
error = _('No asset or inactive asset')
|
||||||
return is_valid, error
|
raise PermissionDenied(error)
|
||||||
if not self.account:
|
if not self.account:
|
||||||
error = _('No account')
|
error = _('No account')
|
||||||
raise PermissionDenied(error)
|
raise PermissionDenied(error)
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from perms.serializers.permission import ActionChoicesField
|
|
||||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
|
||||||
from common.serializers.fields import EncryptedField
|
from common.serializers.fields import EncryptedField
|
||||||
|
from common.utils import lazyproperty
|
||||||
|
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||||
|
from perms.serializers.permission import ActionChoicesField
|
||||||
from ..models import ConnectionToken
|
from ..models import ConnectionToken
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer',
|
'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer',
|
||||||
|
'ConnectionTokenUpdateSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -25,13 +29,13 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
|||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'user', 'asset', 'account', 'input_username',
|
'user', 'asset', 'account', 'input_username',
|
||||||
'input_secret', 'connect_method', 'protocol', 'actions',
|
'input_secret', 'connect_method', 'protocol', 'actions',
|
||||||
'is_active', 'from_ticket', 'from_ticket_info',
|
'is_active', 'is_reusable', 'from_ticket', 'from_ticket_info',
|
||||||
'date_expired', 'date_created', 'date_updated', 'created_by',
|
'date_expired', 'date_created', 'date_updated', 'created_by',
|
||||||
'updated_by', 'org_id', 'org_name',
|
'updated_by', 'org_id', 'org_name',
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
# 普通 Token 不支持指定 user
|
# 普通 Token 不支持指定 user
|
||||||
'user', 'expire_time', 'is_expired',
|
'user', 'expire_time', 'is_expired', 'date_expired',
|
||||||
'user_display', 'asset_display',
|
'user_display', 'asset_display',
|
||||||
]
|
]
|
||||||
fields = fields_small + read_only_fields
|
fields = fields_small + read_only_fields
|
||||||
@ -57,6 +61,33 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTokenUpdateSerializer(ConnectionTokenSerializer):
|
||||||
|
class Meta(ConnectionTokenSerializer.Meta):
|
||||||
|
can_update_fields = ['is_reusable']
|
||||||
|
read_only_fields = list(set(ConnectionTokenSerializer.Meta.fields) - set(can_update_fields))
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def date_expired_max(self):
|
||||||
|
delta = self.instance.date_expired - self.instance.date_created
|
||||||
|
if delta.total_seconds() > 3600 * 24:
|
||||||
|
return self.instance.date_expired
|
||||||
|
|
||||||
|
seconds = settings.CONNECTION_TOKEN_EXPIRATION_MAX
|
||||||
|
return timezone.now() + timezone.timedelta(seconds=seconds)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_is_reusable(value):
|
||||||
|
if value and not settings.CONNECTION_TOKEN_REUSABLE:
|
||||||
|
raise serializers.ValidationError(_('Reusable connection token is not allowed, global setting not enabled'))
|
||||||
|
return value
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
reusable = attrs.get('is_reusable', False)
|
||||||
|
if reusable:
|
||||||
|
attrs['date_expired'] = self.date_expired_max
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class SuperConnectionTokenSerializer(ConnectionTokenSerializer):
|
class SuperConnectionTokenSerializer(ConnectionTokenSerializer):
|
||||||
class Meta(ConnectionTokenSerializer.Meta):
|
class Meta(ConnectionTokenSerializer.Meta):
|
||||||
read_only_fields = list(set(ConnectionTokenSerializer.Meta.read_only_fields) - {'user'})
|
read_only_fields = list(set(ConnectionTokenSerializer.Meta.read_only_fields) - {'user'})
|
||||||
|
@ -45,3 +45,7 @@ def get_remote_addr(request):
|
|||||||
|
|
||||||
def is_true(value):
|
def is_true(value):
|
||||||
return value in BooleanField.TRUE_VALUES
|
return value in BooleanField.TRUE_VALUES
|
||||||
|
|
||||||
|
|
||||||
|
def is_false(value):
|
||||||
|
return value in BooleanField.FALSE_VALUES
|
||||||
|
@ -229,7 +229,9 @@ class Config(dict):
|
|||||||
'SESSION_COOKIE_AGE': 3600 * 24,
|
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||||
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||||
'LOGIN_URL': reverse_lazy('authentication:login'),
|
'LOGIN_URL': reverse_lazy('authentication:login'),
|
||||||
'CONNECTION_TOKEN_EXPIRATION': 5 * 60,
|
'CONNECTION_TOKEN_EXPIRATION': 5 * 60, # 默认
|
||||||
|
'CONNECTION_TOKEN_EXPIRATION_MAX': 60 * 60 * 24 * 30, # 最大
|
||||||
|
'CONNECTION_TOKEN_REUSABLE': False,
|
||||||
|
|
||||||
# Custom Config
|
# Custom Config
|
||||||
'AUTH_CUSTOM': False,
|
'AUTH_CUSTOM': False,
|
||||||
|
@ -131,6 +131,9 @@ TICKETS_ENABLED = CONFIG.TICKETS_ENABLED
|
|||||||
REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED
|
REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED
|
||||||
|
|
||||||
CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED
|
CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED
|
||||||
|
CONNECTION_TOKEN_REUSABLE = CONFIG.CONNECTION_TOKEN_REUSABLE
|
||||||
|
CONNECTION_TOKEN_EXPIRATION_MAX = CONFIG.CONNECTION_TOKEN_EXPIRATION_MAX
|
||||||
|
|
||||||
FORGOT_PASSWORD_URL = CONFIG.FORGOT_PASSWORD_URL
|
FORGOT_PASSWORD_URL = CONFIG.FORGOT_PASSWORD_URL
|
||||||
|
|
||||||
# 自定义默认组织名
|
# 自定义默认组织名
|
||||||
|
@ -48,3 +48,4 @@ class PrivateSettingSerializer(PublicSettingSerializer):
|
|||||||
ANNOUNCEMENT = serializers.DictField()
|
ANNOUNCEMENT = serializers.DictField()
|
||||||
|
|
||||||
TICKETS_ENABLED = serializers.BooleanField()
|
TICKETS_ENABLED = serializers.BooleanField()
|
||||||
|
CONNECTION_TOKEN_REUSABLE = serializers.BooleanField()
|
||||||
|
Loading…
Reference in New Issue
Block a user