mirror of https://github.com/jumpserver/jumpserver
Merge remote-tracking branch 'origin/v3' into v3
commit
b12f83dbea
|
@ -120,6 +120,13 @@ class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField):
|
|||
super().__init__(choices=choices, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
if isinstance(value, list) and len(value) == 1:
|
||||
# Swagger 会使用 field.choices.keys() 迭代传递进来
|
||||
return [
|
||||
{"value": c.name, "label": c.label}
|
||||
for c in self._choice_cls
|
||||
if c.name == value[0]
|
||||
]
|
||||
return [
|
||||
{"value": c.name, "label": c.label}
|
||||
for c in self._choice_cls
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
def bit(x):
|
||||
return 2 ** (x - 1)
|
||||
if x == 0:
|
||||
return 0
|
||||
else:
|
||||
return 2 ** (x - 1)
|
||||
|
|
|
@ -1,53 +1,23 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.generics import ListAPIView, get_object_or_404
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from assets.serializers import AccountSerializer
|
||||
from perms.hands import User, Asset, Account
|
||||
from common.utils import get_logger
|
||||
from perms import serializers
|
||||
from perms.hands import Asset
|
||||
from perms.utils import PermAccountUtil
|
||||
from .mixin import RoleAdminMixin, RoleUserMixin
|
||||
from .mixin import SelfOrPKUserMixin
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserAllGrantedAccountsApi',
|
||||
'MyAllGrantedAccountsApi',
|
||||
'UserGrantedAssetAccountsApi',
|
||||
'MyGrantedAssetAccountsApi',
|
||||
'UserGrantedAssetSpecialAccountsApi',
|
||||
'MyGrantedAssetSpecialAccountsApi',
|
||||
]
|
||||
|
||||
|
||||
class UserAllGrantedAccountsApi(RoleAdminMixin, ListAPIView):
|
||||
""" 授权给用户的所有账号列表 """
|
||||
serializer_class = AccountSerializer
|
||||
filterset_fields = ("name", "username", "privileged", "version")
|
||||
search_fields = filterset_fields
|
||||
|
||||
def get_queryset(self):
|
||||
util = PermAccountUtil()
|
||||
accounts = util.get_perm_accounts_for_user(self.user)
|
||||
return accounts
|
||||
|
||||
|
||||
class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi):
|
||||
""" 授权给我的所有账号列表 """
|
||||
pass
|
||||
|
||||
|
||||
class UserGrantedAssetAccountsApi(ListAPIView):
|
||||
class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView):
|
||||
serializer_class = serializers.AccountsGrantedSerializer
|
||||
|
||||
@lazyproperty
|
||||
def user(self) -> User:
|
||||
user_id = self.kwargs.get('pk')
|
||||
return User.objects.get(id=user_id)
|
||||
|
||||
@lazyproperty
|
||||
@property
|
||||
def asset(self):
|
||||
asset_id = self.kwargs.get('asset_id')
|
||||
kwargs = {'id': asset_id, 'is_active': True}
|
||||
|
@ -55,41 +25,6 @@ class UserGrantedAssetAccountsApi(ListAPIView):
|
|||
return asset
|
||||
|
||||
def get_queryset(self):
|
||||
accounts = PermAccountUtil().get_perm_accounts_for_user_asset(
|
||||
self.user, self.asset, with_actions=True
|
||||
)
|
||||
util = PermAccountUtil()
|
||||
accounts = util.get_permed_accounts_for_user(self.user, self.asset)
|
||||
return accounts
|
||||
|
||||
|
||||
class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
@lazyproperty
|
||||
def user(self):
|
||||
return self.request.user
|
||||
|
||||
|
||||
class UserGrantedAssetSpecialAccountsApi(ListAPIView):
|
||||
serializer_class = serializers.AccountsGrantedSerializer
|
||||
|
||||
@lazyproperty
|
||||
def user(self):
|
||||
return self.request.user
|
||||
|
||||
def get_queryset(self):
|
||||
# 构造默认包含的账号,如: @INPUT @USER
|
||||
accounts = [
|
||||
Account.get_manual_account(),
|
||||
Account.get_user_account(self.user.username)
|
||||
]
|
||||
for account in accounts:
|
||||
account.actions = Action.ALL
|
||||
return accounts
|
||||
|
||||
|
||||
class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
@lazyproperty
|
||||
def user(self):
|
||||
return self.request.user
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from rest_framework.generics import ListAPIView
|
||||
from django.conf import settings
|
||||
from rest_framework.generics import ListAPIView
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin
|
||||
from .mixin import (
|
||||
UserAllGrantedAssetsQuerysetMixin, UserDirectGrantedAssetsQuerysetMixin, UserFavoriteGrantedAssetsMixin,
|
||||
UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, AssetsTreeFormatMixin,
|
||||
)
|
||||
from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin
|
||||
|
||||
__all__ = [
|
||||
'UserDirectGrantedAssetsApi', 'MyDirectGrantedAssetsApi',
|
||||
|
@ -14,7 +14,8 @@ __all__ = [
|
|||
'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi',
|
||||
'MyUngroupAssetsAsTreeApi',
|
||||
'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi',
|
||||
'UserGrantedNodeAssetsApi', 'MyGrantedNodeAssetsApi',
|
||||
'UserGrantedNodeAssetsApi',
|
||||
'MyGrantedNodeAssetsApi',
|
||||
]
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.request import Request
|
||||
|
||||
from common.http import is_true
|
||||
from common.mixins.api import RoleAdminMixin, RoleUserMixin
|
||||
from perms.utils.user_permission import UserGrantedTreeRefreshController
|
||||
from rbac.permissions import RBACPermission
|
||||
from users.models import User
|
||||
|
||||
|
||||
|
@ -34,3 +36,43 @@ class AssetRoleUserMixin(RebuildTreeMixin, RoleUserMixin):
|
|||
('get_tree', 'perms.view_myassets'),
|
||||
('GET', 'perms.view_myassets'),
|
||||
)
|
||||
|
||||
|
||||
class SelfOrPKUserMixin:
|
||||
kwargs: dict
|
||||
request: Request
|
||||
permission_classes = (RBACPermission,)
|
||||
|
||||
@property
|
||||
def self_rbac_perms(self):
|
||||
return (
|
||||
('list', 'perms.view_myassets'),
|
||||
('retrieve', 'perms.view_myassets'),
|
||||
('get_tree', 'perms.view_myassets'),
|
||||
('GET', 'perms.view_myassets'),
|
||||
)
|
||||
|
||||
@property
|
||||
def admin_rbac_perms(self):
|
||||
return (
|
||||
('list', 'perms.view_userassets'),
|
||||
('retrieve', 'perms.view_userassets'),
|
||||
('get_tree', 'perms.view_userassets'),
|
||||
('GET', 'perms.view_userassets'),
|
||||
)
|
||||
|
||||
def get_rbac_perms(self):
|
||||
if self.request_user_is_self():
|
||||
return self.self_rbac_perms
|
||||
else:
|
||||
return self.admin_rbac_perms
|
||||
|
||||
def request_user_is_self(self):
|
||||
return self.kwargs.get('user') in ['my', 'self']
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
if self.request_user_is_self():
|
||||
return self.request.user
|
||||
else:
|
||||
return get_object_or_404(User, pk=self.kwargs.get('user'))
|
||||
|
|
|
@ -10,11 +10,11 @@ __all__ = ["SpecialAccount", "ActionChoices"]
|
|||
|
||||
|
||||
class ActionChoices(BitChoices):
|
||||
connect = bit(0), _("Connect")
|
||||
upload = bit(1), _("Upload")
|
||||
download = bit(2), _("Download")
|
||||
copy = bit(3), _("Copy")
|
||||
paste = bit(4), _("Paste")
|
||||
connect = bit(1), _("Connect")
|
||||
upload = bit(2), _("Upload")
|
||||
download = bit(3), _("Download")
|
||||
copy = bit(4), _("Copy")
|
||||
paste = bit(5), _("Paste")
|
||||
|
||||
@classmethod
|
||||
def is_tree(cls):
|
||||
|
|
|
@ -16,7 +16,7 @@ __all__ = ["AssetPermissionSerializer", "ActionChoicesField"]
|
|||
|
||||
class ActionChoicesField(BitChoicesField):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(ActionChoices, **kwargs)
|
||||
super().__init__(choice_cls=ActionChoices, **kwargs)
|
||||
|
||||
|
||||
class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from assets.models import Node, Asset, Platform, Account
|
||||
from assets.const import Category, AllTypes
|
||||
from assets.models import Node, Asset, Platform, Account
|
||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from perms.serializers.permission import ActionChoicesField
|
||||
|
||||
__all__ = [
|
||||
|
@ -49,13 +49,9 @@ class ActionsSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class AccountsGrantedSerializer(serializers.ModelSerializer):
|
||||
""" 授权的账号序列类 """
|
||||
|
||||
# Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码
|
||||
# need_manual = serializers.BooleanField(label=_('Need manual input'))
|
||||
actions = ActionChoicesField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = ['id', 'name', 'username', 'actions']
|
||||
fields = ['id', 'name', 'username', 'secret_type', 'has_secret', 'actions']
|
||||
read_only_fields = fields
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# coding:utf-8
|
||||
|
||||
from .asset_permission import asset_permission_urlpatterns
|
||||
from .user_permission import user_permission_urlpatterns
|
||||
|
||||
app_name = 'perms'
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += asset_permission_urlpatterns
|
||||
urlpatterns = asset_permission_urlpatterns \
|
||||
+ user_permission_urlpatterns
|
||||
|
|
|
@ -7,81 +7,14 @@ from .. import api
|
|||
|
||||
router = BulkRouter()
|
||||
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
||||
router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation')
|
||||
router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation')
|
||||
router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation')
|
||||
router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation')
|
||||
|
||||
user_permission_urlpatterns = [
|
||||
# 以 serializer 格式返回
|
||||
path('<uuid:pk>/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'),
|
||||
# Tree Node 的数据格式返回
|
||||
path('<uuid:pk>/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
||||
path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
||||
path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'),
|
||||
|
||||
# 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点
|
||||
# 以 serializer 格式返回
|
||||
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||
path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'),
|
||||
# 以 Tree Node 的数据格式返回
|
||||
path('<uuid:pk>/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
||||
path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
|
||||
|
||||
# 一层一层的获取用户授权的节点,
|
||||
# 以 Serializer 的数据格式返回
|
||||
path('<uuid:pk>/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'),
|
||||
path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'),
|
||||
# 以 Tree Node 的数据格式返回
|
||||
path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), name='user-nodes-children-as-tree'),
|
||||
# 部分调用位置
|
||||
# - 普通用户 -> 我的资产 -> 展开节点 时调用
|
||||
path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
|
||||
|
||||
# 此接口会返回整棵树
|
||||
# 普通用户 -> 命令执行 -> 左侧树
|
||||
path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
|
||||
|
||||
# 主要用于 luna 页面,带资产的节点树
|
||||
path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
|
||||
path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
|
||||
|
||||
# 查询授权树上某个节点的所有资产
|
||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||
path('nodes/<uuid:node_id>/assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||
|
||||
# 未分组的资产
|
||||
path('<uuid:pk>/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
||||
path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
|
||||
|
||||
# 收藏的资产
|
||||
path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
||||
path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
|
||||
|
||||
# 获取授权给用户的所有账号
|
||||
path('<uuid:pk>/accounts/', api.UserAllGrantedAccountsApi.as_view(), name='user-accounts'),
|
||||
path('accounts/', api.MyAllGrantedAccountsApi.as_view(), name='my-accounts'),
|
||||
|
||||
# 获取授权给用户某个资产的所有账号
|
||||
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'),
|
||||
path('assets/<uuid:asset_id>/accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'),
|
||||
# 用户登录资产的特殊账号, @INPUT, @USER 等
|
||||
path('<uuid:pk>/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'),
|
||||
path('assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'),
|
||||
]
|
||||
|
||||
user_group_permission_urlpatterns = [
|
||||
# 查询某个用户组授权的资产和资产组
|
||||
path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
||||
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
|
||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
|
||||
|
||||
# 获取所有和资产-用户组关联的账号列表
|
||||
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'),
|
||||
]
|
||||
router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet,
|
||||
'asset-permissions-users-relation')
|
||||
router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet,
|
||||
'asset-permissions-user-groups-relation')
|
||||
router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet,
|
||||
'asset-permissions-assets-relation')
|
||||
router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet,
|
||||
'asset-permissions-nodes-relation')
|
||||
|
||||
permission_urlpatterns = [
|
||||
# 授权规则中授权的资产
|
||||
|
@ -92,8 +25,6 @@ permission_urlpatterns = [
|
|||
|
||||
asset_permission_urlpatterns = [
|
||||
# Assets
|
||||
path('users/', include(user_permission_urlpatterns)),
|
||||
path('user-groups/', include(user_group_permission_urlpatterns)),
|
||||
path('asset-permissions/', include(permission_urlpatterns)),
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
from django.urls import path, include
|
||||
|
||||
from .. import api
|
||||
|
||||
user_permission_urlpatterns = [
|
||||
# 以 serializer 格式返回
|
||||
path('<uuid:pk>/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'),
|
||||
# Tree Node 的数据格式返回
|
||||
path('<uuid:pk>/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
||||
path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
||||
path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'),
|
||||
|
||||
# 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点
|
||||
# 以 serializer 格式返回
|
||||
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||
path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'),
|
||||
# 以 Tree Node 的数据格式返回
|
||||
path('<uuid:pk>/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
||||
path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
|
||||
|
||||
# 一层一层的获取用户授权的节点,
|
||||
# 以 Serializer 的数据格式返回
|
||||
path('<uuid:pk>/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'),
|
||||
path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'),
|
||||
# 以 Tree Node 的数据格式返回
|
||||
path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(),
|
||||
name='user-nodes-children-as-tree'),
|
||||
# 部分调用位置
|
||||
# - 普通用户 -> 我的资产 -> 展开节点 时调用
|
||||
path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
|
||||
|
||||
# 此接口会返回整棵树
|
||||
# 普通用户 -> 命令执行 -> 左侧树
|
||||
path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(),
|
||||
name='my-nodes-with-assets-as-tree'),
|
||||
|
||||
# 主要用于 luna 页面,带资产的节点树
|
||||
path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(),
|
||||
name='user-nodes-children-with-assets-as-tree'),
|
||||
path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(),
|
||||
name='my-nodes-children-with-assets-as-tree'),
|
||||
|
||||
# 查询授权树上某个节点的所有资产
|
||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||
path('nodes/<uuid:node_id>/assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||
|
||||
# 未分组的资产
|
||||
path('<uuid:pk>/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
||||
path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
|
||||
|
||||
# 收藏的资产
|
||||
path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
||||
path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(),
|
||||
name='my-ungrouped-assets'),
|
||||
|
||||
# 获取授权给用户某个资产的所有账号
|
||||
path('<str:user>/assets/<uuid:asset_id>/accounts/', api.UserGrantedAssetAccountsApi.as_view(),
|
||||
name='user-asset-accounts'),
|
||||
]
|
||||
|
||||
user_group_permission_urlpatterns = [
|
||||
# 查询某个用户组授权的资产和资产组
|
||||
path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
||||
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(),
|
||||
name='user-group-nodes-children-as-tree'),
|
||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(),
|
||||
name='user-group-node-assets'),
|
||||
|
||||
# 获取所有和资产-用户组关联的账号列表
|
||||
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(),
|
||||
name='user-group-asset-accounts'),
|
||||
]
|
||||
|
||||
user_permission_urlpatterns = [
|
||||
path('users/', include(user_permission_urlpatterns)),
|
||||
path('user-groups/', include(user_group_permission_urlpatterns)),
|
||||
]
|
|
@ -1,30 +1,31 @@
|
|||
import time
|
||||
from collections import defaultdict
|
||||
from typing import List, Tuple
|
||||
import time
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from common.db.models import output_as_string, UnionQuerySet
|
||||
from common.utils.common import lazyproperty, timeit
|
||||
from assets.models import (
|
||||
Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet
|
||||
)
|
||||
from assets.utils import NodeAssetsUtil
|
||||
from common.utils import get_logger
|
||||
from common.db.models import output_as_string, UnionQuerySet
|
||||
from common.decorator import on_transaction_commit
|
||||
from common.utils import get_logger
|
||||
from common.utils.common import lazyproperty, timeit
|
||||
from orgs.models import Organization
|
||||
from orgs.utils import (
|
||||
tmp_to_org, current_org,
|
||||
ensure_in_real_or_default_org, tmp_to_root_org
|
||||
)
|
||||
from assets.models import (
|
||||
Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet
|
||||
)
|
||||
from users.models import User
|
||||
from orgs.models import Organization
|
||||
from perms.locks import UserGrantedTreeRebuildLock
|
||||
from perms.models import (
|
||||
AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation
|
||||
)
|
||||
from users.models import User
|
||||
|
||||
NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom
|
||||
NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id')
|
||||
|
||||
|
@ -119,8 +120,7 @@ class UserGrantedTreeRefreshController:
|
|||
key = cls.key_template.format(user_id=user_id)
|
||||
p.srem(key, *org_ids)
|
||||
p.execute()
|
||||
logger.info(f'Remove orgs from users built tree: users:{user_ids} '
|
||||
f'orgs:{org_ids}')
|
||||
logger.info(f'Remove orgs from users built tree: users:{user_ids} orgs:{org_ids}')
|
||||
|
||||
@classmethod
|
||||
def add_need_refresh_orgs_for_users(cls, org_ids, user_ids):
|
||||
|
@ -205,28 +205,30 @@ class UserGrantedTreeRefreshController:
|
|||
user = self.user
|
||||
|
||||
with tmp_to_root_org():
|
||||
UserAssetGrantedTreeNodeRelation.objects.filter(user=user)\
|
||||
.exclude(org_id__in=self.org_ids)\
|
||||
UserAssetGrantedTreeNodeRelation.objects.filter(user=user) \
|
||||
.exclude(org_id__in=self.org_ids) \
|
||||
.delete()
|
||||
|
||||
if force or self.have_need_refresh_orgs():
|
||||
with UserGrantedTreeRebuildLock(user_id=user.id):
|
||||
if force:
|
||||
orgs = self.orgs
|
||||
self.set_all_orgs_as_built()
|
||||
else:
|
||||
orgs = self.get_need_refresh_orgs_and_fill_up()
|
||||
if not force and not self.have_need_refresh_orgs():
|
||||
return
|
||||
|
||||
for org in orgs:
|
||||
with tmp_to_org(org):
|
||||
t_start = time.time()
|
||||
logger.info(f'Rebuild user tree: user={self.user} org={current_org}')
|
||||
utils = UserGrantedTreeBuildUtils(user)
|
||||
utils.rebuild_user_granted_tree()
|
||||
logger.info(
|
||||
f'Rebuild user tree ok: cost={time.time() - t_start} '
|
||||
f'user={self.user} org={current_org}'
|
||||
)
|
||||
with UserGrantedTreeRebuildLock(user_id=user.id):
|
||||
if force:
|
||||
orgs = self.orgs
|
||||
self.set_all_orgs_as_built()
|
||||
else:
|
||||
orgs = self.get_need_refresh_orgs_and_fill_up()
|
||||
|
||||
for org in orgs:
|
||||
with tmp_to_org(org):
|
||||
t_start = time.time()
|
||||
logger.info(f'Rebuild user tree: user={self.user} org={current_org}')
|
||||
utils = UserGrantedTreeBuildUtils(user)
|
||||
utils.rebuild_user_granted_tree()
|
||||
logger.info(
|
||||
f'Rebuild user tree ok: cost={time.time() - t_start} '
|
||||
f'user={self.user} org={current_org}'
|
||||
)
|
||||
|
||||
|
||||
class UserGrantedUtilsBase:
|
||||
|
@ -427,8 +429,8 @@ class UserGrantedTreeBuildUtils(UserGrantedUtilsBase):
|
|||
for node_id, asset_id in node_asset_pairs:
|
||||
if node_id not in node_id_key_mapper:
|
||||
continue
|
||||
nkey = node_id_key_mapper[node_id]
|
||||
nodekey_assetsid_mapper[nkey].add(asset_id)
|
||||
node_key = node_id_key_mapper[node_id]
|
||||
nodekey_assetsid_mapper[node_key].add(asset_id)
|
||||
|
||||
util = NodeAssetsUtil(nodes, nodekey_assetsid_mapper)
|
||||
util.generate()
|
||||
|
@ -604,7 +606,10 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase):
|
|||
def get_top_level_nodes(self):
|
||||
nodes = self.get_special_nodes()
|
||||
real_nodes = self.get_indirect_granted_node_children('')
|
||||
nodes.extend(self.sort(real_nodes))
|
||||
nodes.extend(real_nodes)
|
||||
if len(real_nodes) == 1:
|
||||
children = self.get_node_children(real_nodes[0].key)
|
||||
nodes.extend(children)
|
||||
return nodes
|
||||
|
||||
def get_ungrouped_node(self):
|
||||
|
@ -649,11 +654,9 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase):
|
|||
:param with_special:
|
||||
:return:
|
||||
"""
|
||||
nodes = PermNode.objects.filter(
|
||||
granted_node_rels__user=self.user
|
||||
).annotate(
|
||||
**PermNode.annotate_granted_node_rel_fields
|
||||
).distinct()
|
||||
nodes = PermNode.objects.filter(granted_node_rels__user=self.user) \
|
||||
.annotate(**PermNode.annotate_granted_node_rel_fields) \
|
||||
.distinct()
|
||||
|
||||
key_to_node_mapper = {}
|
||||
nodes_descendant_q = Q()
|
||||
|
|
|
@ -2,16 +2,15 @@ from rest_framework import viewsets
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.permissions import IsServiceAccount
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from common.permissions import IsServiceAccount
|
||||
from orgs.utils import tmp_to_builtin_org
|
||||
from terminal.models import AppletHost, AppletHostDeployment
|
||||
from terminal.serializers import (
|
||||
AppletHostSerializer, AppletHostDeploymentSerializer,
|
||||
AppletHostStartupSerializer
|
||||
AppletHostStartupSerializer, AppletHostDeployAppletSerializer
|
||||
)
|
||||
from terminal.models import AppletHost, AppletHostDeployment
|
||||
from terminal.tasks import run_applet_host_deployment
|
||||
|
||||
from terminal.tasks import run_applet_host_deployment, run_applet_host_deployment_install_applet
|
||||
|
||||
__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
|
||||
|
||||
|
@ -41,11 +40,24 @@ class AppletHostViewSet(JMSModelViewSet):
|
|||
class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = AppletHostDeploymentSerializer
|
||||
queryset = AppletHostDeployment.objects.all()
|
||||
rbac_perms = (
|
||||
('applets', 'terminal.view_AppletHostDeployment'),
|
||||
)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
instance = serializer.save()
|
||||
task = run_applet_host_deployment.delay(instance.id)
|
||||
instance.save_task(task.id)
|
||||
return Response({'task': str(task.id)}, status=201)
|
||||
|
||||
@action(methods=['post'], detail=False, serializer_class=AppletHostDeployAppletSerializer)
|
||||
def applets(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
applet_id = serializer.validated_data.get('applet_id')
|
||||
instance = serializer.save()
|
||||
task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id)
|
||||
instance.save_task(task.id)
|
||||
return Response({'task': str(task.id)}, status=201)
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import os
|
||||
import datetime
|
||||
import shutil
|
||||
import os
|
||||
|
||||
import yaml
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.db.utils import safe_db_connection
|
||||
from common.utils import get_logger
|
||||
from ops.ansible import PlaybookRunner, JMSInventory
|
||||
from terminal.models import Applet, AppletHostDeployment
|
||||
|
||||
logger = get_logger(__name__)
|
||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class DeployAppletHostManager:
|
||||
def __init__(self, deployment):
|
||||
def __init__(self, deployment: AppletHostDeployment, applet: Applet = None):
|
||||
self.deployment = deployment
|
||||
self.applet = applet
|
||||
self.run_dir = self.get_run_dir()
|
||||
|
||||
@staticmethod
|
||||
|
@ -25,29 +26,56 @@ class DeployAppletHostManager:
|
|||
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
return os.path.join(base, now)
|
||||
|
||||
def generate_playbook(self):
|
||||
playbook_src = os.path.join(CURRENT_DIR, "playbook.yml")
|
||||
base_site_url = settings.BASE_SITE_URL
|
||||
def run(self, **kwargs):
|
||||
self._run(self._run_initial_deploy, **kwargs)
|
||||
|
||||
def install_applet(self, **kwargs):
|
||||
self._run(self._run_install_applet, **kwargs)
|
||||
|
||||
def _run_initial_deploy(self, **kwargs):
|
||||
playbook = self.generate_initial_playbook
|
||||
return self._run_playbook(playbook, **kwargs)
|
||||
|
||||
def _run_install_applet(self, **kwargs):
|
||||
if self.applet:
|
||||
generate_playbook = self.generate_install_applet_playbook
|
||||
else:
|
||||
generate_playbook = self.generate_install_all_playbook
|
||||
return self._run_playbook(generate_playbook, **kwargs)
|
||||
|
||||
def generate_initial_playbook(self):
|
||||
site_url = settings.SITE_URL
|
||||
bootstrap_token = settings.BOOTSTRAP_TOKEN
|
||||
host_id = str(self.deployment.host.id)
|
||||
if not base_site_url:
|
||||
base_site_url = "http://localhost:8080"
|
||||
with open(playbook_src) as f:
|
||||
plays = yaml.safe_load(f)
|
||||
for play in plays:
|
||||
play["vars"].update(self.deployment.host.deploy_options)
|
||||
play["vars"]["DownloadHost"] = base_site_url + "/download"
|
||||
play["vars"]["CORE_HOST"] = base_site_url
|
||||
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
|
||||
play["vars"]["HOST_ID"] = host_id
|
||||
play["vars"]["HOST_NAME"] = self.deployment.host.name
|
||||
if not site_url:
|
||||
site_url = "http://localhost:8080"
|
||||
options = self.deployment.host.deploy_options
|
||||
|
||||
playbook_dir = os.path.join(self.run_dir, "playbook")
|
||||
playbook_dst = os.path.join(playbook_dir, "main.yml")
|
||||
os.makedirs(playbook_dir, exist_ok=True)
|
||||
with open(playbook_dst, "w") as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
return playbook_dst
|
||||
def handler(plays):
|
||||
for play in plays:
|
||||
play["vars"].update(options)
|
||||
play["vars"]["DownloadHost"] = site_url + "/download"
|
||||
play["vars"]["CORE_HOST"] = site_url
|
||||
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
|
||||
play["vars"]["HOST_ID"] = host_id
|
||||
play["vars"]["HOST_NAME"] = self.deployment.host.name
|
||||
|
||||
return self._generate_playbook("playbook.yml", handler)
|
||||
|
||||
def generate_install_all_playbook(self):
|
||||
return self._generate_playbook("install_all.yml")
|
||||
|
||||
def generate_install_applet_playbook(self):
|
||||
applet_name = self.applet.name
|
||||
options = self.deployment.host.deploy_options
|
||||
|
||||
def handler(plays):
|
||||
for play in plays:
|
||||
play["vars"].update(options)
|
||||
play["vars"]["applet_name"] = applet_name
|
||||
return plays
|
||||
|
||||
return self._generate_playbook("install_applet.yml", handler)
|
||||
|
||||
def generate_inventory(self):
|
||||
inventory = JMSInventory(
|
||||
|
@ -58,18 +86,31 @@ class DeployAppletHostManager:
|
|||
inventory.write_to_file(inventory_path)
|
||||
return inventory_path
|
||||
|
||||
def _run(self, **kwargs):
|
||||
def _generate_playbook(self, playbook_template_name, plays_handler: callable = None):
|
||||
playbook_src = os.path.join(CURRENT_DIR, playbook_template_name)
|
||||
with open(playbook_src) as f:
|
||||
plays = yaml.safe_load(f)
|
||||
if plays_handler:
|
||||
plays = plays_handler(plays)
|
||||
playbook_dir = os.path.join(self.run_dir, "playbook")
|
||||
playbook_dst = os.path.join(playbook_dir, "main.yml")
|
||||
os.makedirs(playbook_dir, exist_ok=True)
|
||||
with open(playbook_dst, "w") as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
return playbook_dst
|
||||
|
||||
def _run_playbook(self, generate_playbook: callable, **kwargs):
|
||||
inventory = self.generate_inventory()
|
||||
playbook = self.generate_playbook()
|
||||
playbook = generate_playbook()
|
||||
runner = PlaybookRunner(
|
||||
inventory=inventory, playbook=playbook, project_dir=self.run_dir
|
||||
)
|
||||
return runner.run(**kwargs)
|
||||
|
||||
def run(self, **kwargs):
|
||||
def _run(self, cb_func: callable, **kwargs):
|
||||
try:
|
||||
self.deployment.date_start = timezone.now()
|
||||
cb = self._run(**kwargs)
|
||||
cb = cb_func(**kwargs)
|
||||
self.deployment.status = cb.status
|
||||
except Exception as e:
|
||||
logger.error("Error: {}".format(e))
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
|
||||
- hosts: all
|
||||
|
||||
tasks:
|
||||
- name: Install all applets
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd install all"
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
|
||||
- hosts: all
|
||||
vars:
|
||||
applet_name: chrome
|
||||
|
||||
tasks:
|
||||
- name: Install applet
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd install --name {{ applet_name }}"
|
||||
when: applet_name != 'all'
|
|
@ -3,7 +3,6 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
DownloadHost: https://demo.jumpserver.org/download
|
||||
Initial: 0
|
||||
HOST_NAME: test
|
||||
HOST_ID: 00000000-0000-0000-0000-000000000000
|
||||
CORE_HOST: https://demo.jumpserver.org
|
||||
|
@ -17,166 +16,166 @@
|
|||
TinkerInstaller: Tinker_Installer_v0.0.1.exe
|
||||
|
||||
tasks:
|
||||
- name: Install RDS-Licensing (RDS)
|
||||
ansible.windows.win_feature:
|
||||
name: RDS-Licensing
|
||||
state: present
|
||||
include_management_tools: yes
|
||||
when: RDS_Licensing
|
||||
- name: Install RDS-Licensing (RDS)
|
||||
ansible.windows.win_feature:
|
||||
name: RDS-Licensing
|
||||
state: present
|
||||
include_management_tools: yes
|
||||
when: RDS_Licensing
|
||||
|
||||
- name: Install RDS-RD-Server (RDS)
|
||||
ansible.windows.win_feature:
|
||||
name: RDS-RD-Server
|
||||
state: present
|
||||
include_management_tools: yes
|
||||
register: rds_install
|
||||
- name: Install RDS-RD-Server (RDS)
|
||||
ansible.windows.win_feature:
|
||||
name: RDS-RD-Server
|
||||
state: present
|
||||
include_management_tools: yes
|
||||
register: rds_install
|
||||
|
||||
- name: Download JumpServer Tinker installer (jumpserver)
|
||||
ansible.windows.win_get_url:
|
||||
- name: Download JumpServer Tinker installer (jumpserver)
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/{{ TinkerInstaller }}"
|
||||
dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
||||
|
||||
- name: Install JumpServer Tinker (jumpserver)
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
||||
arguments:
|
||||
- /VERYSILENT
|
||||
- /SUPPRESSMSGBOXES
|
||||
- /NORESTART
|
||||
state: present
|
||||
- name: Install JumpServer Tinker (jumpserver)
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
||||
arguments:
|
||||
- /VERYSILENT
|
||||
- /SUPPRESSMSGBOXES
|
||||
- /NORESTART
|
||||
state: present
|
||||
|
||||
- name: Set remote-server on the global system path (remote-server)
|
||||
ansible.windows.win_path:
|
||||
elements:
|
||||
- '%USERPROFILE%\AppData\Local\Programs\Tinker\'
|
||||
scope: user
|
||||
- name: Set remote-server on the global system path (remote-server)
|
||||
ansible.windows.win_path:
|
||||
elements:
|
||||
- '%USERPROFILE%\AppData\Local\Programs\Tinker\'
|
||||
scope: user
|
||||
|
||||
- name: Download python-3.10.8
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
|
||||
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||
- name: Download python-3.10.8
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
|
||||
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||
|
||||
- name: Install the python-3.10.8
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||
product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}'
|
||||
arguments:
|
||||
- /quiet
|
||||
- InstallAllUsers=1
|
||||
- PrependPath=1
|
||||
- Include_test=0
|
||||
- Include_launcher=0
|
||||
state: present
|
||||
register: win_install_python
|
||||
- name: Install the python-3.10.8
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||
product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}'
|
||||
arguments:
|
||||
- /quiet
|
||||
- InstallAllUsers=1
|
||||
- PrependPath=1
|
||||
- Include_test=0
|
||||
- Include_launcher=0
|
||||
state: present
|
||||
register: win_install_python
|
||||
|
||||
- name: Reboot if installing requires it
|
||||
ansible.windows.win_reboot:
|
||||
post_reboot_delay: 10
|
||||
test_command: whoami
|
||||
when: rds_install.reboot_required or win_install_python.reboot_required
|
||||
- name: Reboot if installing requires it
|
||||
ansible.windows.win_reboot:
|
||||
post_reboot_delay: 10
|
||||
test_command: whoami
|
||||
when: rds_install.reboot_required or win_install_python.reboot_required
|
||||
|
||||
- name: Set RDS LicenseServer (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: LicenseServers
|
||||
data: "{{ RDS_LicenseServer }}"
|
||||
type: string
|
||||
- name: Set RDS LicenseServer (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: LicenseServers
|
||||
data: "{{ RDS_LicenseServer }}"
|
||||
type: string
|
||||
|
||||
- name: Set RDS LicensingMode (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: LicensingMode
|
||||
data: "{{ RDS_LicensingMode }}"
|
||||
type: dword
|
||||
- name: Set RDS LicensingMode (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: LicensingMode
|
||||
data: "{{ RDS_LicensingMode }}"
|
||||
type: dword
|
||||
|
||||
- name: Set RDS fSingleSessionPerUser (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: fSingleSessionPerUser
|
||||
data: "{{ RDS_fSingleSessionPerUser }}"
|
||||
type: dword
|
||||
- name: Set RDS fSingleSessionPerUser (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: fSingleSessionPerUser
|
||||
data: "{{ RDS_fSingleSessionPerUser }}"
|
||||
type: dword
|
||||
|
||||
- name: Set RDS MaxDisconnectionTime (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: MaxDisconnectionTime
|
||||
data: "{{ RDS_MaxDisconnectionTime }}"
|
||||
type: dword
|
||||
when: RDS_MaxDisconnectionTime >= 60000
|
||||
- name: Set RDS MaxDisconnectionTime (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: MaxDisconnectionTime
|
||||
data: "{{ RDS_MaxDisconnectionTime }}"
|
||||
type: dword
|
||||
when: RDS_MaxDisconnectionTime >= 60000
|
||||
|
||||
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: RemoteAppLogoffTimeLimit
|
||||
data: "{{ RDS_RemoteAppLogoffTimeLimit }}"
|
||||
type: dword
|
||||
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
|
||||
ansible.windows.win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
name: RemoteAppLogoffTimeLimit
|
||||
data: "{{ RDS_RemoteAppLogoffTimeLimit }}"
|
||||
type: dword
|
||||
|
||||
- name: Download pip packages
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip"
|
||||
dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||
- name: Download pip packages
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip"
|
||||
dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||
|
||||
- name: Unzip pip_packages
|
||||
community.windows.win_unzip:
|
||||
src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||
dest: "{{ ansible_env.TEMP }}"
|
||||
- name: Unzip pip_packages
|
||||
community.windows.win_unzip:
|
||||
src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||
dest: "{{ ansible_env.TEMP }}"
|
||||
|
||||
- name: Install python requirements offline
|
||||
ansible.windows.win_shell: >
|
||||
- name: Install python requirements offline
|
||||
ansible.windows.win_shell: >
|
||||
pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt'
|
||||
--no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1'
|
||||
|
||||
- name: Download chromedriver (chrome)
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/chromedriver_win32.107.zip"
|
||||
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
||||
- name: Download chromedriver (chrome)
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/chromedriver_win32.107.zip"
|
||||
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
||||
|
||||
- name: Unzip chromedriver (chrome)
|
||||
community.windows.win_unzip:
|
||||
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
||||
dest: C:\Program Files\JumpServer\drivers
|
||||
- name: Unzip chromedriver (chrome)
|
||||
community.windows.win_unzip:
|
||||
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
||||
dest: C:\Program Files\JumpServer\drivers
|
||||
|
||||
- name: Set chromedriver on the global system path (chrome)
|
||||
ansible.windows.win_path:
|
||||
elements:
|
||||
- 'C:\Program Files\JumpServer\drivers'
|
||||
- name: Set chromedriver on the global system path (chrome)
|
||||
ansible.windows.win_path:
|
||||
elements:
|
||||
- 'C:\Program Files\JumpServer\drivers'
|
||||
|
||||
- name: Download chrome msi package (chrome)
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi"
|
||||
dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||
- name: Download chrome msi package (chrome)
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi"
|
||||
dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||
|
||||
- name: Install chrome (chrome)
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||
state: present
|
||||
arguments:
|
||||
- /quiet
|
||||
- name: Install chrome (chrome)
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||
state: present
|
||||
arguments:
|
||||
- /quiet
|
||||
|
||||
- name: Generate tinkerd component config
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
|
||||
- name: Generate tinkerd component config
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
|
||||
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
|
||||
|
||||
- name: Install tinkerd service
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd service install"
|
||||
- name: Install tinkerd service
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd service install"
|
||||
|
||||
- name: Start tinkerd service
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd service start"
|
||||
- name: Start tinkerd service
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd service start"
|
||||
|
||||
- name: Wait Tinker api health
|
||||
ansible.windows.win_uri:
|
||||
url: http://localhost:6068/api/health/
|
||||
status_code: 200
|
||||
method: GET
|
||||
register: _result
|
||||
until: _result.status_code == 200
|
||||
retries: 30
|
||||
delay: 5
|
||||
- name: Wait Tinker api health
|
||||
ansible.windows.win_uri:
|
||||
url: http://localhost:6068/api/health/
|
||||
status_code: 200
|
||||
method: GET
|
||||
register: _result
|
||||
until: _result.status_code == 200
|
||||
retries: 30
|
||||
delay: 5
|
||||
|
||||
- name: Sync all remote applets
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd install all"
|
||||
- name: Sync all remote applets
|
||||
ansible.windows.win_shell:
|
||||
"tinkerd install all"
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.14 on 2022-11-15 05:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('terminal', '0058_auto_20221103_1624'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='applethostdeployment',
|
||||
name='task',
|
||||
field=models.UUIDField(null=True, verbose_name='Task'),
|
||||
),
|
||||
]
|
|
@ -105,8 +105,23 @@ class AppletHostDeployment(JMSBaseModel):
|
|||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
||||
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
task = models.UUIDField(null=True, verbose_name=_('Task'))
|
||||
|
||||
def start(self, **kwargs):
|
||||
from ...automations.deploy_applet_host import DeployAppletHostManager
|
||||
manager = DeployAppletHostManager(self)
|
||||
manager.run(**kwargs)
|
||||
|
||||
def install_applet(self, applet_id, **kwargs):
|
||||
from ...automations.deploy_applet_host import DeployAppletHostManager
|
||||
from .applet import Applet
|
||||
if applet_id:
|
||||
applet = Applet.objects.get(id=applet_id)
|
||||
else:
|
||||
applet = None
|
||||
manager = DeployAppletHostManager(self, applet=applet)
|
||||
manager.install_applet(**kwargs)
|
||||
|
||||
def save_task(self, task):
|
||||
self.task = task
|
||||
self.save(update_fields=['task'])
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from assets.models import Platform, Account
|
||||
from assets.serializers import HostSerializer
|
||||
from ..models import AppletHost, AppletHostDeployment, Applet
|
||||
from common.drf.fields import LabeledChoiceField
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from .applet import AppletSerializer
|
||||
from .. import const
|
||||
|
||||
from ..models import AppletHost, AppletHostDeployment
|
||||
|
||||
__all__ = [
|
||||
'AppletHostSerializer', 'AppletHostDeploymentSerializer',
|
||||
'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer',
|
||||
'AppletHostStartupSerializer',
|
||||
'AppletHostStartupSerializer', 'AppletHostDeployAppletSerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -29,7 +28,8 @@ class DeployOptionsSerializer(serializers.Serializer):
|
|||
RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing"))
|
||||
RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024)
|
||||
RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode'))
|
||||
RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, label=_("RDS fSingleSessionPerUser"))
|
||||
RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1,
|
||||
label=_("RDS fSingleSessionPerUser"))
|
||||
RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time"))
|
||||
RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit"))
|
||||
|
||||
|
@ -86,7 +86,7 @@ class HostAppletSerializer(AppletSerializer):
|
|||
class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AppletHostDeployment
|
||||
fields_mini = ['id', 'host', 'status']
|
||||
fields_mini = ['id', 'host', 'status', 'task']
|
||||
read_only_fields = [
|
||||
'status', 'date_created', 'date_updated',
|
||||
'date_start', 'date_finished'
|
||||
|
@ -94,6 +94,18 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
|||
fields = fields_mini + ['comment'] + read_only_fields
|
||||
|
||||
|
||||
class AppletHostDeployAppletSerializer(AppletHostDeploymentSerializer):
|
||||
applet_id = serializers.UUIDField(write_only=True, allow_null=True, required=False)
|
||||
|
||||
class Meta(AppletHostDeploymentSerializer.Meta):
|
||||
fields = AppletHostDeploymentSerializer.Meta.fields + ['applet_id']
|
||||
|
||||
def create(self, validated_data):
|
||||
applet_id = validated_data.pop('applet_id', None)
|
||||
deployment = super().create(validated_data)
|
||||
return deployment
|
||||
|
||||
|
||||
class AppletHostAccountSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Account
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import subprocess
|
||||
import datetime
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.utils import timezone
|
||||
from django.core.files.storage import default_storage
|
||||
from django.utils import timezone
|
||||
|
||||
from common.utils import get_log_keep_day
|
||||
from ops.celery.decorator import (
|
||||
register_as_period_task, after_app_ready_start,
|
||||
after_app_shutdown_clean_periodic
|
||||
)
|
||||
from .models import (
|
||||
Status, Session, Command, Task, AppletHost,
|
||||
AppletHostDeployment
|
||||
)
|
||||
from orgs.utils import tmp_to_builtin_org
|
||||
from .backends import server_replay_storage
|
||||
from .models import (
|
||||
Status, Session, Command, Task, AppletHostDeployment
|
||||
)
|
||||
from .utils import find_session_replay_local
|
||||
|
||||
CACHE_REFRESH_INTERVAL = 10
|
||||
|
@ -57,7 +56,7 @@ def clean_orphan_session():
|
|||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@register_as_period_task(interval=3600 * 24)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean_periodic
|
||||
def clean_expired_session_period():
|
||||
|
@ -114,3 +113,10 @@ def run_applet_host_deployment(did):
|
|||
with tmp_to_builtin_org(system=1):
|
||||
deployment = AppletHostDeployment.objects.get(id=did)
|
||||
deployment.start()
|
||||
|
||||
|
||||
@shared_task
|
||||
def run_applet_host_deployment_install_applet(did, applet_id):
|
||||
with tmp_to_builtin_org(system=1):
|
||||
deployment = AppletHostDeployment.objects.get(id=did)
|
||||
deployment.install_applet(applet_id)
|
||||
|
|
|
@ -2,22 +2,20 @@
|
|||
#
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.exceptions import MethodNotAllowed
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.const.http import POST, PUT, PATCH
|
||||
from common.mixins.api import CommonApiMixin
|
||||
from orgs.utils import tmp_to_root_org
|
||||
|
||||
from rbac.permissions import RBACPermission
|
||||
|
||||
from tickets import serializers
|
||||
from tickets import filters
|
||||
from tickets.permissions.ticket import IsAssignee, IsApplicant
|
||||
from tickets import serializers
|
||||
from tickets.models import (
|
||||
Ticket, ApplyAssetTicket, ApplyLoginTicket,
|
||||
ApplyLoginAssetTicket, ApplyCommandTicket
|
||||
)
|
||||
from tickets.permissions.ticket import IsAssignee, IsApplicant
|
||||
|
||||
__all__ = [
|
||||
'TicketViewSet', 'ApplyAssetTicketViewSet',
|
||||
|
@ -27,10 +25,8 @@ __all__ = [
|
|||
|
||||
|
||||
class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||
serializer_class = serializers.TicketDisplaySerializer
|
||||
serializer_class = serializers.TicketSerializer
|
||||
serializer_classes = {
|
||||
'list': serializers.TicketListSerializer,
|
||||
'open': serializers.TicketApplySerializer,
|
||||
'approve': serializers.TicketApproveSerializer
|
||||
}
|
||||
model = Ticket
|
||||
|
@ -40,8 +36,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
|||
'title', 'type', 'status'
|
||||
]
|
||||
ordering_fields = (
|
||||
'title', 'status', 'state',
|
||||
'action_display', 'date_created', 'serial_num',
|
||||
'title', 'status', 'state', 'action_display',
|
||||
'date_created', 'serial_num',
|
||||
)
|
||||
ordering = ('-date_created',)
|
||||
rbac_perms = {
|
||||
|
@ -98,11 +94,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
|||
|
||||
|
||||
class ApplyAssetTicketViewSet(TicketViewSet):
|
||||
serializer_class = serializers.ApplyAssetDisplaySerializer
|
||||
serializer_classes = {
|
||||
'open': serializers.ApplyAssetSerializer,
|
||||
'approve': serializers.ApproveAssetSerializer
|
||||
}
|
||||
serializer_class = serializers.ApplyAssetSerializer
|
||||
model = ApplyAssetTicket
|
||||
filterset_class = filters.ApplyAssetTicketFilter
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from perms.const import ActionChoices
|
||||
from .general import Ticket
|
||||
|
||||
__all__ = ['ApplyAssetTicket']
|
||||
|
@ -14,13 +15,6 @@ class ApplyAssetTicket(Ticket):
|
|||
# 申请信息
|
||||
apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets'))
|
||||
apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts'))
|
||||
apply_actions = models.IntegerField(default=1, verbose_name=_('Actions'))
|
||||
apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all())
|
||||
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
|
||||
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)
|
||||
|
||||
@property
|
||||
def apply_actions_display(self):
|
||||
return 'Todo'
|
||||
|
||||
def get_apply_actions_display(self):
|
||||
return ', '.join(self.apply_actions_display)
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from perms.serializers.permission import ActionChoicesField
|
||||
from perms.models import AssetPermission
|
||||
from orgs.utils import tmp_to_org
|
||||
from assets.models import Asset, Node
|
||||
|
||||
from common.drf.fields import ObjectRelatedField
|
||||
from perms.models import AssetPermission
|
||||
from perms.serializers.permission import ActionChoicesField
|
||||
from tickets.models import ApplyAssetTicket
|
||||
from .common import BaseApplyAssetSerializer
|
||||
from .ticket import TicketApplySerializer
|
||||
from .common import BaseApplyAssetApplicationSerializer
|
||||
|
||||
__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer', 'ApproveAssetSerializer']
|
||||
__all__ = ['ApplyAssetSerializer']
|
||||
|
||||
asset_or_node_help_text = _("Select at least one asset or node")
|
||||
|
||||
|
||||
class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
||||
apply_actions = ActionChoicesField(required=True, allow_empty=False)
|
||||
class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer):
|
||||
apply_assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Apply assets'))
|
||||
apply_nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Apply nodes'))
|
||||
apply_actions = ActionChoicesField(required=False, allow_null=True, label=_("Apply actions"))
|
||||
permission_model = AssetPermission
|
||||
|
||||
class Meta:
|
||||
class Meta(TicketApplySerializer.Meta):
|
||||
model = ApplyAssetTicket
|
||||
fields_mini = ['id', 'title']
|
||||
writeable_fields = [
|
||||
'id', 'title', 'type', 'apply_nodes', 'apply_assets',
|
||||
'id', 'title', 'apply_nodes', 'apply_assets',
|
||||
'apply_accounts', 'apply_actions', 'org_id', 'comment',
|
||||
'apply_date_start', 'apply_date_expired'
|
||||
]
|
||||
fields = TicketApplySerializer.Meta.fields + writeable_fields + [
|
||||
'apply_permission_name', 'apply_actions_display'
|
||||
]
|
||||
fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name', ]
|
||||
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
||||
extra_kwargs = {
|
||||
'apply_nodes': {'required': False, 'allow_empty': True},
|
||||
'apply_assets': {'required': False, 'allow_empty': True},
|
||||
'apply_accounts': {'required': False, 'allow_empty': True},
|
||||
'apply_nodes': {'required': False},
|
||||
'apply_assets': {'required': False},
|
||||
'apply_accounts': {'required': False},
|
||||
}
|
||||
extra_kwargs.update(ticket_extra_kwargs)
|
||||
|
||||
|
@ -45,9 +45,11 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
|||
return self.filter_many_to_many_field(Asset, assets)
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs['type'] = 'apply_asset'
|
||||
attrs = super().validate(attrs)
|
||||
if self.is_final_approval and (
|
||||
not attrs.get('apply_nodes') and not attrs.get('apply_assets')
|
||||
not attrs.get('apply_nodes')
|
||||
and not attrs.get('apply_assets')
|
||||
):
|
||||
raise serializers.ValidationError({
|
||||
'apply_nodes': asset_or_node_help_text,
|
||||
|
@ -56,29 +58,7 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
|||
|
||||
return attrs
|
||||
|
||||
|
||||
class ApproveAssetSerializer(ApplyAssetSerializer):
|
||||
class Meta(ApplyAssetSerializer.Meta):
|
||||
read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + [
|
||||
'title', 'type'
|
||||
]
|
||||
|
||||
|
||||
class ApplyAssetDisplaySerializer(ApplyAssetSerializer):
|
||||
apply_nodes = serializers.SerializerMethodField()
|
||||
apply_assets = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = ApplyAssetSerializer.Meta.model
|
||||
fields = ApplyAssetSerializer.Meta.fields
|
||||
read_only_fields = fields
|
||||
|
||||
@staticmethod
|
||||
def get_apply_nodes(instance):
|
||||
with tmp_to_org(instance.org_id):
|
||||
return instance.apply_nodes.values_list('id', flat=True)
|
||||
|
||||
@staticmethod
|
||||
def get_apply_assets(instance):
|
||||
with tmp_to_org(instance.org_id):
|
||||
return instance.apply_assets.values_list('id', flat=True)
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
queryset = queryset.prefetch_related('apply_nodes', 'apply_assets')
|
||||
return queryset
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from django.db.transaction import atomic
|
||||
from django.db.models import Model
|
||||
from django.db.transaction import atomic
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.utils import tmp_to_org
|
||||
from tickets.models import Ticket
|
||||
|
||||
__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetApplicationSerializer']
|
||||
__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetSerializer']
|
||||
|
||||
|
||||
def get_default_permission_name(ticket):
|
||||
|
@ -34,7 +34,7 @@ class DefaultPermissionName(object):
|
|||
return self.default
|
||||
|
||||
|
||||
class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
||||
class BaseApplyAssetSerializer(serializers.Serializer):
|
||||
permission_model: Model
|
||||
|
||||
@property
|
||||
|
|
|
@ -3,31 +3,35 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.models import Organization
|
||||
from common.drf.fields import LabeledChoiceField
|
||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||
from orgs.models import Organization
|
||||
from tickets.const import TicketType, TicketStatus, TicketState
|
||||
from tickets.models import Ticket, TicketFlow
|
||||
from tickets.const import TicketType
|
||||
|
||||
__all__ = [
|
||||
'TicketDisplaySerializer', 'TicketApplySerializer', 'TicketListSerializer', 'TicketApproveSerializer'
|
||||
'TicketApplySerializer', 'TicketApproveSerializer', 'TicketSerializer',
|
||||
]
|
||||
|
||||
|
||||
class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display'))
|
||||
type = LabeledChoiceField(choices=TicketType.choices, read_only=True, label=_('Type'))
|
||||
status = LabeledChoiceField(choices=TicketStatus.choices, read_only=True, label=_('Status'))
|
||||
state = LabeledChoiceField(choices=TicketState.choices, read_only=True, label=_("State"))
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields_mini = ['id', 'title']
|
||||
fields_small = fields_mini + [
|
||||
'type', 'type_display', 'status', 'status_display',
|
||||
'state', 'approval_step', 'rel_snapshot', 'comment',
|
||||
'type', 'status', 'state', 'approval_step', 'comment',
|
||||
'date_created', 'date_updated', 'org_id', 'rel_snapshot',
|
||||
'process_map', 'org_name', 'serial_num'
|
||||
]
|
||||
fields_fk = ['applicant', ]
|
||||
fields = fields_small + fields_fk
|
||||
extra_kwargs = {
|
||||
'type': {'required': True}
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -41,43 +45,20 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
|||
choices.pop(TicketType.general, None)
|
||||
tp._choices = choices
|
||||
|
||||
|
||||
class TicketListSerializer(TicketSerializer):
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = [
|
||||
'id', 'title', 'serial_num', 'type', 'type_display', 'status',
|
||||
'state', 'rel_snapshot', 'date_created', 'rel_snapshot'
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class TicketDisplaySerializer(TicketSerializer):
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = TicketSerializer.Meta.fields
|
||||
read_only_fields = fields
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
queryset = queryset.prefetch_related('ticket_steps')
|
||||
return queryset
|
||||
|
||||
|
||||
class TicketApproveSerializer(TicketSerializer):
|
||||
class Meta:
|
||||
model = Ticket
|
||||
class Meta(TicketSerializer.Meta):
|
||||
fields = TicketSerializer.Meta.fields
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class TicketApplySerializer(TicketSerializer):
|
||||
org_id = serializers.CharField(
|
||||
required=True, max_length=36,
|
||||
allow_blank=True, label=_("Organization")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = TicketSerializer.Meta.fields
|
||||
extra_kwargs = {
|
||||
'type': {'required': True}
|
||||
}
|
||||
org_id = serializers.CharField(required=True, max_length=36, allow_blank=True, label=_("Organization"))
|
||||
|
||||
@staticmethod
|
||||
def validate_org_id(org_id):
|
||||
|
@ -91,10 +72,13 @@ class TicketApplySerializer(TicketSerializer):
|
|||
if self.instance:
|
||||
return attrs
|
||||
|
||||
print("Attrs: ", attrs)
|
||||
|
||||
ticket_type = attrs.get('type')
|
||||
org_id = attrs.get('org_id')
|
||||
flow = TicketFlow.get_org_related_flows(org_id=org_id)\
|
||||
flow = TicketFlow.get_org_related_flows(org_id=org_id) \
|
||||
.filter(type=ticket_type).first()
|
||||
|
||||
if flow:
|
||||
attrs['flow'] = flow
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue