jumpserver/apps/perms/utils/asset_permission.py

514 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# coding: utf-8
import uuid
from collections import defaultdict
import json
from hashlib import md5
from django.utils import timezone
from django.db.models import Q
from django.core.cache import cache
from django.conf import settings
from django.utils.translation import ugettext as _
from common.utils import get_logger
from common.tree import TreeNode
from .. import const
from ..models import AssetPermission, Action
from ..hands import Node
logger = get_logger(__file__)
__all__ = [
'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
'parse_asset_to_tree_node', 'parse_node_to_tree_node',
'check_system_user_action',
]
class GenerateTree:
def __init__(self):
"""
nodes: {"node_instance": {
"asset_instance": set("system_user")
}
"""
self.__all_nodes = list(Node.objects.all())
self.nodes = defaultdict(dict)
self.direct_nodes = []
self._root_node = None
self._ungroup_node = None
@property
def root_node(self):
if self._root_node:
return self._root_node
all_nodes = self.nodes.keys()
# 如果没有授权节点,就放到默认的根节点下
if not all_nodes:
root_node = Node.root()
self.add_node(root_node)
else:
root_node = max(all_nodes)
self._root_node = root_node
return root_node
@property
def ungrouped_node(self):
if self._ungroup_node:
return self._ungroup_node
node_id = const.UNGROUPED_NODE_ID
node_key = self.root_node.get_next_child_key()
node_value = _("Default")
node = Node(id=node_id, key=node_key, value=node_value)
self.add_node(node)
self._ungroup_node = node
return node
def add_asset(self, asset, system_users):
nodes = asset.nodes.all()
in_nodes = set(self.direct_nodes) & set(nodes)
for node in in_nodes:
self.nodes[node][asset].update(system_users)
if not in_nodes:
self.nodes[self.ungrouped_node][asset].update(system_users)
def get_nodes(self):
for node in self.nodes:
assets = set(self.nodes.get(node).keys())
for n in self.nodes.keys():
if n.key.startswith(node.key + ':'):
assets.update(set(self.nodes[n].keys()))
node.assets_amount = len(assets)
return self.nodes
# 添加节点时,追溯到根节点
def add_node(self, node):
if node in self.nodes:
return
else:
self.nodes[node] = defaultdict(set)
if node.is_root():
return
for n in self.__all_nodes:
if n.key == node.parent_key:
self.add_node(n)
break
# 添加树节点
def add_nodes(self, nodes):
for node in nodes:
self.add_node(node)
self.add_nodes(node.get_all_children(with_self=False))
# 如果是直接授权的节点则放到direct_nodes中
self.direct_nodes.append(node)
def get_user_permissions(user, include_group=True):
if include_group:
groups = user.groups.all()
arg = Q(users=user) | Q(user_groups__in=groups)
else:
arg = Q(users=user)
return AssetPermission.objects.all().valid().filter(arg)
def get_user_group_permissions(user_group):
return AssetPermission.objects.all().valid().filter(
user_groups=user_group
)
def get_asset_permissions(asset, include_node=True):
if include_node:
nodes = asset.get_all_nodes(flat=True)
arg = Q(assets=asset) | Q(nodes__in=nodes)
else:
arg = Q(assets=asset)
return AssetPermission.objects.all().valid().filter(arg)
def get_node_permissions(node):
return AssetPermission.objects.all().valid().filter(nodes=node)
def get_system_user_permissions(system_user):
return AssetPermission.objects.valid().all().filter(
system_users=system_user
)
class AssetPermissionUtil:
get_permissions_map = {
"User": get_user_permissions,
"UserGroup": get_user_group_permissions,
"Asset": get_asset_permissions,
"Node": get_node_permissions,
"SystemUser": get_system_user_permissions,
}
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_'
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_'
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
def __init__(self, obj, cache_policy='0'):
self.object = obj
self.obj_id = str(obj.id)
self._permissions = None
self._permissions_id = None # 标记_permission的唯一值
self._assets = None
self._filter_id = 'None' # 当通过filter更改 permission是标记
self.cache_policy = cache_policy
self.tree = GenerateTree()
@classmethod
def is_not_using_cache(cls, cache_policy):
return cls.CACHE_TIME == 0 or cache_policy in cls.CACHE_POLICY_MAP[0]
@classmethod
def is_using_cache(cls, cache_policy):
return cls.CACHE_TIME != 0 and cache_policy in cls.CACHE_POLICY_MAP[1]
@classmethod
def is_refresh_cache(cls, cache_policy):
return cache_policy in cls.CACHE_POLICY_MAP[2]
def _is_not_using_cache(self):
return self.is_not_using_cache(self.cache_policy)
def _is_using_cache(self):
return self.is_using_cache(self.cache_policy)
def _is_refresh_cache(self):
return self.is_refresh_cache(self.cache_policy)
@property
def permissions(self):
if self._permissions:
return self._permissions
object_cls = self.object.__class__.__name__
func = self.get_permissions_map[object_cls]
permissions = func(self.object)
self._permissions = permissions
return permissions
def filter_permissions(self, **filters):
filters_json = json.dumps(filters, sort_keys=True)
self._permissions = self.permissions.filter(**filters)
self._filter_id = md5(filters_json.encode()).hexdigest()
@staticmethod
def _structured_system_user(system_users, actions):
"""
结构化系统用户
:param system_users:
:param actions:
:return: {system_user1: {'actions': set(), }, }
"""
_attr = {'actions': set(actions)}
_system_users = {system_user: _attr for system_user in system_users}
return _system_users
def get_nodes_direct(self):
"""
返回用户/组授权规则直接关联的节点
:return: {asset1: {system_user1: {'actions': set()},}}
"""
nodes = defaultdict(dict)
permissions = self.permissions.prefetch_related('nodes', 'system_users')
for perm in permissions:
actions = perm.actions.all()
self.tree.add_nodes(perm.nodes.all())
for node in perm.nodes.all():
system_users = perm.system_users.all()
system_users = self._structured_system_user(system_users, actions)
nodes[node].update(system_users)
return nodes
def get_assets_direct(self):
"""
返回用户授权规则直接关联的资产
:return: {asset1: {system_user1: {'actions': set()},}}
"""
assets = defaultdict(dict)
permissions = self.permissions.prefetch_related('assets', 'system_users')
for perm in permissions:
actions = perm.actions.all()
for asset in perm.assets.all().valid().prefetch_related('nodes'):
system_users = perm.system_users.filter(protocol__in=asset.protocols_name)
system_users = self._structured_system_user(system_users, actions)
assets[asset].update(system_users)
return assets
def get_assets_without_cache(self):
"""
:return: {asset1: set(system_user1,)}
"""
if self._assets:
return self._assets
assets = self.get_assets_direct()
nodes = self.get_nodes_direct()
for node, system_users in nodes.items():
_assets = node.get_all_assets().valid().prefetch_related('nodes')
for asset in _assets:
for system_user, attr_dict in system_users.items():
if not asset.has_protocol(system_user.protocol):
continue
if system_user in assets[asset]:
actions = assets[asset][system_user]['actions']
attr_dict['actions'].update(actions)
system_users.update({system_user: attr_dict})
assets[asset].update(system_users)
__assets = defaultdict(set)
for asset, system_users in assets.items():
for system_user, attr_dict in system_users.items():
setattr(system_user, 'actions', attr_dict['actions'])
__assets[asset] = set(system_users.keys())
self._assets = __assets
return self._assets
def get_cache_key(self, resource):
cache_key = self.CACHE_KEY_PREFIX + '{obj_id}_{filter_id}_{resource}'
return cache_key.format(
obj_id=self.obj_id, filter_id=self._filter_id,
resource=resource
)
def node_key(self):
return self.get_cache_key('NODES_WITH_ASSETS')
def asset_key(self):
return self.get_cache_key('ASSETS')
def system_key(self):
return self.get_cache_key('SYSTEM_USER')
def get_assets_from_cache(self):
cached = cache.get(self.asset_key)
if not cached:
self.update_cache()
cached = cache.get(self.asset_key)
return cached
def get_assets(self):
if self._is_not_using_cache():
return self.get_assets_from_cache()
elif self._is_refresh_cache():
self.expire_cache()
return self.get_assets_from_cache()
else:
self.expire_cache()
return self.get_assets_without_cache()
def get_nodes_with_assets_without_cache(self):
"""
返回节点并且包含资产
{"node": {"assets": set("system_user")}}
:return:
"""
assets = self.get_assets_without_cache()
for asset, system_users in assets.items():
self.tree.add_asset(asset, system_users)
return self.tree.get_nodes()
def get_nodes_with_assets_from_cache(self):
cached = cache.get(self.node_key)
if not cached:
self.update_cache()
cached = cache.get(self.node_key)
return cached
def get_nodes_with_assets(self):
if self._is_using_cache():
return self.get_nodes_with_assets_from_cache()
elif self._is_refresh_cache():
self.expire_cache()
return self.get_nodes_with_assets_from_cache()
else:
return self.get_nodes_with_assets_without_cache()
def get_system_user_without_cache(self):
system_users = set()
permissions = self.permissions.prefetch_related('system_users')
for perm in permissions:
system_users.update(perm.system_users.all())
return system_users
def get_system_user_from_cache(self):
cached = cache.get(self.system_key)
if not cached:
self.update_cache()
cached = cache.get(self.system_key)
return cached
def get_system_users(self):
if self._is_using_cache():
return self.get_system_user_from_cache()
elif self._is_refresh_cache():
self.expire_cache()
return self.get_system_user_from_cache()
else:
return self.get_system_user_without_cache()
def get_meta_cache_key(self):
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}'
key = cache_key.format(
obj_id=str(self.object.id), filter_id=self._filter_id
)
return key
@property
def cache_meta(self):
key = self.get_meta_cache_key()
return cache.get(key) or {}
def set_meta_to_cache(self):
key = self.get_meta_cache_key()
meta = {
'id': str(uuid.uuid4()),
'datetime': timezone.now(),
'object': str(self.object)
}
cache.set(key, meta, self.CACHE_TIME)
def expire_cache_meta(self):
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_*'
key = cache_key.format(obj_id=str(self.object.id))
cache.delete_pattern(key)
def update_cache(self):
assets = self.get_assets_without_cache()
nodes = self.get_nodes_with_assets_without_cache()
system_users = self.get_system_user_without_cache()
cache.set(self.asset_key, assets, self.CACHE_TIME)
cache.set(self.node_key, nodes, self.CACHE_TIME)
cache.set(self.system_key, system_users, self.CACHE_TIME)
self.set_meta_to_cache()
def expire_cache(self):
"""
因为 获取用户的节点,资产,系统用户等都能会缓存,这里会清理所有与该对象有关的
缓存,以免造成不统一的情况
:return:
"""
cache_key = self.CACHE_KEY_PREFIX + '{obj_id}_*'
key = cache_key.format(obj_id='*')
cache.delete_pattern(key)
self.expire_cache_meta()
@classmethod
def expire_all_cache_meta(cls):
key = cls.CACHE_META_KEY_PREFIX + '*'
cache.delete_pattern(key)
@classmethod
def expire_all_cache(cls):
key = cls.CACHE_KEY_PREFIX + '*'
cache.delete_pattern(key)
def is_obj_attr_has(obj, val, attrs=("hostname", "ip", "comment")):
if not attrs:
vals = [val for val in obj.__dict__.values() if isinstance(val, (str, int))]
else:
vals = [getattr(obj, attr) for attr in attrs if
hasattr(obj, attr) and isinstance(hasattr(obj, attr), (str, int))]
for v in vals:
if str(v).find(val) != -1:
return True
return False
def sort_assets(assets, order_by='hostname', reverse=False):
if order_by == 'ip':
assets = sorted(assets, key=lambda asset: [int(d) for d in asset.ip.split('.') if d.isdigit()], reverse=reverse)
else:
assets = sorted(assets, key=lambda asset: getattr(asset, order_by), reverse=reverse)
return assets
def parse_node_to_tree_node(node):
from .. import serializers
name = '{} ({})'.format(node.value, node.assets_amount)
node_serializer = serializers.GrantedNodeSerializer(node)
data = {
'id': node.key,
'name': name,
'title': name,
'pId': node.parent_key,
'isParent': True,
'open': node.is_root(),
'meta': {
'node': node_serializer.data,
'type': 'node'
}
}
tree_node = TreeNode(**data)
return tree_node
def parse_asset_to_tree_node(node, asset, system_users):
system_users_protocol_matched = [s for s in system_users if asset.has_protocol(s.protocol)]
icon_skin = 'file'
if asset.platform.lower() == 'windows':
icon_skin = 'windows'
elif asset.platform.lower() == 'linux':
icon_skin = 'linux'
system_users = []
for system_user in system_users_protocol_matched:
system_users.append({
'id': system_user.id,
'name': system_user.name,
'username': system_user.username,
'protocol': system_user.protocol,
'priority': system_user.priority,
'login_mode': system_user.login_mode,
'actions': [action.name for action in system_user.actions],
'comment': system_user.comment,
})
data = {
'id': str(asset.id),
'name': asset.hostname,
'title': asset.ip,
'pId': node.key,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'system_users': system_users,
'type': 'asset',
'asset': {
'id': asset.id,
'hostname': asset.hostname,
'ip': asset.ip,
'protocols': [{"name": p.name, "port": p.port}
for p in asset.protocols.all()],
'platform': asset.platform,
'domain': None if not asset.domain else asset.domain.id,
'is_active': asset.is_active,
'comment': asset.comment
},
}
}
tree_node = TreeNode(**data)
return tree_node
def check_system_user_action(system_user, action):
"""
:param system_user: SystemUser object (包含动态属性: actions)
:param action: Action object
:return: bool
"""
check_actions = [Action.get_action_all(), action]
granted_actions = getattr(system_user, 'actions', [])
actions = list(set(granted_actions).intersection(set(check_actions)))
return bool(actions)