mirror of https://github.com/jumpserver/jumpserver
perf: connect token 允许复用
parent
926550bf26
commit
2aa03d5b79
|
@ -12,14 +12,12 @@ from rest_framework.decorators import action
|
|||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from assets.const import CloudTypes
|
||||
from common.api import JMSModelViewSet
|
||||
from common.exceptions import JMSException
|
||||
from common.utils import random_string, get_logger
|
||||
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 perms.models import ActionChoices
|
||||
from terminal.connect_methods import NativeClient, ConnectMethodUtil
|
||||
|
@ -27,7 +25,7 @@ from terminal.models import EndpointRule
|
|||
from ..models import ConnectionToken, date_expired_default
|
||||
from ..serializers import (
|
||||
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
|
||||
SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer
|
||||
SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer, ConnectionTokenUpdateSerializer
|
||||
)
|
||||
|
||||
__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
|
||||
|
@ -230,10 +228,14 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
|
|||
search_fields = filterset_fields
|
||||
serializer_classes = {
|
||||
'default': ConnectionTokenSerializer,
|
||||
'update': ConnectionTokenUpdateSerializer,
|
||||
'partial_update': ConnectionTokenUpdateSerializer,
|
||||
}
|
||||
http_method_names = ['get', 'post', 'patch', 'head', 'options', 'trace']
|
||||
rbac_perms = {
|
||||
'list': 'authentication.view_connectiontoken',
|
||||
'retrieve': 'authentication.view_connectiontoken',
|
||||
'update': 'authentication.change_connectiontoken',
|
||||
'create': 'authentication.add_connectiontoken',
|
||||
'exchange': 'authentication.add_connectiontoken',
|
||||
'expire': 'authentication.change_connectiontoken',
|
||||
|
@ -370,19 +372,27 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
|||
|
||||
token_id = request.data.get('id') or ''
|
||||
token = get_object_or_404(ConnectionToken, pk=token_id)
|
||||
if token.is_expired:
|
||||
raise ValidationError({'id': 'Token is expired'})
|
||||
|
||||
token.is_valid()
|
||||
serializer = self.get_serializer(instance=token)
|
||||
expire_now = request.data.get('expire_now', True)
|
||||
|
||||
# TODO 暂时特殊处理 k8s 不过期
|
||||
if token.asset.type == CloudTypes.K8S:
|
||||
expire_now = False
|
||||
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 不过期
|
||||
if asset_type in ['k8s', 'kubernetes']:
|
||||
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()
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@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"))
|
||||
user_display = models.CharField(max_length=128, default='', verbose_name=_("User 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"))
|
||||
from_ticket = models.OneToOneField(
|
||||
'tickets.ApplyLoginAssetTicket', related_name='connection_token',
|
||||
|
@ -74,7 +75,7 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||
|
||||
def expire(self):
|
||||
self.date_expired = timezone.now()
|
||||
self.save()
|
||||
self.save(update_fields=['date_expired'])
|
||||
|
||||
def renewal(self):
|
||||
""" 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """
|
||||
|
@ -108,9 +109,8 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||
error = _('No user or invalid user')
|
||||
raise PermissionDenied(error)
|
||||
if not self.asset or not self.asset.is_active:
|
||||
is_valid = False
|
||||
error = _('No asset or inactive asset')
|
||||
return is_valid, error
|
||||
raise PermissionDenied(error)
|
||||
if not self.account:
|
||||
error = _('No account')
|
||||
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 rest_framework import serializers
|
||||
|
||||
from perms.serializers.permission import ActionChoicesField
|
||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||
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
|
||||
|
||||
__all__ = [
|
||||
'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer',
|
||||
'ConnectionTokenUpdateSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -25,13 +29,13 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
|||
fields_small = fields_mini + [
|
||||
'user', 'asset', 'account', 'input_username',
|
||||
'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',
|
||||
'updated_by', 'org_id', 'org_name',
|
||||
]
|
||||
read_only_fields = [
|
||||
# 普通 Token 不支持指定 user
|
||||
'user', 'expire_time', 'is_expired',
|
||||
'user', 'expire_time', 'is_expired', 'date_expired',
|
||||
'user_display', 'asset_display',
|
||||
]
|
||||
fields = fields_small + read_only_fields
|
||||
|
@ -57,6 +61,33 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
|
|||
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 Meta(ConnectionTokenSerializer.Meta):
|
||||
read_only_fields = list(set(ConnectionTokenSerializer.Meta.read_only_fields) - {'user'})
|
||||
|
|
|
@ -45,3 +45,7 @@ def get_remote_addr(request):
|
|||
|
||||
def is_true(value):
|
||||
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_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||
'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
|
||||
'AUTH_CUSTOM': False,
|
||||
|
|
|
@ -131,6 +131,9 @@ TICKETS_ENABLED = CONFIG.TICKETS_ENABLED
|
|||
REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_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
|
||||
|
||||
# 自定义默认组织名
|
||||
|
|
|
@ -48,3 +48,4 @@ class PrivateSettingSerializer(PublicSettingSerializer):
|
|||
ANNOUNCEMENT = serializers.DictField()
|
||||
|
||||
TICKETS_ENABLED = serializers.BooleanField()
|
||||
CONNECTION_TOKEN_REUSABLE = serializers.BooleanField()
|
||||
|
|
Loading…
Reference in New Issue