mirror of https://github.com/jumpserver/jumpserver
perf: ad as asset
parent
5e25361ee8
commit
3f452daee8
|
@ -131,9 +131,26 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
|
|||
|
||||
@lazyproperty
|
||||
def alias(self):
|
||||
"""
|
||||
别称,因为有虚拟账号,@INPUT @MANUAL @USER, 否则为 id
|
||||
"""
|
||||
if self.username.startswith('@'):
|
||||
return self.username
|
||||
return self.name
|
||||
return self.id
|
||||
|
||||
@lazyproperty
|
||||
def ad_domain(self):
|
||||
if self.username.startswith('@'):
|
||||
return None
|
||||
if self.platform.category == 'ad':
|
||||
return self.asset.ad.domain_name
|
||||
return None
|
||||
|
||||
@lazyproperty
|
||||
def full_username(self):
|
||||
if self.ad_domain:
|
||||
return '{}@{}'.format(self.username, self.ad_domain)
|
||||
return self.username
|
||||
|
||||
@lazyproperty
|
||||
def has_secret(self):
|
||||
|
|
|
@ -241,7 +241,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
|||
'date_change_secret', 'change_secret_status'
|
||||
]
|
||||
fields = BaseAccountSerializer.Meta.fields + [
|
||||
'su_from', 'asset', 'version',
|
||||
'su_from', 'asset', 'version', "ad_domain",
|
||||
'source', 'source_id', 'secret_reset',
|
||||
] + AccountCreateUpdateSerializerMixin.Meta.fields + automation_fields
|
||||
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + automation_fields
|
||||
|
|
|
@ -7,3 +7,4 @@ from .gpt import *
|
|||
from .host import *
|
||||
from .permission import *
|
||||
from .web import *
|
||||
from .ad import *
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
from assets.models import AD, Asset
|
||||
from assets.serializers import ADSerializer
|
||||
|
||||
from .asset import AssetViewSet
|
||||
|
||||
__all__ = ['ADViewSet']
|
||||
|
||||
|
||||
class ADViewSet(AssetViewSet):
|
||||
model = AD
|
||||
perm_model = Asset
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
serializer_classes['default'] = ADSerializer
|
||||
return serializer_classes
|
|
@ -11,6 +11,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.status import HTTP_200_OK
|
||||
|
||||
from accounts.serializers import AccountSerializer
|
||||
from accounts.tasks import push_accounts_to_assets_task, verify_accounts_connectivity_task
|
||||
from assets import serializers
|
||||
from assets.exceptions import NotSupportedTemporarilyError
|
||||
|
@ -109,18 +110,19 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
|
|||
("platform", serializers.PlatformSerializer),
|
||||
("suggestion", serializers.MiniAssetSerializer),
|
||||
("gateways", serializers.GatewaySerializer),
|
||||
("accounts", AccountSerializer),
|
||||
)
|
||||
rbac_perms = (
|
||||
("match", "assets.match_asset"),
|
||||
("platform", "assets.view_platform"),
|
||||
("gateways", "assets.view_gateway"),
|
||||
("accounts", "assets.view_account"),
|
||||
("spec_info", "assets.view_asset"),
|
||||
("gathered_info", "assets.view_asset"),
|
||||
("sync_platform_protocols", "assets.change_asset"),
|
||||
)
|
||||
extra_filter_backends = [
|
||||
IpInFilterBackend,
|
||||
NodeFilterBackend, AttrRulesFilterBackend
|
||||
IpInFilterBackend, NodeFilterBackend, AttrRulesFilterBackend
|
||||
]
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
|
@ -156,6 +158,12 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
|
|||
gateways = asset.domain.gateways
|
||||
return self.get_paginated_response_from_queryset(gateways)
|
||||
|
||||
@action(methods=["GET"], detail=True, url_path="accounts")
|
||||
def accounts(self, *args, **kwargs):
|
||||
asset = super().get_object()
|
||||
queryset = asset.all_accounts.all()
|
||||
return self.get_paginated_response_from_queryset(queryset)
|
||||
|
||||
@action(methods=['post'], detail=False, url_path='sync-platform-protocols')
|
||||
def sync_platform_protocols(self, request, *args, **kwargs):
|
||||
platform_id = request.data.get('platform_id')
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .base import BaseType
|
||||
|
||||
|
||||
class ADTypes(BaseType):
|
||||
AD = 'ad', _('Active Directory')
|
||||
WINDOWS_AD = 'windows_ad', _('Windows Active Directory')
|
||||
LDAP = 'ldap', _('LDAP')
|
||||
AZURE_AD = 'azure_ad', _('Azure Active Directory')
|
||||
|
||||
@classmethod
|
||||
def _get_base_constrains(cls) -> dict:
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': True,
|
||||
'ad_enabled': False,
|
||||
'su_enabled': True,
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _get_automation_constrains(cls) -> dict:
|
||||
constrains = {
|
||||
'*': {
|
||||
'ansible_enabled': True,
|
||||
'ping_enabled': True,
|
||||
'gather_facts_enabled': False,
|
||||
'verify_account_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'push_account_enabled': True,
|
||||
'gather_accounts_enabled': True,
|
||||
}
|
||||
}
|
||||
return constrains
|
||||
|
||||
@classmethod
|
||||
def _get_protocol_constrains(cls) -> dict:
|
||||
return {
|
||||
cls.WINDOWS_AD: {
|
||||
'choices': ['rdp', 'ssh', 'vnc', 'winrm']
|
||||
},
|
||||
cls.LDAP: {
|
||||
'choices': ['ssh', 'ldap']
|
||||
},
|
||||
cls.AZURE_AD: {
|
||||
'choices': ['ldap']
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def internal_platforms(cls):
|
||||
return {
|
||||
cls.AD: [
|
||||
{'name': 'Active Directory'}
|
||||
],
|
||||
cls.WINDOWS_AD: [
|
||||
{'name': 'Windows Active Directory'}
|
||||
],
|
||||
cls.LDAP: [
|
||||
{'name': 'LDAP'}
|
||||
],
|
||||
cls.AZURE_AD: [
|
||||
{'name': 'Azure Active Directory'}
|
||||
],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_community_types(cls):
|
||||
return [
|
||||
cls.LDAP,
|
||||
]
|
|
@ -12,6 +12,7 @@ class Category(ChoicesMixin, models.TextChoices):
|
|||
DATABASE = 'database', _("Database")
|
||||
CLOUD = 'cloud', _("Cloud service")
|
||||
WEB = 'web', _("Web")
|
||||
AD = 'ad', _("Active Directory")
|
||||
CUSTOM = 'custom', _("Custom type")
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -20,6 +20,7 @@ class DeviceTypes(BaseType):
|
|||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': True,
|
||||
'ad_enabled': False,
|
||||
'su_enabled': True,
|
||||
'su_methods': ['enable', 'super', 'super_level']
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class HostTypes(BaseType):
|
|||
'charset': 'utf-8', # default
|
||||
'domain_enabled': True,
|
||||
'su_enabled': True,
|
||||
'ad_enabled': True,
|
||||
'su_methods': ['sudo', 'su', 'only_sudo', 'only_su'],
|
||||
},
|
||||
cls.WINDOWS: {
|
||||
|
@ -56,7 +57,6 @@ class HostTypes(BaseType):
|
|||
'change_secret_enabled': True,
|
||||
'push_account_enabled': True,
|
||||
'remove_account_enabled': True,
|
||||
|
||||
},
|
||||
cls.WINDOWS: {
|
||||
'ansible_config': {
|
||||
|
@ -69,7 +69,6 @@ class HostTypes(BaseType):
|
|||
'ping_enabled': False,
|
||||
'gather_facts_enabled': False,
|
||||
'gather_accounts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'push_account_enabled': False
|
||||
},
|
||||
|
@ -126,5 +125,5 @@ class HostTypes(BaseType):
|
|||
@classmethod
|
||||
def get_community_types(cls) -> list:
|
||||
return [
|
||||
cls.LINUX, cls.UNIX, cls.WINDOWS, cls.OTHER_HOST
|
||||
cls.LINUX, cls.WINDOWS, cls.UNIX, cls.OTHER_HOST
|
||||
]
|
||||
|
|
|
@ -16,13 +16,15 @@ from .device import DeviceTypes
|
|||
from .gpt import GPTTypes
|
||||
from .host import HostTypes
|
||||
from .web import WebTypes
|
||||
from .ad import ADTypes
|
||||
|
||||
|
||||
class AllTypes(ChoicesMixin):
|
||||
choices: list
|
||||
includes = [
|
||||
HostTypes, DeviceTypes, DatabaseTypes,
|
||||
CloudTypes, WebTypes, CustomTypes, GPTTypes
|
||||
CloudTypes, WebTypes, CustomTypes,
|
||||
ADTypes, GPTTypes
|
||||
]
|
||||
_category_constrains = {}
|
||||
_automation_methods = None
|
||||
|
@ -173,6 +175,7 @@ class AllTypes(ChoicesMixin):
|
|||
(Category.DATABASE, DatabaseTypes),
|
||||
(Category.WEB, WebTypes),
|
||||
(Category.CLOUD, CloudTypes),
|
||||
(Category.AD, ADTypes),
|
||||
(Category.CUSTOM, CustomTypes)
|
||||
]
|
||||
return types
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
# Generated by Django 4.1.13 on 2025-03-31 02:49
|
||||
|
||||
import json
|
||||
|
||||
import django
|
||||
from django.db import migrations, models
|
||||
|
||||
from assets.const.types import AllTypes
|
||||
|
||||
|
||||
def add_ad_host_type(apps, schema_editor):
|
||||
data = """
|
||||
[
|
||||
{
|
||||
"created_by": "system",
|
||||
"updated_by": "system",
|
||||
"comment": "",
|
||||
"name": "Windows AD",
|
||||
"category": "ad",
|
||||
"type": "windows_ad",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
"automation": {
|
||||
"ansible_enabled": true,
|
||||
"ansible_config": {
|
||||
"ansible_shell_type": "cmd",
|
||||
"ansible_connection": "ssh"
|
||||
},
|
||||
"ping_enabled": true,
|
||||
"ping_method": "ping_by_rdp",
|
||||
"ping_params": {},
|
||||
"gather_facts_enabled": true,
|
||||
"gather_facts_method": "gather_facts_windows",
|
||||
"gather_facts_params": {},
|
||||
"change_secret_enabled": true,
|
||||
"change_secret_method": "change_secret_ad_windows",
|
||||
"change_secret_params": {},
|
||||
"push_account_enabled": true,
|
||||
"push_account_method": "push_account_ad_windows",
|
||||
"push_account_params": {},
|
||||
"verify_account_enabled": true,
|
||||
"verify_account_method": "verify_account_by_rdp",
|
||||
"verify_account_params": {},
|
||||
"gather_accounts_enabled": true,
|
||||
"gather_accounts_method": "gather_accounts_ad_windows",
|
||||
"gather_accounts_params": {},
|
||||
"remove_account_enabled": true,
|
||||
"remove_account_method": "remove_account_ad_windows",
|
||||
"remove_account_params": {}
|
||||
},
|
||||
"protocols": [
|
||||
{
|
||||
"name": "rdp",
|
||||
"port": 3389,
|
||||
"primary": true,
|
||||
"required": false,
|
||||
"default": false,
|
||||
"public": true,
|
||||
"setting": {
|
||||
"console": false,
|
||||
"security": "any"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssh",
|
||||
"port": 22,
|
||||
"primary": false,
|
||||
"required": false,
|
||||
"default": false,
|
||||
"public": true,
|
||||
"setting": {
|
||||
"sftp_enabled": true,
|
||||
"sftp_home": "/tmp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vnc",
|
||||
"port": 5900,
|
||||
"primary": false,
|
||||
"required": false,
|
||||
"default": false,
|
||||
"public": true,
|
||||
"setting": {}
|
||||
},
|
||||
{
|
||||
"name": "winrm",
|
||||
"port": 5985,
|
||||
"primary": false,
|
||||
"required": false,
|
||||
"default": false,
|
||||
"public": false,
|
||||
"setting": {
|
||||
"use_ssl": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"""
|
||||
platform_model = apps.get_model('assets', 'Platform')
|
||||
automation_cls = apps.get_model('assets', 'PlatformAutomation')
|
||||
platform_datas = json.loads(data)
|
||||
|
||||
for platform_data in platform_datas:
|
||||
AllTypes.create_or_update_by_platform_data(platform_data, platform_cls=platform_model,
|
||||
automation_cls=automation_cls)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("assets", "0015_automationexecution_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_ad_host_type),
|
||||
migrations.CreateModel(
|
||||
name="AD",
|
||||
fields=[
|
||||
(
|
||||
"asset_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="assets.asset",
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain_name",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
default="",
|
||||
max_length=128,
|
||||
verbose_name="Domain name",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Active Directory",
|
||||
},
|
||||
bases=("assets.asset",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="platform",
|
||||
name="ad",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="ad_platforms",
|
||||
to="assets.ad",
|
||||
verbose_name="Active Directory",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="platform",
|
||||
name="ad_enabled",
|
||||
field=models.BooleanField(default=False, verbose_name="AD enabled"),
|
||||
),
|
||||
]
|
|
@ -1,3 +1,4 @@
|
|||
from .ad import *
|
||||
from .cloud import *
|
||||
from .common import *
|
||||
from .custom import *
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .common import Asset
|
||||
|
||||
__all__ = ['AD']
|
||||
|
||||
|
||||
class AD(Asset):
|
||||
domain_name = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Domain name"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Active Directory")
|
|
@ -245,6 +245,20 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
|||
auto_config.update(model_to_dict(automation))
|
||||
return auto_config
|
||||
|
||||
@property
|
||||
def all_accounts(self):
|
||||
if not self.joined_ad_id:
|
||||
queryset = self.accounts.all()
|
||||
else:
|
||||
queryset = self.accounts.model.objects.filter(asset__in=[self.id, self.joined_ad_id])
|
||||
return queryset
|
||||
|
||||
@lazyproperty
|
||||
def all_valid_accounts(self):
|
||||
queryset = (self.all_accounts.filter(is_active=True)
|
||||
.prefetch_related('asset', 'asset__platform', 'asset__platform__ad'))
|
||||
return queryset
|
||||
|
||||
@lazyproperty
|
||||
def accounts_amount(self):
|
||||
return self.accounts.count()
|
||||
|
@ -259,6 +273,19 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
|||
protocol = self.protocols.all().filter(name=protocol).first()
|
||||
return protocol.port if protocol else 0
|
||||
|
||||
def is_ad(self):
|
||||
return self.category == const.Category.AD
|
||||
|
||||
@property
|
||||
def joined_ad_id(self):
|
||||
return self.platform.ad_id
|
||||
|
||||
def is_joined_ad(self):
|
||||
if self.joined_ad_id:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
warning = ''
|
||||
|
|
|
@ -102,6 +102,11 @@ class Platform(LabeledMixin, JMSBaseModel):
|
|||
max_length=8, verbose_name=_("Charset")
|
||||
)
|
||||
domain_enabled = models.BooleanField(default=True, verbose_name=_("Gateway enabled"))
|
||||
ad_enabled = models.BooleanField(default=False, verbose_name=_("AD enabled"))
|
||||
ad = models.ForeignKey(
|
||||
'assets.AD', on_delete=models.SET_NULL, null=True, blank=True,
|
||||
verbose_name=_("Active Directory"), related_name='ad_platforms'
|
||||
)
|
||||
# 账号有关的
|
||||
su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled"))
|
||||
su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Su method"))
|
||||
|
@ -115,6 +120,11 @@ class Platform(LabeledMixin, JMSBaseModel):
|
|||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.ad_enabled:
|
||||
self.ad = None
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
linux, created = cls.objects.get_or_create(
|
||||
|
|
|
@ -7,3 +7,4 @@ from .device import *
|
|||
from .gpt import *
|
||||
from .host import *
|
||||
from .web import *
|
||||
from .ad import *
|
|
@ -0,0 +1,23 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import AD
|
||||
from .common import AssetSerializer
|
||||
|
||||
__all__ = ['ADSerializer']
|
||||
|
||||
|
||||
class ADSerializer(AssetSerializer):
|
||||
class Meta(AssetSerializer.Meta):
|
||||
model = AD
|
||||
fields = AssetSerializer.Meta.fields + [
|
||||
'domain_name',
|
||||
]
|
||||
extra_kwargs = {
|
||||
**AssetSerializer.Meta.extra_kwargs,
|
||||
'domain_name': {
|
||||
'help_text': _(
|
||||
'The domain name of the Active Directory'
|
||||
),
|
||||
'label': _('Domain name')}
|
||||
}
|
|
@ -147,7 +147,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
|
|||
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=())
|
||||
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Accounts'))
|
||||
nodes_display = NodeDisplaySerializer(read_only=False, required=False, label=_("Node path"))
|
||||
platform = ObjectRelatedField(queryset=Platform.objects, required=True, label=_('Platform'), attrs=('id', 'name', 'type'))
|
||||
platform = ObjectRelatedField(queryset=Platform.objects, required=True, label=_('Platform'),
|
||||
attrs=('id', 'name', 'type', 'ad_id'))
|
||||
accounts_amount = serializers.IntegerField(read_only=True, label=_('Accounts amount'))
|
||||
_accounts = None
|
||||
|
||||
|
|
|
@ -4,12 +4,11 @@ from django.db.models import Count, Q
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models.gateway import Gateway
|
||||
from common.serializers import ResourceLabelsMixin
|
||||
from common.serializers.fields import ObjectRelatedField
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .gateway import GatewayWithAccountSecretSerializer
|
||||
from ..models import Domain
|
||||
from ..models import Domain, Gateway
|
||||
|
||||
__all__ = ['DomainSerializer', 'DomainWithGatewaySerializer', 'DomainListSerializer']
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ class PlatformSerializer(ResourceLabelsMixin, CommonSerializerMixin, WritableNes
|
|||
]
|
||||
fields_m2m = ['assets', 'assets_amount']
|
||||
fields = fields_small + fields_m2m + [
|
||||
"protocols", "domain_enabled", "su_enabled", "su_method",
|
||||
"protocols", "domain_enabled", "su_enabled", "su_method", "ad_enabled", "ad",
|
||||
"automation", "comment", "custom_fields", "labels"
|
||||
] + read_only_fields
|
||||
extra_kwargs = {
|
||||
|
|
|
@ -16,6 +16,7 @@ router.register(r'databases', api.DatabaseViewSet, 'database')
|
|||
router.register(r'webs', api.WebViewSet, 'web')
|
||||
router.register(r'clouds', api.CloudViewSet, 'cloud')
|
||||
router.register(r'gpts', api.GPTViewSet, 'gpt')
|
||||
router.register(r'directories', api.ADViewSet, 'ad')
|
||||
router.register(r'customs', api.CustomViewSet, 'custom')
|
||||
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||
|
|
|
@ -95,7 +95,9 @@ class QuerySetMixin:
|
|||
get_queryset: Callable
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
return super().get_queryset()
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if not hasattr(self, 'action'):
|
||||
return queryset
|
||||
if self.action == 'metadata':
|
||||
|
@ -105,8 +107,9 @@ class QuerySetMixin:
|
|||
|
||||
def setup_eager_loading(self, queryset, is_paginated=False):
|
||||
is_export_request = self.request.query_params.get('format') in ['csv', 'xlsx']
|
||||
no_request_page = self.request.query_params.get('limit') is None
|
||||
# 不分页不走一般这个,是因为会消耗多余的 sql 查询, 不如分页的时候查询一次
|
||||
if not is_export_request and not is_paginated:
|
||||
if not is_export_request and not is_paginated and not no_request_page:
|
||||
return queryset
|
||||
|
||||
serializer_class = self.get_serializer_class()
|
||||
|
@ -129,7 +132,7 @@ class QuerySetMixin:
|
|||
serializer_class = self.get_serializer_class()
|
||||
if page and serializer_class:
|
||||
ids = [str(obj.id) for obj in page]
|
||||
page = self.get_queryset().filter(id__in=ids)
|
||||
page = model.objects.filter(id__in=ids)
|
||||
page = self.setup_eager_loading(page, is_paginated=True)
|
||||
page_mapper = {str(obj.id): obj for obj in page}
|
||||
page = [page_mapper.get(_id) for _id in ids if _id in page_mapper]
|
||||
|
|
|
@ -59,6 +59,7 @@ class NodePermedSerializer(serializers.ModelSerializer):
|
|||
|
||||
class AccountsPermedSerializer(serializers.ModelSerializer):
|
||||
actions = ActionChoicesField(read_only=True)
|
||||
username = serializers.CharField(source='full_username', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
|
|
|
@ -88,7 +88,7 @@ class PermAssetDetailUtil:
|
|||
if not all_action_bit:
|
||||
return alias_action_bit_mapper, alias_date_expired_mapper
|
||||
|
||||
asset_account_usernames = asset.accounts.all().active().values_list('username', flat=True)
|
||||
asset_account_usernames = asset.all_valid_accounts.values_list('username', flat=True)
|
||||
for username in asset_account_usernames:
|
||||
alias_action_bit_mapper[username] |= all_action_bit
|
||||
alias_date_expired_mapper[username].extend(
|
||||
|
@ -100,7 +100,7 @@ class PermAssetDetailUtil:
|
|||
def map_alias_to_accounts(cls, alias_action_bit_mapper, alias_date_expired_mapper, asset, user):
|
||||
username_accounts_mapper = defaultdict(list)
|
||||
cleaned_accounts_expired = defaultdict(list)
|
||||
asset_accounts = asset.accounts.all().active()
|
||||
asset_accounts = asset.all_valid_accounts
|
||||
|
||||
# 用户名 -> 账号
|
||||
for account in asset_accounts:
|
||||
|
@ -135,11 +135,18 @@ class PermAssetDetailUtil:
|
|||
alias_action_bit_mapper, alias_date_expired_mapper, asset, user
|
||||
)
|
||||
accounts = []
|
||||
virtual_accounts = []
|
||||
for account, action_bit in cleaned_accounts_action_bit.items():
|
||||
account.actions = action_bit
|
||||
account.date_expired = max(cleaned_accounts_expired[account])
|
||||
accounts.append(account)
|
||||
return accounts
|
||||
|
||||
if account.username.startswith('@'):
|
||||
virtual_accounts.append(account)
|
||||
else:
|
||||
accounts.append(account)
|
||||
accounts.sort(key=lambda x: x.username)
|
||||
virtual_accounts.sort(key=lambda x: x.username)
|
||||
return accounts + virtual_accounts
|
||||
|
||||
def check_perm_protocols(self, protocols):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue