mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-01-29 21:51:31 +00:00
refactor: 整合系统用户和管理用户 (#6236)
* perf: 整合系统用户和管理用户 * stash stash perf: 优化系统用户和资产的表结构 * perf: 添加信号 * perf: 添加算法 * perf: 去掉 asset user backends * perf: 整理系统用户api * perfF: 暂存一下 * stash * perf: 暂存一下 * perf: 暂存 * xxx * perf: ... * stash it * xxx * xxx * xxx * xxx * xxx * stash it * 修改Protocols * perf: 修改创建authbook信号 * perf: 添加auth info * .stash * perf: 基本完成 * perf: 修复完成 * perf: 修复更改的id * perf: 修复迁移过去数量不对的问题 * perf: 修改systemuser * fix: 修复批量编辑近期的问题 * fix: 修复authbook加载的问题 * xxx Co-authored-by: ibuler <ibuler@qq.com>
This commit is contained in:
@@ -1 +0,0 @@
|
||||
from .manager import AssetUserManager
|
||||
@@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from abc import abstractmethod
|
||||
|
||||
from ..models import Asset
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
@abstractmethod
|
||||
def all(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, username=None, hostname=None, ip=None, assets=None,
|
||||
node=None, prefer_id=None, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def search(self, item):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_queryset(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, union_id):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def qs_to_values(qs):
|
||||
values = qs.values(
|
||||
'hostname', 'ip', "asset_id",
|
||||
'name', 'username', 'password', 'private_key', 'public_key',
|
||||
'score', 'version',
|
||||
"asset_username", "union_id",
|
||||
'date_created', 'date_updated',
|
||||
'org_id', 'backend', 'backend_display'
|
||||
)
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def make_assets_as_ids(assets):
|
||||
if not assets:
|
||||
return []
|
||||
if isinstance(assets[0], Asset):
|
||||
assets = [a.id for a in assets]
|
||||
return assets
|
||||
@@ -1,332 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from functools import reduce
|
||||
from django.db.models import F, CharField, Value, IntegerField, Q, Count
|
||||
from django.db.models.functions import Concat
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from orgs.utils import current_org
|
||||
from ..models import AuthBook, SystemUser, Asset, AdminUser
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class DBBackend(BaseBackend):
|
||||
union_id_length = 2
|
||||
|
||||
def __init__(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.all()
|
||||
self.queryset = queryset
|
||||
|
||||
def _clone(self):
|
||||
return self.__class__(self.queryset)
|
||||
|
||||
def all(self):
|
||||
return AuthBook.objects.none()
|
||||
|
||||
def count(self):
|
||||
return self.queryset.count()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset
|
||||
|
||||
def delete(self, union_id):
|
||||
cleaned_union_id = union_id.split('_')
|
||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
||||
if not self._check_union_id(union_id, cleaned_union_id):
|
||||
return
|
||||
return self._perform_delete_by_union_id(cleaned_union_id)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
pass
|
||||
|
||||
def filter(self, assets=None, node=None, prefer=None, prefer_id=None,
|
||||
union_id=None, id__in=None, **kwargs):
|
||||
clone = self._clone()
|
||||
clone._filter_union_id(union_id)
|
||||
clone._filter_prefer(prefer, prefer_id)
|
||||
clone._filter_node(node)
|
||||
clone._filter_assets(assets)
|
||||
clone._filter_other(kwargs)
|
||||
clone._filter_id_in(id__in)
|
||||
return clone
|
||||
|
||||
def _filter_union_id(self, union_id):
|
||||
if not union_id:
|
||||
return
|
||||
cleaned_union_id = union_id.split('_')
|
||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
||||
if not self._check_union_id(union_id, cleaned_union_id):
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
return self._perform_filter_union_id(union_id, cleaned_union_id)
|
||||
|
||||
def _check_union_id(self, union_id, cleaned_union_id):
|
||||
return union_id and len(cleaned_union_id) == self.union_id_length
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
self.queryset = self.queryset.filter(union_id=union_id)
|
||||
|
||||
def _filter_assets(self, assets):
|
||||
asset_ids = self.make_assets_as_ids(assets)
|
||||
if asset_ids:
|
||||
self.queryset = self.queryset.filter(asset_id__in=asset_ids)
|
||||
|
||||
def _filter_node(self, node):
|
||||
pass
|
||||
|
||||
def _filter_id_in(self, ids):
|
||||
if ids and isinstance(ids, list):
|
||||
self.queryset = self.queryset.filter(union_id__in=ids)
|
||||
|
||||
@staticmethod
|
||||
def clean_kwargs(kwargs):
|
||||
return {k: v for k, v in kwargs.items() if v}
|
||||
|
||||
def _filter_other(self, kwargs):
|
||||
kwargs = self.clean_kwargs(kwargs)
|
||||
if kwargs:
|
||||
self.queryset = self.queryset.filter(**kwargs)
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
pass
|
||||
|
||||
def search(self, item):
|
||||
qs = []
|
||||
for i in ['hostname', 'ip', 'username']:
|
||||
kwargs = {i + '__startswith': item}
|
||||
qs.append(Q(**kwargs))
|
||||
q = reduce(lambda x, y: x | y, qs)
|
||||
clone = self._clone()
|
||||
clone.queryset = clone.queryset.filter(q).distinct()
|
||||
return clone
|
||||
|
||||
|
||||
class SystemUserBackend(DBBackend):
|
||||
model = SystemUser.assets.through
|
||||
backend = 'system_user'
|
||||
backend_display = _('System user')
|
||||
prefer = backend
|
||||
base_score = 0
|
||||
union_id_length = 2
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if prefer and prefer != self.prefer:
|
||||
self.queryset = self.queryset.none()
|
||||
|
||||
if prefer_id:
|
||||
self.queryset = self.queryset.filter(systemuser__id=prefer_id)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
system_user_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
asset_id=asset_id, systemuser__id=system_user_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
system_user_id, asset_id = union_id_cleaned
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
if all((system_user, asset)):
|
||||
system_user.assets.remove(asset)
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
||||
|
||||
def get_annotate(self):
|
||||
kwargs = dict(
|
||||
hostname=F("asset__hostname"),
|
||||
ip=F("asset__ip"),
|
||||
name=F("systemuser__name"),
|
||||
username=F("systemuser__username"),
|
||||
password=F("systemuser__password"),
|
||||
private_key=F("systemuser__private_key"),
|
||||
public_key=F("systemuser__public_key"),
|
||||
score=F("systemuser__priority") + self.base_score,
|
||||
version=Value(0, IntegerField()),
|
||||
date_created=F("systemuser__date_created"),
|
||||
date_updated=F("systemuser__date_updated"),
|
||||
asset_username=Concat(F("asset__id"), Value("_"),
|
||||
F("systemuser__username"),
|
||||
output_field=CharField()),
|
||||
union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"),
|
||||
output_field=CharField()),
|
||||
org_id=F("asset__org_id"),
|
||||
backend=Value(self.backend, CharField()),
|
||||
backend_display=Value(self.backend_display, CharField()),
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_filter(self):
|
||||
return dict(
|
||||
systemuser__username_same_with_user=False,
|
||||
)
|
||||
|
||||
def all(self):
|
||||
kwargs = self.get_annotate()
|
||||
filters = self.get_filter()
|
||||
qs = self.model.objects.all().annotate(**kwargs)
|
||||
if not current_org.is_root():
|
||||
filters['org_id'] = current_org.org_id()
|
||||
qs = qs.filter(**filters)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
|
||||
class DynamicSystemUserBackend(SystemUserBackend):
|
||||
backend = 'system_user_dynamic'
|
||||
backend_display = _('System user(Dynamic)')
|
||||
prefer = 'system_user'
|
||||
union_id_length = 3
|
||||
|
||||
def get_annotate(self):
|
||||
kwargs = super().get_annotate()
|
||||
kwargs.update(dict(
|
||||
name=Concat(
|
||||
F("systemuser__users__name"), Value('('), F("systemuser__name"), Value(')'),
|
||||
output_field=CharField()
|
||||
),
|
||||
username=F("systemuser__users__username"),
|
||||
asset_username=Concat(
|
||||
F("asset__id"), Value("_"),
|
||||
F("systemuser__users__username"),
|
||||
output_field=CharField()
|
||||
),
|
||||
union_id=Concat(
|
||||
F("systemuser_id"), Value("_"), F("asset_id"),
|
||||
Value("_"), F("systemuser__users__id"),
|
||||
output_field=CharField()
|
||||
),
|
||||
users_count=Count('systemuser__users'),
|
||||
))
|
||||
return kwargs
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
system_user_id, asset_id, user_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
asset_id=asset_id, systemuser_id=system_user_id,
|
||||
union_id=union_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
system_user_id, asset_id, user_id = union_id_cleaned
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
if not system_user:
|
||||
return
|
||||
system_user.users.remove(user_id)
|
||||
if system_user.users.count() == 0:
|
||||
system_user.assets.remove(asset_id)
|
||||
|
||||
def get_filter(self):
|
||||
return dict(
|
||||
users_count__gt=0,
|
||||
systemuser__username_same_with_user=True
|
||||
)
|
||||
|
||||
|
||||
class AdminUserBackend(DBBackend):
|
||||
model = Asset
|
||||
backend = 'admin_user'
|
||||
backend_display = _('Admin user')
|
||||
prefer = backend
|
||||
base_score = 200
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if prefer and prefer != self.backend:
|
||||
self.queryset = self.queryset.none()
|
||||
if prefer_id:
|
||||
self.queryset = self.queryset.filter(admin_user__id=prefer_id)
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(nodes__id=node.id)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
admin_user_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
id=asset_id, admin_user_id=admin_user_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
raise PermissionDenied(_("Could not remove asset admin user"))
|
||||
|
||||
def all(self):
|
||||
qs = self.model.objects.all().annotate(
|
||||
asset_id=F("id"),
|
||||
name=F("admin_user__name"),
|
||||
username=F("admin_user__username"),
|
||||
password=F("admin_user__password"),
|
||||
private_key=F("admin_user__private_key"),
|
||||
public_key=F("admin_user__public_key"),
|
||||
score=Value(self.base_score, IntegerField()),
|
||||
version=Value(0, IntegerField()),
|
||||
date_updated=F("admin_user__date_updated"),
|
||||
asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()),
|
||||
union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()),
|
||||
backend=Value(self.backend, CharField()),
|
||||
backend_display=Value(self.backend_display, CharField()),
|
||||
)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
|
||||
class AuthbookBackend(DBBackend):
|
||||
model = AuthBook
|
||||
backend = 'db'
|
||||
backend_display = _('Database')
|
||||
prefer = backend
|
||||
base_score = 400
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if not prefer or not prefer_id:
|
||||
return
|
||||
if prefer.lower() == "admin_user":
|
||||
model = AdminUser
|
||||
elif prefer.lower() == "system_user":
|
||||
model = SystemUser
|
||||
else:
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
obj = get_object_or_none(model, pk=prefer_id)
|
||||
if obj is None:
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
username = obj.get_username()
|
||||
if isinstance(username, str):
|
||||
self.queryset = self.queryset.filter(username=username)
|
||||
# dynamic system user return more username
|
||||
else:
|
||||
self.queryset = self.queryset.filter(username__in=username)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
authbook_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
id=authbook_id, asset_id=asset_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
authbook_id, asset_id = union_id_cleaned
|
||||
authbook = get_object_or_none(AuthBook, pk=authbook_id)
|
||||
if authbook.is_latest:
|
||||
raise PermissionDenied(_("Latest version could not be delete"))
|
||||
AuthBook.objects.filter(id=authbook_id).delete()
|
||||
|
||||
def all(self):
|
||||
qs = self.model.objects.all().annotate(
|
||||
hostname=F("asset__hostname"),
|
||||
ip=F("asset__ip"),
|
||||
score=F('version') + self.base_score,
|
||||
asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()),
|
||||
union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()),
|
||||
backend=Value(self.backend, CharField()),
|
||||
backend_display=Value(self.backend_display, CharField()),
|
||||
)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from itertools import chain, groupby
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
|
||||
from orgs.utils import current_org
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from common.struct import QuerySetChain
|
||||
|
||||
from ..models import AssetUser, AuthBook
|
||||
from .db import (
|
||||
AuthbookBackend, SystemUserBackend, AdminUserBackend,
|
||||
DynamicSystemUserBackend
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserQueryset:
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
|
||||
def __init__(self, backends=()):
|
||||
self.backends = backends
|
||||
self._distinct_queryset = None
|
||||
|
||||
def backends_queryset(self):
|
||||
return [b.get_queryset() for b in self.backends]
|
||||
|
||||
@lazyproperty
|
||||
def backends_counts(self):
|
||||
return [b.count() for b in self.backends]
|
||||
|
||||
def filter(self, hostname=None, ip=None, username=None,
|
||||
assets=None, asset=None, node=None,
|
||||
id=None, prefer_id=None, prefer=None, id__in=None):
|
||||
if not assets and asset:
|
||||
assets = [asset]
|
||||
|
||||
kwargs = dict(
|
||||
hostname=hostname, ip=ip, username=username,
|
||||
assets=assets, node=node, prefer=prefer, prefer_id=prefer_id,
|
||||
id__in=id__in, union_id=id,
|
||||
)
|
||||
logger.debug("Filter: {}".format(kwargs))
|
||||
backends = []
|
||||
for backend in self.backends:
|
||||
clone = backend.filter(**kwargs)
|
||||
backends.append(clone)
|
||||
return self._clone(backends)
|
||||
|
||||
def _clone(self, backends=None):
|
||||
if backends is None:
|
||||
backends = self.backends
|
||||
return self.__class__(backends)
|
||||
|
||||
def search(self, item):
|
||||
backends = []
|
||||
for backend in self.backends:
|
||||
new = backend.search(item)
|
||||
backends.append(new)
|
||||
return self._clone(backends)
|
||||
|
||||
def distinct(self):
|
||||
logger.debug("Distinct asset user queryset")
|
||||
queryset_chain = chain(*(backend.get_queryset() for backend in self.backends))
|
||||
queryset_sorted = sorted(
|
||||
queryset_chain,
|
||||
key=lambda item: (item["asset_username"], item["score"]),
|
||||
reverse=True,
|
||||
)
|
||||
results = groupby(queryset_sorted, key=lambda item: item["asset_username"])
|
||||
final = [next(result[1]) for result in results]
|
||||
self._distinct_queryset = final
|
||||
return self
|
||||
|
||||
def get(self, latest=False, **kwargs):
|
||||
queryset = self.filter(**kwargs)
|
||||
if latest:
|
||||
queryset = queryset.distinct()
|
||||
queryset = list(queryset)
|
||||
count = len(queryset)
|
||||
if count == 1:
|
||||
data = queryset[0]
|
||||
return data
|
||||
elif count > 1:
|
||||
msg = 'Should return 1 record, but get {}'.format(count)
|
||||
raise MultipleObjectsReturned(msg)
|
||||
else:
|
||||
msg = 'No record found(org is {})'.format(current_org.name)
|
||||
raise ObjectDoesNotExist(msg)
|
||||
|
||||
def get_latest(self, **kwargs):
|
||||
return self.get(latest=True, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def to_asset_user(data):
|
||||
obj = AssetUser()
|
||||
for k, v in data.items():
|
||||
setattr(obj, k, v)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def queryset(self):
|
||||
if self._distinct_queryset is not None:
|
||||
return self._distinct_queryset
|
||||
return QuerySetChain(self.backends_queryset())
|
||||
|
||||
def count(self):
|
||||
if self._distinct_queryset is not None:
|
||||
return len(self._distinct_queryset)
|
||||
else:
|
||||
return sum(self.backends_counts)
|
||||
|
||||
def __getitem__(self, ndx):
|
||||
return self.queryset.__getitem__(ndx)
|
||||
|
||||
def __iter__(self):
|
||||
self._data = iter(self.queryset)
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.to_asset_user(next(self._data))
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
support_backends = (
|
||||
('db', AuthbookBackend),
|
||||
('system_user', SystemUserBackend),
|
||||
('admin_user', AdminUserBackend),
|
||||
('system_user_dynamic', DynamicSystemUserBackend),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.backends = [backend() for name, backend in self.support_backends]
|
||||
self._queryset = AssetUserQueryset(self.backends)
|
||||
|
||||
def all(self):
|
||||
return self._queryset
|
||||
|
||||
def delete(self, obj):
|
||||
name_backends_map = dict(self.support_backends)
|
||||
backend_name = obj.backend
|
||||
backend_cls = name_backends_map.get(backend_name)
|
||||
union_id = obj.union_id
|
||||
if backend_cls:
|
||||
backend_cls().delete(union_id)
|
||||
else:
|
||||
raise ObjectDoesNotExist("Not backend found")
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
# 使用create方法创建AuthBook对象,解决并发创建问题(添加锁机制)
|
||||
authbook = AuthBook.create(**kwargs)
|
||||
return authbook
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self._queryset, item)
|
||||
@@ -1,7 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
# from django.conf import settings
|
||||
|
||||
# from .vault import VaultBackend
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
Reference in New Issue
Block a user