perf: 修改系统用户

pull/8566/head
ibuler 2022-07-28 18:50:58 +08:00
parent 43d3791ddc
commit fb0fb71ea3
34 changed files with 244 additions and 1063 deletions

View File

@ -1,9 +1,7 @@
from .mixin import * from .mixin import *
from .admin_user import *
from .asset import * from .asset import *
from .label import * from .label import *
from .system_user import * from .system_user import *
from .system_user_relation import *
from .accounts import * from .accounts import *
from .node import * from .node import *
from .domain import * from .domain import *

View File

@ -1,30 +0,0 @@
from django.db.models import Count
from orgs.mixins.api import OrgBulkModelViewSet
from common.utils import get_logger
from ..models import SystemUser
from .. import serializers
from rbac.permissions import RBACPermission
logger = get_logger(__file__)
__all__ = ['AdminUserViewSet']
# 兼容一下老的 api
class AdminUserViewSet(OrgBulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
model = SystemUser
filterset_fields = ("name", "username")
search_fields = filterset_fields
serializer_class = serializers.AdminUserSerializer
permission_classes = (RBACPermission,)
ordering_fields = ('name',)
ordering = ('name', )
def get_queryset(self):
queryset = super().get_queryset().filter(type=SystemUser.Type.admin)
queryset = queryset.annotate(assets_amount=Count('assets'))
return queryset

View File

@ -42,7 +42,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
filterset_fields = { filterset_fields = {
'hostname': ['exact'], 'hostname': ['exact'],
'ip': ['exact'], 'ip': ['exact'],
'system_users__id': ['exact'],
'platform__base': ['exact'], 'platform__base': ['exact'],
'is_active': ['exact'], 'is_active': ['exact'],
'protocols': ['exact', 'icontains'] 'protocols': ['exact', 'icontains']

View File

@ -5,23 +5,17 @@ from rest_framework.decorators import action
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.permissions import IsValidUser
from common.mixins.api import SuggestionMixin from common.mixins.api import SuggestionMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from orgs.utils import tmp_to_root_org
from ..models import SystemUser, CommandFilterRule, Account from ..models import SystemUser, CommandFilterRule, Account
from .. import serializers from .. import serializers
from ..tasks import (
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
push_system_user_to_assets
)
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', 'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView', 'SystemUserCommandFilterRuleListApi',
'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', 'SystemUserAssetAccountApi', 'SystemUserAssetAccountApi',
'SystemUserAssetAccountSecretApi', 'SystemUserAssetAccountSecretApi',
] ]
@ -35,7 +29,6 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet):
'name': ['exact'], 'name': ['exact'],
'username': ['exact'], 'username': ['exact'],
'protocol': ['exact', 'in'], 'protocol': ['exact', 'in'],
'type': ['exact', 'in'],
} }
search_fields = filterset_fields search_fields = filterset_fields
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
@ -154,116 +147,6 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
return Response(status=204) return Response(status=204)
class SystemUserTempAuthInfoApi(generics.CreateAPIView):
model = SystemUser
permission_classes = (IsValidUser,)
serializer_class = serializers.SystemUserTempAuthSerializer
def create(self, request, *args, **kwargs):
serializer = super().get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
pk = kwargs.get('pk')
data = serializer.validated_data
asset_or_app_id = data.get('instance_id')
with tmp_to_root_org():
instance = get_object_or_404(SystemUser, pk=pk)
instance.set_temp_auth(asset_or_app_id, self.request.user.id, data)
return Response(serializer.data, status=201)
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
model = SystemUser
serializer_class = serializers.SystemUserWithAuthInfoSerializer
def get_object(self):
instance = super().get_object()
asset_id = self.kwargs.get('asset_id')
user_id = self.request.query_params.get("user_id")
username = self.request.query_params.get("username")
instance.load_asset_more_auth(asset_id, username, user_id)
return instance
class SystemUserAppAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
model = SystemUser
serializer_class = serializers.SystemUserWithAuthInfoSerializer
rbac_perms = {
'retrieve': 'assets.view_systemusersecret',
}
def get_object(self):
instance = super().get_object()
app_id = self.kwargs.get('app_id')
user_id = self.request.query_params.get("user_id")
username = self.request.query_params.get("username")
instance.load_app_more_auth(app_id, username, user_id)
return instance
class SystemUserTaskApi(generics.CreateAPIView):
serializer_class = serializers.SystemUserTaskSerializer
def do_push(self, system_user, asset_ids=None):
if asset_ids is None:
task = push_system_user_to_assets_manual.delay(system_user)
else:
username = self.request.query_params.get('username')
task = push_system_user_to_assets.delay(
system_user.id, asset_ids, username=username
)
return task
@staticmethod
def do_test(system_user, asset_ids):
task = test_system_user_connectivity_manual.delay(system_user, asset_ids)
return task
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk)
def check_permissions(self, request):
action = request.data.get('action')
action_perm_require = {
'push': 'assets.push_assetsystemuser',
'test': 'assets.test_assetconnectivity'
}
perm_required = action_perm_require.get(action)
has = self.request.user.has_perm(perm_required)
if not has:
self.permission_denied(request)
def perform_create(self, serializer):
action = serializer.validated_data["action"]
asset = serializer.validated_data.get('asset')
if asset:
assets = [asset]
else:
assets = serializer.validated_data.get('assets') or []
asset_ids = [asset.id for asset in assets]
asset_ids = asset_ids if asset_ids else None
system_user = self.get_object()
if action == 'push':
task = self.do_push(system_user, asset_ids)
else:
task = self.do_test(system_user, asset_ids)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
class SystemUserCommandFilterRuleListApi(generics.ListAPIView): class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
rbac_perms = { rbac_perms = {
'list': 'assets.view_commandfilterule' 'list': 'assets.view_commandfilterule'
@ -291,19 +174,3 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
) )
return rules return rules
class SystemUserAssetsListView(generics.ListAPIView):
serializer_class = serializers.AssetSimpleSerializer
filterset_fields = ("hostname", "ip")
search_fields = filterset_fields
rbac_perms = {
'list': 'assets.view_asset'
}
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk)
def get_queryset(self):
system_user = self.get_object()
return system_user.get_all_assets()

View File

@ -1,138 +0,0 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models import F, Value, Model
from django.db.models.signals import m2m_changed
from django.db.models.functions import Concat
from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from .. import models, serializers
__all__ = [
'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet',
'SystemUserUserRelationViewSet', 'BaseRelationViewSet',
]
logger = get_logger(__name__)
class RelationMixin:
model: Model
def get_queryset(self):
queryset = self.model.objects.all()
if not current_org.is_root():
org_id = current_org.org_id()
queryset = queryset.filter(systemuser__org_id=org_id)
queryset = queryset.annotate(systemuser_display=Concat(
F('systemuser__name'), Value('('),
F('systemuser__username'), Value(')')
))
return queryset
def send_post_add_signal(self, instance):
if not isinstance(instance, list):
instance = [instance]
system_users_objects_map = defaultdict(list)
model, object_field = self.get_objects_attr()
for i in instance:
_id = getattr(i, object_field).id
system_users_objects_map[i.systemuser].append(_id)
sender = self.get_sender()
for system_user, object_ids in system_users_objects_map.items():
logger.debug('System user relation changed, send m2m_changed signals')
m2m_changed.send(
sender=sender, instance=system_user, action='post_add',
reverse=False, model=model, pk_set=set(object_ids)
)
def get_sender(self):
return self.model
def get_objects_attr(self):
return models.Asset, 'asset'
def perform_create(self, serializer):
instance = serializer.save()
self.send_post_add_signal(instance)
class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
perm_model = models.SystemUser
class SystemUserAssetRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserAssetRelationSerializer
model = models.SystemUser.assets.through
filterset_fields = [
'id', 'asset', 'systemuser',
]
search_fields = [
"id", "asset__hostname", "asset__ip",
"systemuser__name", "systemuser__username",
]
def get_objects_attr(self):
return models.Asset, 'asset'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
asset_display=Concat(
F('asset__hostname'), Value('('),
F('asset__ip'), Value(')')
)
)
return queryset
class SystemUserNodeRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserNodeRelationSerializer
model = models.SystemUser.nodes.through
filterset_fields = [
'id', 'node', 'systemuser',
]
search_fields = [
"node__value", "systemuser__name", "systemuser__username"
]
def get_objects_attr(self):
return models.Node, 'node'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(node_key=F('node__key'))
return queryset
class SystemUserUserRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserUserRelationSerializer
model = models.SystemUser.users.through
filterset_fields = [
'id', 'user', 'systemuser',
]
search_fields = [
"user__username", "user__name",
"systemuser__name", "systemuser__username",
]
def get_objects_attr(self):
from users.models import User
return User, 'user'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
user_display=Concat(
F('user__name'), Value('('),
F('user__username'), Value(')')
)
)
return queryset

View File

@ -0,0 +1,89 @@
# Generated by Django 3.2.14 on 2022-07-28 03:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0093_auto_20220711_1413'),
]
operations = [
migrations.RemoveField(
model_name='cluster',
name='admin_user',
),
migrations.RemoveField(
model_name='systemuser',
name='ad_domain',
),
migrations.RemoveField(
model_name='systemuser',
name='assets',
),
migrations.RemoveField(
model_name='systemuser',
name='auto_push',
),
migrations.RemoveField(
model_name='systemuser',
name='groups',
),
migrations.RemoveField(
model_name='systemuser',
name='home',
),
migrations.RemoveField(
model_name='systemuser',
name='nodes',
),
migrations.RemoveField(
model_name='systemuser',
name='priority',
),
migrations.RemoveField(
model_name='systemuser',
name='sftp_root',
),
migrations.RemoveField(
model_name='systemuser',
name='shell',
),
migrations.RemoveField(
model_name='systemuser',
name='sudo',
),
migrations.RemoveField(
model_name='systemuser',
name='system_groups',
),
migrations.RemoveField(
model_name='systemuser',
name='token',
),
migrations.RemoveField(
model_name='systemuser',
name='type',
),
migrations.RemoveField(
model_name='systemuser',
name='users',
),
migrations.AlterField(
model_name='historicalaccount',
name='version',
field=models.IntegerField(default=0, verbose_name='Version'),
),
migrations.AlterField(
model_name='systemuser',
name='login_mode',
field=models.CharField(choices=[('auto', '使用账号'), ('manual', 'Manually input')], default='auto', max_length=10, verbose_name='Login mode'),
),
migrations.DeleteModel(
name='AdminUser',
),
migrations.DeleteModel(
name='Cluster',
),
]

View File

@ -2,7 +2,6 @@ from .base import *
from .asset import * from .asset import *
from .label import Label from .label import Label
from .user import * from .user import *
from .cluster import *
from .group import * from .group import *
from .domain import * from .domain import *
from .node import * from .node import *
@ -12,5 +11,5 @@ from .utils import *
from .authbook import * from .authbook import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .backup import *
from .account import * from .account import *
from .backup import *

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ['Cluster']
logger = logging.getLogger(__name__)
class Cluster(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=32, verbose_name=_('Name'))
admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
bandwidth = models.CharField(max_length=32, blank=True, verbose_name=_('Bandwidth'))
contact = models.CharField(max_length=128, blank=True, verbose_name=_('Contact'))
phone = models.CharField(max_length=32, blank=True, verbose_name=_('Phone'))
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
@classmethod
def initial(cls):
return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default Cluster'))[0]
class Meta:
ordering = ['name']
verbose_name = _("Cluster")

View File

@ -0,0 +1,65 @@
from django.db import models
class ProtocolMixin:
protocol: str
class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet'
vnc = 'vnc', 'VNC'
mysql = 'mysql', 'MySQL'
oracle = 'oracle', 'Oracle'
mariadb = 'mariadb', 'MariaDB'
postgresql = 'postgresql', 'PostgreSQL'
sqlserver = 'sqlserver', 'SQLServer'
redis = 'redis', 'Redis'
mongodb = 'mongodb', 'MongoDB'
k8s = 'k8s', 'K8S'
SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp]
ASSET_CATEGORY_PROTOCOLS = [
Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc
]
APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [
Protocol.rdp
]
APPLICATION_CATEGORY_DB_PROTOCOLS = [
Protocol.mysql, Protocol.mariadb, Protocol.oracle,
Protocol.postgresql, Protocol.sqlserver,
Protocol.redis, Protocol.mongodb
]
APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [
Protocol.k8s
]
APPLICATION_CATEGORY_PROTOCOLS = [
*APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS,
*APPLICATION_CATEGORY_DB_PROTOCOLS,
*APPLICATION_CATEGORY_CLOUD_PROTOCOLS
]
@property
def is_protocol_support_push(self):
return self.protocol in self.SUPPORT_PUSH_PROTOCOLS
@classmethod
def get_protocol_by_application_type(cls, app_type):
from applications.const import AppType
if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
protocol = app_type
elif app_type in AppType.remote_app_types():
protocol = cls.Protocol.rdp
else:
protocol = None
return protocol
@property
def can_perm_to_asset(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
@property
def is_asset_protocol(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS

View File

@ -6,83 +6,17 @@ import logging
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.core.cache import cache from django.core.cache import cache
from common.utils import signer
from users.models import User
from .base import BaseUser from .base import BaseUser
from .asset import Asset from .protocol import ProtocolMixin
__all__ = ['AdminUser', 'SystemUser', 'ProtocolMixin'] __all__ = ['SystemUser']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ProtocolMixin:
protocol: str
class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet'
vnc = 'vnc', 'VNC'
mysql = 'mysql', 'MySQL'
oracle = 'oracle', 'Oracle'
mariadb = 'mariadb', 'MariaDB'
postgresql = 'postgresql', 'PostgreSQL'
sqlserver = 'sqlserver', 'SQLServer'
redis = 'redis', 'Redis'
mongodb = 'mongodb', 'MongoDB'
k8s = 'k8s', 'K8S'
SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp]
ASSET_CATEGORY_PROTOCOLS = [
Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc
]
APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [
Protocol.rdp
]
APPLICATION_CATEGORY_DB_PROTOCOLS = [
Protocol.mysql, Protocol.mariadb, Protocol.oracle,
Protocol.postgresql, Protocol.sqlserver,
Protocol.redis, Protocol.mongodb
]
APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [
Protocol.k8s
]
APPLICATION_CATEGORY_PROTOCOLS = [
*APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS,
*APPLICATION_CATEGORY_DB_PROTOCOLS,
*APPLICATION_CATEGORY_CLOUD_PROTOCOLS
]
@property
def is_protocol_support_push(self):
return self.protocol in self.SUPPORT_PUSH_PROTOCOLS
@classmethod
def get_protocol_by_application_type(cls, app_type):
from applications.const import AppType
if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
protocol = app_type
elif app_type in AppType.remote_app_types():
protocol = cls.Protocol.rdp
else:
protocol = None
return protocol
@property
def can_perm_to_asset(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
@property
def is_asset_protocol(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
class SystemUser(ProtocolMixin, BaseUser): class SystemUser(ProtocolMixin, BaseUser):
LOGIN_AUTO = 'auto' LOGIN_AUTO = 'auto'
LOGIN_MANUAL = 'manual' LOGIN_MANUAL = 'manual'
@ -91,39 +25,10 @@ class SystemUser(ProtocolMixin, BaseUser):
(LOGIN_MANUAL, _('Manually input')) (LOGIN_MANUAL, _('Manually input'))
) )
class Type(models.TextChoices):
common = 'common', _('Common user')
admin = 'admin', _('Admin user')
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField(
'assets.Asset', blank=True, verbose_name=_("Assets"),
related_name='system_users'
)
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
priority = models.IntegerField(
default=81, verbose_name=_("Priority"),
help_text=_("1-100, the lower the value will be match first"),
validators=[MinValueValidator(1), MaxValueValidator(100)]
)
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
# Todo: 重构平台后或许这里也得变化
# 账号模版
account_template_enabled = models.BooleanField(default=False, verbose_name=_("启用账号模版"))
auto_push_account = models.BooleanField(default=True, verbose_name=_('自动推送账号'))
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
token = models.TextField(default='', verbose_name=_('Token'))
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
ad_domain = models.CharField(default='', max_length=256)
# linux su 命令 (switch user) # linux su 命令 (switch user)
# Todo: 修改为 username, 不必系统用户了 # Todo: 修改为 username, 不必系统用户了
su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_enabled = models.BooleanField(default=False, verbose_name=_('User switch'))
@ -135,32 +40,6 @@ class SystemUser(ProtocolMixin, BaseUser):
username = '*' username = '*'
return '{0.name}({1})'.format(self, username) return '{0.name}({1})'.format(self, username)
@property
def nodes_amount(self):
return self.nodes.all().count()
@property
def login_mode_display(self):
return self.get_login_mode_display()
def is_need_push(self):
if self.auto_push_account and self.is_protocol_support_push:
return True
else:
return False
@property
def is_admin_user(self):
return self.type == self.Type.admin
@property
def is_need_cmd_filter(self):
return self.protocol not in [self.Protocol.rdp, self.Protocol.vnc]
@property
def is_need_test_asset_connective(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
@property @property
def cmd_filter_rules(self): def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule from .cmd_filter import CommandFilterRule
@ -178,30 +57,6 @@ class SystemUser(ProtocolMixin, BaseUser):
return False, matched_cmd return False, matched_cmd
return True, None return True, None
def get_all_assets(self):
from assets.models import Node
nodes_keys = self.nodes.all().values_list('key', flat=True)
asset_ids = set(self.assets.all().values_list('id', flat=True))
nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)
asset_ids.update(nodes_asset_ids)
assets = Asset.objects.filter(id__in=asset_ids)
return assets
def add_related_assets(self, assets_or_ids):
self.assets.add(*tuple(assets_or_ids))
self.add_related_assets_to_su_from_if_need(assets_or_ids)
def add_related_assets_to_su_from_if_need(self, assets_or_ids):
if self.protocol not in [self.Protocol.ssh.value]:
return
if not self.su_enabled:
return
if not self.su_from:
return
if self.su_from.protocol != self.protocol:
return
self.su_from.assets.add(*tuple(assets_or_ids))
@classmethod @classmethod
def create_accounts_with_assets(cls, asset_ids, system_user_ids): def create_accounts_with_assets(cls, asset_ids, system_user_ids):
pass pass
@ -216,6 +71,7 @@ class SystemUser(ProtocolMixin, BaseUser):
def get_auto_account(self, user_id, asset_id): def get_auto_account(self, user_id, asset_id):
from .account import Account from .account import Account
from users.models import User
username = self.username username = self.username
if self.username_same_with_user: if self.username_same_with_user:
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
@ -235,52 +91,3 @@ class SystemUser(ProtocolMixin, BaseUser):
permissions = [ permissions = [
('match_systemuser', _('Can match system user')), ('match_systemuser', _('Can match system user')),
] ]
# Deprecated: 准备废弃
class AdminUser(BaseUser):
"""
A privileged user that ansible can use it to push system user and so on
"""
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', blank=True, max_length=128)
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
_prefer = "admin_user"
def __str__(self):
return self.name
@property
def become_pass(self):
password = signer.unsign(self._become_pass)
if password:
return password
else:
return ""
@become_pass.setter
def become_pass(self, password):
self._become_pass = signer.sign(password)
@property
def become_info(self):
if self.become:
info = {
"method": self.become_method,
"user": self.become_user,
"pass": self.become_pass,
}
else:
info = None
return info
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("Admin user")

View File

@ -2,7 +2,6 @@
# #
from .asset import * from .asset import *
from .admin_user import *
from .label import * from .label import *
from .system_user import * from .system_user import *
from .node import * from .node import *

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
#
from ..models import SystemUser
from .system_user import SystemUserSerializer as SuS
class AdminUserSerializer(SuS):
"""
管理用户
"""
class Meta(SuS.Meta):
fields = SuS.Meta.fields_mini + \
SuS.Meta.fields_write_only + \
SuS.Meta.fields_m2m + \
[
'type', 'protocol', "priority", 'sftp_root', 'ssh_key_fingerprint',
'su_enabled', 'su_from',
'date_created', 'date_updated', 'comment', 'created_by',
]
def validate_type(self, val):
return SystemUser.Type.admin
def validate_protocol(self, val):
return 'ssh'

View File

@ -91,7 +91,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'cpu_info', 'hardware_info', 'cpu_info', 'hardware_info',
] ]
fields_fk = [ fields_fk = [
'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' 'domain', 'domain_display', 'platform',
] ]
fields_m2m = [ fields_m2m = [
'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts' 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts'
@ -114,19 +114,10 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
self.accounts_data = data.pop('accounts', []) self.accounts_data = data.pop('accounts', [])
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def get_fields(self):
fields = super().get_fields()
admin_user_field = fields.get('admin_user')
# 因为 mixin 中对 fields 有处理,可能不需要返回 admin_user
if admin_user_field:
admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin)
return fields
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('domain', 'platform', 'admin_user') queryset = queryset.prefetch_related('domain', 'platform')
queryset = queryset.prefetch_related('nodes', 'labels') queryset = queryset.prefetch_related('nodes', 'labels')
return queryset return queryset
@ -162,7 +153,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
def add_accounts(instance, accounts_data): def add_accounts(instance, accounts_data):
for data in accounts_data: for data in accounts_data:
data['asset'] = instance.id data['asset'] = instance.id
print("Data: ", accounts_data)
serializer = AccountSerializer(data=accounts_data, many=True) serializer = AccountSerializer(data=accounts_data, many=True)
try: try:
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)

View File

@ -1,97 +1,38 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen
from common.drf.fields import EncryptedField
from common.drf.serializers import SecretReadableMixin
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset from ..models import SystemUser
from .utils import validate_password_for_ansible
from .base import AuthSerializerMixin
__all__ = [ __all__ = [
'SystemUserSerializer', 'MiniSystemUserSerializer', 'SystemUserSerializer', 'MiniSystemUserSerializer',
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer', 'SystemUserSimpleSerializer',
'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer',
'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer',
'SystemUserTempAuthSerializer', 'RelationMixin',
] ]
class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class SystemUserSerializer(BulkOrgResourceModelSerializer):
""" """
系统用户 系统用户
""" """
password = EncryptedField(
label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024,
trim_whitespace=False, validators=[validate_password_for_ansible],
write_only=True
)
auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True)
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint'))
token = EncryptedField(
label=_('Token'), required=False, write_only=True, style={'base_template': 'textarea.html'}
)
applications_amount = serializers.IntegerField(
source='apps_amount', read_only=True, label=_('Apps amount')
)
class Meta: class Meta:
model = SystemUser model = SystemUser
fields_mini = ['id', 'name', 'username'] fields_mini = ['id', 'name', 'username', 'protocol']
fields_write_only = ['password', 'public_key', 'private_key', 'passphrase'] fields_small = fields_mini + [
fields_small = fields_mini + fields_write_only + [ 'login_mode', 'su_enabled', 'su_from',
'token', 'ssh_key_fingerprint', 'date_created', 'date_updated', 'comment',
'type', 'type_display', 'protocol', 'is_asset_protocol', 'created_by',
'account_template_enabled', 'login_mode', 'login_mode_display', 'priority',
'sudo', 'shell', 'sftp_root', 'home', 'system_groups', 'ad_domain',
'username_same_with_user', 'auto_push_account', 'auto_generate_key',
'su_enabled', 'su_from',
'date_created', 'date_updated', 'comment', 'created_by',
] ]
fields_m2m = ['cmd_filters', 'assets_amount', 'applications_amount', 'nodes'] fields = fields_small
fields = fields_small + fields_m2m
extra_kwargs = { extra_kwargs = {
'cmd_filters': {"required": False, 'label': _('Command filter')}, 'cmd_filters': {"required": False, 'label': _('Command filter')},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
'nodes_amount': {'label': _('Nodes amount')},
'assets_amount': {'label': _('Assets amount')},
'login_mode_display': {'label': _('Login mode display')}, 'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'created_by': {'read_only': True},
'ad_domain': {'required': False, 'allow_blank': True, 'label': _('Ad domain')}, 'ad_domain': {'required': False, 'allow_blank': True, 'label': _('Ad domain')},
'is_asset_protocol': {'label': _('Is asset protocol')},
'su_from': {'help_text': _('Only ssh and automatic login system users are supported')} 'su_from': {'help_text': _('Only ssh and automatic login system users are supported')}
} }
def validate_auto_push(self, value):
login_mode = self.get_initial_value("login_mode")
protocol = self.get_initial_value("protocol")
if login_mode == SystemUser.LOGIN_MANUAL:
value = False
elif protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
value = False
return value
def validate_auto_generate_key(self, value):
login_mode = self.get_initial_value("login_mode")
protocol = self.get_initial_value("protocol")
if self.context["request"].method.lower() != "post":
value = False
elif self.instance:
value = False
elif login_mode == SystemUser.LOGIN_MANUAL:
value = False
elif protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
value = False
return value
def validate_username_same_with_user(self, username_same_with_user): def validate_username_same_with_user(self, username_same_with_user):
if not username_same_with_user: if not username_same_with_user:
return username_same_with_user return username_same_with_user
@ -132,12 +73,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return username return username
def validate_home(self, home):
username_same_with_user = self.get_initial_value("username_same_with_user")
if username_same_with_user:
return ''
return home
@staticmethod @staticmethod
def validate_sftp_root(value): def validate_sftp_root(value):
if value in ['home', 'tmp']: if value in ['home', 'tmp']:
@ -147,17 +82,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
return value return value
def validate_password(self, password):
super().validate_password(password)
auto_gen_key = self.get_initial_value('auto_generate_key', False)
private_key = self.get_initial_value('private_key')
login_mode = self.get_initial_value('login_mode')
if not self.instance and not auto_gen_key and not password and \
not private_key and login_mode == SystemUser.LOGIN_AUTO:
raise serializers.ValidationError(_("Password or private key required"))
return password
def validate_su_from(self, su_from: SystemUser): def validate_su_from(self, su_from: SystemUser):
# self: su enabled # self: su enabled
su_enabled = self.get_initial_value('su_enabled', default=False) su_enabled = self.get_initial_value('su_enabled', default=False)
@ -181,70 +105,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
return su_from return su_from
def _validate_admin_user(self, attrs):
if self.instance:
tp = self.instance.type
else:
tp = attrs.get('type')
if tp != SystemUser.Type.admin:
return attrs
attrs['protocol'] = SystemUser.Protocol.ssh
attrs['login_mode'] = SystemUser.LOGIN_AUTO
attrs['username_same_with_user'] = False
attrs['auto_push_account'] = False
return attrs
def _validate_gen_key(self, attrs):
username = attrs.get('username', 'manual')
auto_gen_key = attrs.pop('auto_generate_key', False)
protocol = attrs.get('protocol')
if protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
return attrs
# 自动生成
if auto_gen_key and not self.instance:
password = SystemUser.gen_password()
attrs['password'] = password
if protocol == SystemUser.Protocol.ssh:
private_key, public_key = SystemUser.gen_key(username)
attrs['private_key'] = private_key
attrs['public_key'] = public_key
# 如果设置了private key没有设置public key则生成
elif attrs.get('private_key'):
private_key = attrs['private_key']
password = attrs.get('password')
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
attrs['public_key'] = public_key
return attrs
def _validate_login_mode(self, attrs):
if 'login_mode' in attrs:
login_mode = attrs['login_mode']
else:
login_mode = self.instance.login_mode if self.instance else SystemUser.LOGIN_AUTO
if login_mode == SystemUser.LOGIN_MANUAL:
attrs['password'] = ''
attrs['private_key'] = ''
attrs['public_key'] = ''
return attrs
def validate(self, attrs):
attrs = self._validate_admin_user(attrs)
attrs = self._validate_gen_key(attrs)
attrs = self._validate_login_mode(attrs)
return attrs
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset \
.annotate(assets_amount=Count("assets")) \
.prefetch_related('nodes', 'cmd_filters')
return queryset
class MiniSystemUserSerializer(serializers.ModelSerializer): class MiniSystemUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -252,28 +112,6 @@ class MiniSystemUserSerializer(serializers.ModelSerializer):
fields = SystemUserSerializer.Meta.fields_mini fields = SystemUserSerializer.Meta.fields_mini
class SystemUserWithAuthInfoSerializer(SecretReadableMixin, SystemUserSerializer):
class Meta(SystemUserSerializer.Meta):
fields_mini = ['id', 'name', 'username']
fields_write_only = ['password', 'public_key', 'private_key']
fields_small = fields_mini + fields_write_only + [
'protocol', 'login_mode', 'login_mode_display', 'priority',
'sudo', 'shell', 'ad_domain', 'sftp_root', 'token',
"username_same_with_user", 'auto_push_account', 'auto_generate_key',
'comment',
]
fields = fields_small
extra_kwargs = {
'nodes_amount': {'label': _('Node')},
'assets_amount': {'label': _('Asset')},
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True},
'password': {'write_only': False},
'private_key': {'write_only': False},
'token': {'write_only': False}
}
class SystemUserSimpleSerializer(serializers.ModelSerializer): class SystemUserSimpleSerializer(serializers.ModelSerializer):
""" """
系统用户最基本信息的数据结构 系统用户最基本信息的数据结构
@ -284,70 +122,6 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'username') fields = ('id', 'name', 'username')
class RelationMixin(BulkSerializerMixin, serializers.Serializer):
systemuser_display = serializers.ReadOnlyField(label=_("System user name"))
org_name = serializers.ReadOnlyField(label=_("Org name"))
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['systemuser', "systemuser_display", "org_name"])
return fields
class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
asset_display = serializers.ReadOnlyField(label=_('Asset hostname'))
class Meta:
model = SystemUser.assets.through
fields = [
"id", "asset", "asset_display",
"systemuser", "systemuser_display",
]
use_model_bulk_create = True
model_bulk_create_kwargs = {
'ignore_conflicts': True
}
class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
node_display = serializers.SerializerMethodField()
class Meta:
model = SystemUser.nodes.through
fields = [
'id', 'node', "node_display",
]
def get_node_display(self, obj):
return obj.node.full_value
class SystemUserUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
user_display = serializers.ReadOnlyField()
class Meta:
model = SystemUser.users.through
fields = [
'id', "user", "user_display",
]
class SystemUserTaskSerializer(serializers.Serializer):
ACTION_CHOICES = (
("test", "test"),
("push", "push"),
)
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
asset = serializers.PrimaryKeyRelatedField(
queryset=Asset.objects, allow_null=True, required=False, write_only=True
)
assets = serializers.PrimaryKeyRelatedField(
queryset=Asset.objects, allow_null=True, required=False, write_only=True,
many=True
)
task = serializers.CharField(read_only=True)
class SystemUserTempAuthSerializer(SystemUserSerializer): class SystemUserTempAuthSerializer(SystemUserSerializer):
instance_id = serializers.CharField() instance_id = serializers.CharField()

View File

@ -1,5 +1,4 @@
from .asset import * from .asset import *
from .system_user import * from .account import *
from .authbook import *
from .node_assets_amount import * from .node_assets_amount import *
from .node_assets_mapping import * from .node_assets_mapping import *

View File

@ -17,7 +17,6 @@ router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history
router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret') router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret')
router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
router.register(r'system-users', api.SystemUserViewSet, 'system-user') router.register(r'system-users', api.SystemUserViewSet, 'system-user')
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
router.register(r'labels', api.LabelViewSet, 'label') router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domains', api.DomainViewSet, 'domain') router.register(r'domains', api.DomainViewSet, 'domain')
@ -25,9 +24,6 @@ router.register(r'gateways', api.GatewayViewSet, 'gateway')
router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter') router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation')
router.register(r'system-users-nodes-relations', api.SystemUserNodeRelationViewSet, 'system-users-nodes-relation')
router.register(r'system-users-users-relations', api.SystemUserUserRelationViewSet, 'system-users-users-relation')
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
@ -45,11 +41,8 @@ urlpatterns = [
path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'), path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'),
path('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), path('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'),
path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('system-users/<uuid:pk>/applications/<uuid:app_id>/auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'),
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/users/<uuid:user_id>/account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), path('system-users/<uuid:pk>/assets/<uuid:asset_id>/users/<uuid:user_id>/account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'),
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/users/<uuid:user_id>/account-secret/', api.SystemUserAssetAccountSecretApi.as_view(), name='system-user-asset-account-secret'), path('system-users/<uuid:pk>/assets/<uuid:asset_id>/users/<uuid:user_id>/account-secret/', api.SystemUserAssetAccountSecretApi.as_view(), name='system-user-asset-account-secret'),
path('system-users/<uuid:pk>/tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'),
path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'), path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'),

View File

@ -69,11 +69,6 @@ M2M_NEED_RECORD = {
_('{User} JOINED {UserGroup}'), _('{User} JOINED {UserGroup}'),
_('{User} LEFT {UserGroup}') _('{User} LEFT {UserGroup}')
), ),
SystemUser.assets.through._meta.object_name: (
_('Asset and SystemUser'),
_('{Asset} ADD {SystemUser}'),
_('{Asset} REMOVE {SystemUser}')
),
Asset.nodes.through._meta.object_name: ( Asset.nodes.through._meta.object_name: (
_('Node and Asset'), _('Node and Asset'),
_('{Node} ADD {Asset}'), _('{Node} ADD {Asset}'),
@ -99,11 +94,6 @@ M2M_NEED_RECORD = {
_('{AssetPermission} ADD {Node}'), _('{AssetPermission} ADD {Node}'),
_('{AssetPermission} REMOVE {Node}'), _('{AssetPermission} REMOVE {Node}'),
), ),
AssetPermission.system_users.through._meta.object_name: (
_('Asset permission and SystemUser'),
_('{AssetPermission} ADD {SystemUser}'),
_('{AssetPermission} REMOVE {SystemUser}'),
),
ApplicationPermission.users.through._meta.object_name: ( ApplicationPermission.users.through._meta.object_name: (
_('User application permissions'), _('User application permissions'),
_('{ApplicationPermission} ADD {User}'), _('{ApplicationPermission} ADD {User}'),

View File

@ -62,20 +62,19 @@ class ValidateUserApplicationPermissionApi(APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user_id = request.query_params.get('user_id', '') user_id = request.query_params.get('user_id', '')
application_id = request.query_params.get('application_id', '') application_id = request.query_params.get('application_id', '')
system_user_id = request.query_params.get('system_user_id', '') account = system_user_id = request.query_params.get('account', '')
data = { data = {
'has_permission': False, 'has_permission': False,
'expire_at': int(time.time()), 'expire_at': int(time.time()),
'actions': [] 'actions': []
} }
if not all((user_id, application_id, system_user_id)): if not all((user_id, application_id, account)):
return Response(data) return Response(data)
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
application = Application.objects.get(id=application_id) application = Application.objects.get(id=application_id)
system_user = SystemUser.objects.get(id=system_user_id) has_perm, actions, expire_at = validate_permission(user, application, account)
has_perm, actions, expire_at = validate_permission(user, application, system_user)
status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN
data = { data = {
'has_permission': has_perm, 'has_permission': has_perm,

View File

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import generics from rest_framework import generics
from django.db.models import F, Value from django.db.models import F
from django.db.models.functions import Concat
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from orgs.mixins.api import OrgRelationMixin from orgs.mixins.api import OrgRelationMixin
@ -15,8 +14,7 @@ from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils
__all__ = [ __all__ = [
'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet', 'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet',
'AssetPermissionAssetRelationViewSet', 'AssetPermissionNodeRelationViewSet', 'AssetPermissionAssetRelationViewSet', 'AssetPermissionNodeRelationViewSet',
'AssetPermissionSystemUserRelationViewSet', 'AssetPermissionAllAssetListApi', 'AssetPermissionAllAssetListApi', 'AssetPermissionAllUserListApi',
'AssetPermissionAllUserListApi',
] ]
@ -117,21 +115,3 @@ class AssetPermissionNodeRelationViewSet(RelationMixin):
.annotate(node_key=F('node__key')) .annotate(node_key=F('node__key'))
return queryset return queryset
class AssetPermissionSystemUserRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionSystemUserRelationSerializer
m2m_field = models.AssetPermission.system_users.field
filterset_fields = [
'id', 'systemuser', 'assetpermission',
]
search_fields = [
"assetpermission__name", "systemuser__name", "systemuser__username"
]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
systemuser_display=Concat(
F('systemuser__name'), Value('('), F('systemuser__username'), Value(')')
))
return queryset

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.14 on 2022-07-28 09:10
from django.db import migrations, models
def migrate_system_user_to_accounts(apps, schema_editor):
# Todo: 迁移 系统用户为账号
pass
class Migration(migrations.Migration):
dependencies = [
('perms', '0028_auto_20220316_2028'),
]
operations = [
migrations.AddField(
model_name='assetpermission',
name='accounts',
field=models.JSONField(default=list, verbose_name='Accounts'),
),
migrations.RunPython(migrate_system_user_to_accounts),
migrations.RemoveField(
model_name='assetpermission',
name='system_users',
),
]

View File

@ -22,14 +22,13 @@ logger = logging.getLogger(__name__)
class AssetPermission(BasePermission): class AssetPermission(BasePermission):
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', blank=True, verbose_name=_("System user")) accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
class Meta: class Meta:
unique_together = [('org_id', 'name')] unique_together = [('org_id', 'name')]
verbose_name = _("Asset permission") verbose_name = _("Asset permission")
ordering = ('name',) ordering = ('name',)
permissions = [ permissions = []
]
@lazyproperty @lazyproperty
def users_amount(self): def users_amount(self):
@ -47,10 +46,6 @@ class AssetPermission(BasePermission):
def nodes_amount(self): def nodes_amount(self):
return self.nodes.count() return self.nodes.count()
@lazyproperty
def system_users_amount(self):
return self.system_users.count()
@classmethod @classmethod
def get_queryset_with_prefetch(cls): def get_queryset_with_prefetch(cls):
return cls.objects.all().valid().prefetch_related( return cls.objects.all().valid().prefetch_related(
@ -80,10 +75,6 @@ class AssetPermission(BasePermission):
names = [asset.hostname for asset in self.assets.all()] names = [asset.hostname for asset in self.assets.all()]
return names return names
def system_users_display(self):
names = [system_user.name for system_user in self.system_users.all()]
return names
def nodes_display(self): def nodes_display(self):
names = [node.full_value for node in self.nodes.all()] names = [node.full_value for node in self.nodes.all()]
return names return names

View File

@ -22,7 +22,6 @@ class AssetPermissionSerializer(BasePermissionSerializer):
user_groups_display = serializers.ListField(child=serializers.CharField(), label=_('User groups display'), required=False) user_groups_display = serializers.ListField(child=serializers.CharField(), label=_('User groups display'), required=False)
assets_display = serializers.ListField(child=serializers.CharField(), label=_('Assets display'), required=False) assets_display = serializers.ListField(child=serializers.CharField(), label=_('Assets display'), required=False)
nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes display'), required=False) nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes display'), required=False)
system_users_display = serializers.ListField(child=serializers.CharField(), label=_('System users display'), required=False)
class Meta: class Meta:
model = AssetPermission model = AssetPermission
@ -34,9 +33,9 @@ class AssetPermissionSerializer(BasePermissionSerializer):
] ]
fields_m2m = [ fields_m2m = [
'users', 'users_display', 'user_groups', 'user_groups_display', 'assets', 'users', 'users_display', 'user_groups', 'user_groups_display', 'assets',
'assets_display', 'nodes', 'nodes_display', 'system_users', 'system_users_display', 'assets_display', 'nodes', 'nodes_display', 'accounts',
'users_amount', 'user_groups_amount', 'assets_amount', 'users_amount', 'user_groups_amount', 'assets_amount',
'nodes_amount', 'system_users_amount', 'nodes_amount',
] ]
fields = fields_small + fields_m2m fields = fields_small + fields_m2m
read_only_fields = ['created_by', 'date_created', 'from_ticket'] read_only_fields = ['created_by', 'date_created', 'from_ticket']
@ -48,30 +47,16 @@ class AssetPermissionSerializer(BasePermissionSerializer):
'user_groups_amount': {'label': _('User groups amount')}, 'user_groups_amount': {'label': _('User groups amount')},
'assets_amount': {'label': _('Assets amount')}, 'assets_amount': {'label': _('Assets amount')},
'nodes_amount': {'label': _('Nodes amount')}, 'nodes_amount': {'label': _('Nodes amount')},
'system_users_amount': {'label': _('System users amount')},
} }
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset.prefetch_related( queryset = queryset.prefetch_related(
'users', 'user_groups', 'assets', 'nodes', 'system_users' 'users', 'user_groups', 'assets', 'nodes',
) )
return queryset return queryset
def to_internal_value(self, data):
if 'system_users_display' in data:
# system_users_display 转化为 system_users
system_users = data.get('system_users', [])
system_users_display = data.pop('system_users_display')
for name in system_users_display:
system_user = SystemUser.objects.filter(name=name).first()
if system_user and system_user.id not in system_users:
system_users.append(system_user.id)
data['system_users'] = system_users
return super().to_internal_value(data)
@staticmethod @staticmethod
def perform_display_create(instance, **kwargs): def perform_display_create(instance, **kwargs):
# 用户 # 用户

View File

@ -12,7 +12,6 @@ __all__ = [
'AssetPermissionUserGroupRelationSerializer', 'AssetPermissionUserGroupRelationSerializer',
"AssetPermissionAssetRelationSerializer", "AssetPermissionAssetRelationSerializer",
'AssetPermissionNodeRelationSerializer', 'AssetPermissionNodeRelationSerializer',
'AssetPermissionSystemUserRelationSerializer',
'AssetPermissionAllAssetSerializer', 'AssetPermissionAllAssetSerializer',
'AssetPermissionAllUserSerializer', 'AssetPermissionAllUserSerializer',
] ]
@ -99,13 +98,3 @@ class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSeri
fields = [ fields = [
'id', 'node', "node_display", 'id', 'node', "node_display",
] ]
class AssetPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
systemuser_display = serializers.ReadOnlyField()
class Meta:
model = AssetPermission.system_users.through
fields = [
'id', 'systemuser', 'systemuser_display'
]

View File

@ -1,138 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from users.models import User
from assets.models import SystemUser
from common.utils import get_logger
from common.decorator import on_transaction_commit
from common.exceptions import M2MReverseNotAllowed
from common.const.signals import POST_ADD
from perms.models import AssetPermission
logger = get_logger(__file__)
@receiver(m2m_changed, sender=User.groups.through)
@on_transaction_commit
def on_user_groups_change(sender, instance, action, reverse, pk_set, **kwargs):
"""
UserGroup 增加 User 增加的 User 需要与 UserGroup 关联的动态系统用户相关联
"""
user: User
if action != POST_ADD:
return
if not reverse:
# 一个用户添加了多个用户组
user_ids = [instance.id]
system_users = SystemUser.objects.filter(groups__id__in=pk_set).distinct()
else:
# 一个用户组添加了多个用户
user_ids = pk_set
system_users = SystemUser.objects.filter(groups__id=instance.pk).distinct()
for system_user in system_users:
system_user.users.add(*user_ids)
@receiver(m2m_changed, sender=AssetPermission.nodes.through)
@on_transaction_commit
def on_permission_nodes_changed(instance, action, reverse, pk_set, model, **kwargs):
if reverse:
raise M2MReverseNotAllowed
if action != POST_ADD:
return
logger.debug("Asset permission nodes change signal received")
nodes = model.objects.filter(pk__in=pk_set)
system_users = instance.system_users.all()
# TODO 待优化
for system_user in system_users:
system_user.nodes.add(*nodes)
@receiver(m2m_changed, sender=AssetPermission.assets.through)
@on_transaction_commit
def on_permission_assets_changed(instance, action, reverse, pk_set, model, **kwargs):
if reverse:
raise M2MReverseNotAllowed
if action != POST_ADD:
return
logger.debug("Asset permission assets change signal received")
assets = model.objects.filter(pk__in=pk_set)
# TODO 待优化
system_users = instance.system_users.all()
for system_user in system_users:
system_user: SystemUser
system_user.add_related_assets(assets)
@receiver(m2m_changed, sender=AssetPermission.system_users.through)
@on_transaction_commit
def on_asset_permission_system_users_changed(instance, action, reverse, **kwargs):
if reverse:
raise M2MReverseNotAllowed
if action != POST_ADD:
return
logger.debug("Asset permission system_users change signal received")
system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
assets = instance.assets.all().values_list('id', flat=True)
nodes = instance.nodes.all().values_list('id', flat=True)
for system_user in system_users:
system_user.nodes.add(*tuple(nodes))
system_user.add_related_assets(assets)
# 动态系统用户,需要关联用户和用户组了
if system_user.username_same_with_user:
users = instance.users.all().values_list('id', flat=True)
groups = instance.user_groups.all().values_list('id', flat=True)
system_user.groups.add(*tuple(groups))
system_user.users.add(*tuple(users))
@receiver(m2m_changed, sender=AssetPermission.users.through)
@on_transaction_commit
def on_asset_permission_users_changed(instance, action, reverse, pk_set, model, **kwargs):
if reverse:
raise M2MReverseNotAllowed
if action != POST_ADD:
return
logger.debug("Asset permission users change signal received")
users = model.objects.filter(pk__in=pk_set)
system_users = instance.system_users.all()
# TODO 待优化
for system_user in system_users:
if system_user.username_same_with_user:
system_user.users.add(*tuple(users))
@receiver(m2m_changed, sender=AssetPermission.user_groups.through)
@on_transaction_commit
def on_asset_permission_user_groups_changed(instance, action, pk_set, model, reverse, **kwargs):
if reverse:
raise M2MReverseNotAllowed
if action != POST_ADD:
return
logger.debug("Asset permission user groups change signal received")
groups = model.objects.filter(pk__in=pk_set)
system_users = instance.system_users.all()
# TODO 待优化
for system_user in system_users:
if system_user.username_same_with_user:
system_user.groups.add(*tuple(groups))

View File

@ -11,7 +11,6 @@ router.register('asset-permissions-users-relations', api.AssetPermissionUserRela
router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation') router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation')
router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation') router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation')
router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation') router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation')
router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation')
user_permission_urlpatterns = [ user_permission_urlpatterns = [
# 统一说明: # 统一说明:

View File

@ -11,11 +11,7 @@ from perms.utils.asset.user_permission import get_user_all_asset_perm_ids
logger = get_logger(__file__) logger = get_logger(__file__)
def validate_permission(user, asset, system_user, action='connect'): def validate_permission(user, asset, account, action='connect'):
if not system_user.protocol in asset.protocols_as_dict.keys():
return False, time.time()
asset_perm_ids = get_user_all_asset_perm_ids(user) asset_perm_ids = get_user_all_asset_perm_ids(user)
asset_perm_ids_from_asset = AssetPermission.assets.through.objects.filter( asset_perm_ids_from_asset = AssetPermission.assets.through.objects.filter(
@ -28,9 +24,7 @@ def validate_permission(user, asset, system_user, action='connect'):
for node in nodes: for node in nodes:
ancestor_keys = node.get_ancestor_keys(with_self=True) ancestor_keys = node.get_ancestor_keys(with_self=True)
node_keys.update(ancestor_keys) node_keys.update(ancestor_keys)
node_ids = Node.objects.filter(key__in=node_keys).values_list('id', flat=True) node_ids = set(Node.objects.filter(key__in=node_keys).values_list('id', flat=True))
node_ids = set(node_ids)
asset_perm_ids_from_node = AssetPermission.nodes.through.objects.filter( asset_perm_ids_from_node = AssetPermission.nodes.through.objects.filter(
assetpermission_id__in=asset_perm_ids, assetpermission_id__in=asset_perm_ids,
@ -39,16 +33,9 @@ def validate_permission(user, asset, system_user, action='connect'):
asset_perm_ids = {*asset_perm_ids_from_asset, *asset_perm_ids_from_node} asset_perm_ids = {*asset_perm_ids_from_asset, *asset_perm_ids_from_node}
asset_perm_ids = AssetPermission.system_users.through.objects.filter( asset_perms = AssetPermission.objects\
assetpermission_id__in=asset_perm_ids, .filter(id__in=asset_perm_ids, accounts__contains=account)\
systemuser_id=system_user.id .order_by('-date_expired')
).values_list('assetpermission_id', flat=True)
asset_perm_ids = set(asset_perm_ids)
asset_perms = AssetPermission.objects.filter(
id__in=asset_perm_ids
).order_by('-date_expired')
if asset_perms: if asset_perms:
actions = set() actions = set()

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.14 on 2022-07-28 03:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0017_auto_20220623_1027'),
]
operations = [
migrations.AlterField(
model_name='applyapplicationticket',
name='apply_permission_name',
field=models.CharField(max_length=128, verbose_name='Permission name'),
),
migrations.AlterField(
model_name='applyassetticket',
name='apply_permission_name',
field=models.CharField(max_length=128, verbose_name='Permission name'),
),
]

View File

@ -47,12 +47,6 @@ class AssetPermissionGenerator(FakeDataGenerator):
relation_name = 'node_id' relation_name = 'node_id'
self.set_relations(perms, through, relation_name, choices) self.set_relations(perms, through, relation_name, choices)
def set_system_users(self, perms):
through = AssetPermission.system_users.through
choices = self.system_user_ids
relation_name = 'systemuser_id'
self.set_relations(perms, through, relation_name, choices)
def set_relations(self, perms, through, relation_name, choices, choice_count=None): def set_relations(self, perms, through, relation_name, choices, choice_count=None):
relations = [] relations = []
@ -79,4 +73,3 @@ class AssetPermissionGenerator(FakeDataGenerator):
self.set_user_groups(created) self.set_user_groups(created)
self.set_assets(created) self.set_assets(created)
self.set_nodes(created) self.set_nodes(created)
self.set_system_users(created)