Compare commits

...

11 Commits

13 changed files with 101 additions and 74 deletions

View File

@@ -18,6 +18,11 @@ WORKDIR /opt/jumpserver
COPY ./requirements ./requirements
RUN useradd jumpserver
RUN wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo \
&& sed -i 's@/centos/@/centos-vault/@g' /etc/yum.repos.d/CentOS-Base.repo \
&& sed -i 's@$releasever@6.10@g' /etc/yum.repos.d/CentOS-Base.repo
RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=${MYSQL_MIRROR}\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
RUN yum -y install $(cat requirements/rpm_requirements.txt)

View File

@@ -158,9 +158,11 @@ class AuthMixin:
if update_fields:
self.save(update_fields=update_fields)
def has_special_auth(self, asset=None):
def has_special_auth(self, asset=None, username=None):
from .authbook import AuthBook
queryset = AuthBook.objects.filter(username=self.username)
if username is None:
username = self.username
queryset = AuthBook.objects.filter(username=username)
if asset:
queryset = queryset.filter(asset=asset)
return queryset.exists()

View File

@@ -140,6 +140,11 @@ class SystemUser(BaseUser):
def is_need_test_asset_connective(self):
return self.protocol not in [self.PROTOCOL_MYSQL]
def has_special_auth(self, asset=None, username=None):
if username is None and self.username_same_with_user:
raise TypeError('System user is dynamic, username should be pass')
return super().has_special_auth(asset=asset, username=username)
@property
def can_perm_to_asset(self):
return self.protocol not in [self.PROTOCOL_MYSQL]

View File

@@ -134,6 +134,7 @@ def get_push_windows_system_user_tasks(system_user, username=None):
tasks = []
if not password:
logger.error("Error: no password found")
return tasks
task = {
'name': 'Add user {}'.format(username),
@@ -209,14 +210,15 @@ def push_system_user_util(system_user, assets, task_name, username=None):
print(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts)))
if not system_user.has_special_auth():
# 如果没有特殊密码设置,就不需要单独推送某台机器了
if not system_user.has_special_auth(username=username):
logger.debug("System user not has special auth")
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, _hosts)
continue
for _host in _hosts:
system_user.load_asset_special_auth(_host)
system_user.load_asset_special_auth(_host, username=username)
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, [_host])

View File

@@ -54,12 +54,3 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
return Response(value)
else:
return Response({'user': value['user']})
def get_permissions(self):
if self.request.query_params.get('user-only', None):
self.permission_classes = (AllowAny,)
return super().get_permissions()

View File

@@ -38,10 +38,10 @@ def common_exception_handler(exc, context):
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
if isinstance(exc.detail, str) and isinstance(exc.get_codes(), str):
data = {'detail': exc.detail, 'code': exc.get_codes()}
else:
data = {'detail': exc.detail}
data = exc.detail
set_rollback()
return Response(data, status=exc.status_code, headers=headers)

View File

@@ -65,6 +65,8 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
"""
Task result Callback
"""
context = None
def clean_result(self, t, host, task_name, task_result):
contacted = self.results_summary["contacted"]
dark = self.results_summary["dark"]
@@ -133,7 +135,11 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
pass
def set_play_context(self, context):
context.ssh_args = '-C -o ControlMaster=no'
# for k, v in context._attributes.items():
# print("{} ==> {}".format(k, v))
if self.context and isinstance(self.context, dict):
for k, v in self.context.items():
setattr(context, k, v)
class CommandResultCallback(AdHocResultCallback):

View File

@@ -182,6 +182,13 @@ class AdHocRunner:
_options.update(options)
return _options
def set_control_master_if_need(self, cleaned_tasks):
modules = [task.get('action', {}).get('module') for task in cleaned_tasks]
if {'ping', 'win_ping'} & set(modules):
self.results_callback.context = {
'ssh_args': '-C -o ControlMaster=no'
}
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
"""
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
@@ -193,6 +200,7 @@ class AdHocRunner:
self.check_pattern(pattern)
self.results_callback = self.get_result_callback()
cleaned_tasks = self.clean_tasks(tasks)
self.set_control_master_if_need(cleaned_tasks)
context.CLIARGS = ImmutableDict(self.options)
play_source = dict(

View File

@@ -15,7 +15,11 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
disconnected = False
def connect(self):
self.accept()
user = self.scope["user"]
if user.is_authenticated and user.is_org_admin:
self.accept()
else:
self.close()
def receive(self, text_data=None, bytes_data=None, **kwargs):
data = json.loads(text_data)

View File

@@ -14,7 +14,7 @@ router = DefaultRouter()
bulk_router = BulkRouter()
router.register(r'orgs', api.OrgViewSet, 'org')
bulk_router.register(r'org-memeber-relation', api.OrgMemberRelationBulkViewSet, 'org-memeber-relation')
bulk_router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation')
old_version_urlpatterns = [
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)

View File

@@ -9,16 +9,16 @@ from common.permissions import IsValidUser
from common.utils import get_logger, get_object_or_none
from .mixin import UserNodeGrantStatusDispatchMixin, ForUserMixin, ForAdminMixin
from ...utils.user_asset_permission import (
get_user_resources_q_granted_by_permissions,
get_indirect_granted_node_children, UNGROUPED_NODE_KEY, FAVORITE_NODE_KEY,
get_user_direct_granted_assets, get_top_level_granted_nodes,
get_user_granted_nodes_list_via_mapping_node,
get_user_granted_all_assets, rebuild_user_tree_if_need,
get_user_all_assetpermission_ids,
get_user_all_assetpermissions_id,
)
from assets.models import Asset, FavoriteAsset
from assets.api import SerializeToTreeNodeMixin
from orgs.utils import tmp_to_root_org
from ...hands import Node
logger = get_logger(__name__)
@@ -27,6 +27,7 @@ logger = get_logger(__name__)
class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
permission_classes = (IsValidUser,)
@tmp_to_root_org()
def list(self, request: Request, *args, **kwargs):
"""
此算法依赖 UserGrantedMappingNode
@@ -64,7 +65,7 @@ class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(ForAdminMixin, UserNode
def get_data_on_node_indirect_granted(self, key):
user = self.user
asset_perm_ids = get_user_all_assetpermission_ids(user)
asset_perm_ids = get_user_all_assetpermissions_id(user)
nodes = get_indirect_granted_node_children(user, key)

View File

@@ -11,16 +11,19 @@ logger = get_logger(__file__)
def get_asset_system_users_id_with_actions(asset_perm_queryset: BasePermissionQuerySet, asset: Asset):
asset_perms_id = set(asset_perm_queryset.values_list('id', flat=True))
nodes = asset.get_nodes()
node_keys = set()
for node in nodes:
ancestor_keys = node.get_ancestor_keys(with_self=True)
node_keys.update(ancestor_keys)
queryset = asset_perm_queryset.filter(
queryset = AssetPermission.objects.filter(id__in=asset_perms_id).filter(
Q(assets=asset) |
Q(nodes__key__in=node_keys)
)
asset_protocols = asset.protocols_as_dict.keys()
values = queryset.filter(
system_users__protocol__in=asset_protocols

View File

@@ -1,5 +1,5 @@
from functools import reduce, wraps
from operator import or_, and_
from operator import or_
from uuid import uuid4
import threading
import inspect
@@ -32,27 +32,6 @@ TMP_ASSET_GRANTED_FIELD = '_asset_granted'
TMP_GRANTED_ASSETS_AMOUNT_FIELD = '_granted_assets_amount'
# 使用场景
# Asset.objects.filter(get_user_resources_q_granted_by_permissions(user))
def get_user_resources_q_granted_by_permissions(user: User):
"""
获取用户关联的 asset permission 或者 用户组关联的 asset permission 获取规则,
前提 AssetPermission 对象中的 related_name 为 granted_by_permissions
:param user:
:return:
"""
_now = now()
return reduce(and_, (
Q(granted_by_permissions__date_start__lt=_now),
Q(granted_by_permissions__date_expired__gt=_now),
Q(granted_by_permissions__is_active=True),
(
Q(granted_by_permissions__users=user) |
Q(granted_by_permissions__user_groups__users=user)
)
))
# 使用场景
# `Node.objects.annotate(**node_annotate_mapping_node)`
node_annotate_mapping_node = {
@@ -147,12 +126,15 @@ def rebuild_user_mapping_nodes_with_lock(user: User):
@tmp_to_root_org()
def compute_tmp_mapping_node_from_perm(user: User):
def compute_tmp_mapping_node_from_perm(user: User, asset_perms_id=None):
node_only_fields = ('id', 'key', 'parent_key', 'assets_amount')
if asset_perms_id is None:
asset_perms_id = get_user_all_assetpermissions_id(user)
# 查询直接授权节点
nodes = Node.objects.filter(
get_user_resources_q_granted_by_permissions(user)
granted_by_permissions__id__in=asset_perms_id
).distinct().only(*node_only_fields)
granted_key_set = {_node.key for _node in nodes}
@@ -178,7 +160,7 @@ def compute_tmp_mapping_node_from_perm(user: User):
def process_direct_granted_assets():
# 查询直接授权资产
asset_ids = Asset.objects.filter(
get_user_resources_q_granted_by_permissions(user)
granted_by_permissions__id__in=asset_perms_id
).distinct().values_list('id', flat=True)
# 查询授权资产关联的节点设置
granted_asset_nodes = Node.objects.filter(
@@ -211,7 +193,7 @@ def compute_tmp_mapping_node_from_perm(user: User):
return [*leaf_nodes, *ancestors]
def create_mapping_nodes(user, nodes, clear=True):
def create_mapping_nodes(user, nodes):
to_create = []
for node in nodes:
_granted = getattr(node, TMP_GRANTED_FIELD, False)
@@ -227,12 +209,10 @@ def create_mapping_nodes(user, nodes, clear=True):
assets_amount=_granted_assets_amount,
))
if clear:
UserGrantedMappingNode.objects.filter(user=user).delete()
UserGrantedMappingNode.objects.bulk_create(to_create)
def set_node_granted_assets_amount(user, node):
def set_node_granted_assets_amount(user, node, asset_perms_id=None):
"""
不依赖`UserGrantedMappingNode`直接查询授权计算资产数量
"""
@@ -241,17 +221,25 @@ def set_node_granted_assets_amount(user, node):
assets_amount = node.assets_amount
else:
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
assets_amount = count_direct_granted_node_assets(user, node.key)
assets_amount = count_direct_granted_node_assets(user, node.key, asset_perms_id)
else:
assets_amount = count_node_all_granted_assets(user, node.key)
assets_amount = count_node_all_granted_assets(user, node.key, asset_perms_id)
setattr(node, TMP_GRANTED_ASSETS_AMOUNT_FIELD, assets_amount)
@tmp_to_root_org()
def rebuild_user_mapping_nodes(user):
logger.info(f'>>> {dt_formater(now())} start rebuild {user} mapping nodes')
tmp_nodes = compute_tmp_mapping_node_from_perm(user)
# 先删除用户旧的授权树🌲
UserGrantedMappingNode.objects.filter(user=user).delete()
asset_perms_id = get_user_all_assetpermissions_id(user)
if not asset_perms_id:
# 没有授权直接返回
return
tmp_nodes = compute_tmp_mapping_node_from_perm(user, asset_perms_id)
for _node in tmp_nodes:
set_node_granted_assets_amount(user, _node)
set_node_granted_assets_amount(user, _node, asset_perms_id)
create_mapping_nodes(user, tmp_nodes)
logger.info(f'>>> {dt_formater(now())} end rebuild {user} mapping nodes')
@@ -303,7 +291,7 @@ def get_user_granted_nodes_list_via_mapping_node(user):
def get_user_granted_all_assets(user, via_mapping_node=True):
asset_perm_ids = get_user_all_assetpermission_ids(user)
asset_perm_ids = get_user_all_assetpermissions_id(user)
if via_mapping_node:
granted_node_keys = UserGrantedMappingNode.objects.filter(
user=user, granted=True,
@@ -365,7 +353,8 @@ def get_node_all_granted_assets(user: User, key):
if only_asset_granted_nodes_qs:
only_asset_granted_nodes_q = reduce(or_, only_asset_granted_nodes_qs)
only_asset_granted_nodes_q &= get_user_resources_q_granted_by_permissions(user)
asset_perms_id = get_user_all_assetpermissions_id(user)
only_asset_granted_nodes_q &= Q(granted_by_permissions__id__in=asset_perms_id)
q.append(only_asset_granted_nodes_q)
if q:
@@ -373,13 +362,16 @@ def get_node_all_granted_assets(user: User, key):
return assets
def get_direct_granted_node_ids(user: User, key):
granted_q = get_user_resources_q_granted_by_permissions(user)
def get_direct_granted_node_ids(user: User, key, asset_perms_id=None):
if asset_perms_id is None:
asset_perms_id = get_user_all_assetpermissions_id(user)
# 先查出该节点下的直接授权节点
granted_nodes = Node.objects.filter(
Q(key__startswith=f'{key}:') | Q(key=key)
).filter(granted_q).distinct().only('id', 'key')
).filter(
granted_by_permissions__id__in=asset_perms_id
).distinct().only('id', 'key')
node_ids = set()
# 根据直接授权节点查询他们的子节点
@@ -394,33 +386,38 @@ def get_direct_granted_node_ids(user: User, key):
return node_ids
def get_node_all_granted_assets_from_perm(user: User, key):
def get_node_all_granted_assets_from_perm(user: User, key, asset_perms_id=None):
"""
此算法依据 `AssetPermission` 的数据查询
1. 查询该节点下的直接授权节点
2. 查询该节点下授权资产关联的节点
"""
granted_q = get_user_resources_q_granted_by_permissions(user)
if asset_perms_id is None:
asset_perms_id = get_user_all_assetpermissions_id(user)
# 直接授权资产查询条件
q = (Q(nodes__key__startswith=f'{key}:') | Q(nodes__key=key)) & granted_q
node_ids = get_direct_granted_node_ids(user, key)
q = (
Q(nodes__key__startswith=f'{key}:') | Q(nodes__key=key)
) & Q(granted_by_permissions__id__in=asset_perms_id)
node_ids = get_direct_granted_node_ids(user, key, asset_perms_id)
q |= Q(nodes__id__in=node_ids)
asset_qs = Asset.objects.filter(q).distinct()
return asset_qs
def get_direct_granted_node_assets_from_perm(user: User, key):
node_ids = get_direct_granted_node_ids(user, key)
def get_direct_granted_node_assets_from_perm(user: User, key, asset_perms_id=None):
node_ids = get_direct_granted_node_ids(user, key, asset_perms_id)
asset_qs = Asset.objects.filter(nodes__id__in=node_ids).distinct()
return asset_qs
def count_node_all_granted_assets(user: User, key):
return get_node_all_granted_assets_from_perm(user, key).count()
def count_node_all_granted_assets(user: User, key, asset_perms_id=None):
return get_node_all_granted_assets_from_perm(user, key, asset_perms_id).count()
def count_direct_granted_node_assets(user: User, key):
return get_direct_granted_node_assets_from_perm(user, key).count()
def count_direct_granted_node_assets(user: User, key, asset_perms_id=None):
return get_direct_granted_node_assets_from_perm(user, key, asset_perms_id).count()
def get_indirect_granted_node_children(user, key=''):
@@ -453,7 +450,7 @@ def get_top_level_granted_nodes(user):
return nodes
def get_user_all_assetpermission_ids(user: User):
def get_user_all_assetpermissions_id(user: User):
asset_perm_ids = set()
asset_perm_ids.update(
AssetPermission.objects.valid().filter(users=user).distinct().values_list('id', flat=True)
@@ -466,7 +463,7 @@ def get_user_all_assetpermission_ids(user: User):
def get_user_direct_granted_assets(user, asset_perm_ids=None):
if asset_perm_ids is None:
asset_perm_ids = get_user_all_assetpermission_ids(user)
asset_perm_ids = get_user_all_assetpermissions_id(user)
assets = Asset.org_objects.filter(granted_by_permissions__id__in=asset_perm_ids).distinct()
return assets
@@ -507,4 +504,7 @@ def rebuild_user_tree_if_need(request, user):
rebuild_user_mapping_nodes_with_lock(user)
except lock.SomeoneIsDoingThis:
# 您的数据正在初始化,请稍等
raise lock.SomeoneIsDoingThis(detail=_('Please wait while your data is being initialized'))
raise lock.SomeoneIsDoingThis(
detail=_('Please wait while your data is being initialized'),
code='rebuild_tree_conflict'
)