mirror of https://github.com/jumpserver/jumpserver
perf: v3
commit
d43acd8612
|
@ -1,14 +1,10 @@
|
|||
from .mixin import *
|
||||
from .platform import *
|
||||
from .admin_user import *
|
||||
from .asset import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .system_user_relation import *
|
||||
from .accounts import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .account_backup import *
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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()
|
|
@ -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
|
|
@ -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):
|
||||
|
||||
initial = True
|
||||
|
@ -163,6 +155,5 @@ class Migration(migrations.Migration):
|
|||
unique_together=set([('ip', 'port')]),
|
||||
),
|
||||
|
||||
migrations.RunPython(add_default_cluster),
|
||||
migrations.RunPython(add_default_group),
|
||||
]
|
||||
|
|
|
@ -14,9 +14,4 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Generated by Django 3.2.12 on 2022-07-11 08:59
|
||||
|
||||
import assets.models.base
|
||||
import assets.models.user
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
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')),
|
||||
('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')),
|
||||
('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_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
|
@ -75,9 +74,9 @@ class Migration(migrations.Migration):
|
|||
],
|
||||
options={
|
||||
'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')},
|
||||
},
|
||||
bases=(models.Model, assets.models.base.AuthMixin, assets.models.user.ProtocolMixin),
|
||||
bases=(models.Model, assets.models.base.AuthMixin, assets.models.protocol.ProtocolMixin),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -1,17 +1,17 @@
|
|||
from .base import *
|
||||
from .platform import *
|
||||
from ._user import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .user import *
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
from .node import *
|
||||
from .cmd_filter import *
|
||||
from .authbook import *
|
||||
from .utils import *
|
||||
from .authbook import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .backup import *
|
||||
from .account import *
|
||||
from .backup import *
|
||||
# 废弃以下
|
||||
from ._authbook import *
|
||||
from .protocol import *
|
||||
from .cmd_filter import *
|
||||
|
|
|
@ -125,9 +125,8 @@ class AuthBook(BaseUser, AbsConnectivity):
|
|||
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, is_history_model=False):
|
||||
model = cls.history.model if is_history_model else cls
|
||||
queryset = model.objects.all() \
|
||||
def get_queryset(cls):
|
||||
queryset = cls.objects.all() \
|
||||
.annotate(ip=F('asset__ip')) \
|
||||
.annotate(hostname=F('asset__hostname')) \
|
||||
.annotate(platform=F('asset__platform__name')) \
|
||||
|
@ -136,5 +135,3 @@ class AuthBook(BaseUser, AbsConnectivity):
|
|||
|
||||
def __str__(self):
|
||||
return self.smart_name
|
||||
|
||||
|
|
@ -11,68 +11,18 @@ from django.core.validators import MinValueValidator, MaxValueValidator
|
|||
from assets.const import Protocol
|
||||
from common.utils import signer
|
||||
from .base import BaseUser
|
||||
from .asset import Asset
|
||||
from .protocol import ProtocolMixin
|
||||
|
||||
|
||||
__all__ = ['AdminUser', 'SystemUser', 'ProtocolMixin']
|
||||
__all__ = ['SystemUser']
|
||||
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):
|
||||
LOGIN_AUTO = 'auto'
|
||||
LOGIN_MANUAL = 'manual'
|
||||
LOGIN_MODE_CHOICES = (
|
||||
(LOGIN_AUTO, _('使用账号')),
|
||||
(LOGIN_AUTO, _('Automatic managed')),
|
||||
(LOGIN_MANUAL, _('Manually input'))
|
||||
)
|
||||
|
||||
|
@ -84,34 +34,24 @@ class SystemUser(ProtocolMixin, BaseUser):
|
|||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
assets = models.ManyToManyField(
|
||||
'assets.Asset', blank=True, verbose_name=_("Assets"),
|
||||
through='assets.AuthBook', through_fields=['systemuser', 'asset'],
|
||||
related_name='system_users'
|
||||
)
|
||||
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
|
||||
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
|
||||
priority = models.IntegerField(
|
||||
default=81, verbose_name=_("Priority"),
|
||||
help_text=_("1-100, the lower the value will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)]
|
||||
)
|
||||
protocol = models.CharField(max_length=16, choices=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'))
|
||||
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'))
|
||||
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
||||
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"))
|
||||
token = models.TextField(default='', verbose_name=_('Token'))
|
||||
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
|
||||
system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
|
||||
ad_domain = models.CharField(default='', max_length=256)
|
||||
|
||||
# linux su 命令 (switch user)
|
||||
# Todo: 修改为 username, 不必系统用户了
|
||||
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"))
|
||||
|
||||
|
@ -130,7 +70,7 @@ class SystemUser(ProtocolMixin, BaseUser):
|
|||
return self.get_login_mode_display()
|
||||
|
||||
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
|
||||
else:
|
||||
return False
|
||||
|
@ -165,7 +105,7 @@ class SystemUser(ProtocolMixin, BaseUser):
|
|||
return True, None
|
||||
|
||||
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)
|
||||
asset_ids = set(self.assets.all().values_list('id', flat=True))
|
||||
nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)
|
|
@ -2,7 +2,7 @@ from django.db import models
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
from .user import ProtocolMixin
|
||||
from .protocol import ProtocolMixin
|
||||
from .base import BaseUser, AbsConnectivity
|
||||
|
||||
|
||||
|
@ -14,8 +14,10 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin):
|
|||
common = 'common', _('Common user')
|
||||
admin = 'admin', _('Admin user')
|
||||
|
||||
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices,
|
||||
default='ssh', verbose_name=_('Protocol'))
|
||||
protocol = models.CharField(
|
||||
max_length=16, choices=ProtocolMixin.Protocol.choices,
|
||||
default='ssh', verbose_name=_('Protocol')
|
||||
)
|
||||
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'))
|
||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||
|
@ -25,10 +27,10 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin):
|
|||
verbose_name = _('Account')
|
||||
unique_together = [('username', 'asset')]
|
||||
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')),
|
||||
('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')),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -9,7 +9,6 @@ from collections import OrderedDict
|
|||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from common.db.fields import JsonDictTextField
|
||||
from common.utils import lazyproperty
|
||||
|
@ -22,16 +21,6 @@ __all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_
|
|||
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():
|
||||
try:
|
||||
from assets.models import Node
|
||||
|
|
|
@ -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")
|
|
@ -9,7 +9,6 @@ from django.core.validators import MinValueValidator, MaxValueValidator
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models import User, UserGroup
|
||||
from applications.models import Application
|
||||
from ..models import SystemUser, Asset
|
||||
|
||||
from common.utils import lazyproperty, get_logger, get_object_or_none
|
||||
|
@ -125,6 +124,9 @@ class CommandFilterRule(OrgModelMixin):
|
|||
regex.append(cmd)
|
||||
continue
|
||||
|
||||
if not cmd:
|
||||
continue
|
||||
|
||||
# 如果是单个字符
|
||||
if cmd[-1].isalpha():
|
||||
regex.append(r'\b{0}\b'.format(cmd))
|
||||
|
@ -187,6 +189,7 @@ class CommandFilterRule(OrgModelMixin):
|
|||
@classmethod
|
||||
def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None,
|
||||
asset_id=None, application_id=None, org_id=None):
|
||||
from applications.models import Application
|
||||
user_groups = []
|
||||
user = get_object_or_none(User, pk=user_id)
|
||||
if user:
|
||||
|
|
|
@ -7,3 +7,67 @@ from common.db.models import JMSBaseModel
|
|||
class Protocol(JMSBaseModel):
|
||||
name = models.CharField(max_length=32, verbose_name=_("Name"))
|
||||
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
|
||||
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
#
|
||||
|
||||
from .asset import *
|
||||
from .admin_user import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .account import *
|
||||
|
|
|
@ -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'
|
|
@ -94,8 +94,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin):
|
|||
'public_ip', 'number', 'comment',
|
||||
]
|
||||
fields_fk = [
|
||||
'domain', 'domain_display', 'platform', 'platform_display',
|
||||
'admin_user', 'admin_user_display'
|
||||
'domain', 'domain_display', 'platform',
|
||||
]
|
||||
fields_m2m = [
|
||||
'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts'
|
||||
|
@ -137,7 +136,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin):
|
|||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" 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')
|
||||
return queryset
|
||||
|
||||
|
@ -173,7 +172,6 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin):
|
|||
def add_accounts(instance, accounts_data):
|
||||
for data in accounts_data:
|
||||
data['asset'] = instance.id
|
||||
print("Data: ", accounts_data)
|
||||
serializer = AccountSerializer(data=accounts_data, many=True)
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
|
|
@ -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
|
|
@ -1,98 +1,37 @@
|
|||
from rest_framework import serializers
|
||||
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 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__ = [
|
||||
'SystemUserSerializer', 'MiniSystemUserSerializer',
|
||||
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer',
|
||||
'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer',
|
||||
'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer',
|
||||
'SystemUserTempAuthSerializer', 'RelationMixin',
|
||||
'SystemUserSimpleSerializer',
|
||||
]
|
||||
|
||||
|
||||
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:
|
||||
model = SystemUser
|
||||
fields_mini = ['id', 'name', 'username']
|
||||
fields_write_only = ['password', 'public_key', 'private_key', 'passphrase']
|
||||
fields_small = fields_mini + fields_write_only + [
|
||||
'token', 'ssh_key_fingerprint',
|
||||
'type', 'type_display', 'protocol', 'is_asset_protocol',
|
||||
'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_mini = ['id', 'name', 'username', 'protocol']
|
||||
fields_small = fields_mini + [
|
||||
'login_mode', 'su_enabled', 'su_from',
|
||||
'date_created', 'date_updated', 'comment',
|
||||
'created_by',
|
||||
]
|
||||
fields_m2m = ['cmd_filters', 'assets_amount', 'applications_amount', 'nodes']
|
||||
fields = fields_small + fields_m2m
|
||||
fields = fields_small
|
||||
extra_kwargs = {
|
||||
'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')},
|
||||
'created_by': {'read_only': True},
|
||||
'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')}
|
||||
}
|
||||
|
||||
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):
|
||||
if not username_same_with_user:
|
||||
return username_same_with_user
|
||||
|
@ -133,12 +72,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
raise serializers.ValidationError(msg)
|
||||
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
|
||||
def validate_sftp_root(value):
|
||||
if value in ['home', 'tmp']:
|
||||
|
@ -148,17 +81,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
raise serializers.ValidationError(error)
|
||||
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):
|
||||
# self: su enabled
|
||||
su_enabled = self.get_initial_value('su_enabled', default=False)
|
||||
|
@ -182,70 +104,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
raise serializers.ValidationError(error)
|
||||
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 Meta:
|
||||
|
@ -253,28 +111,6 @@ class MiniSystemUserSerializer(serializers.ModelSerializer):
|
|||
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):
|
||||
"""
|
||||
系统用户最基本信息的数据结构
|
||||
|
@ -285,70 +121,6 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
|||
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):
|
||||
instance_id = serializers.CharField()
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from .asset import *
|
||||
from .system_user import *
|
||||
from .authbook import *
|
||||
from .account import *
|
||||
from .node_assets_amount import *
|
||||
from .node_assets_mapping import *
|
||||
|
|
|
@ -8,11 +8,10 @@ from django.dispatch import receiver
|
|||
from common.const.signals import POST_ADD, POST_REMOVE, PRE_REMOVE
|
||||
from common.utils import get_logger
|
||||
from common.decorator import on_transaction_commit
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
from assets.models import Asset, Node
|
||||
from assets.tasks import (
|
||||
update_assets_hardware_info_util,
|
||||
test_asset_connectivity_util,
|
||||
push_system_user_to_assets,
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
# 查询所有祖先节点关联的系统用户,都是要跟资产建立关系的
|
||||
system_user_ids = SystemUser.objects.filter(
|
||||
nodes__key__in=nodes_ancestors_keys
|
||||
).distinct().values_list('id', flat=True)
|
||||
# system_user_ids = SystemUser.objects.filter(
|
||||
# nodes__key__in=nodes_ancestors_keys
|
||||
# ).distinct().values_list('id', flat=True)
|
||||
|
||||
# 查询所有已存在的关系
|
||||
m2m_model = SystemUser.assets.through
|
||||
exist = set(m2m_model.objects.filter(
|
||||
systemuser_id__in=system_user_ids, asset_id__in=asset_ids
|
||||
).values_list('systemuser_id', 'asset_id'))
|
||||
# m2m_model = SystemUser.assets.through
|
||||
# exist = set(m2m_model.objects.filter(
|
||||
# systemuser_id__in=system_user_ids, asset_id__in=asset_ids
|
||||
# ).values_list('systemuser_id', 'asset_id'))
|
||||
# TODO 优化
|
||||
# to_create = []
|
||||
# for system_user_id in system_user_ids:
|
||||
|
|
|
@ -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)
|
|
@ -6,7 +6,5 @@ from .asset_connectivity import *
|
|||
from .account_connectivity import *
|
||||
from .gather_asset_users import *
|
||||
from .gather_asset_hardware_info import *
|
||||
from .push_system_user import *
|
||||
from .system_user_connectivity import *
|
||||
from .nodes_amount import *
|
||||
from .backup import *
|
||||
|
|
|
@ -1,38 +1,2 @@
|
|||
# -*- 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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret')
|
||||
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'nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||
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'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-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 = [
|
||||
# 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/<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('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('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 = [
|
||||
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
|
||||
urlpatterns += router.urls
|
||||
|
||||
|
|
|
@ -69,11 +69,6 @@ M2M_NEED_RECORD = {
|
|||
_('{User} JOINED {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: (
|
||||
_('Node and Asset'),
|
||||
_('{Node} ADD {Asset}'),
|
||||
|
@ -99,11 +94,6 @@ M2M_NEED_RECORD = {
|
|||
_('{AssetPermission} ADD {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: (
|
||||
_('User application permissions'),
|
||||
_('{ApplicationPermission} ADD {User}'),
|
||||
|
|
|
@ -20,7 +20,6 @@ from common.decorator import on_transaction_commit
|
|||
from common.signals import django_ready
|
||||
from common.utils import get_logger
|
||||
from common.utils.connection import RedisPubSub
|
||||
from assets.models import CommandFilterRule
|
||||
from users.signals import post_user_leave_org
|
||||
|
||||
|
||||
|
@ -141,7 +140,7 @@ def _clear_users_from_org(org, users):
|
|||
for m in models:
|
||||
_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)
|
||||
|
|
|
@ -62,20 +62,19 @@ class ValidateUserApplicationPermissionApi(APIView):
|
|||
def get(self, request, *args, **kwargs):
|
||||
user_id = request.query_params.get('user_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 = {
|
||||
'has_permission': False,
|
||||
'expire_at': int(time.time()),
|
||||
'actions': []
|
||||
}
|
||||
if not all((user_id, application_id, system_user_id)):
|
||||
if not all((user_id, application_id, account)):
|
||||
return Response(data)
|
||||
|
||||
user = User.objects.get(id=user_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, system_user)
|
||||
has_perm, actions, expire_at = validate_permission(user, application, account)
|
||||
status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN
|
||||
data = {
|
||||
'has_permission': has_perm,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import generics
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.functions import Concat
|
||||
from django.db.models import F
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from orgs.mixins.api import OrgRelationMixin
|
||||
|
@ -15,8 +14,7 @@ from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils
|
|||
__all__ = [
|
||||
'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet',
|
||||
'AssetPermissionAssetRelationViewSet', 'AssetPermissionNodeRelationViewSet',
|
||||
'AssetPermissionSystemUserRelationViewSet', 'AssetPermissionAllAssetListApi',
|
||||
'AssetPermissionAllUserListApi',
|
||||
'AssetPermissionAllAssetListApi', 'AssetPermissionAllUserListApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -117,21 +115,3 @@ class AssetPermissionNodeRelationViewSet(RelationMixin):
|
|||
.annotate(node_key=F('node__key'))
|
||||
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
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -7,7 +7,7 @@ from django.db import models
|
|||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.utils import lazyproperty
|
||||
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
|
||||
|
||||
|
@ -23,14 +23,13 @@ logger = logging.getLogger(__name__)
|
|||
class AssetPermission(BasePermission):
|
||||
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"))
|
||||
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:
|
||||
unique_together = [('org_id', 'name')]
|
||||
verbose_name = _("Asset permission")
|
||||
ordering = ('name',)
|
||||
permissions = [
|
||||
]
|
||||
permissions = []
|
||||
|
||||
@lazyproperty
|
||||
def users_amount(self):
|
||||
|
@ -48,16 +47,11 @@ class AssetPermission(BasePermission):
|
|||
def nodes_amount(self):
|
||||
return self.nodes.count()
|
||||
|
||||
@lazyproperty
|
||||
def system_users_amount(self):
|
||||
return self.system_users.count()
|
||||
|
||||
@classmethod
|
||||
def get_queryset_with_prefetch(cls):
|
||||
return cls.objects.all().valid().prefetch_related(
|
||||
models.Prefetch('nodes', queryset=Node.objects.all().only('key')),
|
||||
models.Prefetch('assets', queryset=Asset.objects.all().only('id')),
|
||||
models.Prefetch('system_users', queryset=SystemUser.objects.all().only('id'))
|
||||
).order_by()
|
||||
|
||||
def get_all_assets(self):
|
||||
|
@ -81,10 +75,6 @@ class AssetPermission(BasePermission):
|
|||
names = [asset.hostname for asset in self.assets.all()]
|
||||
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):
|
||||
names = [node.full_value for node in self.nodes.all()]
|
||||
return names
|
||||
|
|
|
@ -22,7 +22,6 @@ class AssetPermissionSerializer(BasePermissionSerializer):
|
|||
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)
|
||||
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:
|
||||
model = AssetPermission
|
||||
|
@ -34,9 +33,9 @@ class AssetPermissionSerializer(BasePermissionSerializer):
|
|||
]
|
||||
fields_m2m = [
|
||||
'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',
|
||||
'nodes_amount', 'system_users_amount',
|
||||
'nodes_amount',
|
||||
]
|
||||
fields = fields_small + fields_m2m
|
||||
read_only_fields = ['created_by', 'date_created', 'from_ticket']
|
||||
|
@ -48,30 +47,16 @@ class AssetPermissionSerializer(BasePermissionSerializer):
|
|||
'user_groups_amount': {'label': _('User groups amount')},
|
||||
'assets_amount': {'label': _('Assets amount')},
|
||||
'nodes_amount': {'label': _('Nodes amount')},
|
||||
'system_users_amount': {'label': _('System users amount')},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related(
|
||||
'users', 'user_groups', 'assets', 'nodes', 'system_users'
|
||||
'users', 'user_groups', 'assets', 'nodes',
|
||||
)
|
||||
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
|
||||
def perform_display_create(instance, **kwargs):
|
||||
# 用户
|
||||
|
|
|
@ -12,7 +12,6 @@ __all__ = [
|
|||
'AssetPermissionUserGroupRelationSerializer',
|
||||
"AssetPermissionAssetRelationSerializer",
|
||||
'AssetPermissionNodeRelationSerializer',
|
||||
'AssetPermissionSystemUserRelationSerializer',
|
||||
'AssetPermissionAllAssetSerializer',
|
||||
'AssetPermissionAllUserSerializer',
|
||||
]
|
||||
|
@ -99,13 +98,3 @@ class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSeri
|
|||
fields = [
|
||||
'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'
|
||||
]
|
||||
|
|
|
@ -1,138 +1,10 @@
|
|||
# -*- 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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-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 = [
|
||||
# 统一说明:
|
||||
|
|
|
@ -11,11 +11,7 @@ from perms.utils.asset.user_permission import get_user_all_asset_perm_ids
|
|||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def validate_permission(user, asset, system_user, action='connect'):
|
||||
|
||||
if not system_user.protocol in asset.protocols_as_dict.keys():
|
||||
return False, time.time()
|
||||
|
||||
def validate_permission(user, asset, account, action='connect'):
|
||||
asset_perm_ids = get_user_all_asset_perm_ids(user)
|
||||
|
||||
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:
|
||||
ancestor_keys = node.get_ancestor_keys(with_self=True)
|
||||
node_keys.update(ancestor_keys)
|
||||
node_ids = Node.objects.filter(key__in=node_keys).values_list('id', flat=True)
|
||||
|
||||
node_ids = set(node_ids)
|
||||
node_ids = set(Node.objects.filter(key__in=node_keys).values_list('id', flat=True))
|
||||
|
||||
asset_perm_ids_from_node = AssetPermission.nodes.through.objects.filter(
|
||||
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 = AssetPermission.system_users.through.objects.filter(
|
||||
assetpermission_id__in=asset_perm_ids,
|
||||
systemuser_id=system_user.id
|
||||
).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')
|
||||
asset_perms = AssetPermission.objects\
|
||||
.filter(id__in=asset_perm_ids, accounts__contains=account)\
|
||||
.order_by('-date_expired')
|
||||
|
||||
if asset_perms:
|
||||
actions = set()
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -47,12 +47,6 @@ class AssetPermissionGenerator(FakeDataGenerator):
|
|||
relation_name = 'node_id'
|
||||
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):
|
||||
relations = []
|
||||
|
||||
|
@ -79,4 +73,3 @@ class AssetPermissionGenerator(FakeDataGenerator):
|
|||
self.set_user_groups(created)
|
||||
self.set_assets(created)
|
||||
self.set_nodes(created)
|
||||
self.set_system_users(created)
|
||||
|
|
Loading…
Reference in New Issue