Merge branch 'dev' of github.com:jumpserver/jumpserver into dev

pull/6225/head
ibuler 2021-06-03 14:28:39 +08:00
commit 46e99d10cb
14 changed files with 209 additions and 49 deletions

View File

@ -1,16 +1,15 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser
from common.drf.filters import CustomFilter
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from orgs.utils import tmp_to_org
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from .. import serializers from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer
from ..tasks import ( from ..tasks import (
push_system_user_to_assets_manual, test_system_user_connectivity_manual, push_system_user_to_assets_manual, test_system_user_connectivity_manual,
push_system_user_to_assets push_system_user_to_assets
@ -21,6 +20,7 @@ logger = get_logger(__file__)
__all__ = [ __all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView', 'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView',
'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi',
] ]
@ -57,6 +57,23 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
return Response(status=204) return Response(status=204)
class SystemUserTempAuthInfoApi(generics.CreateAPIView):
model = SystemUser
permission_classes = (IsValidUser,)
serializer_class = SystemUserTempAuthSerializer
def create(self, request, *args, **kwargs):
serializer = super().get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
pk = kwargs.get('pk')
instance = get_object_or_404(SystemUser, pk=pk)
data = serializer.validated_data
user = self.request.user
instance_id = data.get('instance_id')
instance.set_temp_auth(instance_id, user, data)
return Response(serializer.data, status=201)
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
""" """
Get system user with asset auth info Get system user with asset auth info
@ -65,22 +82,30 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer serializer_class = SystemUserWithAuthInfoSerializer
def get_exception_handler(self): def get_object(self):
def handler(e, context): instance = super().get_object()
return Response({"error": str(e)}, status=400) asset_id = self.kwargs.get('asset_id')
return handler user_id = self.request.query_params.get("user_id")
username = self.request.query_params.get("username")
instance.load_asset_more_auth(asset_id=asset_id, user_id=user_id, username=username)
return instance
class SystemUserAppAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer
def get_object(self): def get_object(self):
instance = super().get_object() instance = super().get_object()
username = instance.username app_id = self.kwargs.get('app_id')
if instance.username_same_with_user: user_id = self.request.query_params.get("user_id")
username = self.request.query_params.get("username") if user_id:
asset_id = self.kwargs.get('aid') instance.load_app_more_auth(app_id, user_id)
asset = get_object_or_404(Asset, pk=asset_id) return instance
with tmp_to_org(asset.org_id):
instance.load_asset_special_auth(asset=asset, username=username)
return instance
class SystemUserTaskApi(generics.CreateAPIView): class SystemUserTaskApi(generics.CreateAPIView):

View File

@ -11,8 +11,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from common.db.models import ChoiceSet from common.utils import random_string, signer
from common.utils import random_string
from common.utils import ( from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
) )

View File

@ -7,9 +7,10 @@ import logging
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.cache import cache
from common.utils import signer from common.utils import signer, get_object_or_none
from common.fields.model import JsonListCharField from common.exceptions import JMSException
from .base import BaseUser from .base import BaseUser
from .asset import Asset from .asset import Asset
@ -185,6 +186,81 @@ class SystemUser(BaseUser):
if self.username_same_with_user: if self.username_same_with_user:
self.username = other.username self.username = other.username
def set_temp_auth(self, asset_or_app_id, user_id, auth, ttl=300):
if not auth:
raise ValueError('Auth not set')
key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id)
logger.debug(f'Set system user temp auth: {key}')
cache.set(key, auth, ttl)
def get_temp_auth(self, asset_or_app_id, user_id):
key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id)
logger.debug(f'Get system user temp auth: {key}')
password = cache.get(key)
return password
def load_tmp_auth_if_has(self, asset_or_app_id, user):
if not asset_or_app_id or not user:
return
if self.login_mode != self.LOGIN_MANUAL:
pass
auth = self.get_temp_auth(asset_or_app_id, user)
if not auth:
return
username = auth.get('username')
password = auth.get('password')
if username:
self.username = username
if password:
self.password = password
def load_app_more_auth(self, app_id=None, user_id=None):
from users.models import User
if self.login_mode == self.LOGIN_MANUAL:
self.password = ''
self.private_key = ''
if not user_id:
return
user = get_object_or_none(User, pk=user_id)
if not user:
return
self.load_tmp_auth_if_has(app_id, user)
def load_asset_more_auth(self, asset_id=None, username=None, user_id=None):
from users.models import User
if self.login_mode == self.LOGIN_MANUAL:
self.password = ''
self.private_key = ''
asset = None
if asset_id:
asset = get_object_or_none(Asset, pk=asset_id)
# 没有资产就没有必要继续了
if not asset:
logger.debug('Asset not found, pass')
return
user = None
if user_id:
user = get_object_or_none(User, pk=user_id)
if self.username_same_with_user:
if user and not username:
username = user.username
# 加载某个资产的特殊配置认证信息
try:
self.load_asset_special_auth(asset, username)
except Exception as e:
logger.error('Load special auth Error: ', e)
pass
self.load_tmp_auth_if_has(asset_id, user)
@property @property
def cmd_filter_rules(self): def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule from .cmd_filter import CommandFilterRule

View File

@ -14,6 +14,7 @@ __all__ = [
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer', 'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer',
'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer', 'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer',
'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer', 'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer',
'SystemUserTempAuthSerializer',
] ]
@ -272,3 +273,10 @@ class SystemUserTaskSerializer(serializers.Serializer):
many=True many=True
) )
task = serializers.CharField(read_only=True) task = serializers.CharField(read_only=True)
class SystemUserTempAuthSerializer(SystemUserSerializer):
instance_id = serializers.CharField()
class Meta(SystemUserSerializer.Meta):
fields = ['instance_id', 'username', 'password']

View File

@ -46,7 +46,9 @@ urlpatterns = [
path('system-users/<uuid:pk>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'), path('system-users/<uuid:pk>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('system-users/<uuid:pk>/assets/<uuid:aid>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), path('system-users/<uuid:pk>/assets/<uuid:asset_id>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
path('system-users/<uuid:pk>/applications/<uuid:app_id>/auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'),
path('system-users/<uuid:pk>/temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'),
path('system-users/<uuid:pk>/tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users/<uuid:pk>/tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'),
path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),

View File

@ -93,7 +93,7 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView
token = self.create_token(user, asset, application, system_user) token = self.create_token(user, asset, application, system_user)
return Response({"token": token}, status=201) return Response({"token": token}, status=201)
@action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file', permission_classes=[IsValidUser])
def get_rdp_file(self, request, *args, **kwargs): def get_rdp_file(self, request, *args, **kwargs):
options = { options = {
'full address:s': '', 'full address:s': '',
@ -134,13 +134,15 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView
asset = serializer.validated_data.get('asset') asset = serializer.validated_data.get('asset')
application = serializer.validated_data.get('application') application = serializer.validated_data.get('application')
system_user = serializer.validated_data['system_user'] system_user = serializer.validated_data['system_user']
user = serializer.validated_data.get('user')
height = serializer.validated_data.get('height') height = serializer.validated_data.get('height')
width = serializer.validated_data.get('width') width = serializer.validated_data.get('width')
user = request.user
token = self.create_token(user, asset, application, system_user) token = self.create_token(user, asset, application, system_user)
# Todo: 上线后地址是 JumpServerAddr:3389 # Todo: 上线后地址是 JumpServerAddr:3389
address = self.request.query_params.get('address') or '1.1.1.1' address = settings.RDP_ADDR
if address == 'localhost:3389':
address = request.get_host().split(':')[0] + ':3389'
options['full address:s'] = address options['full address:s'] = address
options['username:s'] = '{}|{}'.format(user.username, token) options['username:s'] = '{}|{}'.format(user.username, token)
options['desktopwidth:i'] = width options['desktopwidth:i'] = width
@ -217,10 +219,16 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView
if value.get('type') == 'asset': if value.get('type') == 'asset':
asset_detail = self._get_asset_secret_detail(value, user=user, system_user=system_user) asset_detail = self._get_asset_secret_detail(value, user=user, system_user=system_user)
asset = asset_detail.get('asset')
if asset:
system_user.load_asset_more_auth(asset.id, user.username, user.id)
data['type'] = 'asset' data['type'] = 'asset'
data.update(asset_detail) data.update(asset_detail)
else: else:
app_detail = self._get_application_secret_detail(value) app_detail = self._get_application_secret_detail(value)
app = app_detail.get("application")
if app:
system_user.load_app_more_auth(app.id, user.id)
data['type'] = 'application' data['type'] = 'application'
data.update(app_detail) data.update(app_detail)

View File

@ -199,5 +199,5 @@ class ConnectionTokenSecretSerializer(serializers.Serializer):
class RDPFileSerializer(ConnectionTokenSerializer): class RDPFileSerializer(ConnectionTokenSerializer):
width = serializers.IntegerField(default=1280) width = serializers.IntegerField(default=1600)
height = serializers.IntegerField(default=800) height = serializers.IntegerField(default=900)

View File

@ -300,7 +300,9 @@ class Config(dict):
'SESSION_SAVE_EVERY_REQUEST': True, 'SESSION_SAVE_EVERY_REQUEST': True,
'SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE': False, 'SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE': False,
'FORGOT_PASSWORD_URL': '', 'FORGOT_PASSWORD_URL': '',
'HEALTH_CHECK_TOKEN': '' 'HEALTH_CHECK_TOKEN': '',
'RDP_ADDR': 'localhost:3389'
} }
def compatible_auth_openid_of_key(self): def compatible_auth_openid_of_key(self):

View File

@ -125,3 +125,5 @@ FORGOT_PASSWORD_URL = CONFIG.FORGOT_PASSWORD_URL
# 自定义默认组织名 # 自定义默认组织名
GLOBAL_ORG_DISPLAY_NAME = CONFIG.GLOBAL_ORG_DISPLAY_NAME GLOBAL_ORG_DISPLAY_NAME = CONFIG.GLOBAL_ORG_DISPLAY_NAME
HEALTH_CHECK_TOKEN = CONFIG.HEALTH_CHECK_TOKEN HEALTH_CHECK_TOKEN = CONFIG.HEALTH_CHECK_TOKEN
RDP_ADDR = CONFIG.RDP_ADDR

View File

@ -48,7 +48,6 @@ class OrgViewSet(BulkModelViewSet):
queryset = Organization.objects.all() queryset = Organization.objects.all()
serializer_class = OrgSerializer serializer_class = OrgSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
org = None
def get_serializer_class(self): def get_serializer_class(self):
mapper = { mapper = {
@ -58,32 +57,36 @@ class OrgViewSet(BulkModelViewSet):
return mapper.get(self.action, super().get_serializer_class()) return mapper.get(self.action, super().get_serializer_class())
@tmp_to_root_org() @tmp_to_root_org()
def get_data_from_model(self, model): def get_data_from_model(self, org, model):
if model == User: if model == User:
data = model.objects.filter( data = model.objects.filter(
orgs__id=self.org.id, orgs__id=org.id, m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR]
m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR]
) )
elif model == Node: elif model == Node:
# 节点不能手动删除,所以排除检查 # 节点不能手动删除,所以排除检查
data = model.objects.filter(org_id=self.org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') data = model.objects.filter(org_id=org.id).exclude(parent_key='', key__regex=r'^[0-9]+$')
else: else:
data = model.objects.filter(org_id=self.org.id) data = model.objects.filter(org_id=org.id)
return data return data
def destroy(self, request, *args, **kwargs): def allow_bulk_destroy(self, qs, filtered):
self.org = self.get_object() return False
def perform_destroy(self, instance):
if str(current_org) == str(instance):
msg = _('The current organization ({}) cannot be deleted'.format(current_org))
raise PermissionDenied(detail=msg)
for model in org_related_models: for model in org_related_models:
data = self.get_data_from_model(model) data = self.get_data_from_model(instance, model)
if data: if not data:
msg = _('Have {} exists, Please delete').format(model._meta.verbose_name) continue
return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN) msg = _(
else: 'The organization have resource ({}) cannot be deleted'
if str(current_org) == str(self.org): ).format(model._meta.verbose_name)
msg = _('The current organization cannot be deleted') raise PermissionDenied(detail=msg)
return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN)
self.org.delete() super().perform_destroy(instance)
return Response({'msg': True}, status=status.HTTP_200_OK)
class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet): class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet):

View File

@ -13,7 +13,12 @@ __all__ = [
class BasicSettingSerializer(serializers.Serializer): class BasicSettingSerializer(serializers.Serializer):
SITE_URL = serializers.URLField( SITE_URL = serializers.URLField(
required=True, label=_("Site url"), required=True, label=_("Site url"),
help_text=_('eg: http://demo.jumpserver.org:8080') help_text=_('eg: http://dev.jumpserver.org:8080')
)
RDP_ADDR = serializers.CharField(
required=True, label=_("RDP address"),
max_length=1024,
help_text=_('RDP visit address, eg: dev.jumpserver.org:3389')
) )
USER_GUIDE_URL = serializers.URLField( USER_GUIDE_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("User guide url"), required=False, allow_blank=True, allow_null=True, label=_("User guide url"),

View File

@ -16,6 +16,7 @@ class ReplayStorageTypeChoices(TextChoices):
swift = 'swift', 'Swift' swift = 'swift', 'Swift'
oss = 'oss', 'OSS' oss = 'oss', 'OSS'
azure = 'azure', 'Azure' azure = 'azure', 'Azure'
obs = 'obs', 'OBS'
class CommandStorageTypeChoices(TextChoices): class CommandStorageTypeChoices(TextChoices):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.6 on 2021-04-12 11:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0034_auto_20210406_1434'),
]
operations = [
migrations.AlterField(
model_name='replaystorage',
name='type',
field=models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure'), ('obs', 'OBS')], default='server', max_length=16, verbose_name='Type'),
),
]

View File

@ -82,6 +82,16 @@ class ReplayStorageTypeOSSSerializer(ReplayStorageTypeBaseSerializer):
) )
class ReplayStorageTypeOBSSerializer(ReplayStorageTypeBaseSerializer):
endpoint_help_text = '''
OBS format: obs.{REGION_NAME}.myhuaweicloud.com
Such as: obs.cn-north-4.myhuaweicloud.com
'''
ENDPOINT = serializers.CharField(
max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True,
)
class ReplayStorageTypeAzureSerializer(serializers.Serializer): class ReplayStorageTypeAzureSerializer(serializers.Serializer):
class EndpointSuffixChoices(TextChoices): class EndpointSuffixChoices(TextChoices):
china = 'core.chinacloudapi.cn', 'core.chinacloudapi.cn' china = 'core.chinacloudapi.cn', 'core.chinacloudapi.cn'
@ -105,7 +115,8 @@ replay_storage_type_serializer_classes_mapping = {
const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer, const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer,
const.ReplayStorageTypeChoices.swift.value: ReplayStorageTypeSwiftSerializer, const.ReplayStorageTypeChoices.swift.value: ReplayStorageTypeSwiftSerializer,
const.ReplayStorageTypeChoices.oss.value: ReplayStorageTypeOSSSerializer, const.ReplayStorageTypeChoices.oss.value: ReplayStorageTypeOSSSerializer,
const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer,
const.ReplayStorageTypeChoices.obs.value: ReplayStorageTypeOBSSerializer
} }
# ReplayStorageSerializer # ReplayStorageSerializer