mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-16 23:38:36 +00:00
feat: 为rdp 添加一个api
This commit is contained in:
@@ -1,40 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.generics import CreateAPIView
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from common.utils import get_logger, random_string
|
||||
from common.drf.api import SerializerMixin2
|
||||
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsSuperUserOrAppUser
|
||||
from orgs.mixins.api import RootOrgViewMixin
|
||||
|
||||
from ..serializers import ConnectionTokenSerializer
|
||||
from ..serializers import ConnectionTokenSerializer, ConnectionTokenSecretSerializer
|
||||
|
||||
logger = get_logger(__name__)
|
||||
__all__ = ['UserConnectionTokenApi']
|
||||
__all__ = ['UserConnectionTokenViewSet']
|
||||
|
||||
|
||||
class UserConnectionTokenApi(RootOrgViewMixin, CreateAPIView):
|
||||
class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericViewSet):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
serializer_class = ConnectionTokenSerializer
|
||||
serializer_classes = {
|
||||
'default': ConnectionTokenSerializer,
|
||||
'get_secret_detail': ConnectionTokenSecretSerializer
|
||||
}
|
||||
CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}'
|
||||
|
||||
def perform_create(self, serializer):
|
||||
user = serializer.validated_data['user']
|
||||
asset = serializer.validated_data['asset']
|
||||
system_user = serializer.validated_data['system_user']
|
||||
token = str(uuid.uuid4())
|
||||
@staticmethod
|
||||
def check_resource_permission(user, asset, application, system_user):
|
||||
from perms.utils.asset import has_asset_system_permission
|
||||
from perms.utils.application import has_application_system_permission
|
||||
if asset and not has_asset_system_permission(user, asset, system_user):
|
||||
error = f'User not has this asset and system user permission: ' \
|
||||
f'user={user.id} system_user={system_user.id} asset={asset.id}'
|
||||
raise PermissionDenied(error)
|
||||
if application and not has_application_system_permission(user, application, system_user):
|
||||
error = f'User not has this application and system user permission: ' \
|
||||
f'user={user.id} system_user={system_user.id} application={application.id}'
|
||||
raise PermissionDenied(error)
|
||||
return True
|
||||
|
||||
def create_token(self, user, asset, application, system_user):
|
||||
self.check_resource_permission(user, asset, application, system_user)
|
||||
token = random_string(36)
|
||||
value = {
|
||||
'user': str(user.id),
|
||||
'username': user.username,
|
||||
'asset': str(asset.id),
|
||||
'hostname': asset.hostname,
|
||||
'system_user': str(system_user.id),
|
||||
'system_user_name': system_user.name
|
||||
}
|
||||
cache.set(token, value, timeout=20)
|
||||
|
||||
if asset:
|
||||
value.update({
|
||||
'type': 'asset',
|
||||
'asset': str(asset.id),
|
||||
'hostname': asset.hostname,
|
||||
})
|
||||
elif application:
|
||||
value.update({
|
||||
'type': 'application',
|
||||
'application': application.id,
|
||||
'application_name': str(application)
|
||||
})
|
||||
|
||||
key = self.CACHE_KEY_PREFIX.format(token)
|
||||
cache.set(key, value, timeout=20)
|
||||
return token
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
@@ -42,18 +75,107 @@ class UserConnectionTokenApi(RootOrgViewMixin, CreateAPIView):
|
||||
data = {'error': 'Connection token disabled'}
|
||||
return Response(data, status=400)
|
||||
|
||||
if not request.user.is_superuser:
|
||||
data = {'error': 'Only super user can create token'}
|
||||
return Response(data, status=403)
|
||||
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
token = self.perform_create(serializer)
|
||||
|
||||
user = serializer.validated_data.get('user', None)
|
||||
if not request.user.is_superuser and user:
|
||||
raise PermissionDenied('Only super user can create user token')
|
||||
if not request.user.is_superuser:
|
||||
user = self.request.user
|
||||
|
||||
asset = serializer.validated_data.get('asset')
|
||||
application = serializer.validated_data.get('application')
|
||||
system_user = serializer.validated_data['system_user']
|
||||
|
||||
token = self.create_token(user, asset, application, system_user)
|
||||
return Response({"token": token}, status=201)
|
||||
|
||||
@staticmethod
|
||||
def _get_application_secret_detail(value):
|
||||
from applications.models import Application
|
||||
from perms.models import Action
|
||||
application = get_object_or_404(Application, id=value.get('application'))
|
||||
gateway = None
|
||||
|
||||
if not application.category_remote_app:
|
||||
actions = Action.NONE
|
||||
remote_app = {}
|
||||
asset = None
|
||||
domain = application.domain
|
||||
else:
|
||||
remote_app = application.get_rdp_remote_app_setting()
|
||||
actions = Action.CONNECT
|
||||
asset = application.get_remote_app_asset()
|
||||
domain = asset.domain
|
||||
|
||||
if domain and domain.has_gateway():
|
||||
gateway = domain.random_gateway()
|
||||
|
||||
return {
|
||||
'asset': asset,
|
||||
'application': application,
|
||||
'gateway': gateway,
|
||||
'remote_app': remote_app,
|
||||
'actions': actions
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_asset_secret_detail(value, user, system_user):
|
||||
from assets.models import Asset
|
||||
from perms.utils.asset import get_asset_system_users_id_with_actions_by_user
|
||||
asset = get_object_or_404(Asset, id=value.get('asset'))
|
||||
systemuserid_actions_mapper = get_asset_system_users_id_with_actions_by_user(user, asset)
|
||||
actions = systemuserid_actions_mapper.get(system_user.id, [])
|
||||
gateway = None
|
||||
if asset and asset.domain and asset.domain.has_gateway():
|
||||
gateway = asset.domain.random_gateway()
|
||||
return {
|
||||
'asset': asset,
|
||||
'application': None,
|
||||
'gateway': gateway,
|
||||
'remote_app': None,
|
||||
'actions': actions,
|
||||
}
|
||||
|
||||
@action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail')
|
||||
def get_secret_detail(self, request, *args, **kwargs):
|
||||
from users.models import User
|
||||
from assets.models import SystemUser
|
||||
|
||||
token = request.data.get('token', '')
|
||||
key = self.CACHE_KEY_PREFIX.format(token)
|
||||
value = cache.get(key, None)
|
||||
if not value:
|
||||
return Response(status=404)
|
||||
user = get_object_or_404(User, id=value.get('user'))
|
||||
system_user = get_object_or_404(SystemUser, id=value.get('system_user'))
|
||||
data = dict(user=user, system_user=system_user)
|
||||
|
||||
if value.get('type') == 'asset':
|
||||
asset_detail = self._get_asset_secret_detail(value, user=user, system_user=system_user)
|
||||
data['type'] = 'asset'
|
||||
data.update(asset_detail)
|
||||
else:
|
||||
app_detail = self._get_application_secret_detail(value)
|
||||
data['type'] = 'application'
|
||||
data.update(app_detail)
|
||||
|
||||
serializer = self.get_serializer(data)
|
||||
return Response(data=serializer.data, status=200)
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "create":
|
||||
if self.request.data.get('user', None):
|
||||
self.permission_classes = (IsSuperUser,)
|
||||
else:
|
||||
self.permission_classes = (IsValidUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
def get(self, request):
|
||||
token = request.query_params.get('token')
|
||||
value = cache.get(token, None)
|
||||
key = self.CACHE_KEY_PREFIX.format(token)
|
||||
value = cache.get(key, None)
|
||||
|
||||
if not value:
|
||||
return Response('', status=404)
|
||||
|
@@ -4,14 +4,17 @@ from rest_framework import serializers
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from users.models import User
|
||||
from assets.models import Asset, SystemUser, Gateway
|
||||
from applications.models import Application
|
||||
from users.serializers import UserProfileSerializer
|
||||
from perms.serializers.asset.permission import ActionsField
|
||||
from .models import AccessKey, LoginConfirmSetting, SSOToken
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer',
|
||||
'MFAChallengeSerializer', 'LoginConfirmSettingSerializer', 'SSOTokenSerializer',
|
||||
'ConnectionTokenSerializer',
|
||||
'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer'
|
||||
]
|
||||
|
||||
|
||||
@@ -86,9 +89,10 @@ class SSOTokenSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class ConnectionTokenSerializer(serializers.Serializer):
|
||||
user = serializers.CharField(max_length=128, required=True)
|
||||
user = serializers.CharField(max_length=128, required=False, allow_blank=True)
|
||||
system_user = serializers.CharField(max_length=128, required=True)
|
||||
asset = serializers.CharField(max_length=128, required=True)
|
||||
asset = serializers.CharField(max_length=128, required=False)
|
||||
application = serializers.CharField(max_length=128, required=False)
|
||||
|
||||
@staticmethod
|
||||
def validate_user(user_id):
|
||||
@@ -113,3 +117,67 @@ class ConnectionTokenSerializer(serializers.Serializer):
|
||||
if asset is None:
|
||||
raise serializers.ValidationError('asset id not exist')
|
||||
return asset
|
||||
|
||||
@staticmethod
|
||||
def validate_application(app_id):
|
||||
from applications.models import Application
|
||||
app = Application.objects.filter(id=app_id).first()
|
||||
if app is None:
|
||||
raise serializers.ValidationError('app id not exist')
|
||||
return app
|
||||
|
||||
def validate(self, attrs):
|
||||
asset = attrs.get('asset')
|
||||
application = attrs.get('application')
|
||||
if not asset and not application:
|
||||
raise serializers.ValidationError('asset or application required')
|
||||
if asset and application:
|
||||
raise serializers.ValidationError('asset and application should only one')
|
||||
return super().validate(attrs)
|
||||
|
||||
|
||||
class ConnectionTokenUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'name', 'username', 'email']
|
||||
|
||||
|
||||
class ConnectionTokenAssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'ip', 'port', 'org_id']
|
||||
|
||||
|
||||
class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ['id', 'name', 'username', 'password', 'private_key']
|
||||
|
||||
|
||||
class ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Gateway
|
||||
fields = ['id', 'ip', 'port', 'username', 'password', 'private_key']
|
||||
|
||||
|
||||
class ConnectionTokenRemoteAppSerializer(serializers.Serializer):
|
||||
program = serializers.CharField()
|
||||
working_directory = serializers.CharField()
|
||||
parameters = serializers.CharField()
|
||||
|
||||
|
||||
class ConnectionTokenApplicationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Application
|
||||
fields = ['id', 'name', 'category', 'type']
|
||||
|
||||
|
||||
class ConnectionTokenSecretSerializer(serializers.Serializer):
|
||||
type = serializers.ChoiceField(choices=[('application', 'Application'), ('asset', 'Asset')])
|
||||
user = ConnectionTokenUserSerializer(read_only=True)
|
||||
asset = ConnectionTokenAssetSerializer(read_only=True)
|
||||
remote_app = ConnectionTokenRemoteAppSerializer(read_only=True)
|
||||
application = ConnectionTokenApplicationSerializer(read_only=True)
|
||||
system_user = ConnectionTokenSystemUserSerializer(read_only=True)
|
||||
gateway = ConnectionTokenGatewaySerializer(read_only=True)
|
||||
actions = ActionsField()
|
||||
|
@@ -9,6 +9,7 @@ app_name = 'authentication'
|
||||
router = DefaultRouter()
|
||||
router.register('access-keys', api.AccessKeyViewSet, 'access-key')
|
||||
router.register('sso', api.SSOViewSet, 'sso')
|
||||
router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -16,8 +17,6 @@ urlpatterns = [
|
||||
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
|
||||
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
||||
path('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'),
|
||||
path('connection-token/',
|
||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
||||
path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'),
|
||||
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')
|
||||
|
Reference in New Issue
Block a user