pull/8873/head
ibuler 2022-08-03 16:21:34 +08:00
commit d43acd8612
48 changed files with 206 additions and 1931 deletions

View File

@ -1,14 +1,10 @@
from .mixin import * from .mixin import *
from .platform import * from .platform import *
from .admin_user import *
from .asset import * from .asset import *
from .label import * from .label 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 *
from .cmd_filter import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account_backup import * from .account_backup 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

@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
#
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
from django.shortcuts import get_object_or_404
from common.utils import reverse
from common.utils import lazyproperty
from orgs.mixins.api import OrgBulkModelViewSet
from ..models import CommandFilter, CommandFilterRule
from .. import serializers
__all__ = [
'CommandFilterViewSet', 'CommandFilterRuleViewSet', 'CommandConfirmAPI',
]
class CommandFilterViewSet(OrgBulkModelViewSet):
model = CommandFilter
filterset_fields = ("name",)
search_fields = filterset_fields
serializer_class = serializers.CommandFilterSerializer
class CommandFilterRuleViewSet(OrgBulkModelViewSet):
model = CommandFilterRule
filterset_fields = ('content',)
search_fields = filterset_fields
serializer_class = serializers.CommandFilterRuleSerializer
def get_queryset(self):
fpk = self.kwargs.get('filter_pk')
if not fpk:
return CommandFilterRule.objects.none()
cmd_filter = get_object_or_404(CommandFilter, pk=fpk)
return cmd_filter.rules.all()
class CommandConfirmAPI(CreateAPIView):
serializer_class = serializers.CommandConfirmSerializer
rbac_perms = {
'POST': 'tickets.add_superticket'
}
def create(self, request, *args, **kwargs):
ticket = self.create_command_confirm_ticket()
response_data = self.get_response_data(ticket)
return Response(data=response_data, status=200)
def create_command_confirm_ticket(self):
ticket = self.serializer.cmd_filter_rule.create_command_confirm_ticket(
run_command=self.serializer.data.get('run_command'),
session=self.serializer.session,
cmd_filter_rule=self.serializer.cmd_filter_rule,
org_id=self.serializer.org.id,
)
return ticket
@staticmethod
def get_response_data(ticket):
confirm_status_url = reverse(
view_name='api-tickets:super-ticket-status',
kwargs={'pk': str(ticket.id)}
)
ticket_detail_url = reverse(
view_name='api-tickets:ticket-detail',
kwargs={'pk': str(ticket.id)},
external=True, api_to_ui=True
)
ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type)
ticket_assignees = ticket.current_step.ticket_assignees.all()
return {
'check_confirm_status': {'method': 'GET', 'url': confirm_status_url},
'close_confirm': {'method': 'DELETE', 'url': confirm_status_url},
'ticket_detail_url': ticket_detail_url,
'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees]
}
@lazyproperty
def serializer(self):
serializer = self.get_serializer(data=self.request.data)
serializer.is_valid(raise_exception=True)
return serializer

View File

@ -1,252 +0,0 @@
# ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.decorators import action
from common.utils import get_logger, get_object_or_none
from common.permissions import IsValidUser
from common.mixins.api import SuggestionMixin
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from orgs.utils import tmp_to_root_org
from assets.const import Protocol
from ..models import SystemUser, CommandFilterRule
from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer
from ..tasks import (
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
push_system_user_to_assets
)
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView',
'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi',
]
class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
model = SystemUser
filterset_fields = {
'name': ['exact'],
'username': ['exact'],
'protocol': ['exact', 'in'],
'type': ['exact', 'in'],
}
search_fields = filterset_fields
serializer_class = serializers.SystemUserSerializer
serializer_classes = {
'default': serializers.SystemUserSerializer,
'suggestion': serializers.MiniSystemUserSerializer
}
ordering_fields = ('name', 'protocol', 'login_mode')
ordering = ('name', )
rbac_perms = {
'su_from': 'assets.view_systemuser',
'su_to': 'assets.view_systemuser',
'match': 'assets.match_systemuser'
}
@action(methods=['get'], detail=False, url_path='su-from')
def su_from(self, request, *args, **kwargs):
""" API 获取可选的 su_from 系统用户"""
queryset = self.filter_queryset(self.get_queryset())
queryset = queryset.filter(
protocol=Protocol.ssh, login_mode=SystemUser.LOGIN_AUTO
)
return self.get_paginate_response_if_need(queryset)
@action(methods=['get'], detail=True, url_path='su-to')
def su_to(self, request, *args, **kwargs):
""" 获取系统用户的所有 su_to 系统用户 """
pk = kwargs.get('pk')
system_user = get_object_or_404(SystemUser, pk=pk)
queryset = system_user.su_to.all()
queryset = self.filter_queryset(queryset)
return self.get_paginate_response_if_need(queryset)
def get_paginate_response_if_need(self, queryset):
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
Get system user auth info
"""
model = SystemUser
serializer_class = SystemUserWithAuthInfoSerializer
rbac_perms = {
'retrieve': 'assets.view_systemusersecret',
'list': 'assets.view_systemusersecret',
'change': 'assets.change_systemuser',
'destroy': 'assets.change_systemuser',
}
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.clear_auth()
return Response(status=204)
class SystemUserTempAuthInfoApi(generics.CreateAPIView):
model = SystemUser
permission_classes = (IsValidUser,)
serializer_class = 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 = 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 = 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):
rbac_perms = {
'list': 'assets.view_commandfilterule'
}
def get_serializer_class(self):
from ..serializers import CommandFilterRuleSerializer
return CommandFilterRuleSerializer
def get_queryset(self):
user_id = self.request.query_params.get('user_id')
user_group_id = self.request.query_params.get('user_group_id')
system_user_id = self.kwargs.get('pk', None)
system_user = get_object_or_none(SystemUser, pk=system_user_id)
if not system_user:
system_user_id = self.request.query_params.get('system_user_id')
asset_id = self.request.query_params.get('asset_id')
application_id = self.request.query_params.get('application_id')
rules = CommandFilterRule.get_queryset(
user_id=user_id,
user_group_id=user_group_id,
system_user_id=system_user_id,
asset_id=asset_id,
application_id=application_id
)
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

@ -16,14 +16,6 @@ def add_default_group(apps, schema_editor):
) )
def add_default_cluster(apps, schema_editor):
cluster_model = apps.get_model("assets", "Cluster")
db_alias = schema_editor.connection.alias
cluster_model.objects.using(db_alias).create(
name="Default"
)
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
@ -163,6 +155,5 @@ class Migration(migrations.Migration):
unique_together=set([('ip', 'port')]), unique_together=set([('ip', 'port')]),
), ),
migrations.RunPython(add_default_cluster),
migrations.RunPython(add_default_group), migrations.RunPython(add_default_group),
] ]

View File

@ -14,9 +14,4 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AlterField(
model_name='asset',
name='cluster',
field=models.ForeignKey(default=assets.models.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
] ]

View File

@ -1,7 +1,6 @@
# Generated by Django 3.2.12 on 2022-07-11 08:59 # Generated by Django 3.2.12 on 2022-07-11 08:59
import assets.models.base import assets.models.base
import assets.models.user
import common.db.fields import common.db.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -36,7 +35,7 @@ class Migration(migrations.Migration):
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')),
('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')),
('version', models.IntegerField(default=1, verbose_name='Version')), ('version', models.IntegerField(default=0, verbose_name='Version')),
('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)), ('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)), ('history_change_reason', models.CharField(max_length=100, null=True)),
@ -75,9 +74,9 @@ class Migration(migrations.Migration):
], ],
options={ options={
'verbose_name': 'Account', 'verbose_name': 'Account',
'permissions': [('view_assetaccountsecret', 'Can view asset account secret'), ('change_assetaccountsecret', 'Can change asset account secret'), ('view_assethistoryaccount', 'Can view asset history account'), ('view_assethistoryaccountsecret', 'Can view asset history account secret')], 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')],
'unique_together': {('username', 'asset')}, 'unique_together': {('username', 'asset')},
}, },
bases=(models.Model, assets.models.base.AuthMixin, assets.models.user.ProtocolMixin), bases=(models.Model, assets.models.base.AuthMixin, assets.models.protocol.ProtocolMixin),
), ),
] ]

View File

@ -0,0 +1,16 @@
# Generated by Django 3.2.14 on 2022-08-03 06:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0093_auto_20220711_1413'),
]
operations = [
migrations.DeleteModel(
name='Cluster',
),
]

View File

@ -1,17 +1,17 @@
from .base import * from .base import *
from .platform import * from .platform import *
from ._user import *
from .asset import * from .asset import *
from .label import Label from .label import Label
from .user import *
from .cluster import *
from .group import * from .group import *
from .domain import * from .domain import *
from .node import * from .node import *
from .cmd_filter import *
from .authbook import *
from .utils import * from .utils 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 *
# 废弃以下
from ._authbook import *
from .protocol import *
from .cmd_filter import *

View File

@ -125,9 +125,8 @@ class AuthBook(BaseUser, AbsConnectivity):
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
@classmethod @classmethod
def get_queryset(cls, is_history_model=False): def get_queryset(cls):
model = cls.history.model if is_history_model else cls queryset = cls.objects.all() \
queryset = model.objects.all() \
.annotate(ip=F('asset__ip')) \ .annotate(ip=F('asset__ip')) \
.annotate(hostname=F('asset__hostname')) \ .annotate(hostname=F('asset__hostname')) \
.annotate(platform=F('asset__platform__name')) \ .annotate(platform=F('asset__platform__name')) \
@ -136,5 +135,3 @@ class AuthBook(BaseUser, AbsConnectivity):
def __str__(self): def __str__(self):
return self.smart_name return self.smart_name

View File

@ -11,68 +11,18 @@ from django.core.validators import MinValueValidator, MaxValueValidator
from assets.const import Protocol from assets.const import Protocol
from common.utils import signer from common.utils import signer
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
Protocol = Protocol
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'
LOGIN_MODE_CHOICES = ( LOGIN_MODE_CHOICES = (
(LOGIN_AUTO, _('使用账号')), (LOGIN_AUTO, _('Automatic managed')),
(LOGIN_MANUAL, _('Manually input')) (LOGIN_MANUAL, _('Manually input'))
) )
@ -84,34 +34,24 @@ class SystemUser(ProtocolMixin, BaseUser):
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField( assets = models.ManyToManyField(
'assets.Asset', blank=True, verbose_name=_("Assets"), 'assets.Asset', blank=True, verbose_name=_("Assets"),
through='assets.AuthBook', through_fields=['systemuser', 'asset'],
related_name='system_users' related_name='system_users'
) )
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) 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=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'))
# 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')) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
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'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
token = models.TextField(default='', verbose_name=_('Token')) token = models.TextField(default='', verbose_name=_('Token'))
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) 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) system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
ad_domain = models.CharField(default='', max_length=256) ad_domain = models.CharField(default='', max_length=256)
# linux su 命令 (switch user) # linux su 命令 (switch user)
# Todo: 修改为 username, 不必系统用户了
su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_enabled = models.BooleanField(default=False, verbose_name=_('User switch'))
su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from"))
@ -130,7 +70,7 @@ class SystemUser(ProtocolMixin, BaseUser):
return self.get_login_mode_display() return self.get_login_mode_display()
def is_need_push(self): def is_need_push(self):
if self.auto_push_account and self.is_protocol_support_push: if self.auto_push and self.is_protocol_support_push:
return True return True
else: else:
return False return False
@ -165,7 +105,7 @@ class SystemUser(ProtocolMixin, BaseUser):
return True, None return True, None
def get_all_assets(self): def get_all_assets(self):
from assets.models import Node from assets.models import Node, Asset
nodes_keys = self.nodes.all().values_list('key', flat=True) nodes_keys = self.nodes.all().values_list('key', flat=True)
asset_ids = set(self.assets.all().values_list('id', 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) nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)

View File

@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from .user import ProtocolMixin from .protocol import ProtocolMixin
from .base import BaseUser, AbsConnectivity from .base import BaseUser, AbsConnectivity
@ -14,8 +14,10 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin):
common = 'common', _('Common user') common = 'common', _('Common user')
admin = 'admin', _('Admin user') admin = 'admin', _('Admin user')
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, protocol = models.CharField(
default='ssh', verbose_name=_('Protocol')) max_length=16, choices=ProtocolMixin.Protocol.choices,
default='ssh', verbose_name=_('Protocol')
)
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type")) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type"))
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
version = models.IntegerField(default=0, verbose_name=_('Version')) version = models.IntegerField(default=0, verbose_name=_('Version'))
@ -25,10 +27,10 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin):
verbose_name = _('Account') verbose_name = _('Account')
unique_together = [('username', 'asset')] unique_together = [('username', 'asset')]
permissions = [ permissions = [
('view_assetaccountsecret', _('Can view asset account secret')), ('view_accountsecret', _('Can view asset account secret')),
('change_assetaccountsecret', _('Can change asset account secret')), ('change_accountsecret', _('Can change asset account secret')),
('view_assethistoryaccount', _('Can view asset history account')), ('view_historyaccount', _('Can view asset history account')),
('view_assethistoryaccountsecret', _('Can view asset history account secret')), ('view_historyaccountsecret', _('Can view asset history account secret')),
] ]
def __str__(self): def __str__(self):

View File

@ -9,7 +9,6 @@ from collections import OrderedDict
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 rest_framework.exceptions import ValidationError
from common.db.fields import JsonDictTextField from common.db.fields import JsonDictTextField
from common.utils import lazyproperty from common.utils import lazyproperty
@ -22,16 +21,6 @@ __all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def default_cluster():
from assets.models import Cluster
name = "Default"
defaults = {"name": name}
cluster, created = Cluster.objects.get_or_create(
defaults=defaults, name=name
)
return cluster.id
def default_node(): def default_node():
try: try:
from assets.models import Node from assets.models import Node

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

@ -9,7 +9,6 @@ from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from users.models import User, UserGroup from users.models import User, UserGroup
from applications.models import Application
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from common.utils import lazyproperty, get_logger, get_object_or_none from common.utils import lazyproperty, get_logger, get_object_or_none
@ -125,6 +124,9 @@ class CommandFilterRule(OrgModelMixin):
regex.append(cmd) regex.append(cmd)
continue continue
if not cmd:
continue
# 如果是单个字符 # 如果是单个字符
if cmd[-1].isalpha(): if cmd[-1].isalpha():
regex.append(r'\b{0}\b'.format(cmd)) regex.append(r'\b{0}\b'.format(cmd))
@ -187,6 +189,7 @@ class CommandFilterRule(OrgModelMixin):
@classmethod @classmethod
def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None,
asset_id=None, application_id=None, org_id=None): asset_id=None, application_id=None, org_id=None):
from applications.models import Application
user_groups = [] user_groups = []
user = get_object_or_none(User, pk=user_id) user = get_object_or_none(User, pk=user_id)
if user: if user:

View File

@ -7,3 +7,67 @@ from common.db.models import JMSBaseModel
class Protocol(JMSBaseModel): class Protocol(JMSBaseModel):
name = models.CharField(max_length=32, verbose_name=_("Name")) name = models.CharField(max_length=32, verbose_name=_("Name"))
port = models.IntegerField(verbose_name=_("Port")) port = models.IntegerField(verbose_name=_("Port"))
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

@ -2,12 +2,10 @@
# #
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 *
from .domain import * from .domain import *
from .cmd_filter import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account 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

@ -94,8 +94,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin):
'public_ip', 'number', 'comment', 'public_ip', 'number', 'comment',
] ]
fields_fk = [ fields_fk = [
'domain', 'domain_display', 'platform', 'platform_display', 'domain', 'domain_display', 'platform',
'admin_user', 'admin_user_display'
] ]
fields_m2m = [ fields_m2m = [
'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts' 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts'
@ -137,7 +136,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin):
@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
@ -173,7 +172,6 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin):
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,110 +0,0 @@
# -*- coding: utf-8 -*-
#
import re
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from ..models import CommandFilter, CommandFilterRule
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from orgs.utils import tmp_to_root_org
from common.utils import get_object_or_none, lazyproperty
from terminal.models import Session
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
class Meta:
model = CommandFilter
fields_mini = ['id', 'name']
fields_small = fields_mini + [
'org_id', 'org_name', 'is_active',
'date_created', 'date_updated',
'comment', 'created_by',
]
fields_fk = ['rules']
fields_m2m = ['users', 'user_groups', 'system_users', 'assets', 'applications']
fields = fields_small + fields_fk + fields_m2m
extra_kwargs = {
'rules': {'read_only': True},
'date_created': {'label': _("Date created")},
'date_updated': {'label': _("Date updated")},
}
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display"))
action_display = serializers.ReadOnlyField(source='get_action_display', label=_("Action display"))
class Meta:
model = CommandFilterRule
fields_mini = ['id']
fields_small = fields_mini + [
'type', 'type_display', 'content', 'ignore_case', 'pattern',
'priority', 'action', 'action_display', 'reviewers',
'date_created', 'date_updated', 'comment', 'created_by',
]
fields_fk = ['filter']
fields = fields_small + fields_fk
extra_kwargs = {
'date_created': {'label': _("Date created")},
'date_updated': {'label': _("Date updated")},
'action_display': {'label': _("Action display")},
'pattern': {'label': _("Pattern")}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_action_choices()
def set_action_choices(self):
from django.conf import settings
action = self.fields.get('action')
if not action:
return
choices = action._choices
if not settings.XPACK_ENABLED:
choices.pop(CommandFilterRule.ActionChoices.confirm, None)
action._choices = choices
def validate_content(self, content):
tp = self.initial_data.get("type")
if tp == CommandFilterRule.TYPE_COMMAND:
regex = CommandFilterRule.construct_command_regex(content)
else:
regex = content
ignore_case = self.initial_data.get('ignore_case')
succeed, error, pattern = CommandFilterRule.compile_regex(regex, ignore_case)
if not succeed:
raise serializers.ValidationError(error)
return content
class CommandConfirmSerializer(serializers.Serializer):
session_id = serializers.UUIDField(required=True, allow_null=False)
cmd_filter_rule_id = serializers.UUIDField(required=True, allow_null=False)
run_command = serializers.CharField(required=True, allow_null=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.session = None
self.cmd_filter_rule = None
def validate_session_id(self, session_id):
self.session = self.validate_object_exist(Session, session_id)
return session_id
def validate_cmd_filter_rule_id(self, cmd_filter_rule_id):
self.cmd_filter_rule = self.validate_object_exist(CommandFilterRule, cmd_filter_rule_id)
return cmd_filter_rule_id
@staticmethod
def validate_object_exist(model, field_id):
with tmp_to_root_org():
obj = get_object_or_none(model, id=field_id)
if not obj:
error = '{} Model object does not exist'.format(model.__name__)
raise serializers.ValidationError(error)
return obj
@lazyproperty
def org(self):
return self.session.org

View File

@ -1,98 +1,37 @@
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 assets.const import Protocol
from ..models import SystemUser, Asset
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
@ -133,12 +72,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']:
@ -148,17 +81,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)
@ -182,70 +104,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'] = 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 == 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:
@ -253,28 +111,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):
""" """
系统用户最基本信息的数据结构 系统用户最基本信息的数据结构
@ -285,70 +121,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
fields = [
"id", "asset", "asset_display", 'systemuser', 'systemuser_display',
"connectivity", 'date_verified', 'org_id'
]
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

@ -8,11 +8,10 @@ from django.dispatch import receiver
from common.const.signals import POST_ADD, POST_REMOVE, PRE_REMOVE from common.const.signals import POST_ADD, POST_REMOVE, PRE_REMOVE
from common.utils import get_logger from common.utils import get_logger
from common.decorator import on_transaction_commit from common.decorator import on_transaction_commit
from assets.models import Asset, SystemUser, Node from assets.models import Asset, Node
from assets.tasks import ( from assets.tasks import (
update_assets_hardware_info_util, update_assets_hardware_info_util,
test_asset_connectivity_util, test_asset_connectivity_util,
push_system_user_to_assets,
) )
logger = get_logger(__file__) logger = get_logger(__file__)
@ -77,15 +76,15 @@ def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs):
nodes_ancestors_keys.update(Node.get_node_ancestor_keys(node, with_self=True)) nodes_ancestors_keys.update(Node.get_node_ancestor_keys(node, with_self=True))
# 查询所有祖先节点关联的系统用户,都是要跟资产建立关系的 # 查询所有祖先节点关联的系统用户,都是要跟资产建立关系的
system_user_ids = SystemUser.objects.filter( # system_user_ids = SystemUser.objects.filter(
nodes__key__in=nodes_ancestors_keys # nodes__key__in=nodes_ancestors_keys
).distinct().values_list('id', flat=True) # ).distinct().values_list('id', flat=True)
# 查询所有已存在的关系 # 查询所有已存在的关系
m2m_model = SystemUser.assets.through # m2m_model = SystemUser.assets.through
exist = set(m2m_model.objects.filter( # exist = set(m2m_model.objects.filter(
systemuser_id__in=system_user_ids, asset_id__in=asset_ids # systemuser_id__in=system_user_ids, asset_id__in=asset_ids
).values_list('systemuser_id', 'asset_id')) # ).values_list('systemuser_id', 'asset_id'))
# TODO 优化 # TODO 优化
# to_create = [] # to_create = []
# for system_user_id in system_user_ids: # for system_user_id in system_user_ids:

View File

@ -1,115 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.db.models.signals import (
post_save, m2m_changed, pre_save, pre_delete, post_delete
)
from django.dispatch import receiver
from common.exceptions import M2MReverseNotAllowed
from common.const.signals import POST_ADD
from common.utils import get_logger
from common.decorator import on_transaction_commit
from assets.models import Asset, SystemUser, Node
from users.models import User
from assets.tasks import (
push_system_user_to_assets_manual,
push_system_user_to_assets,
add_nodes_assets_to_system_users
)
logger = get_logger(__file__)
@receiver(m2m_changed, sender=SystemUser.assets.through)
@on_transaction_commit
def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
"""
当系统用户和资产关系发生变化时应该重新推送系统用户到新添加的资产中
"""
logger.debug("System user assets change signal recv: {}".format(instance))
if not instance:
logger.debug('No system user found')
return
if model == Asset:
system_user_ids = [instance.id]
asset_ids = pk_set
else:
system_user_ids = pk_set
asset_ids = [instance.id]
# todo: Auto create account if need
@receiver(m2m_changed, sender=SystemUser.users.through)
@on_transaction_commit
def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_set, reverse, **kwargs):
"""
当系统用户和用户关系发生变化时应该重新推送系统用户资产中
"""
if action != POST_ADD:
return
if reverse:
raise M2MReverseNotAllowed
if not instance.username_same_with_user:
return
logger.debug("System user users change signal recv: {}".format(instance))
usernames = model.objects.filter(pk__in=pk_set).values_list('username', flat=True)
for username in usernames:
push_system_user_to_assets_manual.delay(instance, username)
@receiver(m2m_changed, sender=SystemUser.nodes.through)
@on_transaction_commit
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
"""
当系统用户和节点关系发生变化时应该将节点下资产关联到新的系统用户上
"""
if action != POST_ADD:
return
logger.info("System user nodes update signal recv: {}".format(instance))
queryset = model.objects.filter(pk__in=pk_set)
if model == Node:
nodes_keys = queryset.values_list('key', flat=True)
system_users = [instance]
else:
nodes_keys = [instance.key]
system_users = queryset
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
@receiver(m2m_changed, sender=SystemUser.groups.through)
def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs):
"""
当系统用户和用户组关系发生变化时应该将组下用户关联到新的系统用户上
"""
if action != POST_ADD:
return
if reverse:
raise M2MReverseNotAllowed
logger.info("System user groups update signal recv: {}".format(instance))
users = User.objects.filter(groups__id__in=pk_set).distinct()
instance.users.add(*users)
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
@on_transaction_commit
def on_system_user_update(instance: SystemUser, created, **kwargs):
"""
当系统用户更新时可能更新了密钥用户名等这时要自动推送系统用户到资产上,
其实应该当 用户名密码密钥 sudo等更新时再推送这里偷个懒,
这里直接取了 instance.assets 因为nodes和系统用户发生变化时会自动将nodes下的资产
关联到上面
"""
if instance and not created:
logger.info("System user update signal recv: {}".format(instance))
assets = instance.assets.all().valid()
push_system_user_to_assets.delay(instance.id, [_asset.id for _asset in assets])
# add assets to su_from
instance.add_related_assets_to_su_from_if_need(assets)

View File

@ -6,7 +6,5 @@ from .asset_connectivity import *
from .account_connectivity import * from .account_connectivity import *
from .gather_asset_users import * from .gather_asset_users import *
from .gather_asset_hardware_info import * from .gather_asset_hardware_info import *
from .push_system_user import *
from .system_user_connectivity import *
from .nodes_amount import * from .nodes_amount import *
from .backup import * from .backup import *

View File

@ -1,38 +1,2 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from celery import shared_task
from orgs.utils import tmp_to_root_org
from assets.models import AuthBook
__all__ = ['add_nodes_assets_to_system_users']
# Todo: 等待优化
@shared_task
@tmp_to_root_org()
def add_nodes_assets_to_system_users(nodes_keys, system_users):
from ..models import Node
from assets.tasks import push_system_user_to_assets
nodes = Node.objects.filter(key__in=nodes_keys)
assets = Node.get_nodes_all_assets(*nodes)
for system_user in system_users:
""" 解决资产和节点进行关联时,已经关联过的节点不会触发 authbook post_save 信号,
无法更新节点下所有资产的管理用户的问题 """
need_push_asset_ids = []
for asset in assets:
defaults = {'asset': asset, 'systemuser': system_user, 'org_id': asset.org_id}
instance, created = AuthBook.objects.update_or_create(
defaults=defaults, asset=asset, systemuser=system_user
)
if created:
need_push_asset_ids.append(asset.id)
# 不再自动更新资产管理用户,只允许用户手动指定。
# 只要关联都需要更新资产的管理用户
# instance.update_asset_admin_user_if_need()
if need_push_asset_ids:
push_system_user_to_assets.delay(system_user.id, need_push_asset_ids)

View File

@ -1,307 +0,0 @@
# ~*~ coding: utf-8 ~*~
from itertools import groupby
from celery import shared_task
from common.db.utils import get_object_if_need, get_objects
from django.utils.translation import ugettext as _, gettext_noop
from django.db.models import Empty
from common.utils import encrypt_password, get_logger
from assets.models import SystemUser, Asset
from orgs.utils import org_aware_func, tmp_to_root_org
from . import const
from .utils import clean_ansible_task_hosts, group_asset_by_platform
logger = get_logger(__file__)
__all__ = [
'push_system_user_util', 'push_system_user_to_assets',
'push_system_user_to_assets_manual', 'push_system_user_a_asset_manual',
'push_system_users_a_asset'
]
def _split_by_comma(raw: str):
try:
return [i.strip() for i in raw.split(',')]
except AttributeError:
return []
def _dump_args(args: dict):
return ' '.join([f'{k}={v}' for k, v in args.items() if v is not Empty])
def get_push_unixlike_system_user_tasks(system_user, username=None, **kwargs):
algorithm = kwargs.get('algorithm')
if username is None:
username = system_user.username
comment = system_user.name
if system_user.username_same_with_user:
from users.models import User
user = User.objects.filter(username=username).only('name', 'username').first()
if user:
comment = f'{system_user.name}[{str(user)}]'
comment = comment.replace(' ', '')
password = system_user.password
public_key = system_user.public_key
groups = _split_by_comma(system_user.system_groups)
if groups:
groups = '"%s"' % ','.join(groups)
add_user_args = {
'name': username,
'shell': system_user.shell or Empty,
'state': 'present',
'home': system_user.home or Empty,
'expires': -1,
'groups': groups or Empty,
'comment': comment
}
tasks = [
{
'name': 'Add user {}'.format(username),
'action': {
'module': 'user',
'args': _dump_args(add_user_args),
}
},
{
'name': 'Add group {}'.format(username),
'action': {
'module': 'group',
'args': 'name={} state=present'.format(username),
}
}
]
if not system_user.home:
tasks.extend([
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
},
'when': 'home_existed.stat.exists == true'
}
])
if password:
tasks.append({
'name': 'Set {} password'.format(username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
username, system_user.shell,
encrypt_password(password, salt="K3mIlKK", algorithm=algorithm),
),
}
})
if public_key:
tasks.append({
'name': 'Set {} authorized key'.format(username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
username, public_key
)
}
})
if system_user.sudo:
sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
sudo_list = sudo.split('\n')
sudo_tmp = []
for s in sudo_list:
sudo_tmp.append(s.strip(','))
sudo = ','.join(sudo_tmp)
tasks.append({
'name': 'Set {} sudo setting'.format(username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(username, sudo)
}
})
return tasks
def get_push_windows_system_user_tasks(system_user: SystemUser, username=None, **kwargs):
if username is None:
username = system_user.username
password = system_user.password
groups = {'Users', 'Remote Desktop Users'}
if system_user.system_groups:
groups.update(_split_by_comma(system_user.system_groups))
groups = ','.join(groups)
tasks = []
if not password:
logger.error("Error: no password found")
return tasks
if system_user.ad_domain:
logger.error('System user with AD domain do not support push.')
return tasks
task = {
'name': 'Add user {}'.format(username),
'action': {
'module': 'win_user',
'args': 'fullname={} '
'name={} '
'password={} '
'state=present '
'update_password=always '
'password_expired=no '
'password_never_expires=yes '
'groups="{}" '
'groups_action=add '
''.format(username, username, password, groups),
}
}
tasks.append(task)
return tasks
def get_push_system_user_tasks(system_user, platform="unixlike", username=None, algorithm=None):
"""
获取推送系统用户的 ansible 命令跟资产无关
:param system_user:
:param platform:
:param username: 当动态时近推送某个
:return:
"""
get_task_map = {
"unixlike": get_push_unixlike_system_user_tasks,
"windows": get_push_windows_system_user_tasks,
}
get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks)
if not system_user.username_same_with_user:
return get_tasks(system_user, algorithm=algorithm)
tasks = []
# 仅推送这个username
if username is not None:
tasks.extend(get_tasks(system_user, username, algorithm=algorithm))
return tasks
users = system_user.users.all().values_list('username', flat=True)
print(_("System user is dynamic: {}").format(list(users)))
for _username in users:
tasks.extend(get_tasks(system_user, _username, algorithm=algorithm))
return tasks
@org_aware_func("system_user")
def push_system_user_util(system_user, assets, task_name, username=None):
from ops.utils import update_or_create_ansible_task
assets = clean_ansible_task_hosts(assets, system_user=system_user)
if not assets:
return {}
# 资产按平台分类
assets_sorted = sorted(assets, key=group_asset_by_platform)
platform_hosts = groupby(assets_sorted, key=group_asset_by_platform)
if system_user.username_same_with_user:
if username is None:
# 动态系统用户,但是没有指定 username
usernames = list(system_user.users.all().values_list('username', flat=True).distinct())
else:
usernames = [username]
else:
# 非动态系统用户指定 username 无效
assert username is None, 'Only Dynamic user can assign `username`'
usernames = [system_user.username]
def run_task(_tasks, _hosts):
if not _tasks:
return
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
)
task.run()
for platform, _assets in platform_hosts:
_assets = list(_assets)
if not _assets:
continue
print(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_assets)))
for u in usernames:
for a in _assets:
system_user.load_asset_special_auth(a, u)
algorithm = 'des' if a.platform.name == 'AIX' else 'sha512'
tasks = get_push_system_user_tasks(
system_user, platform, username=u,
algorithm=algorithm
)
run_task(tasks, [a])
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_user_to_assets_manual(system_user, username=None):
"""
将系统用户推送到与它关联的所有资产上
"""
system_user = get_object_if_need(SystemUser, system_user)
assets = system_user.get_related_assets()
task_name = gettext_noop("Push system users to assets: ") + system_user.name
return push_system_user_util(system_user, assets, task_name=task_name, username=username)
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_user_a_asset_manual(system_user, asset, username=None):
"""
将系统用户推送到一个资产上
"""
# if username is None:
# username = system_user.username
task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format(
system_user.name, username or system_user.username, asset
)
return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_users_a_asset(system_users, asset):
for system_user in system_users:
push_system_user_a_asset_manual(system_user, asset)
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_user_to_assets(system_user_id, asset_ids, username=None):
"""
推送系统用户到指定的若干资产上
"""
system_user = SystemUser.objects.get(id=system_user_id)
assets = get_objects(Asset, asset_ids)
task_name = gettext_noop("Push system users to assets: ") + system_user.name
return push_system_user_util(system_user, assets, task_name, username=username)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)

View File

@ -1,151 +0,0 @@
from itertools import groupby
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _, gettext_noop
from assets.models import Asset
from common.utils import get_logger
from orgs.utils import tmp_to_org, org_aware_func
from ..models import SystemUser, Connectivity, Account
from . import const
from .utils import (
clean_ansible_task_hosts, group_asset_by_platform
)
logger = get_logger(__name__)
__all__ = [
'test_system_user_connectivity_util', 'test_system_user_connectivity_manual',
'test_system_user_connectivity_period', 'test_system_user_connectivity_a_asset',
'test_system_users_connectivity_a_asset'
]
def set_assets_accounts_connectivity(system_user, assets, results_summary):
asset_ids_ok = set()
asset_ids_failed = set()
asset_hostnames_ok = results_summary.get('contacted', {}).keys()
for asset in assets:
if asset.hostname in asset_hostnames_ok:
asset_ids_ok.add(asset.id)
else:
asset_ids_failed.add(asset.id)
accounts_ok = Account.objects.filter(asset_id__in=asset_ids_ok, systemuser=system_user)
accounts_failed = Account.objects.filter(asset_id__in=asset_ids_failed, systemuser=system_user)
Account.bulk_set_connectivity(accounts_ok, Connectivity.ok)
Account.bulk_set_connectivity(accounts_failed, Connectivity.failed)
@org_aware_func("system_user")
def test_system_user_connectivity_util(system_user, assets, task_name):
"""
Test system cant connect his assets or not.
:param system_user:
:param assets:
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
if system_user.username_same_with_user:
logger.error(_("Dynamic system user not support test"))
return
# hosts = clean_ansible_task_hosts(assets, system_user=system_user)
# TODO: 这里不传递系统用户因为clean_ansible_task_hosts会通过system_user来判断是否可以推送
# 不符合测试可连接性逻辑, 后面需要优化此逻辑
hosts = clean_ansible_task_hosts(assets)
if not hosts:
return {}
platform_hosts_map = {}
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
for i in platform_hosts:
platform_hosts_map[i[0]] = list(i[1])
platform_tasks_map = {
"unixlike": const.PING_UNIXLIKE_TASKS,
"windows": const.PING_WINDOWS_TASKS
}
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
def run_task(_tasks, _hosts, _username):
old_name = "{}".format(system_user)
new_name = "{}({})".format(system_user.name, _username)
_task_name = task_name.replace(old_name, new_name)
_task, created = update_or_create_ansible_task(
task_name=_task_name, hosts=_hosts, tasks=_tasks,
pattern='all', options=const.TASK_OPTIONS,
run_as=_username, system_user=system_user
)
raw, summary = _task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
for platform, _hosts in platform_hosts_map.items():
if not _hosts:
continue
if platform not in ["unixlike", "windows"]:
continue
tasks = platform_tasks_map[platform]
print(_("Start test system user connectivity for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts)))
# 用户名不是动态的,用户名则是一个
logger.debug("System user not has special auth")
run_task(tasks, _hosts, system_user.username)
set_assets_accounts_connectivity(system_user, hosts, results_summary)
return results_summary
@shared_task(queue="ansible")
@org_aware_func("system_user")
def test_system_user_connectivity_manual(system_user, asset_ids=None):
task_name = gettext_noop("Test system user connectivity: ") + str(system_user)
if asset_ids:
assets = Asset.objects.filter(id__in=asset_ids)
else:
assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task(queue="ansible")
@org_aware_func("system_user")
def test_system_user_connectivity_a_asset(system_user, asset):
task_name = gettext_noop("Test system user connectivity: ") + "{} => {}".format(
system_user, asset
)
test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task(queue="ansible")
def test_system_users_connectivity_a_asset(system_users, asset):
for system_user in system_users:
test_system_user_connectivity_a_asset(system_user, asset)
@shared_task(queue="ansible")
def test_system_user_connectivity_period():
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, test system user connectivity pass")
return
queryset_map = SystemUser.objects.all_group_by_org()
for org, system_user in queryset_map.items():
task_name = gettext_noop("Test system user connectivity period: ") + str(system_user)
with tmp_to_org(org):
assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name)

View File

@ -18,24 +18,15 @@ router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history') 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'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')
router.register(r'gateways', api.GatewayViewSet, 'gateway') router.register(r'gateways', api.GatewayViewSet, 'gateway')
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')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
urlpatterns = [ urlpatterns = [
# path('assets/<uuid:pk>/gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), # path('assets/<uuid:pk>/gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'),
@ -46,15 +37,6 @@ 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>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
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>/temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'),
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('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'),
path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'), path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'),
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
@ -69,14 +51,7 @@ urlpatterns = [
path('nodes/<uuid:pk>/tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'), path('nodes/<uuid:pk>/tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'),
path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
path('cmd-filters/command-confirm/', api.CommandConfirmAPI.as_view(), name='command-confirm'),
] ]
old_version_urlpatterns = [ urlpatterns += router.urls
re_path('(?P<resource>admin-user|system-user|domain|gateway|cmd-filter|asset-user)/.*', capi.redirect_plural_name_api)
]
urlpatterns += router.urls + cmd_filter_router.urls + old_version_urlpatterns

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

@ -20,7 +20,6 @@ from common.decorator import on_transaction_commit
from common.signals import django_ready from common.signals import django_ready
from common.utils import get_logger from common.utils import get_logger
from common.utils.connection import RedisPubSub from common.utils.connection import RedisPubSub
from assets.models import CommandFilterRule
from users.signals import post_user_leave_org from users.signals import post_user_leave_org
@ -141,7 +140,7 @@ def _clear_users_from_org(org, users):
for m in models: for m in models:
_remove_users(m, users, org) _remove_users(m, users, org)
_remove_users(CommandFilterRule, users, org, user_field_name='reviewers') # _remove_users(CommandFilterRule, users, org, user_field_name='reviewers')
@receiver(post_save, sender=User) @receiver(post_save, sender=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

@ -7,7 +7,7 @@ from django.db import models
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from common.utils import lazyproperty from common.utils import lazyproperty
from common.db.models import BaseCreateUpdateModel from common.db.models import BaseCreateUpdateModel
from assets.models import Asset, SystemUser, Node, FamilyMixin from assets.models import Asset, Node, FamilyMixin
from .base import BasePermission from .base import BasePermission
@ -23,14 +23,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):
@ -48,16 +47,11 @@ 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(
models.Prefetch('nodes', queryset=Node.objects.all().only('key')), models.Prefetch('nodes', queryset=Node.objects.all().only('key')),
models.Prefetch('assets', queryset=Asset.objects.all().only('id')), models.Prefetch('assets', queryset=Asset.objects.all().only('id')),
models.Prefetch('system_users', queryset=SystemUser.objects.all().only('id'))
).order_by() ).order_by()
def get_all_assets(self): def get_all_assets(self):
@ -81,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)