perf: remove system user

pull/8873/head
ibuler 2022-08-17 11:54:18 +08:00
parent 2948d5af7f
commit 3f47e63080
46 changed files with 67 additions and 1071 deletions

View File

@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from orgs.models import Organization
from assets.models import SystemUser
from assets.const import Protocol
from acls import models
@ -60,14 +59,6 @@ class LoginAssetACLSystemUsersSerializer(serializers.Serializer):
)
)
@staticmethod
def validate_protocol_group(protocol_group):
unsupported_protocols = set(protocol_group) - set(SystemUser.ASSET_CATEGORY_PROTOCOLS + ['*'])
if unsupported_protocols:
error = _('Unsupported protocols: {}').format(unsupported_protocols)
raise serializers.ValidationError(error)
return protocol_group
class LoginAssetACLSerializer(BulkOrgResourceModelSerializer):
users = LoginAssetACLUsersSerializer()

View File

@ -1,9 +1,8 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from orgs.utils import tmp_to_root_org
from common.utils import get_object_or_none, lazyproperty
from users.models import User
from assets.models import Asset, SystemUser
from assets.models import Asset
__all__ = ['LoginAssetCheckSerializer']
@ -30,21 +29,21 @@ class LoginAssetCheckSerializer(serializers.Serializer):
self.asset = self.validate_object_exist(Asset, asset_id)
return asset_id
def validate_system_user_id(self, system_user_id):
self._system_user = self.validate_object_exist(SystemUser, system_user_id)
return system_user_id
def validate_system_user_username(self, system_user_username):
system_user_id = self.initial_data.get('system_user_id')
system_user = self.validate_object_exist(SystemUser, system_user_id)
if self._system_user.login_mode == SystemUser.LOGIN_MANUAL \
and not system_user.username \
and not system_user.username_same_with_user \
and not system_user_username:
error = 'Missing parameter: system_user_username'
raise serializers.ValidationError(error)
self._system_user_username = system_user_username
return system_user_username
# def validate_system_user_id(self, system_user_id):
# self._system_user = self.validate_object_exist(SystemUser, system_user_id)
# return system_user_id
#
# def validate_system_user_username(self, system_user_username):
# system_user_id = self.initial_data.get('system_user_id')
# system_user = self.validate_object_exist(SystemUser, system_user_id)
# if self._system_user.login_mode == SystemUser.LOGIN_MANUAL \
# and not system_user.username \
# and not system_user.username_same_with_user \
# and not system_user_username:
# error = 'Missing parameter: system_user_username'
# raise serializers.ValidationError(error)
# self._system_user_username = system_user_username
# return system_user_username
@staticmethod
def validate_object_exist(model, field_id):
@ -55,16 +54,16 @@ class LoginAssetCheckSerializer(serializers.Serializer):
raise serializers.ValidationError(error)
return obj
@lazyproperty
def system_user(self):
if self._system_user.username_same_with_user:
username = self.user.username
elif self._system_user.login_mode == SystemUser.LOGIN_MANUAL:
username = self._system_user_username
else:
username = self._system_user.username
self._system_user.username = username
return self._system_user
# @lazyproperty
# def system_user(self):
# if self._system_user.username_same_with_user:
# username = self.user.username
# elif self._system_user.login_mode == SystemUser.LOGIN_MANUAL:
# username = self._system_user_username
# else:
# username = self._system_user.username
# self._system_user.username = username
# return self._system_user
@lazyproperty
def org(self):

View File

@ -1,85 +0,0 @@
# coding: utf-8
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
class AppCategory(models.TextChoices):
db = 'db', _('Database')
remote_app = 'remote_app', _('Remote app')
cloud = 'cloud', 'Cloud'
@classmethod
def get_label(cls, category):
return dict(cls.choices).get(category, '')
@classmethod
def is_xpack(cls, category):
return category in ['remote_app']
class AppType(models.TextChoices):
# db category
mysql = 'mysql', 'MySQL'
mariadb = 'mariadb', 'MariaDB'
oracle = 'oracle', 'Oracle'
pgsql = 'postgresql', 'PostgreSQL'
sqlserver = 'sqlserver', 'SQLServer'
redis = 'redis', 'Redis'
mongodb = 'mongodb', 'MongoDB'
# remote-app category
chrome = 'chrome', 'Chrome'
mysql_workbench = 'mysql_workbench', 'MySQL Workbench'
vmware_client = 'vmware_client', 'vSphere Client'
custom = 'custom', _('Custom')
# cloud category
k8s = 'k8s', 'Kubernetes'
@classmethod
def category_types_mapper(cls):
return {
AppCategory.db: [
cls.mysql, cls.mariadb, cls.oracle, cls.pgsql,
cls.sqlserver, cls.redis, cls.mongodb
],
AppCategory.remote_app: [
cls.chrome, cls.mysql_workbench,
cls.vmware_client, cls.custom
],
AppCategory.cloud: [cls.k8s]
}
@classmethod
def type_category_mapper(cls):
mapper = {}
for category, tps in cls.category_types_mapper().items():
for tp in tps:
mapper[tp] = category
return mapper
@classmethod
def get_label(cls, tp):
return dict(cls.choices).get(tp, '')
@classmethod
def db_types(cls):
return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]]
@classmethod
def remote_app_types(cls):
return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]]
@classmethod
def cloud_types(cls):
return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]]
@classmethod
def is_xpack(cls, tp):
tp_category_mapper = cls.type_category_mapper()
category = tp_category_mapper[tp]
if AppCategory.is_xpack(category):
return True
return tp in ['oracle', 'postgresql', 'sqlserver']

View File

@ -1,59 +0,0 @@
# Generated by Django 3.2.14 on 2022-08-16 08:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tickets', '0019_delete_applyapplicationticket'),
('authentication', '0012_auto_20220816_1629'),
('applications', '0023_auto_20220816_1021'),
]
operations = [
migrations.AlterUniqueTogether(
name='account',
unique_together=None,
),
migrations.RemoveField(
model_name='account',
name='app',
),
migrations.RemoveField(
model_name='account',
name='systemuser',
),
migrations.AlterUniqueTogether(
name='application',
unique_together=None,
),
migrations.RemoveField(
model_name='application',
name='domain',
),
migrations.RemoveField(
model_name='historicalaccount',
name='app',
),
migrations.RemoveField(
model_name='historicalaccount',
name='history_user',
),
migrations.RemoveField(
model_name='historicalaccount',
name='systemuser',
),
migrations.DeleteModel(
name='ApplicationUser',
),
migrations.DeleteModel(
name='Account',
),
migrations.DeleteModel(
name='Application',
),
migrations.DeleteModel(
name='HistoricalAccount',
),
]

View File

@ -1,2 +0,0 @@
# from .application import *
# from .account import *

View File

@ -1,117 +0,0 @@
from django.db import models
from simple_history.models import HistoricalRecords
from django.db.models import F
from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from assets.models.base import BaseAccount
from assets.models import SystemUser
class Account(BaseAccount):
app = models.ForeignKey(
'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Application')
)
systemuser = models.ForeignKey(
'assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")
)
version = models.IntegerField(default=1, verbose_name=_('Version'))
history = HistoricalRecords()
auth_attrs = ['username', 'password', 'private_key', 'public_key']
class Meta:
verbose_name = _('Application account')
unique_together = [('username', 'app', 'systemuser')]
permissions = [
('view_applicationaccountsecret', _('Can view application account secret')),
('change_appplicationaccountsecret', _('Can change application account secret')),
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.auth_snapshot = {}
def get_or_systemuser_attr(self, attr):
val = getattr(self, attr, None)
if val:
return val
if self.systemuser:
return getattr(self.systemuser, attr, '')
return ''
def load_auth(self):
for attr in self.auth_attrs:
value = self.get_or_systemuser_attr(attr)
self.auth_snapshot[attr] = [getattr(self, attr), value]
setattr(self, attr, value)
def unload_auth(self):
if not self.systemuser:
return
for attr, values in self.auth_snapshot.items():
origin_value, loaded_value = values
current_value = getattr(self, attr, '')
if current_value == loaded_value:
setattr(self, attr, origin_value)
def save(self, *args, **kwargs):
self.unload_auth()
instance = super().save(*args, **kwargs)
self.load_auth()
return instance
@lazyproperty
def category(self):
return self.app.category
@lazyproperty
def type(self):
return self.app.type
@lazyproperty
def attrs(self):
return self.app.attrs
@lazyproperty
def app_display(self):
return self.systemuser.name
@property
def username_display(self):
return self.get_or_systemuser_attr('username') or ''
@lazyproperty
def systemuser_display(self):
if not self.systemuser:
return ''
return str(self.systemuser)
@property
def smart_name(self):
username = self.username_display
if self.app:
app = str(self.app)
else:
app = '*'
return '{}@{}'.format(username, app)
@classmethod
def get_queryset(cls):
queryset = cls.objects.all() \
.annotate(type=F('app__type')) \
.annotate(app_display=F('app__name')) \
.annotate(systemuser_display=F('systemuser__name')) \
.annotate(category=F('app__category'))
return queryset
def __str__(self):
return self.smart_name
class ApplicationUser(SystemUser):
class Meta:
proxy = True
verbose_name = _('Application user')

View File

@ -1,97 +0,0 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from common.utils import is_uuid
from assets.models import Asset
from .. import const
from .tree import ApplicationTreeNodeMixin
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
name = models.CharField(max_length=128, verbose_name=_('Name'))
category = models.CharField(
max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category')
)
type = models.CharField(
max_length=16, choices=const.AppType.choices, verbose_name=_('Type')
)
domain = models.ForeignKey(
'assets.Domain', null=True, blank=True, related_name='applications',
on_delete=models.SET_NULL, verbose_name=_("Domain"),
)
attrs = models.JSONField(default=dict, verbose_name=_('Attrs'))
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
class Meta:
verbose_name = _('Application')
unique_together = [('org_id', 'name')]
ordering = ('name',)
permissions = [
('match_application', _('Can match application')),
]
def __str__(self):
category_display = self.get_category_display()
type_display = self.get_type_display()
return f'{self.name}({type_display})[{category_display}]'
@property
def category_remote_app(self):
return self.category == const.AppCategory.remote_app.value
@property
def category_cloud(self):
return self.category == const.AppCategory.cloud.value
@property
def category_db(self):
return self.category == const.AppCategory.db.value
def get_rdp_remote_app_setting(self):
from applications.serializers.attrs import get_serializer_class_by_application_type
if not self.category_remote_app:
raise ValueError(f"Not a remote app application: {self.name}")
serializer_class = get_serializer_class_by_application_type(self.type)
fields = serializer_class().get_fields()
parameters = [self.type]
for field_name in list(fields.keys()):
if field_name in ['asset']:
continue
value = self.attrs.get(field_name)
if not value:
continue
if field_name == 'path':
value = '\"%s\"' % value
parameters.append(str(value))
parameters = ' '.join(parameters)
return {
'program': '||jmservisor',
'working_directory': '',
'parameters': parameters
}
def get_remote_app_asset(self, raise_exception=True):
asset_id = self.attrs.get('asset')
if is_uuid(asset_id):
return Asset.objects.filter(id=asset_id).first()
if raise_exception:
raise ValueError("Remote App not has asset attr")
def get_target_ip(self):
target_ip = ''
if self.category_remote_app:
asset = self.get_remote_app_asset()
target_ip = asset.ip if asset else target_ip
elif self.category_cloud:
target_ip = self.attrs.get('cluster')
elif self.category_db:
target_ip = self.attrs.get('host')
return target_ip

View File

@ -1,13 +0,0 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from .application import Application
class Database(Application):
host = models.CharField(max_length=1024, verbose_name=_('Host'))
port = models.IntegerField(verbose_name=_("Port"))
database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database"))
class Meta:
verbose_name = _("Database")

View File

@ -1,207 +0,0 @@
from collections import defaultdict
from urllib.parse import urlencode, parse_qsl
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.tree import TreeNode
from .. import const
class ApplicationTreeNodeMixin:
id: str
name: str
type: str
category: str
attrs: dict
@staticmethod
def create_tree_id(pid, type, v):
i = dict(parse_qsl(pid))
i[type] = v
tree_id = urlencode(i)
return tree_id
@classmethod
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
show_empty=True, show_count=True):
count = counts.get(c.value, 0)
if count == 0 and not show_empty:
return None
label = c.label
if count is not None and show_count:
label = '{} ({})'.format(label, count)
data = {
'id': id_,
'name': label,
'title': label,
'pId': pid,
'isParent': bool(count),
'open': opened,
'iconSkin': '',
'meta': {
'type': tp,
'data': {
'name': c.name,
'value': c.value
}
}
}
return TreeNode(**data)
@classmethod
def create_root_tree_node(cls, queryset, show_count=True):
count = queryset.count() if show_count else None
root_id = 'applications'
root_name = _('Applications')
if count is not None and show_count:
root_name = '{} ({})'.format(root_name, count)
node = TreeNode(**{
'id': root_id,
'name': root_name,
'title': root_name,
'pId': '',
'isParent': True,
'open': True,
'iconSkin': '',
'meta': {
'type': 'applications_root',
}
})
return node
@classmethod
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
nodes = []
categories = const.AppType.category_types_mapper().keys()
for category in categories:
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
continue
i = cls.create_tree_id(pid, 'category', category.value)
node = cls.create_choice_node(
category, i, pid=pid, tp='category',
counts=counts, opened=False, show_empty=show_empty,
show_count=show_count
)
if not node:
continue
nodes.append(node)
return nodes
@classmethod
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
nodes = []
temp_pid = pid
type_category_mapper = const.AppType.type_category_mapper()
types = const.AppType.type_category_mapper().keys()
for tp in types:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
continue
category = type_category_mapper.get(tp)
pid = cls.create_tree_id(pid, 'category', category.value)
i = cls.create_tree_id(pid, 'type', tp.value)
node = cls.create_choice_node(
tp, i, pid, tp='type', counts=counts, opened=False,
show_empty=show_empty, show_count=show_count
)
pid = temp_pid
if not node:
continue
nodes.append(node)
return nodes
@staticmethod
def get_tree_node_counts(queryset):
counts = defaultdict(int)
values = queryset.values_list('type', 'category')
for i in values:
tp = i[0]
category = i[1]
counts[tp] += 1
counts[category] += 1
return counts
@classmethod
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
counts = cls.get_tree_node_counts(queryset)
tree_nodes = []
# 类别的节点
tree_nodes += cls.create_category_tree_nodes(
pid, counts, show_empty=show_empty,
show_count=show_count
)
# 类型的节点
tree_nodes += cls.create_types_tree_nodes(
pid, counts, show_empty=show_empty,
show_count=show_count
)
return tree_nodes
@classmethod
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
tree_nodes = []
# 根节点有可能是组织名称
if root_node is None:
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
tree_nodes.append(root_node)
tree_nodes += cls.create_category_type_tree_nodes(
queryset, root_node.id, show_empty=show_empty, show_count=show_count
)
# 应用的节点
for app in queryset:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
continue
node = app.as_tree_node(root_node.id)
tree_nodes.append(node)
return tree_nodes
def create_app_tree_pid(self, root_id):
pid = self.create_tree_id(root_id, 'category', self.category)
pid = self.create_tree_id(pid, 'type', self.type)
return pid
def as_tree_node(self, pid, k8s_as_tree=False):
if self.type == const.AppType.k8s and k8s_as_tree:
node = KubernetesTree(pid).as_tree_node(self)
else:
node = self._as_tree_node(pid)
return node
def _attrs_to_tree(self):
if self.category == const.AppCategory.db:
return self.attrs
return {}
def _as_tree_node(self, pid):
icon_skin_category_mapper = {
'remote_app': 'chrome',
'db': 'database',
'cloud': 'cloud'
}
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
pid = self.create_app_tree_pid(pid)
node = TreeNode(**{
'id': str(self.id),
'name': self.name,
'title': self.name,
'pId': pid,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'application',
'data': {
'category': self.category,
'type': self.type,
'attrs': self._attrs_to_tree()
}
}
})
return node

View File

@ -11,19 +11,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='AccountTemplate',
fields=[
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
],
options={
'abstract': False,
},
),
migrations.RemoveField(
model_name='account',
name='protocol',

View File

@ -1,6 +1,7 @@
# Generated by Django 3.2.14 on 2022-08-16 02:22
import time
from django.db import migrations, models
from django.db.models import Count
def migrate_command_filter_to_assets(apps, schema_editor):
@ -50,9 +51,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.DeleteModel(
name='AccountTemplate',
),
migrations.AddField(
model_name='commandfilter',
name='accounts',

View File

@ -1,6 +1,6 @@
from .base import *
from .platform import *
from ._user import *
# from ._user import *
from .asset import *
from .label import Label
from .group import *
@ -12,7 +12,7 @@ from .favorite_asset import *
from .account import *
from .backup import *
# 废弃以下
from ._authbook import *
# from ._authbook import *
from .protocol import *
from .cmd_filter import *

View File

@ -11,24 +11,10 @@ from common.utils import validate_ssh_private_key
__all__ = [
'init_model', 'generate_fake', 'private_key_validator',
'private_key_validator',
]
def init_model():
from . import SystemUser, AdminUser, Asset
for cls in [SystemUser, AdminUser, Asset]:
if hasattr(cls, 'initial'):
cls.initial()
def generate_fake():
from . import SystemUser, AdminUser, Asset
for cls in [SystemUser, AdminUser, Asset]:
if hasattr(cls, 'generate_fake'):
cls.generate_fake()
def private_key_validator(value):
if not validate_ssh_private_key(value):
raise ValidationError(

View File

@ -16,7 +16,7 @@ from django.utils import translation
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from assets.models import Asset, SystemUser
from assets.models import Asset
from authentication.signals import post_auth_failed, post_auth_success
from authentication.utils import check_different_city_login_if_need
from jumpserver.utils import current_request

View File

@ -13,7 +13,7 @@ from rest_framework.request import Request
from common.drf.api import JMSModelViewSet
from common.http import is_true
from orgs.mixins.api import RootOrgViewMixin
from perms.models.base import Action
from perms.models import Action
from terminal.models import EndpointRule
from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer,

View File

@ -3,14 +3,14 @@
from django.db import migrations, models
def migrate_system_user_to_accounts(apps, schema_editor):
connection_token_model = apps.get_model("perms", "ConnectionToken")
def migrate_system_user_to_account(apps, schema_editor):
connection_token_model = apps.get_model("authentication", "ConnectionToken")
count = 0
bulk_size = 10000
while True:
connection_tokens = connection_token_model.objects \
.prefect_related('system_users')[count:bulk_size]
.prefetch_related('system_users')[count:bulk_size]
if not connection_tokens:
break
count += len(connection_tokens)
@ -45,7 +45,7 @@ class Migration(migrations.Migration):
name='account',
field=models.CharField(default='', max_length=128, verbose_name='Account'),
),
migrations.RunPython(migrate_system_user_to_accounts),
migrations.RunPython(migrate_system_user_to_account),
migrations.RemoveField(
model_name='connectiontoken',
name='system_user',

View File

@ -5,7 +5,7 @@ from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from authentication.models import ConnectionToken
from common.utils import pretty_string
from common.utils.random import random_string
from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule
from assets.models import Asset, Gateway, Domain, CommandFilterRule
from users.models import User
from perms.serializers.base import ActionsField
@ -123,15 +123,6 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer):
fields = ['id', 'name', 'ip', 'protocols', 'org_id']
class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = [
'id', 'name', 'username', 'password', 'private_key',
'protocol', 'ad_domain', 'org_id'
]
class ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
class Meta:
model = Gateway
@ -165,7 +156,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
user = ConnectionTokenUserSerializer(read_only=True)
asset = ConnectionTokenAssetSerializer(read_only=True)
remote_app = ConnectionTokenRemoteAppSerializer(read_only=True)
system_user = ConnectionTokenSystemUserSerializer(read_only=True)
account = serializers.CharField(read_only=True)
gateway = ConnectionTokenGatewaySerializer(read_only=True)
domain = ConnectionTokenDomainSerializer(read_only=True)
cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True)

View File

@ -157,7 +157,6 @@ class AdHoc(OrgModelMixin):
hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
run_as = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Username'))
run_system_user = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE)
become = EncryptJsonDictCharField(max_length=1024, default='', blank=True, null=True, verbose_name=_("Become"))
created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by'))
date_created = models.DateTimeField(auto_now_add=True, db_index=True)

View File

@ -23,6 +23,7 @@ class CommandExecution(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
hosts = models.ManyToManyField('assets.Asset')
run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE)
account = models.CharField(max_length=128, verbose_name=_('account'))
command = models.TextField(verbose_name=_("Command"))
_result = models.TextField(blank=True, null=True, verbose_name=_('Result'))
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True)

View File

@ -14,7 +14,7 @@ from .serializers import (
)
from users.models import User, UserGroup
from assets.models import (
Asset, Domain, SystemUser, Label, Node, Gateway,
Asset, Domain, Label, Node, Gateway,
CommandFilter, CommandFilterRule, GatheredUser
)
from perms.models import AssetPermission
@ -27,7 +27,7 @@ logger = get_logger(__file__)
# 部分 org 相关的 model需要清空这些数据之后才能删除该组织
org_related_models = [
User, UserGroup, Asset, Label, Domain, Gateway, Node, SystemUser, Label,
User, UserGroup, Asset, Label, Domain, Gateway, Node, Label,
CommandFilter, CommandFilterRule, GatheredUser,
AssetPermission,
]

View File

@ -6,7 +6,7 @@ from orgs.utils import current_org, tmp_to_org
from common.cache import Cache, IntegerField
from common.utils import get_logger
from users.models import UserGroup, User
from assets.models import Node, SystemUser, Domain, Gateway, Asset
from assets.models import Node, Domain, Gateway, Asset
from terminal.models import Session
from perms.models import AssetPermission
@ -71,12 +71,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
def get_current_org(self):
return self.org
def compute_admin_users_amount(self):
return SystemUser.objects.filter(type=SystemUser.Type.admin).count()
def compute_system_users_amount(self):
return SystemUser.objects.filter(type=SystemUser.Type.common).count()
def compute_users_amount(self):
amount = User.get_org_users(self.org).count()
return amount

View File

@ -8,7 +8,7 @@ from users.models import UserGroup, User
from users.signals import pre_user_leave_org
from terminal.models import Session
from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding
from assets.models import Asset, SystemUser, Domain, Gateway
from assets.models import Asset, Domain, Gateway
from orgs.caches import OrgResourceStatisticsCache
from orgs.utils import current_org
from common.utils import get_logger
@ -77,7 +77,6 @@ class OrgResourceStatisticsRefreshUtil:
AssetPermission: ['asset_perms_amount'],
Gateway: ['gateways_amount'],
Domain: ['domains_amount'],
SystemUser: ['system_users_amount', 'admin_users_amount'],
Node: ['nodes_amount'],
Asset: ['assets_amount'],
UserGroup: ['groups_amount'],

View File

@ -14,7 +14,6 @@ from orgs.models import Organization
from orgs.hands import set_current_org, Node, get_current_org
from perms.models import AssetPermission
from users.models import UserGroup, User
from assets.models import SystemUser
from common.const.signals import PRE_REMOVE, POST_REMOVE
from common.decorator import on_transaction_commit
from common.signals import django_ready
@ -135,7 +134,7 @@ def _clear_users_from_org(org, users):
if not users:
return
models = (AssetPermission, UserGroup, SystemUser)
models = (AssetPermission, UserGroup)
for m in models:
_remove_users(m, users, org)

View File

@ -2,4 +2,3 @@
#
from .asset import *
from .system_user_permission import *

View File

@ -8,7 +8,7 @@ from django.utils.decorators import method_decorator
from rest_framework.views import APIView, Response
from rest_framework import status
from rest_framework.generics import (
ListAPIView, get_object_or_404, RetrieveAPIView, DestroyAPIView
ListAPIView, get_object_or_404, RetrieveAPIView
)
from orgs.utils import tmp_to_root_org
@ -16,7 +16,7 @@ from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_
from common.permissions import IsValidUser
from common.utils import get_logger, lazyproperty
from perms.hands import User, Asset, SystemUser
from perms.hands import User, Asset
from perms import serializers
logger = get_logger(__name__)
@ -25,7 +25,6 @@ __all__ = [
'UserGrantedAssetSystemUsersForAdminApi',
'ValidateUserAssetPermissionApi',
'GetUserAssetPermissionActionsApi',
'UserAssetPermissionsCacheApi',
'MyGrantedAssetSystemUsersApi',
]
@ -45,19 +44,18 @@ class GetUserAssetPermissionActionsApi(RetrieveAPIView):
def get_object(self):
asset_id = self.request.query_params.get('asset_id', '')
system_id = self.request.query_params.get('system_user_id', '')
account = self.request.query_params.get('account', '')
try:
asset_id = uuid.UUID(asset_id)
system_id = uuid.UUID(system_id)
except ValueError:
return Response({'msg': False}, status=403)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = get_asset_system_user_ids_with_actions_by_user(self.get_user(), asset)
actions = system_users_actions.get(system_user.id)
# actions = system_users_actions.get(system_user.id)
actions = system_users_actions.get(account)
return {"actions": actions}
@ -70,7 +68,7 @@ class ValidateUserAssetPermissionApi(APIView):
def get(self, request, *args, **kwargs):
user_id = self.request.query_params.get('user_id', '')
asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '')
account = request.query_params.get('account', '')
action_name = request.query_params.get('action_name', '')
data = {
@ -79,14 +77,13 @@ class ValidateUserAssetPermissionApi(APIView):
'actions': []
}
if not all((user_id, asset_id, system_id, action_name)):
if not all((user_id, asset_id, account, action_name)):
return Response(data)
user = User.objects.get(id=user_id)
asset = Asset.objects.valid().get(id=asset_id)
system_user = SystemUser.objects.get(id=system_id)
has_perm, actions, expire_at = validate_permission(user, asset, system_user, action_name)
has_perm, actions, expire_at = validate_permission(user, asset, account, action_name)
status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN
data = {
'has_permission': has_perm,
@ -97,8 +94,6 @@ class ValidateUserAssetPermissionApi(APIView):
class UserGrantedAssetSystemUsersForAdminApi(ListAPIView):
serializer_class = serializers.AssetSystemUserSerializer
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
rbac_perms = {
'list': 'perms.view_userassets'
}
@ -117,13 +112,6 @@ class UserGrantedAssetSystemUsersForAdminApi(ListAPIView):
def get_asset_system_user_ids_with_actions(self, asset):
return get_asset_system_user_ids_with_actions_by_user(self.user, asset)
def get_queryset(self):
system_user_ids = self.system_users_with_actions.keys()
system_users = SystemUser.objects.filter(id__in=system_user_ids) \
.only(*self.serializer_class.Meta.only_fields) \
.order_by('name')
return system_users
def paginate_queryset(self, queryset):
page = super().paginate_queryset(queryset)
@ -148,8 +136,3 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi):
def user(self):
return self.request.user
# TODO 删除
class UserAssetPermissionsCacheApi(DestroyAPIView):
def destroy(self, request, *args, **kwargs):
return Response(status=204)

View File

@ -1,21 +0,0 @@
from rest_framework import generics
from assets.models import SystemUser
from common.permissions import IsValidUser
from perms.utils.asset.user_permission import get_user_all_asset_perm_ids
from .. import serializers
class SystemUserPermission(generics.ListAPIView):
permission_classes = (IsValidUser,)
serializer_class = serializers.SystemUserSerializer
def get_queryset(self):
user = self.request.user
asset_perm_ids = get_user_all_asset_perm_ids(user)
queryset = SystemUser.objects.filter(
granted_by_permissions__id__in=asset_perm_ids
).distinct()
return queryset

View File

@ -5,7 +5,7 @@ from common.db.models import UnionQuerySet
from common.drf.filters import BaseFilterSet
from common.utils import get_object_or_none
from users.models import User, UserGroup
from assets.models import Node, Asset, SystemUser
from assets.models import Node, Asset
from perms.models import AssetPermission
@ -30,7 +30,6 @@ class PermissionBaseFilter(BaseFilterSet):
qs = super().qs
qs = self.filter_valid(qs)
qs = self.filter_user(qs)
qs = self.filter_system_user(qs)
qs = self.filter_user_group(qs)
return qs
@ -74,21 +73,6 @@ class PermissionBaseFilter(BaseFilterSet):
).distinct()
return queryset
def filter_system_user(self, queryset):
system_user_id = self.get_query_param('system_user_id')
system_user_name = self.get_query_param('system_user')
if system_user_id:
system_user = get_object_or_none(SystemUser, pk=system_user_id)
elif system_user_name:
system_user = get_object_or_none(SystemUser, name=system_user_name)
else:
return queryset
if not system_user:
return queryset.none()
queryset = queryset.filter(system_users=system_user)
return queryset
def filter_user_group(self, queryset):
user_group_id = self.get_query_param('user_group_id')
user_group_name = self.get_query_param('user_group')

View File

@ -2,12 +2,12 @@
#
from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node, Label, FavoriteAsset
from assets.models import Asset, Node, Label, FavoriteAsset
from assets.serializers import NodeSerializer
__all__ = [
'User', 'UserGroup',
'Asset', 'SystemUser', 'Node', 'Label', 'FavoriteAsset',
'Asset', 'Node', 'Label', 'FavoriteAsset',
'NodeSerializer',
]

View File

@ -1,8 +1,4 @@
# Generated by Django 3.2.14 on 2022-08-16 03:32
import time
from django.db import migrations, models

View File

@ -2,5 +2,3 @@
#
from .asset_permission import *
# from .application_permission import *
from .base import *

View File

@ -13,7 +13,9 @@ from common.db.models import BaseCreateUpdateModel, BitOperationChoice, UnionQue
__all__ = [
'AssetPermission', 'PermNode', 'UserAssetGrantedTreeNodeRelation',
'AssetPermission', 'PermNode',
'UserAssetGrantedTreeNodeRelation',
'Action'
]
# 使用场景

View File

@ -1,160 +0,0 @@
# coding: utf-8
#
# TODO: v3 delete 整个文件
import uuid
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.db.models import Q
from django.utils import timezone
from orgs.mixins.models import OrgModelMixin
from common.db.models import UnionQuerySet, BitOperationChoice
from common.utils import date_expired_default, lazyproperty
from orgs.mixins.models import OrgManager
__all__ = [
'BasePermission', 'BasePermissionQuerySet', 'Action'
]
class BasePermissionQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def valid(self):
return self.active().filter(date_start__lt=timezone.now()) \
.filter(date_expired__gt=timezone.now())
def inactive(self):
return self.filter(is_active=False)
def invalid(self):
now = timezone.now()
q = (Q(is_active=False) | Q(date_start__gt=now) | Q(date_expired__lt=now))
return self.filter(q)
class BasePermissionManager(OrgManager):
def valid(self):
return self.get_queryset().valid()
class Action(BitOperationChoice):
ALL = 0xff
CONNECT = 0b1
UPLOAD = 0b1 << 1
DOWNLOAD = 0b1 << 2
CLIPBOARD_COPY = 0b1 << 3
CLIPBOARD_PASTE = 0b1 << 4
UPDOWNLOAD = UPLOAD | DOWNLOAD
CLIPBOARD_COPY_PASTE = CLIPBOARD_COPY | CLIPBOARD_PASTE
DB_CHOICES = (
(ALL, _('All')),
(CONNECT, _('Connect')),
(UPLOAD, _('Upload file')),
(DOWNLOAD, _('Download file')),
(UPDOWNLOAD, _("Upload download")),
(CLIPBOARD_COPY, _('Clipboard copy')),
(CLIPBOARD_PASTE, _('Clipboard paste')),
(CLIPBOARD_COPY_PASTE, _('Clipboard copy paste'))
)
NAME_MAP = {
ALL: "all",
CONNECT: "connect",
UPLOAD: "upload_file",
DOWNLOAD: "download_file",
UPDOWNLOAD: "updownload",
CLIPBOARD_COPY: 'clipboard_copy',
CLIPBOARD_PASTE: 'clipboard_paste',
CLIPBOARD_COPY_PASTE: 'clipboard_copy_paste'
}
NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()}
CHOICES = []
for i, j in DB_CHOICES:
CHOICES.append((NAME_MAP[i], j))
class BasePermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), related_name='%(class)ss')
user_groups = models.ManyToManyField(
'users.UserGroup', blank=True, verbose_name=_("User group"), related_name='%(class)ss')
actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions"))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket'))
objects = BasePermissionManager.from_queryset(BasePermissionQuerySet)()
class Meta:
abstract = True
def __str__(self):
return self.name
@property
def id_str(self):
return str(self.id)
@property
def is_expired(self):
if self.date_expired > timezone.now() > self.date_start:
return False
return True
@property
def is_valid(self):
if not self.is_expired and self.is_active:
return True
return False
@property
def all_users(self):
from users.models import User
users_query = self._meta.get_field('users').related_query_name()
user_groups_query = self._meta.get_field('user_groups').related_query_name()
users_q = Q(**{
f'{users_query}': self
})
user_groups_q = Q(**{
f'groups__{user_groups_query}': self
})
return User.objects.filter(users_q | user_groups_q).distinct()
def get_all_users(self):
from users.models import User
user_ids = self.users.all().values_list('id', flat=True)
group_ids = self.user_groups.all().values_list('id', flat=True)
user_ids = list(user_ids)
group_ids = list(group_ids)
qs1 = User.objects.filter(id__in=user_ids).distinct()
qs2 = User.objects.filter(groups__id__in=group_ids).distinct()
qs = UnionQuerySet(qs1, qs2)
return qs
@lazyproperty
def users_amount(self):
return self.users.count()
@lazyproperty
def user_groups_amount(self):
return self.user_groups.count()

View File

@ -2,4 +2,3 @@
#
from .base import *
from .asset import *
from .system_user_permission import *

View File

@ -5,9 +5,8 @@ from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action
from assets.models import Asset, Node, SystemUser
from assets.models import Asset, Node
from users.models import User, UserGroup
from ..base import ActionsField, BasePermissionSerializer

View File

@ -4,35 +4,16 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from assets.models import Node, SystemUser, Asset, Platform
from assets.models import Node, Asset, Platform
from perms.serializers.base import ActionsField
__all__ = [
'NodeGrantedSerializer',
'AssetGrantedSerializer',
'ActionsSerializer', 'AssetSystemUserSerializer',
'RemoteAppSystemUserSerializer',
'DatabaseAppSystemUserSerializer',
'K8sAppSystemUserSerializer',
'ActionsSerializer',
]
class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
查看授权的资产系统用户的数据结构这个和AssetSerializer不同字段少
"""
actions = ActionsField(read_only=True)
class Meta:
model = SystemUser
only_fields = (
'id', 'name', 'username', 'priority', 'protocol', 'login_mode',
'sftp_root', 'username_same_with_user', 'su_enabled', 'su_from',
)
fields = list(only_fields) + ["actions"]
read_only_fields = fields
class AssetGrantedSerializer(serializers.ModelSerializer):
"""
被授权资产的数据结构
@ -63,34 +44,3 @@ class NodeGrantedSerializer(serializers.ModelSerializer):
class ActionsSerializer(serializers.Serializer):
actions = ActionsField(read_only=True)
# TODO: 删除
class RemoteAppSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
only_fields = (
'id', 'name', 'username', 'priority', 'protocol', 'login_mode',
)
fields = list(only_fields)
read_only_fields = fields
class DatabaseAppSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
only_fields = (
'id', 'name', 'username', 'priority', 'protocol', 'login_mode',
)
fields = list(only_fields)
read_only_fields = fields
class K8sAppSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
only_fields = (
'id', 'name', 'username', 'priority', 'protocol', 'login_mode',
)
fields = list(only_fields)
read_only_fields = fields

View File

@ -1,19 +0,0 @@
from rest_framework import serializers
from ..hands import SystemUser
__all__ = [
'SystemUserSerializer',
]
class SystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = [
'id', 'name', 'username', 'protocol',
'login_mode', 'login_mode_display',
'priority', 'username_same_with_user',
'auto_push_account', 'cmd_filters', 'sudo', 'shell', 'comment',
'sftp_root', 'date_created', 'created_by'
]
ref_name = 'PermedSystemUserSerializer'

View File

@ -73,11 +73,6 @@ user_permission_urlpatterns = [
# Asset System users
path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'),
path('assets/<uuid:asset_id>/system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
# TODO 要废弃 Expire user permission cache
path('<uuid:pk>/asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(),
name='user-asset-permission-cache'),
path('asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), name='my-asset-permission-cache'),
]
user_group_permission_urlpatterns = [

View File

@ -5,7 +5,7 @@ from django.db.models import Q
from common.utils import get_logger
from perms.models import AssetPermission, Action
from perms.hands import Asset, User, UserGroup, SystemUser, Node
from perms.hands import Asset, User, UserGroup, Node
from perms.utils.asset.user_permission import get_user_all_asset_perm_ids
logger = get_logger(__file__)
@ -83,7 +83,7 @@ def get_asset_system_user_ids_with_actions_by_user(user: User, asset: Asset):
return get_asset_system_user_ids_with_actions(asset_perm_ids, asset)
def has_asset_system_permission(user: User, asset: Asset, system_user: SystemUser):
def has_asset_system_permission(user: User, asset: Asset, account: str):
systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset)
actions = systemuser_actions_mapper.get(system_user.id, 0)
if actions:

View File

@ -1,34 +0,0 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from .general import Ticket
from applications.const import AppCategory, AppType
__all__ = ['ApplyApplicationTicket']
class ApplyApplicationTicket(Ticket):
apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name'))
# 申请信息
apply_category = models.CharField(
max_length=16, choices=AppCategory.choices, verbose_name=_('Category')
)
apply_type = models.CharField(
max_length=16, choices=AppType.choices, verbose_name=_('Type')
)
apply_applications = models.ManyToManyField(
'applications.Application', verbose_name=_('Apply applications'),
)
apply_system_users = models.ManyToManyField(
'assets.SystemUser', verbose_name=_('Apply system users'),
)
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_category_display(self):
return AppCategory.get_label(self.apply_category)
@property
def apply_type_display(self):
return AppType.get_label(self.apply_type)

View File

@ -17,6 +17,7 @@ class ApplyAssetTicket(Ticket):
apply_system_users = models.ManyToManyField(
'assets.SystemUser', verbose_name=_('Apply system users')
)
apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts'))
apply_actions = models.IntegerField(
choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_('Actions')
)

View File

@ -14,6 +14,7 @@ class ApplyCommandTicket(Ticket):
'assets.SystemUser', on_delete=models.SET_NULL,
null=True, verbose_name=_('Run system user')
)
apply_run_account = models.CharField(max_length=128, verbose_name=_('Run account'))
apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command'))
apply_from_session = models.ForeignKey(
'terminal.Session', on_delete=models.SET_NULL,

View File

@ -19,3 +19,4 @@ class ApplyLoginAssetTicket(Ticket):
'assets.SystemUser', on_delete=models.SET_NULL, null=True,
verbose_name=_('Login system user'),
)
apply_login_account = models.CharField(max_length=128, verbose_name=_('Login account'))

View File

@ -3,7 +3,6 @@ from django.db.models import Model
from django.utils.translation import ugettext as _
from rest_framework import serializers
from assets.models import SystemUser
from orgs.utils import tmp_to_org
from tickets.models import Ticket
@ -54,7 +53,7 @@ class BaseApplyAssetApplicationSerializer(serializers.Serializer):
qs = model.objects.filter(id__in=ids, **kwargs).values_list('id', flat=True)
return list(qs)
def validate_apply_system_users(self, system_users):
def validate_apply_account(self, system_users):
if self.is_final_approval and not system_users:
raise serializers.ValidationError(_('This field is required.'))
return self.filter_many_to_many_field(SystemUser, system_users)

View File

@ -5,45 +5,6 @@ import forgery_py
from .base import FakeDataGenerator
from assets.models import *
from assets.const import Protocol
class AdminUsersGenerator(FakeDataGenerator):
resource = 'admin_user'
def do_generate(self, batch, batch_size):
admin_users = []
for i in batch:
username = forgery_py.internet.user_name(True)
password = forgery_py.basic.password()
admin_users.append(AdminUser(
name=username.title(),
username=username,
password=password,
org_id=self.org.id,
created_by='Fake',
))
AdminUser.objects.bulk_create(admin_users, ignore_conflicts=True)
class SystemUsersGenerator(FakeDataGenerator):
def do_generate(self, batch, batch_size):
system_users = []
protocols = list(dict(Protocol.choices).keys())
for i in batch:
username = forgery_py.internet.user_name(True)
protocol = random.choice(protocols)
name = username.title()
name = f'{name}-{protocol}'
system_users.append(SystemUser(
name=name,
username=username,
password=forgery_py.basic.password(),
protocol=protocol,
org_id=self.org.id,
created_by='Fake',
))
SystemUser.objects.bulk_create(system_users, ignore_conflicts=True)
class NodesGenerator(FakeDataGenerator):
@ -62,7 +23,6 @@ class AssetsGenerator(FakeDataGenerator):
node_ids: list
def pre_generate(self):
self.admin_user_ids = list(AdminUser.objects.all().values_list('id', flat=True))
self.node_ids = list(Node.objects.all().values_list('id', flat=True))
def set_assets_nodes(self, assets):

View File

@ -19,7 +19,6 @@ class AssetPermissionGenerator(FakeDataGenerator):
def pre_generate(self):
self.node_ids = list(Node.objects.all().values_list('id', flat=True))
self.asset_ids = list(Asset.objects.all().values_list('id', flat=True))
self.system_user_ids = list(SystemUser.objects.all().values_list('id', flat=True))
self.user_ids = list(User.objects.all().values_list('id', flat=True))
self.user_group_ids = list(UserGroup.objects.all().values_list('id', flat=True))