mirror of https://github.com/jumpserver/jumpserver
merge: with v3
commit
e91cbb9c97
|
@ -0,0 +1,32 @@
|
||||||
|
name: "Run Build Test"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- pr@*
|
||||||
|
- repr@*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
tags: jumpserver/core:test
|
||||||
|
file: Dockerfile
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- uses: LouisBrunner/checks-action@v1.5.0
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
name: Check Build
|
||||||
|
conclusion: ${{ job.status }}
|
|
@ -0,0 +1,3 @@
|
||||||
|
[settings]
|
||||||
|
line_length=120
|
||||||
|
known_first_party=common,users,assets,perms,authentication,jumpserver,notification,ops,orgs,rbac,settings,terminal,tickets
|
|
@ -99,7 +99,8 @@ VOLUME /opt/jumpserver/data
|
||||||
VOLUME /opt/jumpserver/logs
|
VOLUME /opt/jumpserver/logs
|
||||||
|
|
||||||
ENV LANG=zh_CN.UTF-8
|
ENV LANG=zh_CN.UTF-8
|
||||||
|
ENV ANSIBLE_LIBRARY=/opt/jumpserver/apps/ops/ansible/modules
|
||||||
|
|
||||||
EXPOSE 8070 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
|
@ -91,6 +91,6 @@ VOLUME /opt/jumpserver/logs
|
||||||
|
|
||||||
ENV LANG=zh_CN.UTF-8
|
ENV LANG=zh_CN.UTF-8
|
||||||
|
|
||||||
EXPOSE 8070 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from .command_acl import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .login_asset_check import *
|
from .login_asset_check import *
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from .. import models, serializers
|
||||||
|
|
||||||
|
__all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class CommandGroupViewSet(OrgBulkModelViewSet):
|
||||||
|
model = models.CommandGroup
|
||||||
|
filterset_fields = ('name',)
|
||||||
|
search_fields = filterset_fields
|
||||||
|
serializer_class = serializers.CommandGroupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFilterACLViewSet(OrgBulkModelViewSet):
|
||||||
|
model = models.CommandFilterACL
|
||||||
|
filterset_fields = ('name',)
|
||||||
|
search_fields = filterset_fields
|
||||||
|
serializer_class = serializers.CommandFilterACLSerializer
|
|
@ -1,10 +1,10 @@
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.utils import reverse, lazyproperty
|
from common.utils import reverse, lazyproperty
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
from ..models import LoginAssetACL
|
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
from ..models import LoginAssetACL
|
||||||
|
|
||||||
__all__ = ['LoginAssetCheckAPI']
|
__all__ = ['LoginAssetCheckAPI']
|
||||||
|
|
||||||
|
@ -20,34 +20,40 @@ class LoginAssetCheckAPI(CreateAPIView):
|
||||||
return LoginAssetACL.objects.all()
|
return LoginAssetACL.objects.all()
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
is_need_confirm, response_data = self.check_if_need_confirm()
|
data = self.check_confirm()
|
||||||
return Response(data=response_data, status=200)
|
return Response(data=data, status=200)
|
||||||
|
|
||||||
def check_if_need_confirm(self):
|
@lazyproperty
|
||||||
queries = {
|
def serializer(self):
|
||||||
'user': self.serializer.user, 'asset': self.serializer.asset,
|
serializer = self.get_serializer(data=self.request.data)
|
||||||
'system_user': self.serializer.system_user,
|
serializer.is_valid(raise_exception=True)
|
||||||
'action': LoginAssetACL.ActionChoices.login_confirm
|
return serializer
|
||||||
}
|
|
||||||
with tmp_to_org(self.serializer.org):
|
|
||||||
acl = LoginAssetACL.filter(**queries).valid().first()
|
|
||||||
|
|
||||||
if not acl:
|
def check_confirm(self):
|
||||||
is_need_confirm = False
|
with tmp_to_org(self.serializer.asset.org):
|
||||||
response_data = {}
|
kwargs = {
|
||||||
else:
|
'user': self.serializer.user,
|
||||||
is_need_confirm = True
|
'asset': self.serializer.asset,
|
||||||
|
'account_username': self.serializer.validated_data.get('account_username'),
|
||||||
|
'action': LoginAssetACL.ActionChoices.review
|
||||||
|
}
|
||||||
|
acl = LoginAssetACL.filter_queryset(**kwargs).valid().first()
|
||||||
|
if acl:
|
||||||
|
need_confirm = True
|
||||||
response_data = self._get_response_data_of_need_confirm(acl)
|
response_data = self._get_response_data_of_need_confirm(acl)
|
||||||
response_data['need_confirm'] = is_need_confirm
|
else:
|
||||||
return is_need_confirm, response_data
|
need_confirm = False
|
||||||
|
response_data = {}
|
||||||
|
response_data['need_confirm'] = need_confirm
|
||||||
|
return response_data
|
||||||
|
|
||||||
def _get_response_data_of_need_confirm(self, acl):
|
def _get_response_data_of_need_confirm(self, acl) -> dict:
|
||||||
ticket = LoginAssetACL.create_login_asset_confirm_ticket(
|
ticket = LoginAssetACL.create_login_asset_confirm_ticket(
|
||||||
user=self.serializer.user,
|
user=self.serializer.user,
|
||||||
asset=self.serializer.asset,
|
asset=self.serializer.asset,
|
||||||
system_user=self.serializer.system_user,
|
account_username=self.serializer.validated_data.get('account_username'),
|
||||||
assignees=acl.reviewers.all(),
|
assignees=acl.reviewers.all(),
|
||||||
org_id=self.serializer.org.id,
|
org_id=self.serializer.asset.org.id,
|
||||||
)
|
)
|
||||||
confirm_status_url = reverse(
|
confirm_status_url = reverse(
|
||||||
view_name='api-tickets:super-ticket-status',
|
view_name='api-tickets:super-ticket-status',
|
||||||
|
@ -68,10 +74,3 @@ class LoginAssetCheckAPI(CreateAPIView):
|
||||||
'ticket_id': str(ticket.id)
|
'ticket_id': str(ticket.id)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def serializer(self):
|
|
||||||
serializer = self.get_serializer(data=self.request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
return serializer
|
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,11 @@ def migrate_login_confirm(apps, schema_editor):
|
||||||
if reviewers.count() == 0:
|
if reviewers.count() == 0:
|
||||||
continue
|
continue
|
||||||
data = {
|
data = {
|
||||||
|
|
||||||
'user': user,
|
'user': user,
|
||||||
'name': f'{user.name}-{login_confirm} ({date_created})',
|
'name': f'{user.name}-{login_confirm} ({date_created})',
|
||||||
'created_by': instance.created_by,
|
'created_by': instance.created_by,
|
||||||
'action': LoginACL.ActionChoices.confirm,
|
'action': 'confirm',
|
||||||
'rules': {'ip_group': ['*'], 'time_period': DEFAULT_TIME_PERIODS}
|
'rules': {'ip_group': ['*'], 'time_period': DEFAULT_TIME_PERIODS}
|
||||||
}
|
}
|
||||||
instance = login_acl_model.objects.create(**data)
|
instance = login_acl_model.objects.create(**data)
|
||||||
|
@ -44,7 +45,7 @@ def migrate_ip_group(apps, schema_editor):
|
||||||
login_acl_model = apps.get_model("acls", "LoginACL")
|
login_acl_model = apps.get_model("acls", "LoginACL")
|
||||||
updates = list()
|
updates = list()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for instance in login_acl_model.objects.exclude(action=LoginACL.ActionChoices.confirm):
|
for instance in login_acl_model.objects.exclude(action='confirm'):
|
||||||
instance.rules = {'ip_group': instance.ip_group, 'time_period': DEFAULT_TIME_PERIODS}
|
instance.rules = {'ip_group': instance.ip_group, 'time_period': DEFAULT_TIME_PERIODS}
|
||||||
updates.append(instance)
|
updates.append(instance)
|
||||||
login_acl_model.objects.bulk_update(updates, ['rules', ])
|
login_acl_model.objects.bulk_update(updates, ['rules', ])
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 3.2.13 on 2022-08-31 08:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_system_users_to_accounts(apps, schema_editor):
|
||||||
|
login_asset_acl_model = apps.get_model('acls', 'LoginAssetACL')
|
||||||
|
qs = login_asset_acl_model.objects.all()
|
||||||
|
login_asset_acls = []
|
||||||
|
for instance in qs:
|
||||||
|
instance.accounts = instance.system_users
|
||||||
|
login_asset_acls.append(instance)
|
||||||
|
login_asset_acl_model.objects.bulk_update(login_asset_acls, ['accounts'])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0003_auto_20211130_1037'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='accounts',
|
||||||
|
field=models.JSONField(verbose_name='Account'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_system_users_to_accounts),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='system_users',
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-12-01 10:46
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('acls', '0004_auto_20220831_1658'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='loginacl',
|
||||||
|
name='action',
|
||||||
|
field=models.CharField(default='reject', max_length=64, verbose_name='Action'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='loginacl',
|
||||||
|
name='reviewers',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='action',
|
||||||
|
field=models.CharField(default='reject', max_length=64, verbose_name='Action'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='reviewers',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-12-01 11:39
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('acls', '0005_auto_20221201_1846'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CommandGroup',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command',
|
||||||
|
max_length=16, verbose_name='Type')),
|
||||||
|
('content', models.TextField(help_text='One line one command', verbose_name='Content')),
|
||||||
|
('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Command filter rule',
|
||||||
|
'unique_together': {('org_id', 'name')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CommandFilterACL',
|
||||||
|
fields=[
|
||||||
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
|
||||||
|
validators=[django.core.validators.MinValueValidator(1),
|
||||||
|
django.core.validators.MaxValueValidator(100)],
|
||||||
|
verbose_name='Priority')),
|
||||||
|
('action', models.CharField(default='reject', max_length=64, verbose_name='Action')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('users', models.JSONField(verbose_name='User')),
|
||||||
|
('accounts', models.JSONField(verbose_name='Account')),
|
||||||
|
('assets', models.JSONField(verbose_name='Asset')),
|
||||||
|
('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')),
|
||||||
|
(
|
||||||
|
'reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Command acl',
|
||||||
|
'ordering': ('priority', '-date_updated', 'name'),
|
||||||
|
'unique_together': {('name', 'org_id')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-12-02 02:48
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_login_type(apps, schema_editor):
|
||||||
|
login_asset_model = apps.get_model('acls', 'LoginAssetACL')
|
||||||
|
login_asset_model.objects.filter(action='login_confirm').update(action='review')
|
||||||
|
|
||||||
|
login_system_model = apps.get_model('acls', 'LoginACL')
|
||||||
|
login_system_model.objects.filter(action='confirm').update(action='review')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0006_commandfilteracl_commandgroup'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_login_type),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-12-02 04:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0007_auto_20221202_1048'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandgroup',
|
||||||
|
name='comment',
|
||||||
|
field=models.TextField(blank=True, verbose_name='Comment'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-12-03 16:01
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0008_commandgroup_comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='commandgroup',
|
||||||
|
options={'verbose_name': 'Command group'},
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='commands',
|
||||||
|
new_name='command_groups',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-12-05 03:22
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0009_auto_20221204_0001'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='commandfilteracl',
|
||||||
|
options={'ordering': ('priority', 'name'), 'verbose_name': 'Command acl'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='loginacl',
|
||||||
|
options={'ordering': ('priority', 'name'), 'verbose_name': 'Login acl'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='loginassetacl',
|
||||||
|
options={'ordering': ('priority', 'name'), 'verbose_name': 'Login asset acl'},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,2 +1,3 @@
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
|
from .command_acl import *
|
||||||
|
|
|
@ -1,10 +1,25 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.mixins import CommonModelMixin
|
from common.mixins import CommonModelMixin
|
||||||
|
from common.utils import contains_ip
|
||||||
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ACLManager',
|
||||||
|
'BaseACL',
|
||||||
|
'BaseACLQuerySet',
|
||||||
|
'UserAssetAccountBaseACL',
|
||||||
|
'UserAssetAccountACLQuerySet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['BaseACL', 'BaseACLQuerySet']
|
class ActionChoices(models.TextChoices):
|
||||||
|
reject = 'reject', _('Reject')
|
||||||
|
accept = 'accept', _('Accept')
|
||||||
|
review = 'review', _('Review')
|
||||||
|
|
||||||
|
|
||||||
class BaseACLQuerySet(models.QuerySet):
|
class BaseACLQuerySet(models.QuerySet):
|
||||||
|
@ -21,6 +36,37 @@ class BaseACLQuerySet(models.QuerySet):
|
||||||
return self.inactive()
|
return self.inactive()
|
||||||
|
|
||||||
|
|
||||||
|
class UserAssetAccountACLQuerySet(BaseACLQuerySet):
|
||||||
|
def filter_user(self, username):
|
||||||
|
q = Q(users__username_group__contains=username) | \
|
||||||
|
Q(users__username_group__contains='*')
|
||||||
|
return self.filter(q)
|
||||||
|
|
||||||
|
def filter_asset(self, name=None, address=None):
|
||||||
|
queryset = self.filter()
|
||||||
|
if name:
|
||||||
|
q = Q(assets__name_group__contains=name) | \
|
||||||
|
Q(assets__name_group__contains='*')
|
||||||
|
queryset = queryset.filter(q)
|
||||||
|
if address:
|
||||||
|
ids = [
|
||||||
|
q.id for q in queryset
|
||||||
|
if contains_ip(address, q.assets.get('address_group', []))
|
||||||
|
]
|
||||||
|
queryset = queryset.filter(id__in=ids)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def filter_account(self, username):
|
||||||
|
q = Q(accounts__username_group__contains=username) | \
|
||||||
|
Q(accounts__username_group__contains='*')
|
||||||
|
return self.filter(q)
|
||||||
|
|
||||||
|
|
||||||
|
class ACLManager(models.Manager):
|
||||||
|
def valid(self):
|
||||||
|
return self.get_queryset().valid()
|
||||||
|
|
||||||
|
|
||||||
class BaseACL(CommonModelMixin):
|
class BaseACL(CommonModelMixin):
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
priority = models.IntegerField(
|
priority = models.IntegerField(
|
||||||
|
@ -28,8 +74,49 @@ class BaseACL(CommonModelMixin):
|
||||||
help_text=_("1-100, the lower the value will be match first"),
|
help_text=_("1-100, the lower the value will be match first"),
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(100)]
|
validators=[MinValueValidator(1), MaxValueValidator(100)]
|
||||||
)
|
)
|
||||||
|
action = models.CharField(max_length=64, default=ActionChoices.reject, verbose_name=_('Action'))
|
||||||
|
reviewers = models.ManyToManyField('users.User', blank=True, verbose_name=_("Reviewers"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
|
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
|
ActionChoices = ActionChoices
|
||||||
|
objects = ACLManager.from_queryset(BaseACLQuerySet)()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('priority', 'name')
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
|
||||||
|
# username_group
|
||||||
|
users = models.JSONField(verbose_name=_('User'))
|
||||||
|
# name_group, address_group
|
||||||
|
assets = models.JSONField(verbose_name=_('Asset'))
|
||||||
|
# username_group
|
||||||
|
accounts = models.JSONField(verbose_name=_('Account'))
|
||||||
|
|
||||||
|
objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)()
|
||||||
|
|
||||||
|
class Meta(BaseACL.Meta):
|
||||||
|
unique_together = ('name', 'org_id')
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter_queryset(cls, user=None, asset=None, account=None, account_username=None, **kwargs):
|
||||||
|
queryset = cls.objects.all()
|
||||||
|
org_id = None
|
||||||
|
if user:
|
||||||
|
queryset = queryset.filter_user(user.username)
|
||||||
|
if asset:
|
||||||
|
org_id = asset.org_id
|
||||||
|
queryset = queryset.filter_asset(asset.name, asset.address)
|
||||||
|
if account:
|
||||||
|
org_id = account.org_id
|
||||||
|
queryset = queryset.filter_account(account.username)
|
||||||
|
if account_username:
|
||||||
|
queryset = queryset.filter_account(username=account_username)
|
||||||
|
if org_id:
|
||||||
|
kwargs['org_id'] = org_id
|
||||||
|
if kwargs:
|
||||||
|
queryset = queryset.filter(**kwargs)
|
||||||
|
return queryset
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from common.utils import lazyproperty, get_logger
|
||||||
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
|
|
||||||
|
from .base import UserAssetAccountBaseACL
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class TypeChoices(models.TextChoices):
|
||||||
|
command = 'command', _('Command')
|
||||||
|
regex = 'regex', _('Regex')
|
||||||
|
|
||||||
|
|
||||||
|
class CommandGroup(JMSOrgBaseModel):
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
|
type = models.CharField(
|
||||||
|
max_length=16, default=TypeChoices.command, choices=TypeChoices.choices,
|
||||||
|
verbose_name=_("Type")
|
||||||
|
)
|
||||||
|
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
|
||||||
|
ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case'))
|
||||||
|
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
||||||
|
|
||||||
|
TypeChoices = TypeChoices
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = [('org_id', 'name')]
|
||||||
|
verbose_name = _("Command group")
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def pattern(self):
|
||||||
|
if self.type == 'command':
|
||||||
|
s = self.construct_command_regex(self.content)
|
||||||
|
else:
|
||||||
|
s = r'{0}'.format(self.content)
|
||||||
|
return s
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def construct_command_regex(cls, content):
|
||||||
|
regex = []
|
||||||
|
content = content.replace('\r\n', '\n')
|
||||||
|
for _cmd in content.split('\n'):
|
||||||
|
cmd = re.sub(r'\s+', ' ', _cmd)
|
||||||
|
cmd = re.escape(cmd)
|
||||||
|
cmd = cmd.replace('\\ ', '\s+')
|
||||||
|
|
||||||
|
# 有空格就不能 铆钉单词了
|
||||||
|
if ' ' in _cmd:
|
||||||
|
regex.append(cmd)
|
||||||
|
continue
|
||||||
|
if not cmd:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果是单个字符
|
||||||
|
if cmd[-1].isalpha():
|
||||||
|
regex.append(r'\b{0}\b'.format(cmd))
|
||||||
|
else:
|
||||||
|
regex.append(r'\b{0}'.format(cmd))
|
||||||
|
s = r'{}'.format('|'.join(regex))
|
||||||
|
return s
|
||||||
|
|
||||||
|
def match(self, data):
|
||||||
|
succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case)
|
||||||
|
if not succeed:
|
||||||
|
return False, ''
|
||||||
|
|
||||||
|
found = pattern.search(data)
|
||||||
|
if not found:
|
||||||
|
return False, ''
|
||||||
|
else:
|
||||||
|
return True, found.group()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compile_regex(regex, ignore_case):
|
||||||
|
args = []
|
||||||
|
if ignore_case:
|
||||||
|
args.append(re.IGNORECASE)
|
||||||
|
try:
|
||||||
|
pattern = re.compile(regex, *args)
|
||||||
|
except Exception as e:
|
||||||
|
error = _('The generated regular expression is incorrect: {}').format(str(e))
|
||||||
|
logger.error(error)
|
||||||
|
return False, error, None
|
||||||
|
return True, '', pattern
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} % {}'.format(self.name, self.type)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFilterACL(UserAssetAccountBaseACL):
|
||||||
|
command_groups = models.ManyToManyField(CommandGroup, verbose_name=_('Commands'))
|
||||||
|
|
||||||
|
class Meta(UserAssetAccountBaseACL.Meta):
|
||||||
|
abstract = False
|
||||||
|
verbose_name = _('Command acl')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id):
|
||||||
|
from tickets.const import TicketType
|
||||||
|
from tickets.models import ApplyCommandTicket
|
||||||
|
data = {
|
||||||
|
'title': _('Command confirm') + ' ({})'.format(session.user),
|
||||||
|
'type': TicketType.command_confirm,
|
||||||
|
'applicant': session.user_obj,
|
||||||
|
'apply_run_user_id': session.user_id,
|
||||||
|
'apply_run_asset': str(session.asset),
|
||||||
|
'apply_run_account': str(session.account),
|
||||||
|
'apply_run_command': run_command[:4090],
|
||||||
|
'apply_from_session_id': str(session.id),
|
||||||
|
'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id),
|
||||||
|
'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id),
|
||||||
|
'org_id': org_id,
|
||||||
|
}
|
||||||
|
ticket = ApplyCommandTicket.objects.create(**data)
|
||||||
|
assignees = self.reviewers.all()
|
||||||
|
ticket.open_by_system(assignees)
|
||||||
|
return ticket
|
|
@ -1,45 +1,23 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from .base import BaseACL, BaseACLQuerySet
|
|
||||||
from common.utils import get_request_ip, get_ip_city
|
from common.utils import get_request_ip, get_ip_city
|
||||||
from common.utils.ip import contains_ip
|
from common.utils.ip import contains_ip
|
||||||
from common.utils.time_period import contains_time_period
|
from common.utils.time_period import contains_time_period
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
||||||
|
from .base import BaseACL
|
||||||
|
|
||||||
class ACLManager(models.Manager):
|
|
||||||
|
|
||||||
def valid(self):
|
|
||||||
return self.get_queryset().valid()
|
|
||||||
|
|
||||||
|
|
||||||
class LoginACL(BaseACL):
|
class LoginACL(BaseACL):
|
||||||
class ActionChoices(models.TextChoices):
|
|
||||||
reject = 'reject', _('Reject')
|
|
||||||
allow = 'allow', _('Allow')
|
|
||||||
confirm = 'confirm', _('Login confirm')
|
|
||||||
|
|
||||||
# 用户
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
'users.User', on_delete=models.CASCADE, verbose_name=_('User'),
|
'users.User', on_delete=models.CASCADE, related_name='login_acls', verbose_name=_('User')
|
||||||
related_name='login_acls'
|
|
||||||
)
|
)
|
||||||
# 规则
|
# 规则, ip_group, time_period
|
||||||
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
||||||
# 动作
|
|
||||||
action = models.CharField(
|
|
||||||
max_length=64, verbose_name=_('Action'),
|
|
||||||
choices=ActionChoices.choices, default=ActionChoices.reject
|
|
||||||
)
|
|
||||||
reviewers = models.ManyToManyField(
|
|
||||||
'users.User', verbose_name=_("Reviewers"),
|
|
||||||
related_name="login_confirm_acls", blank=True
|
|
||||||
)
|
|
||||||
objects = ACLManager.from_queryset(BaseACLQuerySet)()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseACL.Meta):
|
||||||
ordering = ('priority', '-date_updated', 'name')
|
|
||||||
verbose_name = _('Login acl')
|
verbose_name = _('Login acl')
|
||||||
|
abstract = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -53,12 +31,13 @@ class LoginACL(BaseACL):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def match(user, ip):
|
def match(user, ip):
|
||||||
acls = LoginACL.filter_acl(user)
|
acl_qs = LoginACL.filter_acl(user)
|
||||||
if not acls:
|
if not acl_qs:
|
||||||
return
|
return
|
||||||
|
|
||||||
for acl in acls:
|
for acl in acl_qs:
|
||||||
if acl.is_action(LoginACL.ActionChoices.confirm) and not acl.reviewers.exists():
|
if acl.is_action(LoginACL.ActionChoices.review) and \
|
||||||
|
not acl.reviewers.exists():
|
||||||
continue
|
continue
|
||||||
ip_group = acl.rules.get('ip_group')
|
ip_group = acl.rules.get('ip_group')
|
||||||
time_periods = acl.rules.get('time_period')
|
time_periods = acl.rules.get('time_period')
|
||||||
|
@ -79,12 +58,12 @@ class LoginACL(BaseACL):
|
||||||
login_datetime = local_now_display()
|
login_datetime = local_now_display()
|
||||||
data = {
|
data = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'type': const.TicketType.login_confirm,
|
|
||||||
'applicant': self.user,
|
'applicant': self.user,
|
||||||
'apply_login_city': login_city,
|
|
||||||
'apply_login_ip': login_ip,
|
'apply_login_ip': login_ip,
|
||||||
'apply_login_datetime': login_datetime,
|
|
||||||
'org_id': Organization.ROOT_ID,
|
'org_id': Organization.ROOT_ID,
|
||||||
|
'apply_login_city': login_city,
|
||||||
|
'apply_login_datetime': login_datetime,
|
||||||
|
'type': const.TicketType.login_confirm,
|
||||||
}
|
}
|
||||||
ticket = ApplyLoginTicket.objects.create(**data)
|
ticket = ApplyLoginTicket.objects.create(**data)
|
||||||
assignees = self.reviewers.all()
|
assignees = self.reviewers.all()
|
||||||
|
|
|
@ -1,102 +1,32 @@
|
||||||
from django.db import models
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
|
||||||
from .base import BaseACL, BaseACLQuerySet
|
|
||||||
from common.utils.ip import contains_ip
|
|
||||||
|
|
||||||
|
|
||||||
class ACLManager(OrgManager):
|
from .base import UserAssetAccountBaseACL
|
||||||
|
|
||||||
def valid(self):
|
|
||||||
return self.get_queryset().valid()
|
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACL(BaseACL, OrgModelMixin):
|
class LoginAssetACL(UserAssetAccountBaseACL):
|
||||||
class ActionChoices(models.TextChoices):
|
|
||||||
login_confirm = 'login_confirm', _('Login confirm')
|
|
||||||
|
|
||||||
# 条件
|
class Meta(UserAssetAccountBaseACL.Meta):
|
||||||
users = models.JSONField(verbose_name=_('User'))
|
|
||||||
system_users = models.JSONField(verbose_name=_('System User'))
|
|
||||||
assets = models.JSONField(verbose_name=_('Asset'))
|
|
||||||
# 动作
|
|
||||||
action = models.CharField(
|
|
||||||
max_length=64, choices=ActionChoices.choices, default=ActionChoices.login_confirm,
|
|
||||||
verbose_name=_('Action')
|
|
||||||
)
|
|
||||||
# 动作: 附加字段
|
|
||||||
# - login_confirm
|
|
||||||
reviewers = models.ManyToManyField(
|
|
||||||
'users.User', related_name='review_login_asset_acls', blank=True,
|
|
||||||
verbose_name=_("Reviewers")
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = ACLManager.from_queryset(BaseACLQuerySet)()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ('name', 'org_id')
|
|
||||||
ordering = ('priority', '-date_updated', 'name')
|
|
||||||
verbose_name = _('Login asset acl')
|
verbose_name = _('Login asset acl')
|
||||||
|
abstract = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter(cls, user, asset, system_user, action):
|
def create_login_asset_confirm_ticket(cls, user, asset, account_username, assignees, org_id):
|
||||||
queryset = cls.objects.filter(action=action)
|
|
||||||
queryset = cls.filter_user(user, queryset)
|
|
||||||
queryset = cls.filter_asset(asset, queryset)
|
|
||||||
queryset = cls.filter_system_user(system_user, queryset)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def filter_user(cls, user, queryset):
|
|
||||||
queryset = queryset.filter(
|
|
||||||
Q(users__username_group__contains=user.username) |
|
|
||||||
Q(users__username_group__contains='*')
|
|
||||||
)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def filter_asset(cls, asset, queryset):
|
|
||||||
queryset = queryset.filter(
|
|
||||||
Q(assets__hostname_group__contains=asset.hostname) |
|
|
||||||
Q(assets__hostname_group__contains='*')
|
|
||||||
)
|
|
||||||
ids = [q.id for q in queryset if contains_ip(asset.ip, q.assets.get('ip_group', []))]
|
|
||||||
queryset = cls.objects.filter(id__in=ids)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def filter_system_user(cls, system_user, queryset):
|
|
||||||
queryset = queryset.filter(
|
|
||||||
Q(system_users__name_group__contains=system_user.name) |
|
|
||||||
Q(system_users__name_group__contains='*')
|
|
||||||
).filter(
|
|
||||||
Q(system_users__username_group__contains=system_user.username) |
|
|
||||||
Q(system_users__username_group__contains='*')
|
|
||||||
).filter(
|
|
||||||
Q(system_users__protocol_group__contains=system_user.protocol) |
|
|
||||||
Q(system_users__protocol_group__contains='*')
|
|
||||||
)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_login_asset_confirm_ticket(cls, user, asset, system_user, assignees, org_id):
|
|
||||||
from tickets.const import TicketType
|
from tickets.const import TicketType
|
||||||
from tickets.models import ApplyLoginAssetTicket
|
from tickets.models import ApplyLoginAssetTicket
|
||||||
title = _('Login asset confirm') + ' ({})'.format(user)
|
title = _('Login asset confirm') + ' ({})'.format(user)
|
||||||
data = {
|
data = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'type': TicketType.login_asset_confirm,
|
'org_id': org_id,
|
||||||
'applicant': user,
|
'applicant': user,
|
||||||
'apply_login_user': user,
|
'apply_login_user': user,
|
||||||
'apply_login_asset': asset,
|
'apply_login_asset': asset,
|
||||||
'apply_login_system_user': system_user,
|
'apply_login_account': account_username,
|
||||||
'org_id': org_id,
|
'type': TicketType.login_asset_confirm,
|
||||||
}
|
}
|
||||||
ticket = ApplyLoginAssetTicket.objects.create(**data)
|
ticket = ApplyLoginAssetTicket.objects.create(**data)
|
||||||
ticket.open_by_system(assignees)
|
ticket.open_by_system(assignees)
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .login_asset_check import *
|
from .login_asset_check import *
|
||||||
|
from .command_acl import *
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from acls.models.base import ActionChoices
|
||||||
|
from common.drf.fields import LabeledChoiceField, ObjectRelatedField
|
||||||
|
from orgs.models import Organization
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
common_help_text = _(
|
||||||
|
"Format for comma-delimited string, with * indicating a match all. "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ACLUsersSerializer(serializers.Serializer):
|
||||||
|
username_group = serializers.ListField(
|
||||||
|
default=["*"],
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
label=_("Username"),
|
||||||
|
help_text=common_help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ACLAssestsSerializer(serializers.Serializer):
|
||||||
|
address_group_help_text = _(
|
||||||
|
"Format for comma-delimited string, with * indicating a match all. "
|
||||||
|
"Such as: "
|
||||||
|
"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
|
||||||
|
" (Domain name support)"
|
||||||
|
)
|
||||||
|
|
||||||
|
name_group = serializers.ListField(
|
||||||
|
default=["*"],
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
label=_("Name"),
|
||||||
|
help_text=common_help_text,
|
||||||
|
)
|
||||||
|
address_group = serializers.ListField(
|
||||||
|
default=["*"],
|
||||||
|
child=serializers.CharField(max_length=1024),
|
||||||
|
label=_("IP/Host"),
|
||||||
|
help_text=address_group_help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ACLAccountsSerializer(serializers.Serializer):
|
||||||
|
username_group = serializers.ListField(
|
||||||
|
default=["*"],
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
label=_("Username"),
|
||||||
|
help_text=common_help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer):
|
||||||
|
users = ACLUsersSerializer(label=_('User'))
|
||||||
|
assets = ACLAssestsSerializer(label=_('Asset'))
|
||||||
|
accounts = ACLAccountsSerializer(label=_('Account'))
|
||||||
|
reviewers = ObjectRelatedField(
|
||||||
|
queryset=User.objects, many=True, required=False, label=_('Reviewers')
|
||||||
|
)
|
||||||
|
reviewers_amount = serializers.IntegerField(read_only=True, source="reviewers.count")
|
||||||
|
action = LabeledChoiceField(
|
||||||
|
choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields_mini = ["id", "name"]
|
||||||
|
fields_small = fields_mini + [
|
||||||
|
"users", "accounts", "assets", "is_active",
|
||||||
|
"date_created", "date_updated", "priority",
|
||||||
|
"action", "comment", "created_by", "org_id",
|
||||||
|
]
|
||||||
|
fields_m2m = ["reviewers", "reviewers_amount"]
|
||||||
|
fields = fields_small + fields_m2m
|
||||||
|
extra_kwargs = {
|
||||||
|
"reviewers": {"allow_null": False, "required": True},
|
||||||
|
"priority": {"default": 50},
|
||||||
|
"is_active": {"default": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_reviewers(self, reviewers):
|
||||||
|
action = self.initial_data.get('action')
|
||||||
|
if not action and self.instance:
|
||||||
|
action = self.instance.action
|
||||||
|
if action != ActionChoices.review:
|
||||||
|
return reviewers
|
||||||
|
org_id = self.fields["org_id"].default()
|
||||||
|
org = Organization.get_instance(org_id)
|
||||||
|
if not org:
|
||||||
|
error = _("The organization `{}` does not exist".format(org_id))
|
||||||
|
raise serializers.ValidationError(error)
|
||||||
|
users = org.get_members()
|
||||||
|
valid_reviewers = list(set(reviewers) & set(users))
|
||||||
|
if not valid_reviewers:
|
||||||
|
error = _(
|
||||||
|
"None of the reviewers belong to Organization `{}`".format(org.name)
|
||||||
|
)
|
||||||
|
raise serializers.ValidationError(error)
|
||||||
|
return valid_reviewers
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from acls.models import CommandGroup, CommandFilterACL
|
||||||
|
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
|
||||||
|
|
||||||
|
__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer"]
|
||||||
|
|
||||||
|
|
||||||
|
class CommandGroupSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
type = LabeledChoiceField(
|
||||||
|
choices=CommandGroup.TypeChoices.choices, default=CommandGroup.TypeChoices.command,
|
||||||
|
label=_('Type')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CommandGroup
|
||||||
|
fields = ['id', 'name', 'type', 'content', 'ignore_case', 'comment']
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
|
||||||
|
command_groups = ObjectRelatedField(
|
||||||
|
queryset=CommandGroup.objects, many=True, required=False, label=_('Command group')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseSerializer.Meta):
|
||||||
|
model = CommandFilterACL
|
||||||
|
fields = BaseSerializer.Meta.fields + ['command_groups']
|
|
@ -1,39 +1,48 @@
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from common.drf.serializers import BulkModelSerializer
|
|
||||||
from common.drf.serializers import MethodSerializer
|
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
|
from common.drf.serializers import BulkModelSerializer, MethodSerializer
|
||||||
from jumpserver.utils import has_valid_xpack_license
|
from jumpserver.utils import has_valid_xpack_license
|
||||||
from ..models import LoginACL
|
from users.models import User
|
||||||
from .rules import RuleSerializer
|
from .rules import RuleSerializer
|
||||||
|
from ..models import LoginACL
|
||||||
|
|
||||||
__all__ = ['LoginACLSerializer', ]
|
__all__ = [
|
||||||
|
"LoginACLSerializer",
|
||||||
|
]
|
||||||
|
|
||||||
common_help_text = _('Format for comma-delimited string, with * indicating a match all. ')
|
common_help_text = _(
|
||||||
|
"Format for comma-delimited string, with * indicating a match all. "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LoginACLSerializer(BulkModelSerializer):
|
class LoginACLSerializer(BulkModelSerializer):
|
||||||
user_display = serializers.ReadOnlyField(source='user.username', label=_('Username'))
|
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
|
||||||
reviewers_display = serializers.SerializerMethodField(label=_('Reviewers'))
|
reviewers = ObjectRelatedField(
|
||||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
queryset=User.objects, label=_("Reviewers"), many=True, required=False
|
||||||
reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count')
|
)
|
||||||
|
action = LabeledChoiceField(choices=LoginACL.ActionChoices.choices)
|
||||||
|
reviewers_amount = serializers.IntegerField(
|
||||||
|
read_only=True, source="reviewers.count"
|
||||||
|
)
|
||||||
rules = MethodSerializer()
|
rules = MethodSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LoginACL
|
model = LoginACL
|
||||||
fields_mini = ['id', 'name']
|
fields_mini = ["id", "name"]
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'priority', 'rules', 'action', 'action_display',
|
"priority", "user", "rules", "action",
|
||||||
'is_active', 'user', 'user_display',
|
"is_active", "date_created", "date_updated",
|
||||||
'date_created', 'date_updated', 'reviewers_amount',
|
"reviewers_amount", "comment", "created_by",
|
||||||
'comment', 'created_by'
|
|
||||||
]
|
]
|
||||||
fields_fk = ['user', 'user_display']
|
fields_fk = ["user"]
|
||||||
fields_m2m = ['reviewers', 'reviewers_display']
|
fields_m2m = ["reviewers"]
|
||||||
fields = fields_small + fields_fk + fields_m2m
|
fields = fields_small + fields_fk + fields_m2m
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'priority': {'default': 50},
|
"priority": {"default": 50},
|
||||||
'is_active': {'default': True},
|
"is_active": {"default": True},
|
||||||
"reviewers": {'allow_null': False, 'required': True},
|
"reviewers": {"allow_null": False, "required": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -41,16 +50,13 @@ class LoginACLSerializer(BulkModelSerializer):
|
||||||
self.set_action_choices()
|
self.set_action_choices()
|
||||||
|
|
||||||
def set_action_choices(self):
|
def set_action_choices(self):
|
||||||
action = self.fields.get('action')
|
action = self.fields.get("action")
|
||||||
if not action:
|
if not action:
|
||||||
return
|
return
|
||||||
choices = action._choices
|
choices = action._choices
|
||||||
if not has_valid_xpack_license():
|
if not has_valid_xpack_license():
|
||||||
choices.pop(LoginACL.ActionChoices.confirm, None)
|
choices.pop(LoginACL.ActionChoices.review, None)
|
||||||
action._choices = choices
|
action._choices = choices
|
||||||
|
|
||||||
def get_rules_serializer(self):
|
def get_rules_serializer(self):
|
||||||
return RuleSerializer()
|
return RuleSerializer()
|
||||||
|
|
||||||
def get_reviewers_display(self, obj):
|
|
||||||
return ','.join([str(user) for user in obj.reviewers.all()])
|
|
||||||
|
|
|
@ -1,105 +1,11 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from assets.models import SystemUser
|
|
||||||
from acls import models
|
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
|
||||||
from orgs.models import Organization
|
from ..models import LoginAssetACL
|
||||||
|
|
||||||
|
__all__ = ["LoginAssetACLSerializer"]
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['LoginAssetACLSerializer']
|
class LoginAssetACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
|
||||||
|
class Meta(BaseSerializer.Meta):
|
||||||
|
model = LoginAssetACL
|
||||||
common_help_text = _('Format for comma-delimited string, with * indicating a match all. ')
|
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACLUsersSerializer(serializers.Serializer):
|
|
||||||
username_group = serializers.ListField(
|
|
||||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Username'),
|
|
||||||
help_text=common_help_text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACLAssestsSerializer(serializers.Serializer):
|
|
||||||
ip_group_help_text = _(
|
|
||||||
'Format for comma-delimited string, with * indicating a match all. '
|
|
||||||
'Such as: '
|
|
||||||
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 '
|
|
||||||
'(Domain name support)'
|
|
||||||
)
|
|
||||||
|
|
||||||
ip_group = serializers.ListField(
|
|
||||||
default=['*'], child=serializers.CharField(max_length=1024), label=_('IP'),
|
|
||||||
help_text=ip_group_help_text
|
|
||||||
)
|
|
||||||
hostname_group = serializers.ListField(
|
|
||||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Hostname'),
|
|
||||||
help_text=common_help_text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACLSystemUsersSerializer(serializers.Serializer):
|
|
||||||
protocol_group_help_text = _(
|
|
||||||
'Format for comma-delimited string, with * indicating a match all. '
|
|
||||||
'Protocol options: {}'
|
|
||||||
)
|
|
||||||
|
|
||||||
name_group = serializers.ListField(
|
|
||||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Name'),
|
|
||||||
help_text=common_help_text
|
|
||||||
)
|
|
||||||
username_group = serializers.ListField(
|
|
||||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Username'),
|
|
||||||
help_text=common_help_text
|
|
||||||
)
|
|
||||||
protocol_group = serializers.ListField(
|
|
||||||
default=['*'], child=serializers.CharField(max_length=16), label=_('Protocol'),
|
|
||||||
help_text=protocol_group_help_text.format(
|
|
||||||
', '.join([SystemUser.Protocol.ssh, SystemUser.Protocol.telnet])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def validate_protocol_group(protocol_group):
|
|
||||||
unsupported_protocols = set(protocol_group) - set(SystemUser.ASSET_CATEGORY_PROTOCOLS + ['*'])
|
|
||||||
if unsupported_protocols:
|
|
||||||
error = _('Unsupported protocols: {}').format(unsupported_protocols)
|
|
||||||
raise serializers.ValidationError(error)
|
|
||||||
return protocol_group
|
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACLSerializer(BulkOrgResourceModelSerializer):
|
|
||||||
users = LoginAssetACLUsersSerializer()
|
|
||||||
assets = LoginAssetACLAssestsSerializer()
|
|
||||||
system_users = LoginAssetACLSystemUsersSerializer()
|
|
||||||
reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count')
|
|
||||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.LoginAssetACL
|
|
||||||
fields_mini = ['id', 'name']
|
|
||||||
fields_small = fields_mini + [
|
|
||||||
'users', 'system_users', 'assets',
|
|
||||||
'is_active',
|
|
||||||
'date_created', 'date_updated',
|
|
||||||
'priority', 'action', 'action_display', 'comment', 'created_by', 'org_id'
|
|
||||||
]
|
|
||||||
fields_m2m = ['reviewers', 'reviewers_amount']
|
|
||||||
fields = fields_small + fields_m2m
|
|
||||||
extra_kwargs = {
|
|
||||||
"reviewers": {'allow_null': False, 'required': True},
|
|
||||||
'priority': {'default': 50},
|
|
||||||
'is_active': {'default': True},
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate_reviewers(self, reviewers):
|
|
||||||
org_id = self.fields['org_id'].default()
|
|
||||||
org = Organization.get_instance(org_id)
|
|
||||||
if not org:
|
|
||||||
error = _('The organization `{}` does not exist'.format(org_id))
|
|
||||||
raise serializers.ValidationError(error)
|
|
||||||
users = org.get_members()
|
|
||||||
valid_reviewers = list(set(reviewers) & set(users))
|
|
||||||
if not valid_reviewers:
|
|
||||||
error = _('None of the reviewers belong to Organization `{}`'.format(org.name))
|
|
||||||
raise serializers.ValidationError(error)
|
|
||||||
return valid_reviewers
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
from common.utils import get_object_or_none, lazyproperty
|
from common.utils import get_object_or_none, lazyproperty
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset, Account
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['LoginAssetCheckSerializer']
|
__all__ = ['LoginAssetCheckSerializer']
|
||||||
|
|
||||||
|
@ -12,60 +10,26 @@ __all__ = ['LoginAssetCheckSerializer']
|
||||||
class LoginAssetCheckSerializer(serializers.Serializer):
|
class LoginAssetCheckSerializer(serializers.Serializer):
|
||||||
user_id = serializers.UUIDField(required=True, allow_null=False)
|
user_id = serializers.UUIDField(required=True, allow_null=False)
|
||||||
asset_id = serializers.UUIDField(required=True, allow_null=False)
|
asset_id = serializers.UUIDField(required=True, allow_null=False)
|
||||||
system_user_id = serializers.UUIDField(required=True, allow_null=False)
|
account_username = serializers.CharField(max_length=128, default='')
|
||||||
system_user_username = serializers.CharField(max_length=128, default='')
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.user = None
|
self.user = None
|
||||||
self.asset = None
|
self.asset = None
|
||||||
self._system_user = None
|
|
||||||
self._system_user_username = None
|
|
||||||
|
|
||||||
def validate_user_id(self, user_id):
|
def validate_user_id(self, user_id):
|
||||||
self.user = self.validate_object_exist(User, user_id)
|
self.user = self.get_object(User, user_id)
|
||||||
return user_id
|
return user_id
|
||||||
|
|
||||||
def validate_asset_id(self, asset_id):
|
def validate_asset_id(self, asset_id):
|
||||||
self.asset = self.validate_object_exist(Asset, asset_id)
|
self.asset = self.get_object(Asset, asset_id)
|
||||||
return asset_id
|
return asset_id
|
||||||
|
|
||||||
def validate_system_user_id(self, system_user_id):
|
|
||||||
self._system_user = self.validate_object_exist(SystemUser, system_user_id)
|
|
||||||
return system_user_id
|
|
||||||
|
|
||||||
def validate_system_user_username(self, system_user_username):
|
|
||||||
system_user_id = self.initial_data.get('system_user_id')
|
|
||||||
system_user = self.validate_object_exist(SystemUser, system_user_id)
|
|
||||||
if self._system_user.login_mode == SystemUser.LOGIN_MANUAL \
|
|
||||||
and not system_user.username \
|
|
||||||
and not system_user.username_same_with_user \
|
|
||||||
and not system_user_username:
|
|
||||||
error = 'Missing parameter: system_user_username'
|
|
||||||
raise serializers.ValidationError(error)
|
|
||||||
self._system_user_username = system_user_username
|
|
||||||
return system_user_username
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_object_exist(model, field_id):
|
def get_object(model, pk):
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
obj = get_object_or_none(model, pk=field_id)
|
obj = get_object_or_none(model, pk=pk)
|
||||||
if not obj:
|
if obj:
|
||||||
error = '{} Model object does not exist'.format(model.__name__)
|
return obj
|
||||||
raise serializers.ValidationError(error)
|
error = '{} Model object does not exist'.format(model.__name__)
|
||||||
return obj
|
raise serializers.ValidationError(error)
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def system_user(self):
|
|
||||||
if self._system_user.username_same_with_user:
|
|
||||||
username = self.user.username
|
|
||||||
elif self._system_user.login_mode == SystemUser.LOGIN_MANUAL:
|
|
||||||
username = self._system_user_username
|
|
||||||
else:
|
|
||||||
username = self._system_user.username
|
|
||||||
self._system_user.username = username
|
|
||||||
return self._system_user
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def org(self):
|
|
||||||
return self.asset.org
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework_bulk.routes import BulkRouter
|
from rest_framework_bulk.routes import BulkRouter
|
||||||
|
|
||||||
from .. import api
|
from .. import api
|
||||||
|
|
||||||
|
|
||||||
app_name = 'acls'
|
app_name = 'acls'
|
||||||
|
|
||||||
|
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register(r'login-acls', api.LoginACLViewSet, 'login-acl')
|
router.register(r'login-acls', api.LoginACLViewSet, 'login-acl')
|
||||||
router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl')
|
router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl')
|
||||||
|
router.register(r'command-groups', api.CommandGroupViewSet, 'command-group')
|
||||||
|
router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),
|
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from .application import *
|
|
||||||
from .account import *
|
|
||||||
from .remote_app import *
|
|
|
@ -1,66 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
|
|
||||||
from django_filters import rest_framework as filters
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from common.drf.filters import BaseFilterSet
|
|
||||||
from common.drf.api import JMSBulkModelViewSet
|
|
||||||
from common.mixins import RecordViewLogMixin
|
|
||||||
from common.permissions import UserConfirmation
|
|
||||||
from authentication.const import ConfirmType
|
|
||||||
from rbac.permissions import RBACPermission
|
|
||||||
from assets.models import SystemUser
|
|
||||||
from ..models import Account
|
|
||||||
from .. import serializers
|
|
||||||
|
|
||||||
|
|
||||||
class AccountFilterSet(BaseFilterSet):
|
|
||||||
username = filters.CharFilter(method='do_nothing')
|
|
||||||
type = filters.CharFilter(field_name='type', lookup_expr='exact')
|
|
||||||
category = filters.CharFilter(field_name='category', lookup_expr='exact')
|
|
||||||
app_display = filters.CharFilter(field_name='app_display', lookup_expr='exact')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Account
|
|
||||||
fields = ['app', 'systemuser']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def qs(self):
|
|
||||||
qs = super().qs
|
|
||||||
qs = self.filter_username(qs)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def filter_username(self, qs):
|
|
||||||
username = self.get_query_param('username')
|
|
||||||
if not username:
|
|
||||||
return qs
|
|
||||||
q = Q(username=username) | Q(systemuser__username=username)
|
|
||||||
qs = qs.filter(q).distinct()
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationAccountViewSet(JMSBulkModelViewSet):
|
|
||||||
model = Account
|
|
||||||
search_fields = ['username', 'app_display']
|
|
||||||
filterset_class = AccountFilterSet
|
|
||||||
filterset_fields = ['username', 'app_display', 'type', 'category', 'app']
|
|
||||||
serializer_class = serializers.AppAccountSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = Account.get_queryset()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAppRelationViewSet(ApplicationAccountViewSet):
|
|
||||||
perm_model = SystemUser
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationAccountSecretViewSet(RecordViewLogMixin, ApplicationAccountViewSet):
|
|
||||||
serializer_class = serializers.AppAccountSecretSerializer
|
|
||||||
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
|
||||||
http_method_names = ['get', 'options']
|
|
||||||
rbac_perms = {
|
|
||||||
'retrieve': 'applications.view_applicationaccountsecret',
|
|
||||||
'list': 'applications.view_applicationaccountsecret',
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
|
|
||||||
from common.tree import TreeNodeSerializer
|
|
||||||
from common.mixins.api import SuggestionMixin
|
|
||||||
from .. import serializers
|
|
||||||
from ..models import Application
|
|
||||||
|
|
||||||
__all__ = ['ApplicationViewSet']
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet):
|
|
||||||
model = Application
|
|
||||||
filterset_fields = {
|
|
||||||
'name': ['exact'],
|
|
||||||
'category': ['exact', 'in'],
|
|
||||||
'type': ['exact', 'in'],
|
|
||||||
}
|
|
||||||
search_fields = ('name', 'type', 'category')
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AppSerializer,
|
|
||||||
'get_tree': TreeNodeSerializer,
|
|
||||||
'suggestion': serializers.MiniAppSerializer
|
|
||||||
}
|
|
||||||
rbac_perms = {
|
|
||||||
'get_tree': 'applications.view_application',
|
|
||||||
'match': 'applications.match_application'
|
|
||||||
}
|
|
||||||
|
|
||||||
@action(methods=['GET'], detail=False, url_path='tree')
|
|
||||||
def get_tree(self, request, *args, **kwargs):
|
|
||||||
show_count = request.query_params.get('show_count', '1') == '1'
|
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
|
||||||
tree_nodes = Application.create_tree_nodes(queryset, show_count=show_count)
|
|
||||||
serializer = self.get_serializer(tree_nodes, many=True)
|
|
||||||
return Response(serializer.data)
|
|
|
@ -1,16 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
|
|
||||||
from orgs.mixins import generics
|
|
||||||
from .. import models
|
|
||||||
from ..serializers import RemoteAppConnectionInfoSerializer
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'RemoteAppConnectionInfoApi',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
|
||||||
model = models.Application
|
|
||||||
serializer_class = RemoteAppConnectionInfoSerializer
|
|
|
@ -1,86 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class AppCategory(models.TextChoices):
|
|
||||||
db = 'db', _('Database')
|
|
||||||
remote_app = 'remote_app', _('Remote app')
|
|
||||||
cloud = 'cloud', 'Cloud'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_label(cls, category):
|
|
||||||
return dict(cls.choices).get(category, '')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_xpack(cls, category):
|
|
||||||
return category in ['remote_app']
|
|
||||||
|
|
||||||
|
|
||||||
class AppType(models.TextChoices):
|
|
||||||
# db category
|
|
||||||
mysql = 'mysql', 'MySQL'
|
|
||||||
mariadb = 'mariadb', 'MariaDB'
|
|
||||||
oracle = 'oracle', 'Oracle'
|
|
||||||
pgsql = 'postgresql', 'PostgreSQL'
|
|
||||||
sqlserver = 'sqlserver', 'SQLServer'
|
|
||||||
redis = 'redis', 'Redis'
|
|
||||||
mongodb = 'mongodb', 'MongoDB'
|
|
||||||
clickhouse = 'clickhouse', 'ClickHouse'
|
|
||||||
|
|
||||||
# remote-app category
|
|
||||||
chrome = 'chrome', 'Chrome'
|
|
||||||
mysql_workbench = 'mysql_workbench', 'MySQL Workbench'
|
|
||||||
vmware_client = 'vmware_client', 'vSphere Client'
|
|
||||||
custom = 'custom', _('Custom')
|
|
||||||
|
|
||||||
# cloud category
|
|
||||||
k8s = 'k8s', 'Kubernetes'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def category_types_mapper(cls):
|
|
||||||
return {
|
|
||||||
AppCategory.db: [
|
|
||||||
cls.mysql, cls.mariadb, cls.oracle, cls.pgsql,
|
|
||||||
cls.sqlserver, cls.redis, cls.mongodb, cls.clickhouse
|
|
||||||
],
|
|
||||||
AppCategory.remote_app: [
|
|
||||||
cls.chrome, cls.mysql_workbench,
|
|
||||||
cls.vmware_client, cls.custom
|
|
||||||
],
|
|
||||||
AppCategory.cloud: [cls.k8s]
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def type_category_mapper(cls):
|
|
||||||
mapper = {}
|
|
||||||
for category, tps in cls.category_types_mapper().items():
|
|
||||||
for tp in tps:
|
|
||||||
mapper[tp] = category
|
|
||||||
return mapper
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_label(cls, tp):
|
|
||||||
return dict(cls.choices).get(tp, '')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def db_types(cls):
|
|
||||||
return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def remote_app_types(cls):
|
|
||||||
return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def cloud_types(cls):
|
|
||||||
return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_xpack(cls, tp):
|
|
||||||
tp_category_mapper = cls.type_category_mapper()
|
|
||||||
category = tp_category_mapper[tp]
|
|
||||||
|
|
||||||
if AppCategory.is_xpack(category):
|
|
||||||
return True
|
|
||||||
return tp in ['oracle', 'postgresql', 'sqlserver', 'clickhouse']
|
|
|
@ -1,14 +0,0 @@
|
||||||
"""
|
|
||||||
jumpserver.__app__.hands.py
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This app depends other apps api, function .. should be import or write mack here.
|
|
||||||
|
|
||||||
Other module of this app shouldn't connect with other app.
|
|
||||||
|
|
||||||
:copyright: (c) 2014-2018 by JumpServer Team.
|
|
||||||
:license: GPL v2, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from users.models import User, UserGroup
|
|
|
@ -71,6 +71,6 @@ class Migration(migrations.Migration):
|
||||||
'verbose_name': 'Account',
|
'verbose_name': 'Account',
|
||||||
'unique_together': {('username', 'app', 'systemuser')},
|
'unique_together': {('username', 'app', 'systemuser')},
|
||||||
},
|
},
|
||||||
bases=(models.Model, assets.models.base.AuthMixin),
|
bases=(models.Model,),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 3.2.12 on 2022-07-14 02:46
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_db_oracle_version_to_attrs(apps, schema_editor):
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
model = apps.get_model("applications", "Application")
|
|
||||||
oracles = list(model.objects.using(db_alias).filter(type='oracle'))
|
|
||||||
for o in oracles:
|
|
||||||
o.attrs['version'] = '12c'
|
|
||||||
model.objects.using(db_alias).bulk_update(oracles, ['attrs'])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('applications', '0021_auto_20220629_1826'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(migrate_db_oracle_version_to_attrs)
|
|
||||||
]
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-08-17 05:46
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_db_oracle_version_to_attrs(apps, schema_editor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
model = apps.get_model("applications", "Application")
|
||||||
|
oracles = list(model.objects.using(db_alias).filter(type='oracle'))
|
||||||
|
for o in oracles:
|
||||||
|
o.attrs['version'] = '12c'
|
||||||
|
model.objects.using(db_alias).bulk_update(oracles, ['attrs'])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('applications', '0021_auto_20220629_1826'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_db_oracle_version_to_attrs),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='account',
|
||||||
|
unique_together=None,
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='account',
|
||||||
|
name='app',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='account',
|
||||||
|
name='systemuser',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='application',
|
||||||
|
name='domain',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalaccount',
|
||||||
|
name='app',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalaccount',
|
||||||
|
name='history_user',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalaccount',
|
||||||
|
name='systemuser',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,5 @@
|
||||||
# Generated by Django 3.1.14 on 2022-07-15 07:56
|
# Generated by Django 3.2.14 on 2022-08-17 09:16
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
@ -40,9 +39,22 @@ def migrate_account_dirty_data(apps, schema_editor):
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('applications', '0022_auto_20220714_1046'),
|
('applications', '0022_auto_20220817_1346'),
|
||||||
|
('perms', '0031_auto_20220816_1600'),
|
||||||
|
('ops', '0022_auto_20220817_1346'),
|
||||||
|
('assets', '0105_auto_20220817_1544'),
|
||||||
|
('tickets', '0020_auto_20220817_1346'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(migrate_account_dirty_data),
|
migrations.RunPython(migrate_account_dirty_data),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Account',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='HistoricalAccount',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='ApplicationUser',
|
||||||
|
),
|
||||||
]
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-08-18 02:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('applications', '0023_auto_20220817_1716'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='application',
|
||||||
|
name='category',
|
||||||
|
field=models.CharField(max_length=16, verbose_name='Category'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='application',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(max_length=16, verbose_name='Type'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
from common.mixins import CommonModelMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Application(CommonModelMixin, OrgModelMixin):
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
|
category = models.CharField(
|
||||||
|
max_length=16, verbose_name=_('Category')
|
||||||
|
)
|
||||||
|
type = models.CharField(
|
||||||
|
max_length=16, verbose_name=_('Type')
|
||||||
|
)
|
||||||
|
attrs = models.JSONField(default=dict, verbose_name=_('Attrs'))
|
||||||
|
comment = models.TextField(
|
||||||
|
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Application')
|
||||||
|
unique_together = [('org_id', 'name')]
|
||||||
|
ordering = ('name',)
|
||||||
|
permissions = [
|
||||||
|
('match_application', _('Can match application')),
|
||||||
|
]
|
|
@ -1,2 +0,0 @@
|
||||||
from .application import *
|
|
||||||
from .account import *
|
|
|
@ -1,110 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from simple_history.models import HistoricalRecords
|
|
||||||
from django.db.models import F
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from common.utils import lazyproperty
|
|
||||||
from assets.models.base import BaseUser
|
|
||||||
|
|
||||||
|
|
||||||
class Account(BaseUser):
|
|
||||||
app = models.ForeignKey(
|
|
||||||
'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Application')
|
|
||||||
)
|
|
||||||
systemuser = models.ForeignKey(
|
|
||||||
'assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")
|
|
||||||
)
|
|
||||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
|
||||||
history = HistoricalRecords()
|
|
||||||
|
|
||||||
auth_attrs = ['username', 'password', 'private_key', 'public_key']
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Application account')
|
|
||||||
unique_together = [('username', 'app', 'systemuser')]
|
|
||||||
permissions = [
|
|
||||||
('view_applicationaccountsecret', _('Can view application account secret')),
|
|
||||||
('change_appplicationaccountsecret', _('Can change application account secret')),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.auth_snapshot = {}
|
|
||||||
|
|
||||||
def get_or_systemuser_attr(self, attr):
|
|
||||||
val = getattr(self, attr, None)
|
|
||||||
if val:
|
|
||||||
return val
|
|
||||||
if self.systemuser:
|
|
||||||
return getattr(self.systemuser, attr, '')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def load_auth(self):
|
|
||||||
for attr in self.auth_attrs:
|
|
||||||
value = self.get_or_systemuser_attr(attr)
|
|
||||||
self.auth_snapshot[attr] = [getattr(self, attr), value]
|
|
||||||
setattr(self, attr, value)
|
|
||||||
|
|
||||||
def unload_auth(self):
|
|
||||||
if not self.systemuser:
|
|
||||||
return
|
|
||||||
|
|
||||||
for attr, values in self.auth_snapshot.items():
|
|
||||||
origin_value, loaded_value = values
|
|
||||||
current_value = getattr(self, attr, '')
|
|
||||||
if current_value == loaded_value:
|
|
||||||
setattr(self, attr, origin_value)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.unload_auth()
|
|
||||||
instance = super().save(*args, **kwargs)
|
|
||||||
self.load_auth()
|
|
||||||
return instance
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def category(self):
|
|
||||||
return self.app.category
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def type(self):
|
|
||||||
return self.app.type
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def attrs(self):
|
|
||||||
return self.app.attrs
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def app_display(self):
|
|
||||||
return self.systemuser.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def username_display(self):
|
|
||||||
return self.get_or_systemuser_attr('username') or ''
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def systemuser_display(self):
|
|
||||||
if not self.systemuser:
|
|
||||||
return ''
|
|
||||||
return str(self.systemuser)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def smart_name(self):
|
|
||||||
username = self.username_display
|
|
||||||
|
|
||||||
if self.app:
|
|
||||||
app = str(self.app)
|
|
||||||
else:
|
|
||||||
app = '*'
|
|
||||||
return '{}@{}'.format(username, app)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_queryset(cls):
|
|
||||||
queryset = cls.objects.all() \
|
|
||||||
.annotate(type=F('app__type')) \
|
|
||||||
.annotate(app_display=F('app__name')) \
|
|
||||||
.annotate(systemuser_display=F('systemuser__name')) \
|
|
||||||
.annotate(category=F('app__category'))
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.smart_name
|
|
|
@ -1,310 +0,0 @@
|
||||||
from collections import defaultdict
|
|
||||||
from urllib.parse import urlencode, parse_qsl
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from orgs.mixins.models import OrgModelMixin
|
|
||||||
from common.mixins import CommonModelMixin
|
|
||||||
from common.tree import TreeNode
|
|
||||||
from common.utils import is_uuid
|
|
||||||
from assets.models import Asset, SystemUser
|
|
||||||
|
|
||||||
from .. import const
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationTreeNodeMixin:
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
type: str
|
|
||||||
category: str
|
|
||||||
attrs: dict
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_tree_id(pid, type, v):
|
|
||||||
i = dict(parse_qsl(pid))
|
|
||||||
i[type] = v
|
|
||||||
tree_id = urlencode(i)
|
|
||||||
return tree_id
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
|
|
||||||
show_empty=True, show_count=True):
|
|
||||||
count = counts.get(c.value, 0)
|
|
||||||
if count == 0 and not show_empty:
|
|
||||||
return None
|
|
||||||
label = c.label
|
|
||||||
if count is not None and show_count:
|
|
||||||
label = '{} ({})'.format(label, count)
|
|
||||||
data = {
|
|
||||||
'id': id_,
|
|
||||||
'name': label,
|
|
||||||
'title': label,
|
|
||||||
'pId': pid,
|
|
||||||
'isParent': bool(count),
|
|
||||||
'open': opened,
|
|
||||||
'iconSkin': '',
|
|
||||||
'meta': {
|
|
||||||
'type': tp,
|
|
||||||
'data': {
|
|
||||||
'name': c.name,
|
|
||||||
'value': c.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TreeNode(**data)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_root_tree_node(cls, queryset, show_count=True):
|
|
||||||
count = queryset.count() if show_count else None
|
|
||||||
root_id = 'applications'
|
|
||||||
root_name = _('Applications')
|
|
||||||
if count is not None and show_count:
|
|
||||||
root_name = '{} ({})'.format(root_name, count)
|
|
||||||
node = TreeNode(**{
|
|
||||||
'id': root_id,
|
|
||||||
'name': root_name,
|
|
||||||
'title': root_name,
|
|
||||||
'pId': '',
|
|
||||||
'isParent': True,
|
|
||||||
'open': True,
|
|
||||||
'iconSkin': '',
|
|
||||||
'meta': {
|
|
||||||
'type': 'applications_root',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return node
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
|
|
||||||
nodes = []
|
|
||||||
categories = const.AppType.category_types_mapper().keys()
|
|
||||||
for category in categories:
|
|
||||||
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
|
|
||||||
continue
|
|
||||||
i = cls.create_tree_id(pid, 'category', category.value)
|
|
||||||
node = cls.create_choice_node(
|
|
||||||
category, i, pid=pid, tp='category',
|
|
||||||
counts=counts, opened=False, show_empty=show_empty,
|
|
||||||
show_count=show_count
|
|
||||||
)
|
|
||||||
if not node:
|
|
||||||
continue
|
|
||||||
nodes.append(node)
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
|
|
||||||
nodes = []
|
|
||||||
temp_pid = pid
|
|
||||||
type_category_mapper = const.AppType.type_category_mapper()
|
|
||||||
types = const.AppType.type_category_mapper().keys()
|
|
||||||
|
|
||||||
for tp in types:
|
|
||||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
|
|
||||||
continue
|
|
||||||
category = type_category_mapper.get(tp)
|
|
||||||
pid = cls.create_tree_id(pid, 'category', category.value)
|
|
||||||
i = cls.create_tree_id(pid, 'type', tp.value)
|
|
||||||
node = cls.create_choice_node(
|
|
||||||
tp, i, pid, tp='type', counts=counts, opened=False,
|
|
||||||
show_empty=show_empty, show_count=show_count
|
|
||||||
)
|
|
||||||
pid = temp_pid
|
|
||||||
if not node:
|
|
||||||
continue
|
|
||||||
nodes.append(node)
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_tree_node_counts(queryset):
|
|
||||||
counts = defaultdict(int)
|
|
||||||
values = queryset.values_list('type', 'category')
|
|
||||||
for i in values:
|
|
||||||
tp = i[0]
|
|
||||||
category = i[1]
|
|
||||||
counts[tp] += 1
|
|
||||||
counts[category] += 1
|
|
||||||
return counts
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
|
|
||||||
counts = cls.get_tree_node_counts(queryset)
|
|
||||||
tree_nodes = []
|
|
||||||
|
|
||||||
# 类别的节点
|
|
||||||
tree_nodes += cls.create_category_tree_nodes(
|
|
||||||
pid, counts, show_empty=show_empty,
|
|
||||||
show_count=show_count
|
|
||||||
)
|
|
||||||
|
|
||||||
# 类型的节点
|
|
||||||
tree_nodes += cls.create_types_tree_nodes(
|
|
||||||
pid, counts, show_empty=show_empty,
|
|
||||||
show_count=show_count
|
|
||||||
)
|
|
||||||
return tree_nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
|
|
||||||
tree_nodes = []
|
|
||||||
|
|
||||||
# 根节点有可能是组织名称
|
|
||||||
if root_node is None:
|
|
||||||
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
|
|
||||||
tree_nodes.append(root_node)
|
|
||||||
|
|
||||||
tree_nodes += cls.create_category_type_tree_nodes(
|
|
||||||
queryset, root_node.id, show_empty=show_empty, show_count=show_count
|
|
||||||
)
|
|
||||||
|
|
||||||
# 应用的节点
|
|
||||||
for app in queryset:
|
|
||||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
|
|
||||||
continue
|
|
||||||
node = app.as_tree_node(root_node.id)
|
|
||||||
tree_nodes.append(node)
|
|
||||||
return tree_nodes
|
|
||||||
|
|
||||||
def create_app_tree_pid(self, root_id):
|
|
||||||
pid = self.create_tree_id(root_id, 'category', self.category)
|
|
||||||
pid = self.create_tree_id(pid, 'type', self.type)
|
|
||||||
return pid
|
|
||||||
|
|
||||||
def as_tree_node(self, pid, k8s_as_tree=False):
|
|
||||||
from ..utils import KubernetesTree
|
|
||||||
if self.type == const.AppType.k8s and k8s_as_tree:
|
|
||||||
node = KubernetesTree(pid).as_tree_node(self)
|
|
||||||
else:
|
|
||||||
node = self._as_tree_node(pid)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def _attrs_to_tree(self):
|
|
||||||
if self.category == const.AppCategory.db:
|
|
||||||
return self.attrs
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _as_tree_node(self, pid):
|
|
||||||
icon_skin_category_mapper = {
|
|
||||||
'remote_app': 'chrome',
|
|
||||||
'db': 'database',
|
|
||||||
'cloud': 'cloud'
|
|
||||||
}
|
|
||||||
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
|
|
||||||
pid = self.create_app_tree_pid(pid)
|
|
||||||
node = TreeNode(**{
|
|
||||||
'id': str(self.id),
|
|
||||||
'name': self.name,
|
|
||||||
'title': self.name,
|
|
||||||
'pId': pid,
|
|
||||||
'isParent': False,
|
|
||||||
'open': False,
|
|
||||||
'iconSkin': icon_skin,
|
|
||||||
'meta': {
|
|
||||||
'type': 'application',
|
|
||||||
'data': {
|
|
||||||
'category': self.category,
|
|
||||||
'type': self.type,
|
|
||||||
'attrs': self._attrs_to_tree()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
|
||||||
APP_TYPE = const.AppType
|
|
||||||
|
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
|
||||||
category = models.CharField(
|
|
||||||
max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category')
|
|
||||||
)
|
|
||||||
type = models.CharField(
|
|
||||||
max_length=16, choices=const.AppType.choices, verbose_name=_('Type')
|
|
||||||
)
|
|
||||||
domain = models.ForeignKey(
|
|
||||||
'assets.Domain', null=True, blank=True, related_name='applications',
|
|
||||||
on_delete=models.SET_NULL, verbose_name=_("Domain"),
|
|
||||||
)
|
|
||||||
attrs = models.JSONField(default=dict, verbose_name=_('Attrs'))
|
|
||||||
comment = models.TextField(
|
|
||||||
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Application')
|
|
||||||
unique_together = [('org_id', 'name')]
|
|
||||||
ordering = ('name',)
|
|
||||||
permissions = [
|
|
||||||
('match_application', _('Can match application')),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
category_display = self.get_category_display()
|
|
||||||
type_display = self.get_type_display()
|
|
||||||
return f'{self.name}({type_display})[{category_display}]'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def category_remote_app(self):
|
|
||||||
return self.category == const.AppCategory.remote_app.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def category_cloud(self):
|
|
||||||
return self.category == const.AppCategory.cloud.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def category_db(self):
|
|
||||||
return self.category == const.AppCategory.db.value
|
|
||||||
|
|
||||||
def is_type(self, tp):
|
|
||||||
return self.type == tp
|
|
||||||
|
|
||||||
def get_rdp_remote_app_setting(self):
|
|
||||||
from applications.serializers.attrs import get_serializer_class_by_application_type
|
|
||||||
if not self.category_remote_app:
|
|
||||||
raise ValueError(f"Not a remote app application: {self.name}")
|
|
||||||
serializer_class = get_serializer_class_by_application_type(self.type)
|
|
||||||
fields = serializer_class().get_fields()
|
|
||||||
|
|
||||||
parameters = [self.type]
|
|
||||||
for field_name in list(fields.keys()):
|
|
||||||
if field_name in ['asset']:
|
|
||||||
continue
|
|
||||||
value = self.attrs.get(field_name)
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
if field_name == 'path':
|
|
||||||
value = '\"%s\"' % value
|
|
||||||
parameters.append(str(value))
|
|
||||||
|
|
||||||
parameters = ' '.join(parameters)
|
|
||||||
return {
|
|
||||||
'program': '||jmservisor',
|
|
||||||
'working_directory': '',
|
|
||||||
'parameters': parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_remote_app_asset(self, raise_exception=True):
|
|
||||||
asset_id = self.attrs.get('asset')
|
|
||||||
if is_uuid(asset_id):
|
|
||||||
return Asset.objects.filter(id=asset_id).first()
|
|
||||||
if raise_exception:
|
|
||||||
raise ValueError("Remote App not has asset attr")
|
|
||||||
|
|
||||||
def get_target_ip(self):
|
|
||||||
target_ip = ''
|
|
||||||
if self.category_remote_app:
|
|
||||||
asset = self.get_remote_app_asset()
|
|
||||||
target_ip = asset.ip if asset else target_ip
|
|
||||||
elif self.category_cloud:
|
|
||||||
target_ip = self.attrs.get('cluster')
|
|
||||||
elif self.category_db:
|
|
||||||
target_ip = self.attrs.get('host')
|
|
||||||
return target_ip
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationUser(SystemUser):
|
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
||||||
verbose_name = _('Application user')
|
|
|
@ -1,9 +0,0 @@
|
||||||
from rest_framework import permissions
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['IsRemoteApp']
|
|
||||||
|
|
||||||
|
|
||||||
class IsRemoteApp(permissions.BasePermission):
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
return obj.category_remote_app
|
|
|
@ -1,2 +0,0 @@
|
||||||
from .application import *
|
|
||||||
from .remote_app import *
|
|
|
@ -1,184 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
|
||||||
from assets.serializers.base import AuthSerializerMixin
|
|
||||||
from common.drf.serializers import MethodSerializer, SecretReadableMixin
|
|
||||||
from .attrs import (
|
|
||||||
category_serializer_classes_mapping,
|
|
||||||
type_serializer_classes_mapping,
|
|
||||||
type_secret_serializer_classes_mapping
|
|
||||||
)
|
|
||||||
from .. import models
|
|
||||||
from .. import const
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'AppSerializer', 'MiniAppSerializer', 'AppSerializerMixin',
|
|
||||||
'AppAccountSerializer', 'AppAccountSecretSerializer', 'AppAccountBackUpSerializer'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AppSerializerMixin(serializers.Serializer):
|
|
||||||
attrs = MethodSerializer()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def app(self):
|
|
||||||
if isinstance(self.instance, models.Application):
|
|
||||||
instance = self.instance
|
|
||||||
else:
|
|
||||||
instance = None
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def get_attrs_serializer(self):
|
|
||||||
instance = self.app
|
|
||||||
tp = getattr(self, 'tp', None)
|
|
||||||
default_serializer = serializers.Serializer(read_only=True)
|
|
||||||
if not tp:
|
|
||||||
if instance:
|
|
||||||
tp = instance.type
|
|
||||||
category = instance.category
|
|
||||||
else:
|
|
||||||
tp = self.context['request'].query_params.get('type')
|
|
||||||
category = self.context['request'].query_params.get('category')
|
|
||||||
if tp:
|
|
||||||
if isinstance(self, AppAccountBackUpSerializer):
|
|
||||||
serializer_class = type_secret_serializer_classes_mapping.get(tp)
|
|
||||||
else:
|
|
||||||
serializer_class = type_serializer_classes_mapping.get(tp)
|
|
||||||
elif category:
|
|
||||||
serializer_class = category_serializer_classes_mapping.get(category)
|
|
||||||
else:
|
|
||||||
serializer_class = default_serializer
|
|
||||||
|
|
||||||
if not serializer_class:
|
|
||||||
serializer_class = default_serializer
|
|
||||||
|
|
||||||
if isinstance(serializer_class, type):
|
|
||||||
serializer = serializer_class()
|
|
||||||
else:
|
|
||||||
serializer = serializer_class
|
|
||||||
return serializer
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
return super().create(validated_data)
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
return super().update(instance, validated_data)
|
|
||||||
|
|
||||||
|
|
||||||
class AppSerializer(AppSerializerMixin, BulkOrgResourceModelSerializer):
|
|
||||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display'))
|
|
||||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Application
|
|
||||||
fields_mini = ['id', 'name']
|
|
||||||
fields_small = fields_mini + [
|
|
||||||
'category', 'category_display', 'type', 'type_display',
|
|
||||||
'attrs', 'date_created', 'date_updated', 'created_by', 'comment'
|
|
||||||
]
|
|
||||||
fields_fk = ['domain']
|
|
||||||
fields = fields_small + fields_fk
|
|
||||||
read_only_fields = [
|
|
||||||
'created_by', 'date_created', 'date_updated', 'get_type_display',
|
|
||||||
]
|
|
||||||
|
|
||||||
def validate_attrs(self, attrs):
|
|
||||||
_attrs = self.instance.attrs if self.instance else {}
|
|
||||||
_attrs.update(attrs)
|
|
||||||
return _attrs
|
|
||||||
|
|
||||||
|
|
||||||
class MiniAppSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = models.Application
|
|
||||||
fields = AppSerializer.Meta.fields_mini
|
|
||||||
|
|
||||||
|
|
||||||
class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|
||||||
category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True)
|
|
||||||
category_display = serializers.SerializerMethodField(label=_('Category display'))
|
|
||||||
type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True)
|
|
||||||
type_display = serializers.SerializerMethodField(label=_('Type display'))
|
|
||||||
date_created = serializers.DateTimeField(label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True)
|
|
||||||
date_updated = serializers.DateTimeField(label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True)
|
|
||||||
|
|
||||||
category_mapper = dict(const.AppCategory.choices)
|
|
||||||
type_mapper = dict(const.AppType.choices)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Account
|
|
||||||
fields_mini = ['id', 'username', 'version']
|
|
||||||
fields_write_only = ['password', 'private_key', 'public_key', 'passphrase']
|
|
||||||
fields_other = ['date_created', 'date_updated']
|
|
||||||
fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display']
|
|
||||||
fields = fields_mini + fields_fk + fields_write_only + fields_other + [
|
|
||||||
'type', 'type_display', 'category', 'category_display', 'attrs'
|
|
||||||
]
|
|
||||||
extra_kwargs = {
|
|
||||||
'username': {'default': '', 'required': False},
|
|
||||||
'password': {'write_only': True},
|
|
||||||
'app_display': {'label': _('Application display')},
|
|
||||||
'systemuser_display': {'label': _('System User')},
|
|
||||||
'account': {'label': _('account')}
|
|
||||||
}
|
|
||||||
use_model_bulk_create = True
|
|
||||||
model_bulk_create_kwargs = {
|
|
||||||
'ignore_conflicts': True
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def app(self):
|
|
||||||
if isinstance(self.instance, models.Account):
|
|
||||||
instance = self.instance.app
|
|
||||||
else:
|
|
||||||
instance = None
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def get_category_display(self, obj):
|
|
||||||
return self.category_mapper.get(obj.category)
|
|
||||||
|
|
||||||
def get_type_display(self, obj):
|
|
||||||
return self.type_mapper.get(obj.type)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setup_eager_loading(cls, queryset):
|
|
||||||
""" Perform necessary eager loading of data. """
|
|
||||||
queryset = queryset.prefetch_related('systemuser', 'app')
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def to_representation(self, instance):
|
|
||||||
instance.load_auth()
|
|
||||||
return super().to_representation(instance)
|
|
||||||
|
|
||||||
|
|
||||||
class AppAccountSecretSerializer(SecretReadableMixin, AppAccountSerializer):
|
|
||||||
class Meta(AppAccountSerializer.Meta):
|
|
||||||
extra_kwargs = {
|
|
||||||
'password': {'write_only': False},
|
|
||||||
'private_key': {'write_only': False},
|
|
||||||
'public_key': {'write_only': False},
|
|
||||||
'app_display': {'label': _('Application display')},
|
|
||||||
'systemuser_display': {'label': _('System User')}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AppAccountBackUpSerializer(AppAccountSecretSerializer):
|
|
||||||
class Meta(AppAccountSecretSerializer.Meta):
|
|
||||||
fields = [
|
|
||||||
'id', 'app_display', 'attrs', 'username', 'password', 'private_key',
|
|
||||||
'public_key', 'date_created', 'date_updated', 'version'
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.tp = kwargs.pop('tp', None)
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setup_eager_loading(cls, queryset):
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def to_representation(self, instance):
|
|
||||||
return super(AppAccountSerializer, self).to_representation(instance)
|
|
|
@ -1 +0,0 @@
|
||||||
from .attrs import *
|
|
|
@ -1,3 +0,0 @@
|
||||||
from .remote_app import *
|
|
||||||
from .db import *
|
|
||||||
from .cloud import *
|
|
|
@ -1,8 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
__all__ = ['CloudSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class CloudSerializer(serializers.Serializer):
|
|
||||||
cluster = serializers.CharField(max_length=1024, label=_('Cluster'), allow_null=True)
|
|
|
@ -1,26 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['DBSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class DBSerializer(serializers.Serializer):
|
|
||||||
host = serializers.CharField(max_length=128, label=_('Host'), allow_null=True)
|
|
||||||
port = serializers.IntegerField(label=_('Port'), allow_null=True)
|
|
||||||
database = serializers.CharField(
|
|
||||||
max_length=128, required=True, allow_null=True, label=_('Database')
|
|
||||||
)
|
|
||||||
use_ssl = serializers.BooleanField(default=False, label=_('Use SSL'))
|
|
||||||
ca_cert = serializers.CharField(
|
|
||||||
required=False, allow_null=True, label=_('CA certificate')
|
|
||||||
)
|
|
||||||
client_cert = serializers.CharField(
|
|
||||||
required=False, allow_null=True, label=_('Client certificate file')
|
|
||||||
)
|
|
||||||
cert_key = serializers.CharField(
|
|
||||||
required=False, allow_null=True, label=_('Certificate key file')
|
|
||||||
)
|
|
||||||
allow_invalid_cert = serializers.BooleanField(default=False, label=_('Allow invalid cert'))
|
|
|
@ -1,60 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
|
|
||||||
from common.utils import get_logger, is_uuid, get_object_or_none
|
|
||||||
from assets.models import Asset
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
__all__ = ['RemoteAppSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class ExistAssetPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
|
||||||
instance = super().to_internal_value(data)
|
|
||||||
return str(instance.id)
|
|
||||||
|
|
||||||
def to_representation(self, _id):
|
|
||||||
# _id 是 instance.id
|
|
||||||
if self.pk_field is not None:
|
|
||||||
return self.pk_field.to_representation(_id)
|
|
||||||
# 解决删除资产后,远程应用更新页面会显示资产ID的问题
|
|
||||||
asset = get_object_or_none(Asset, id=_id)
|
|
||||||
if not asset:
|
|
||||||
return None
|
|
||||||
return _id
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteAppSerializer(serializers.Serializer):
|
|
||||||
asset_info = serializers.SerializerMethodField(label=_('Asset Info'))
|
|
||||||
asset = ExistAssetPrimaryKeyRelatedField(
|
|
||||||
queryset=Asset.objects, required=True, label=_("Asset"), allow_null=True
|
|
||||||
)
|
|
||||||
path = serializers.CharField(
|
|
||||||
max_length=128, label=_('Application path'), allow_null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_asset(self, asset):
|
|
||||||
if not asset:
|
|
||||||
raise serializers.ValidationError(_('This field is required.'))
|
|
||||||
return asset
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_asset_info(obj):
|
|
||||||
asset_id = obj.get('asset')
|
|
||||||
if not asset_id or not is_uuid(asset_id):
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
asset = Asset.objects.get(id=str(asset_id))
|
|
||||||
except ObjectDoesNotExist as e:
|
|
||||||
logger.error(e)
|
|
||||||
return {}
|
|
||||||
if not asset:
|
|
||||||
return {}
|
|
||||||
asset_info = {'id': str(asset.id), 'hostname': asset.hostname}
|
|
||||||
return asset_info
|
|
|
@ -1,16 +0,0 @@
|
||||||
|
|
||||||
from .mysql import *
|
|
||||||
from .mariadb import *
|
|
||||||
from .oracle import *
|
|
||||||
from .pgsql import *
|
|
||||||
from .sqlserver import *
|
|
||||||
from .redis import *
|
|
||||||
from .mongodb import *
|
|
||||||
from .clickhouse import *
|
|
||||||
|
|
||||||
from .chrome import *
|
|
||||||
from .mysql_workbench import *
|
|
||||||
from .vmware_client import *
|
|
||||||
from .custom import *
|
|
||||||
|
|
||||||
from .k8s import *
|
|
|
@ -1,34 +0,0 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
|
||||||
from ..application_category import RemoteAppSerializer
|
|
||||||
|
|
||||||
__all__ = ['ChromeSerializer', 'ChromeSecretSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class ChromeSerializer(RemoteAppSerializer):
|
|
||||||
CHROME_PATH = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
|
|
||||||
|
|
||||||
path = serializers.CharField(
|
|
||||||
max_length=128, label=_('Application path'), default=CHROME_PATH, allow_null=True,
|
|
||||||
)
|
|
||||||
chrome_target = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Target URL'), allow_null=True,
|
|
||||||
)
|
|
||||||
chrome_username = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Chrome username'), allow_null=True,
|
|
||||||
)
|
|
||||||
chrome_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Chrome password'), allow_null=True, encrypted_key='chrome_password'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ChromeSecretSerializer(ChromeSerializer):
|
|
||||||
chrome_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Chrome password'), allow_null=True, write_only=False
|
|
||||||
)
|
|
|
@ -1,33 +0,0 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
|
||||||
from ..application_category import RemoteAppSerializer
|
|
||||||
|
|
||||||
__all__ = ['CustomSerializer', 'CustomSecretSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSerializer(RemoteAppSerializer):
|
|
||||||
custom_cmdline = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Operating parameter'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
custom_target = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Target url'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
custom_username = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Custom Username'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
custom_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Custom password'), allow_null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSecretSerializer(RemoteAppSerializer):
|
|
||||||
custom_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False, write_only=False,
|
|
||||||
label=_('Custom password'), allow_null=True,
|
|
||||||
)
|
|
|
@ -1,7 +0,0 @@
|
||||||
from ..application_category import CloudSerializer
|
|
||||||
|
|
||||||
__all__ = ['K8SSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class K8SSerializer(CloudSerializer):
|
|
||||||
pass
|
|
|
@ -1,7 +0,0 @@
|
||||||
from .mysql import MySQLSerializer
|
|
||||||
|
|
||||||
__all__ = ['MariaDBSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class MariaDBSerializer(MySQLSerializer):
|
|
||||||
pass
|
|
|
@ -1,11 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ..application_category import DBSerializer
|
|
||||||
|
|
||||||
__all__ = ['MongoDBSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class MongoDBSerializer(DBSerializer):
|
|
||||||
port = serializers.IntegerField(default=27017, label=_('Port'), allow_null=True)
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ..application_category import DBSerializer
|
|
||||||
|
|
||||||
__all__ = ['MySQLSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class MySQLSerializer(DBSerializer):
|
|
||||||
port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True)
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
|
||||||
from ..application_category import RemoteAppSerializer
|
|
||||||
|
|
||||||
__all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class MySQLWorkbenchSerializer(RemoteAppSerializer):
|
|
||||||
MYSQL_WORKBENCH_PATH = 'C:\Program Files\MySQL\MySQL Workbench 8.0 CE\MySQLWorkbench.exe'
|
|
||||||
|
|
||||||
path = serializers.CharField(
|
|
||||||
max_length=128, label=_('Application path'), default=MYSQL_WORKBENCH_PATH,
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
mysql_workbench_ip = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('IP'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
mysql_workbench_port = serializers.IntegerField(
|
|
||||||
required=False, label=_('Port'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
mysql_workbench_name = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Database'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
mysql_workbench_username = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'),
|
|
||||||
allow_null=True,
|
|
||||||
)
|
|
||||||
mysql_workbench_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Mysql workbench password'), allow_null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MySQLWorkbenchSecretSerializer(RemoteAppSerializer):
|
|
||||||
mysql_workbench_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False, write_only=False,
|
|
||||||
label=_('Mysql workbench password'), allow_null=True,
|
|
||||||
)
|
|
|
@ -1,10 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ..application_category import DBSerializer
|
|
||||||
|
|
||||||
__all__ = ['OracleSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class OracleSerializer(DBSerializer):
|
|
||||||
port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True)
|
|
|
@ -1,10 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ..application_category import DBSerializer
|
|
||||||
|
|
||||||
__all__ = ['PostgreSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class PostgreSerializer(DBSerializer):
|
|
||||||
port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True)
|
|
|
@ -1,11 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ..application_category import DBSerializer
|
|
||||||
|
|
||||||
__all__ = ['RedisSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class RedisSerializer(DBSerializer):
|
|
||||||
port = serializers.IntegerField(default=6379, label=_('Port'), allow_null=True)
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ..application_category import DBSerializer
|
|
||||||
|
|
||||||
__all__ = ['SQLServerSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class SQLServerSerializer(DBSerializer):
|
|
||||||
port = serializers.IntegerField(default=1433, label=_('Port'), allow_null=True)
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
|
||||||
from ..application_category import RemoteAppSerializer
|
|
||||||
|
|
||||||
__all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class VMwareClientSerializer(RemoteAppSerializer):
|
|
||||||
PATH = r'''
|
|
||||||
C:\Program Files (x86)\VMware\Infrastructure\Virtual Infrastructure Client\Launcher\VpxClient
|
|
||||||
.exe
|
|
||||||
'''
|
|
||||||
VMWARE_CLIENT_PATH = ''.join(PATH.split())
|
|
||||||
|
|
||||||
path = serializers.CharField(
|
|
||||||
max_length=128, label=_('Application path'), default=VMWARE_CLIENT_PATH,
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
vmware_target = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Target URL'),
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
vmware_username = serializers.CharField(
|
|
||||||
max_length=128, allow_blank=True, required=False, label=_('Vmware username'),
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
vmware_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False,
|
|
||||||
label=_('Vmware password'), allow_null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VMwareClientSecretSerializer(RemoteAppSerializer):
|
|
||||||
vmware_password = EncryptedField(
|
|
||||||
max_length=128, allow_blank=True, required=False, write_only=False,
|
|
||||||
label=_('Vmware password'), allow_null=True
|
|
||||||
)
|
|
|
@ -1,63 +0,0 @@
|
||||||
import copy
|
|
||||||
|
|
||||||
from applications import const
|
|
||||||
from . import application_category, application_type
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'category_serializer_classes_mapping',
|
|
||||||
'type_serializer_classes_mapping',
|
|
||||||
'get_serializer_class_by_application_type',
|
|
||||||
'type_secret_serializer_classes_mapping'
|
|
||||||
]
|
|
||||||
|
|
||||||
# define `attrs` field `category serializers mapping`
|
|
||||||
# ---------------------------------------------------
|
|
||||||
|
|
||||||
category_serializer_classes_mapping = {
|
|
||||||
const.AppCategory.db.value: application_category.DBSerializer,
|
|
||||||
const.AppCategory.remote_app.value: application_category.RemoteAppSerializer,
|
|
||||||
const.AppCategory.cloud.value: application_category.CloudSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
# define `attrs` field `type serializers mapping`
|
|
||||||
# -----------------------------------------------
|
|
||||||
|
|
||||||
type_serializer_classes_mapping = {
|
|
||||||
# db
|
|
||||||
const.AppType.mysql.value: application_type.MySQLSerializer,
|
|
||||||
const.AppType.mariadb.value: application_type.MariaDBSerializer,
|
|
||||||
const.AppType.oracle.value: application_type.OracleSerializer,
|
|
||||||
const.AppType.pgsql.value: application_type.PostgreSerializer,
|
|
||||||
const.AppType.sqlserver.value: application_type.SQLServerSerializer,
|
|
||||||
const.AppType.redis.value: application_type.RedisSerializer,
|
|
||||||
const.AppType.mongodb.value: application_type.MongoDBSerializer,
|
|
||||||
const.AppType.clickhouse.value: application_type.ClickHouseSerializer,
|
|
||||||
# cloud
|
|
||||||
const.AppType.k8s.value: application_type.K8SSerializer
|
|
||||||
}
|
|
||||||
|
|
||||||
remote_app_serializer_classes_mapping = {
|
|
||||||
# remote-app
|
|
||||||
const.AppType.chrome.value: application_type.ChromeSerializer,
|
|
||||||
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
|
|
||||||
const.AppType.vmware_client.value: application_type.VMwareClientSerializer,
|
|
||||||
const.AppType.custom.value: application_type.CustomSerializer
|
|
||||||
}
|
|
||||||
|
|
||||||
type_serializer_classes_mapping.update(remote_app_serializer_classes_mapping)
|
|
||||||
|
|
||||||
remote_app_secret_serializer_classes_mapping = {
|
|
||||||
# remote-app
|
|
||||||
const.AppType.chrome.value: application_type.ChromeSecretSerializer,
|
|
||||||
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSecretSerializer,
|
|
||||||
const.AppType.vmware_client.value: application_type.VMwareClientSecretSerializer,
|
|
||||||
const.AppType.custom.value: application_type.CustomSecretSerializer
|
|
||||||
}
|
|
||||||
|
|
||||||
type_secret_serializer_classes_mapping = copy.deepcopy(type_serializer_classes_mapping)
|
|
||||||
|
|
||||||
type_secret_serializer_classes_mapping.update(remote_app_secret_serializer_classes_mapping)
|
|
||||||
|
|
||||||
|
|
||||||
def get_serializer_class_by_application_type(_application_type):
|
|
||||||
return type_serializer_classes_mapping.get(_application_type)
|
|
|
@ -1,31 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
from rest_framework import serializers
|
|
||||||
from common.utils import get_logger
|
|
||||||
from ..models import Application
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['RemoteAppConnectionInfoSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
|
||||||
parameter_remote_app = serializers.SerializerMethodField()
|
|
||||||
asset = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Application
|
|
||||||
fields = [
|
|
||||||
'id', 'name', 'asset', 'parameter_remote_app',
|
|
||||||
]
|
|
||||||
read_only_fields = ['parameter_remote_app']
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_asset(obj):
|
|
||||||
return obj.attrs.get('asset')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameter_remote_app(obj):
|
|
||||||
return obj.get_rdp_remote_app_setting()
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,7 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# coding:utf-8
|
|
||||||
#
|
|
||||||
from django.urls import path
|
|
||||||
from rest_framework_bulk.routes import BulkRouter
|
|
||||||
from .. import api
|
|
||||||
|
|
||||||
|
|
||||||
app_name = 'applications'
|
|
||||||
|
|
||||||
|
|
||||||
router = BulkRouter()
|
|
||||||
router.register(r'applications', api.ApplicationViewSet, 'application')
|
|
||||||
router.register(r'accounts', api.ApplicationAccountViewSet, 'application-account')
|
|
||||||
router.register(r'system-users-apps-relations', api.SystemUserAppRelationViewSet, 'system-users-apps-relation')
|
|
||||||
router.register(r'account-secrets', api.ApplicationAccountSecretViewSet, 'application-account-secret')
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
|
|
||||||
# path('accounts/', api.ApplicationAccountViewSet.as_view(), name='application-account'),
|
|
||||||
# path('account-secrets/', api.ApplicationAccountSecretViewSet.as_view(), name='application-account-secret')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns += router.urls
|
|
|
@ -1,4 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
from .kubernetes_util import *
|
|
|
@ -1,186 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from urllib3.exceptions import MaxRetryError
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
from kubernetes.client import api_client
|
|
||||||
from kubernetes.client.api import core_v1_api
|
|
||||||
from kubernetes import client
|
|
||||||
from kubernetes.client.exceptions import ApiException
|
|
||||||
|
|
||||||
from rest_framework.generics import get_object_or_404
|
|
||||||
|
|
||||||
from common.utils import get_logger
|
|
||||||
from common.tree import TreeNode
|
|
||||||
from assets.models import SystemUser
|
|
||||||
|
|
||||||
from .. import const
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
class KubernetesClient:
|
|
||||||
def __init__(self, url, token):
|
|
||||||
self.url = url
|
|
||||||
self.token = token
|
|
||||||
|
|
||||||
def get_api(self):
|
|
||||||
configuration = client.Configuration()
|
|
||||||
configuration.host = self.url
|
|
||||||
configuration.verify_ssl = False
|
|
||||||
configuration.api_key = {"authorization": "Bearer " + self.token}
|
|
||||||
c = api_client.ApiClient(configuration=configuration)
|
|
||||||
api = core_v1_api.CoreV1Api(c)
|
|
||||||
return api
|
|
||||||
|
|
||||||
def get_namespace_list(self):
|
|
||||||
api = self.get_api()
|
|
||||||
namespace_list = []
|
|
||||||
for ns in api.list_namespace().items:
|
|
||||||
namespace_list.append(ns.metadata.name)
|
|
||||||
return namespace_list
|
|
||||||
|
|
||||||
def get_services(self):
|
|
||||||
api = self.get_api()
|
|
||||||
ret = api.list_service_for_all_namespaces(watch=False)
|
|
||||||
for i in ret.items:
|
|
||||||
print("%s \t%s \t%s \t%s \t%s \n" % (
|
|
||||||
i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports))
|
|
||||||
|
|
||||||
def get_pod_info(self, namespace, pod):
|
|
||||||
api = self.get_api()
|
|
||||||
resp = api.read_namespaced_pod(namespace=namespace, name=pod)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def get_pod_logs(self, namespace, pod):
|
|
||||||
api = self.get_api()
|
|
||||||
log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200)
|
|
||||||
return log_content
|
|
||||||
|
|
||||||
def get_pods(self):
|
|
||||||
api = self.get_api()
|
|
||||||
try:
|
|
||||||
ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3))
|
|
||||||
except MaxRetryError:
|
|
||||||
logger.warning('Kubernetes connection timed out')
|
|
||||||
return
|
|
||||||
except ApiException as e:
|
|
||||||
if e.status == 401:
|
|
||||||
logger.warning('Kubernetes User not authenticated')
|
|
||||||
else:
|
|
||||||
logger.warning(e)
|
|
||||||
return
|
|
||||||
data = {}
|
|
||||||
for i in ret.items:
|
|
||||||
namespace = i.metadata.namespace
|
|
||||||
pod_info = {
|
|
||||||
'pod_name': i.metadata.name,
|
|
||||||
'containers': [j.name for j in i.spec.containers]
|
|
||||||
}
|
|
||||||
if namespace in data:
|
|
||||||
data[namespace].append(pod_info)
|
|
||||||
else:
|
|
||||||
data[namespace] = [pod_info, ]
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_kubernetes_data(app_id, system_user_id):
|
|
||||||
from ..models import Application
|
|
||||||
app = get_object_or_404(Application, id=app_id)
|
|
||||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
|
||||||
k8s = KubernetesClient(app.attrs['cluster'], system_user.token)
|
|
||||||
return k8s.get_pods()
|
|
||||||
|
|
||||||
|
|
||||||
class KubernetesTree:
|
|
||||||
def __init__(self, tree_id):
|
|
||||||
self.tree_id = tree_id
|
|
||||||
|
|
||||||
def as_tree_node(self, app):
|
|
||||||
pid = app.create_app_tree_pid(self.tree_id)
|
|
||||||
app_id = str(app.id)
|
|
||||||
parent_info = {'app_id': app_id}
|
|
||||||
node = self.create_tree_node(
|
|
||||||
app_id, pid, app.name, 'k8s', parent_info
|
|
||||||
)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def as_system_user_tree_node(self, system_user, parent_info):
|
|
||||||
from ..models import ApplicationTreeNodeMixin
|
|
||||||
system_user_id = str(system_user.id)
|
|
||||||
username = system_user.username
|
|
||||||
username = username if username else '*'
|
|
||||||
name = f'{system_user.name}({username})'
|
|
||||||
pid = urlencode({'app_id': self.tree_id})
|
|
||||||
i = ApplicationTreeNodeMixin.create_tree_id(pid, 'system_user_id', system_user_id)
|
|
||||||
parent_info.update({'system_user_id': system_user_id})
|
|
||||||
node = self.create_tree_node(
|
|
||||||
i, pid, name, 'system_user', parent_info, icon='user-tie'
|
|
||||||
)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def as_namespace_pod_tree_node(self, name, meta, type, counts=0, is_container=False):
|
|
||||||
from ..models import ApplicationTreeNodeMixin
|
|
||||||
i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name)
|
|
||||||
meta.update({type: name})
|
|
||||||
name = name if is_container else f'{name}({counts})'
|
|
||||||
node = self.create_tree_node(
|
|
||||||
i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container
|
|
||||||
)
|
|
||||||
return node
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_tree_node(id_, pid, name, identity, parent_info, icon='', is_container=False):
|
|
||||||
node = TreeNode(**{
|
|
||||||
'id': id_,
|
|
||||||
'name': name,
|
|
||||||
'title': name,
|
|
||||||
'pId': pid,
|
|
||||||
'isParent': not is_container,
|
|
||||||
'open': False,
|
|
||||||
'iconSkin': icon,
|
|
||||||
'parentInfo': urlencode(parent_info),
|
|
||||||
'meta': {
|
|
||||||
'type': 'application',
|
|
||||||
'data': {
|
|
||||||
'category': const.AppCategory.cloud,
|
|
||||||
'type': const.AppType.k8s,
|
|
||||||
'identity': identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return node
|
|
||||||
|
|
||||||
def async_tree_node(self, parent_info):
|
|
||||||
pod_name = parent_info.get('pod')
|
|
||||||
app_id = parent_info.get('app_id')
|
|
||||||
namespace = parent_info.get('namespace')
|
|
||||||
system_user_id = parent_info.get('system_user_id')
|
|
||||||
|
|
||||||
tree_nodes = []
|
|
||||||
data = KubernetesClient.get_kubernetes_data(app_id, system_user_id)
|
|
||||||
if not data:
|
|
||||||
return tree_nodes
|
|
||||||
|
|
||||||
if pod_name:
|
|
||||||
for container in next(
|
|
||||||
filter(
|
|
||||||
lambda x: x['pod_name'] == pod_name, data[namespace]
|
|
||||||
)
|
|
||||||
)['containers']:
|
|
||||||
container_node = self.as_namespace_pod_tree_node(
|
|
||||||
container, parent_info, 'container', is_container=True
|
|
||||||
)
|
|
||||||
tree_nodes.append(container_node)
|
|
||||||
elif namespace:
|
|
||||||
for pod in data[namespace]:
|
|
||||||
pod_nodes = self.as_namespace_pod_tree_node(
|
|
||||||
pod['pod_name'], parent_info, 'pod', len(pod['containers'])
|
|
||||||
)
|
|
||||||
tree_nodes.append(pod_nodes)
|
|
||||||
elif system_user_id:
|
|
||||||
for namespace, pods in data.items():
|
|
||||||
namespace_node = self.as_namespace_pod_tree_node(
|
|
||||||
namespace, parent_info, 'namespace', len(pods)
|
|
||||||
)
|
|
||||||
tree_nodes.append(namespace_node)
|
|
||||||
return tree_nodes
|
|
|
@ -1,14 +1,11 @@
|
||||||
from .mixin import *
|
from .mixin import *
|
||||||
from .admin_user import *
|
from .category import *
|
||||||
|
from .platform import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .label import *
|
from .label import *
|
||||||
from .system_user import *
|
from .account import *
|
||||||
from .system_user_relation import *
|
|
||||||
from .accounts import *
|
|
||||||
from .node import *
|
from .node import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .cmd_filter import *
|
from .automations import *
|
||||||
from .gathered_user import *
|
from .gathered_user import *
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
from .account_backup import *
|
|
||||||
from .account_history import *
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .account import *
|
||||||
|
from .backup import *
|
||||||
|
from .template import *
|
|
@ -0,0 +1,97 @@
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.generics import CreateAPIView, ListAPIView
|
||||||
|
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
|
from common.mixins import RecordViewLogMixin
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
|
from authentication.const import ConfirmType
|
||||||
|
from assets.models import Account
|
||||||
|
from assets.filters import AccountFilterSet
|
||||||
|
from assets.tasks import verify_accounts_connectivity
|
||||||
|
from assets import serializers
|
||||||
|
|
||||||
|
__all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI']
|
||||||
|
|
||||||
|
|
||||||
|
class AccountViewSet(OrgBulkModelViewSet):
|
||||||
|
model = Account
|
||||||
|
search_fields = ('username', 'asset__address', 'name')
|
||||||
|
filterset_class = AccountFilterSet
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.AccountSerializer,
|
||||||
|
'verify': serializers.AssetTaskSerializer
|
||||||
|
}
|
||||||
|
rbac_perms = {
|
||||||
|
'verify': 'assets.test_account',
|
||||||
|
'partial_update': 'assets.change_accountsecret',
|
||||||
|
}
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=True, url_path='verify')
|
||||||
|
def verify_account(self, request, *args, **kwargs):
|
||||||
|
account = super().get_object()
|
||||||
|
account_ids = [account.id]
|
||||||
|
asset_ids = [account.asset_id]
|
||||||
|
task = verify_accounts_connectivity.delay(account_ids, asset_ids)
|
||||||
|
return Response(data={'task': task.id})
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
||||||
|
"""
|
||||||
|
因为可能要导出所有账号,所以单独建立了一个 viewset
|
||||||
|
"""
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.AccountSecretSerializer,
|
||||||
|
}
|
||||||
|
http_method_names = ['get', 'options']
|
||||||
|
# Todo: 记得打开
|
||||||
|
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
|
rbac_perms = {
|
||||||
|
'list': 'assets.view_accountsecret',
|
||||||
|
'retrieve': 'assets.view_accountsecret',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
|
||||||
|
model = Account.history.model
|
||||||
|
serializer_class = serializers.AccountHistorySerializer
|
||||||
|
http_method_names = ['get', 'options']
|
||||||
|
# Todo: 记得打开
|
||||||
|
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
|
rbac_perms = {
|
||||||
|
'list': 'assets.view_accountsecret',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.filter(id=self.kwargs.get('pk'))
|
||||||
|
|
||||||
|
|
||||||
|
class AccountTaskCreateAPI(CreateAPIView):
|
||||||
|
serializer_class = serializers.AccountTaskSerializer
|
||||||
|
search_fields = AccountViewSet.search_fields
|
||||||
|
filterset_class = AccountViewSet.filterset_class
|
||||||
|
|
||||||
|
def check_permissions(self, request):
|
||||||
|
return request.user.has_perm('assets.test_assetconnectivity')
|
||||||
|
|
||||||
|
def get_accounts(self):
|
||||||
|
queryset = Account.objects.all()
|
||||||
|
queryset = self.filter_queryset(queryset)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
accounts = self.get_accounts()
|
||||||
|
account_ids = accounts.values_list('id', flat=True)
|
||||||
|
asset_ids = [account.asset_id for account in accounts]
|
||||||
|
task = verify_accounts_connectivity.delay(account_ids, asset_ids)
|
||||||
|
data = getattr(serializer, '_data', {})
|
||||||
|
data["task"] = task.id
|
||||||
|
setattr(serializer, '_data', data)
|
||||||
|
return task
|
||||||
|
|
||||||
|
def get_exception_handler(self):
|
||||||
|
def handler(e, context):
|
||||||
|
return Response({"error": str(e)}, status=400)
|
||||||
|
return handler
|
|
@ -4,9 +4,10 @@ from rest_framework import status, viewsets
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from .. import serializers
|
from common.const.choices import Trigger
|
||||||
from ..tasks import execute_account_backup_plan
|
from assets import serializers
|
||||||
from ..models import (
|
from assets.tasks import execute_account_backup_plan
|
||||||
|
from assets.models import (
|
||||||
AccountBackupPlan, AccountBackupPlanExecution
|
AccountBackupPlan, AccountBackupPlanExecution
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,9 +39,7 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
pid = serializer.data.get('plan')
|
pid = serializer.data.get('plan')
|
||||||
task = execute_account_backup_plan.delay(
|
task = execute_account_backup_plan.delay(pid=pid, trigger=Trigger.manual)
|
||||||
pid=pid, trigger=AccountBackupPlanExecution.Trigger.manual
|
|
||||||
)
|
|
||||||
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
|
@ -0,0 +1,29 @@
|
||||||
|
from assets import serializers
|
||||||
|
from assets.models import AccountTemplate
|
||||||
|
from rbac.permissions import RBACPermission
|
||||||
|
from authentication.const import ConfirmType
|
||||||
|
from common.mixins import RecordViewLogMixin
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
|
||||||
|
|
||||||
|
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
||||||
|
model = AccountTemplate
|
||||||
|
filterset_fields = ("username", 'name')
|
||||||
|
search_fields = ('username', 'name')
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.AccountTemplateSerializer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.AccountTemplateSecretSerializer,
|
||||||
|
}
|
||||||
|
http_method_names = ['get', 'options']
|
||||||
|
# Todo: 记得打开
|
||||||
|
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
|
rbac_perms = {
|
||||||
|
'list': 'assets.view_accounttemplatesecret',
|
||||||
|
'retrieve': 'assets.view_accounttemplatesecret',
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
from django.db.models import F
|
|
||||||
|
|
||||||
from assets.api.accounts import (
|
|
||||||
AccountFilterSet, AccountViewSet, AccountSecretsViewSet
|
|
||||||
)
|
|
||||||
from common.mixins import RecordViewLogMixin
|
|
||||||
from .. import serializers
|
|
||||||
from ..models import AuthBook
|
|
||||||
|
|
||||||
__all__ = ['AccountHistoryViewSet', 'AccountHistorySecretsViewSet']
|
|
||||||
|
|
||||||
|
|
||||||
class AccountHistoryFilterSet(AccountFilterSet):
|
|
||||||
class Meta:
|
|
||||||
model = AuthBook.history.model
|
|
||||||
fields = AccountFilterSet.Meta.fields
|
|
||||||
|
|
||||||
|
|
||||||
class AccountHistoryViewSet(AccountViewSet):
|
|
||||||
model = AuthBook.history.model
|
|
||||||
filterset_class = AccountHistoryFilterSet
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AccountHistorySerializer,
|
|
||||||
}
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'assets.view_assethistoryaccount',
|
|
||||||
'retrieve': 'assets.view_assethistoryaccount',
|
|
||||||
}
|
|
||||||
|
|
||||||
http_method_names = ['get', 'options']
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = self.model.objects.all() \
|
|
||||||
.annotate(ip=F('asset__ip')) \
|
|
||||||
.annotate(hostname=F('asset__hostname')) \
|
|
||||||
.annotate(platform=F('asset__platform__name')) \
|
|
||||||
.annotate(protocols=F('asset__protocols'))
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AccountHistorySecretsViewSet(RecordViewLogMixin, AccountHistoryViewSet):
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AccountHistorySecretSerializer
|
|
||||||
}
|
|
||||||
http_method_names = ['get']
|
|
||||||
permission_classes = AccountSecretsViewSet.permission_classes
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'assets.view_assethistoryaccountsecret',
|
|
||||||
'retrieve': 'assets.view_assethistoryaccountsecret',
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
from django.db.models import Q
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django_filters import rest_framework as filters
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.generics import CreateAPIView
|
|
||||||
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from rbac.permissions import RBACPermission
|
|
||||||
from common.drf.filters import BaseFilterSet
|
|
||||||
from common.mixins import RecordViewLogMixin
|
|
||||||
from common.permissions import UserConfirmation
|
|
||||||
from authentication.const import ConfirmType
|
|
||||||
from ..tasks.account_connectivity import test_accounts_connectivity_manual
|
|
||||||
from ..models import AuthBook, Node
|
|
||||||
from .. import serializers
|
|
||||||
|
|
||||||
__all__ = ['AccountFilterSet', 'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI']
|
|
||||||
|
|
||||||
|
|
||||||
class AccountFilterSet(BaseFilterSet):
|
|
||||||
username = filters.CharFilter(method='do_nothing')
|
|
||||||
ip = filters.CharFilter(field_name='ip', lookup_expr='exact')
|
|
||||||
hostname = filters.CharFilter(field_name='hostname', lookup_expr='exact')
|
|
||||||
node = filters.CharFilter(method='do_nothing')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def qs(self):
|
|
||||||
qs = super().qs
|
|
||||||
qs = self.filter_username(qs)
|
|
||||||
qs = self.filter_node(qs)
|
|
||||||
qs = qs.distinct()
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def filter_username(self, qs):
|
|
||||||
username = self.get_query_param('username')
|
|
||||||
if not username:
|
|
||||||
return qs
|
|
||||||
qs = qs.filter(Q(username=username) | Q(systemuser__username=username)).distinct()
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def filter_node(self, qs):
|
|
||||||
node_id = self.get_query_param('node')
|
|
||||||
if not node_id:
|
|
||||||
return qs
|
|
||||||
node = get_object_or_404(Node, pk=node_id)
|
|
||||||
node_ids = node.get_all_children(with_self=True).values_list('id', flat=True)
|
|
||||||
node_ids = list(node_ids)
|
|
||||||
qs = qs.filter(asset__nodes__in=node_ids)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AuthBook
|
|
||||||
fields = [
|
|
||||||
'asset', 'systemuser', 'id',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AccountViewSet(OrgBulkModelViewSet):
|
|
||||||
model = AuthBook
|
|
||||||
filterset_fields = ("username", "asset", "systemuser", 'ip', 'hostname')
|
|
||||||
search_fields = ('username', 'ip', 'hostname', 'systemuser__username')
|
|
||||||
filterset_class = AccountFilterSet
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AccountSerializer,
|
|
||||||
'verify_account': serializers.AssetTaskSerializer
|
|
||||||
}
|
|
||||||
rbac_perms = {
|
|
||||||
'verify_account': 'assets.test_authbook',
|
|
||||||
'partial_update': 'assets.change_assetaccountsecret',
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = AuthBook.get_queryset()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
@action(methods=['post'], detail=True, url_path='verify')
|
|
||||||
def verify_account(self, request, *args, **kwargs):
|
|
||||||
account = super().get_object()
|
|
||||||
task = test_accounts_connectivity_manual.delay([account])
|
|
||||||
return Response(data={'task': task.id})
|
|
||||||
|
|
||||||
|
|
||||||
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
|
||||||
"""
|
|
||||||
因为可能要导出所有账号,所以单独建立了一个 viewset
|
|
||||||
"""
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AccountSecretSerializer
|
|
||||||
}
|
|
||||||
http_method_names = ['get']
|
|
||||||
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'assets.view_assetaccountsecret',
|
|
||||||
'retrieve': 'assets.view_assetaccountsecret',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTaskCreateAPI(CreateAPIView):
|
|
||||||
serializer_class = serializers.AccountTaskSerializer
|
|
||||||
filterset_fields = AccountViewSet.filterset_fields
|
|
||||||
search_fields = AccountViewSet.search_fields
|
|
||||||
filterset_class = AccountViewSet.filterset_class
|
|
||||||
|
|
||||||
def check_permissions(self, request):
|
|
||||||
return request.user.has_perm('assets.test_assetconnectivity')
|
|
||||||
|
|
||||||
def get_accounts(self):
|
|
||||||
queryset = AuthBook.objects.all()
|
|
||||||
queryset = self.filter_queryset(queryset)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
accounts = self.get_accounts()
|
|
||||||
task = test_accounts_connectivity_manual.delay(accounts)
|
|
||||||
data = getattr(serializer, '_data', {})
|
|
||||||
data["task"] = task.id
|
|
||||||
setattr(serializer, '_data', data)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def get_exception_handler(self):
|
|
||||||
def handler(e, context):
|
|
||||||
return Response({"error": str(e)}, status=400)
|
|
||||||
return handler
|
|
|
@ -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,310 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
|
||||||
from rest_framework.generics import RetrieveAPIView, ListAPIView
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
|
||||||
from common.mixins.api import SuggestionMixin, RenderToJsonMixin
|
|
||||||
from users.models import User, UserGroup
|
|
||||||
from users.serializers import UserSerializer, UserGroupSerializer
|
|
||||||
from users.filters import UserFilter
|
|
||||||
from perms.models import AssetPermission
|
|
||||||
from perms.serializers import AssetPermissionSerializer
|
|
||||||
from perms.filters import AssetPermissionFilter
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from orgs.mixins import generics
|
|
||||||
from assets.api import FilterAssetByNodeMixin
|
|
||||||
from ..models import Asset, Node, Platform, Gateway
|
|
||||||
from .. import serializers
|
|
||||||
from ..tasks import (
|
|
||||||
update_assets_hardware_info_manual, test_assets_connectivity_manual,
|
|
||||||
test_system_users_connectivity_a_asset, push_system_users_a_asset
|
|
||||||
)
|
|
||||||
from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
__all__ = [
|
|
||||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
|
||||||
'AssetGatewayListApi', 'AssetPlatformViewSet',
|
|
||||||
'AssetTaskCreateApi', 'AssetsTaskCreateApi',
|
|
||||||
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
|
|
||||||
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet):
|
|
||||||
"""
|
|
||||||
API endpoint that allows Asset to be viewed or edited.
|
|
||||||
"""
|
|
||||||
model = Asset
|
|
||||||
filterset_fields = {
|
|
||||||
'hostname': ['exact'],
|
|
||||||
'ip': ['exact'],
|
|
||||||
'system_users__id': ['exact'],
|
|
||||||
'platform__base': ['exact'],
|
|
||||||
'is_active': ['exact'],
|
|
||||||
'protocols': ['exact', 'icontains']
|
|
||||||
}
|
|
||||||
search_fields = ("hostname", "ip")
|
|
||||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
|
||||||
ordering = ('hostname', )
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AssetSerializer,
|
|
||||||
'suggestion': serializers.MiniAssetSerializer
|
|
||||||
}
|
|
||||||
rbac_perms = {
|
|
||||||
'match': 'assets.match_asset'
|
|
||||||
}
|
|
||||||
extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
|
|
||||||
|
|
||||||
def set_assets_node(self, assets):
|
|
||||||
if not isinstance(assets, list):
|
|
||||||
assets = [assets]
|
|
||||||
node_id = self.request.query_params.get('node_id')
|
|
||||||
if not node_id:
|
|
||||||
return
|
|
||||||
node = get_object_or_none(Node, pk=node_id)
|
|
||||||
if not node:
|
|
||||||
return
|
|
||||||
node.assets.add(*assets)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
assets = serializer.save()
|
|
||||||
self.set_assets_node(assets)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
|
||||||
queryset = Platform.objects.all()
|
|
||||||
serializer_class = serializers.PlatformSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'retrieve': 'assets.view_gateway'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
asset_pk = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_pk)
|
|
||||||
return asset.platform
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformViewSet(ModelViewSet, RenderToJsonMixin):
|
|
||||||
queryset = Platform.objects.all()
|
|
||||||
serializer_class = serializers.PlatformSerializer
|
|
||||||
filterset_fields = ['name', 'base']
|
|
||||||
search_fields = ['name']
|
|
||||||
|
|
||||||
def check_object_permissions(self, request, obj):
|
|
||||||
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
|
||||||
self.permission_denied(
|
|
||||||
request, message={"detail": "Internal platform"}
|
|
||||||
)
|
|
||||||
return super().check_object_permissions(request, obj)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsTaskMixin:
|
|
||||||
|
|
||||||
def perform_assets_task(self, serializer):
|
|
||||||
data = serializer.validated_data
|
|
||||||
action = data['action']
|
|
||||||
assets = data.get('assets', [])
|
|
||||||
if action == "refresh":
|
|
||||||
task = update_assets_hardware_info_manual.delay(assets)
|
|
||||||
else:
|
|
||||||
# action == 'test':
|
|
||||||
task = test_assets_connectivity_manual.delay(assets)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
task = self.perform_assets_task(serializer)
|
|
||||||
self.set_task_to_serializer_data(serializer, task)
|
|
||||||
|
|
||||||
def set_task_to_serializer_data(self, serializer, task):
|
|
||||||
data = getattr(serializer, '_data', {})
|
|
||||||
data["task"] = task.id
|
|
||||||
setattr(serializer, '_data', data)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
|
||||||
model = Asset
|
|
||||||
serializer_class = serializers.AssetTaskSerializer
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
pk = self.kwargs.get('pk')
|
|
||||||
request.data['asset'] = pk
|
|
||||||
request.data['assets'] = [pk]
|
|
||||||
return super().create(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def check_permissions(self, request):
|
|
||||||
action = request.data.get('action')
|
|
||||||
action_perm_require = {
|
|
||||||
'refresh': 'assets.refresh_assethardwareinfo',
|
|
||||||
'push_system_user': 'assets.push_assetsystemuser',
|
|
||||||
'test': 'assets.test_assetconnectivity',
|
|
||||||
'test_system_user': '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_asset_task(self, serializer):
|
|
||||||
data = serializer.validated_data
|
|
||||||
action = data['action']
|
|
||||||
if action not in ['push_system_user', 'test_system_user']:
|
|
||||||
return
|
|
||||||
|
|
||||||
asset = data['asset']
|
|
||||||
system_users = data.get('system_users')
|
|
||||||
if not system_users:
|
|
||||||
system_users = asset.get_all_system_users()
|
|
||||||
if action == 'push_system_user':
|
|
||||||
task = push_system_users_a_asset.delay(system_users, asset=asset)
|
|
||||||
elif action == 'test_system_user':
|
|
||||||
task = test_system_users_connectivity_a_asset.delay(system_users, asset=asset)
|
|
||||||
else:
|
|
||||||
task = None
|
|
||||||
return task
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
task = self.perform_asset_task(serializer)
|
|
||||||
if not task:
|
|
||||||
task = self.perform_assets_task(serializer)
|
|
||||||
self.set_task_to_serializer_data(serializer, task)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
|
||||||
model = Asset
|
|
||||||
serializer_class = serializers.AssetsTaskSerializer
|
|
||||||
|
|
||||||
def check_permissions(self, request):
|
|
||||||
action = request.data.get('action')
|
|
||||||
action_perm_require = {
|
|
||||||
'refresh': 'assets.refresh_assethardwareinfo',
|
|
||||||
}
|
|
||||||
perm_required = action_perm_require.get(action)
|
|
||||||
has = self.request.user.has_perm(perm_required)
|
|
||||||
if not has:
|
|
||||||
self.permission_denied(request)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetGatewayListApi(generics.ListAPIView):
|
|
||||||
serializer_class = serializers.GatewayWithAuthSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'assets.view_gateway'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
asset_id = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
|
||||||
if not asset.domain:
|
|
||||||
return Gateway.objects.none()
|
|
||||||
queryset = asset.domain.gateways.filter(protocol='ssh')
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAssetPermUserOrUserGroupListApi(ListAPIView):
|
|
||||||
rbac_perms = {
|
|
||||||
'GET': 'perms.view_assetpermission'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
asset_id = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
|
||||||
return asset
|
|
||||||
|
|
||||||
def get_asset_related_perms(self):
|
|
||||||
asset = self.get_object()
|
|
||||||
nodes = asset.get_all_nodes(flat=True)
|
|
||||||
perms = AssetPermission.objects.filter(Q(assets=asset) | Q(nodes__in=nodes))
|
|
||||||
return perms
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi):
|
|
||||||
filterset_class = UserFilter
|
|
||||||
search_fields = ('username', 'email', 'name', 'id', 'source', 'role')
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'GET': 'perms.view_assetpermission'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
perms = self.get_asset_related_perms()
|
|
||||||
users = User.objects.filter(
|
|
||||||
Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms)
|
|
||||||
).distinct()
|
|
||||||
return users
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
|
|
||||||
serializer_class = UserGroupSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
perms = self.get_asset_related_perms()
|
|
||||||
user_groups = UserGroup.objects.filter(assetpermissions__in=perms).distinct()
|
|
||||||
return user_groups
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
|
|
||||||
model = AssetPermission
|
|
||||||
serializer_class = AssetPermissionSerializer
|
|
||||||
filterset_class = AssetPermissionFilter
|
|
||||||
search_fields = ('name',)
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'perms.view_assetpermission'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
asset_id = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
|
||||||
return asset
|
|
||||||
|
|
||||||
def filter_asset_related(self, queryset):
|
|
||||||
asset = self.get_object()
|
|
||||||
nodes = asset.get_all_nodes(flat=True)
|
|
||||||
perms = queryset.filter(Q(assets=asset) | Q(nodes__in=nodes))
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = self.filter_asset_related(queryset)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = self.filter_user_related(queryset)
|
|
||||||
queryset = queryset.distinct()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_user_related(self, queryset):
|
|
||||||
user = self.get_perm_user()
|
|
||||||
user_groups = user.groups.all()
|
|
||||||
perms = queryset.filter(Q(users=user) | Q(user_groups__in=user_groups))
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def get_perm_user(self):
|
|
||||||
user_id = self.kwargs.get('perm_user_id')
|
|
||||||
user = get_object_or_404(User, pk=user_id)
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = self.filter_user_group_related(queryset)
|
|
||||||
queryset = queryset.distinct()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_user_group_related(self, queryset):
|
|
||||||
user_group = self.get_perm_user_group()
|
|
||||||
perms = queryset.filter(user_groups=user_group)
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def get_perm_user_group(self):
|
|
||||||
user_group_id = self.kwargs.get('perm_user_group_id')
|
|
||||||
user_group = get_object_or_404(UserGroup, pk=user_group_id)
|
|
||||||
return user_group
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from .asset import *
|
||||||
|
from .host import *
|
||||||
|
from .database import *
|
||||||
|
from .web import *
|
||||||
|
from .cloud import *
|
||||||
|
from .device import *
|
||||||
|
from .permission import *
|
|
@ -0,0 +1,170 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
import django_filters
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from assets import serializers
|
||||||
|
from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend
|
||||||
|
from assets.models import Asset, Gateway
|
||||||
|
from assets.tasks import (
|
||||||
|
push_accounts_to_assets, test_assets_connectivity_manual,
|
||||||
|
update_assets_hardware_info_manual, verify_accounts_connectivity,
|
||||||
|
)
|
||||||
|
from common.drf.filters import BaseFilterSet
|
||||||
|
from common.mixins.api import SuggestionMixin
|
||||||
|
from common.utils import get_logger
|
||||||
|
from orgs.mixins import generics
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from ..mixin import NodeFilterMixin
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
__all__ = [
|
||||||
|
"AssetViewSet",
|
||||||
|
"AssetTaskCreateApi",
|
||||||
|
"AssetsTaskCreateApi",
|
||||||
|
'AssetFilterSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetFilterSet(BaseFilterSet):
|
||||||
|
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
|
||||||
|
category = django_filters.CharFilter(
|
||||||
|
field_name="platform__category", lookup_expr="exact"
|
||||||
|
)
|
||||||
|
hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Asset
|
||||||
|
fields = [
|
||||||
|
"id", "name", "address", "is_active",
|
||||||
|
"type", "category", "hostname"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows Asset to be viewed or edited.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = Asset
|
||||||
|
filterset_class = AssetFilterSet
|
||||||
|
search_fields = ("name", "address")
|
||||||
|
ordering_fields = ("name", "address")
|
||||||
|
ordering = ("name",)
|
||||||
|
serializer_classes = (
|
||||||
|
("default", serializers.AssetSerializer),
|
||||||
|
("suggestion", serializers.MiniAssetSerializer),
|
||||||
|
("platform", serializers.PlatformSerializer),
|
||||||
|
("gateways", serializers.GatewaySerializer),
|
||||||
|
)
|
||||||
|
rbac_perms = (
|
||||||
|
("match", "assets.match_asset"),
|
||||||
|
("platform", "assets.view_platform"),
|
||||||
|
("gateways", "assets.view_gateway"),
|
||||||
|
)
|
||||||
|
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=True, url_path="platform")
|
||||||
|
def platform(self, *args, **kwargs):
|
||||||
|
asset = self.get_object()
|
||||||
|
serializer = self.get_serializer(asset.platform)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=True, url_path="gateways")
|
||||||
|
def gateways(self, *args, **kwargs):
|
||||||
|
asset = self.get_object()
|
||||||
|
if not asset.domain:
|
||||||
|
gateways = Gateway.objects.none()
|
||||||
|
else:
|
||||||
|
gateways = asset.domain.gateways
|
||||||
|
return self.get_paginated_response_from_queryset(gateways)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetsTaskMixin:
|
||||||
|
def perform_assets_task(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
assets = data.get("assets", [])
|
||||||
|
asset_ids = [asset.id for asset in assets]
|
||||||
|
if data["action"] == "refresh":
|
||||||
|
task = update_assets_hardware_info_manual.delay(asset_ids)
|
||||||
|
else:
|
||||||
|
task = test_assets_connectivity_manual.delay(asset_ids)
|
||||||
|
return task
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
task = self.perform_assets_task(serializer)
|
||||||
|
self.set_task_to_serializer_data(serializer, task)
|
||||||
|
|
||||||
|
def set_task_to_serializer_data(self, serializer, task):
|
||||||
|
data = getattr(serializer, "_data", {})
|
||||||
|
data["task"] = task.id
|
||||||
|
setattr(serializer, "_data", data)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
||||||
|
model = Asset
|
||||||
|
serializer_class = serializers.AssetTaskSerializer
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
pk = self.kwargs.get("pk")
|
||||||
|
request.data["asset"] = pk
|
||||||
|
request.data["assets"] = [pk]
|
||||||
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def check_permissions(self, request):
|
||||||
|
action = request.data.get("action")
|
||||||
|
action_perm_require = {
|
||||||
|
"refresh": "assets.refresh_assethardwareinfo",
|
||||||
|
"push_account": "assets.push_assetsystemuser",
|
||||||
|
"test": "assets.test_assetconnectivity",
|
||||||
|
"test_account": "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)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def perform_asset_task(serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
if data["action"] not in ["push_system_user", "test_system_user"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
asset = data["asset"]
|
||||||
|
accounts = data.get("accounts")
|
||||||
|
if not accounts:
|
||||||
|
accounts = asset.accounts.all()
|
||||||
|
|
||||||
|
asset_ids = [asset.id]
|
||||||
|
account_ids = accounts.values_list("id", flat=True)
|
||||||
|
if action == "push_account":
|
||||||
|
task = push_accounts_to_assets.delay(account_ids, asset_ids)
|
||||||
|
elif action == "test_account":
|
||||||
|
task = verify_accounts_connectivity.delay(account_ids, asset_ids)
|
||||||
|
else:
|
||||||
|
task = None
|
||||||
|
return task
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
task = self.perform_asset_task(serializer)
|
||||||
|
if not task:
|
||||||
|
task = self.perform_assets_task(serializer)
|
||||||
|
self.set_task_to_serializer_data(serializer, task)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
||||||
|
model = Asset
|
||||||
|
serializer_class = serializers.AssetsTaskSerializer
|
||||||
|
|
||||||
|
def check_permissions(self, request):
|
||||||
|
action = request.data.get("action")
|
||||||
|
action_perm_require = {
|
||||||
|
"refresh": "assets.refresh_assethardwareinfo",
|
||||||
|
}
|
||||||
|
perm_required = action_perm_require.get(action)
|
||||||
|
has = self.request.user.has_perm(perm_required)
|
||||||
|
if not has:
|
||||||
|
self.permission_denied(request)
|
|
@ -0,0 +1,15 @@
|
||||||
|
from assets.models import Cloud
|
||||||
|
from assets.serializers import CloudSerializer
|
||||||
|
|
||||||
|
from .asset import AssetViewSet
|
||||||
|
|
||||||
|
__all__ = ['CloudViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class CloudViewSet(AssetViewSet):
|
||||||
|
model = Cloud
|
||||||
|
|
||||||
|
def get_serializer_classes(self):
|
||||||
|
serializer_classes = super().get_serializer_classes()
|
||||||
|
serializer_classes['default'] = CloudSerializer
|
||||||
|
return serializer_classes
|
|
@ -0,0 +1,15 @@
|
||||||
|
from assets.models import Database
|
||||||
|
from assets.serializers import DatabaseSerializer
|
||||||
|
|
||||||
|
from .asset import AssetViewSet
|
||||||
|
|
||||||
|
__all__ = ['DatabaseViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseViewSet(AssetViewSet):
|
||||||
|
model = Database
|
||||||
|
|
||||||
|
def get_serializer_classes(self):
|
||||||
|
serializer_classes = super().get_serializer_classes()
|
||||||
|
serializer_classes['default'] = DatabaseSerializer
|
||||||
|
return serializer_classes
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
from assets.serializers import DeviceSerializer
|
||||||
|
from assets.models import Device
|
||||||
|
from .asset import AssetViewSet
|
||||||
|
|
||||||
|
__all__ = ['DeviceViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceViewSet(AssetViewSet):
|
||||||
|
model = Device
|
||||||
|
|
||||||
|
def get_serializer_classes(self):
|
||||||
|
serializer_classes = super().get_serializer_classes()
|
||||||
|
serializer_classes['default'] = DeviceSerializer
|
||||||
|
return serializer_classes
|
|
@ -0,0 +1,14 @@
|
||||||
|
from assets.models import Host
|
||||||
|
from assets.serializers import HostSerializer
|
||||||
|
from .asset import AssetViewSet
|
||||||
|
|
||||||
|
__all__ = ['HostViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class HostViewSet(AssetViewSet):
|
||||||
|
model = Host
|
||||||
|
|
||||||
|
def get_serializer_classes(self):
|
||||||
|
serializer_classes = super().get_serializer_classes()
|
||||||
|
serializer_classes['default'] = HostSerializer
|
||||||
|
return serializer_classes
|
|
@ -0,0 +1,127 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from rest_framework.generics import ListAPIView
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from common.utils import get_logger
|
||||||
|
from users.models import User, UserGroup
|
||||||
|
from users.serializers import UserSerializer, UserGroupSerializer
|
||||||
|
from users.filters import UserFilter
|
||||||
|
from perms.models import AssetPermission
|
||||||
|
from perms.serializers import AssetPermissionSerializer
|
||||||
|
from perms.filters import AssetPermissionFilter
|
||||||
|
from orgs.mixins import generics
|
||||||
|
from assets.models import Asset
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
__all__ = [
|
||||||
|
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
|
||||||
|
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAssetPermUserOrUserGroupListApi(ListAPIView):
|
||||||
|
rbac_perms = {
|
||||||
|
'GET': 'perms.view_assetpermission'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
asset_id = self.kwargs.get('pk')
|
||||||
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
|
return asset
|
||||||
|
|
||||||
|
def get_asset_related_perms(self):
|
||||||
|
asset = self.get_object()
|
||||||
|
nodes = asset.get_all_nodes(flat=True)
|
||||||
|
perms = AssetPermission.objects.filter(Q(assets=asset) | Q(nodes__in=nodes))
|
||||||
|
return perms
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi):
|
||||||
|
filterset_class = UserFilter
|
||||||
|
search_fields = ('username', 'email', 'name', 'id', 'source', 'role')
|
||||||
|
serializer_class = UserSerializer
|
||||||
|
rbac_perms = {
|
||||||
|
'GET': 'perms.view_assetpermission'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
perms = self.get_asset_related_perms()
|
||||||
|
users = User.objects.filter(
|
||||||
|
Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms)
|
||||||
|
).distinct()
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
|
||||||
|
serializer_class = UserGroupSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
perms = self.get_asset_related_perms()
|
||||||
|
user_groups = UserGroup.objects.filter(assetpermissions__in=perms).distinct()
|
||||||
|
return user_groups
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAssetRelatedPermissionListApi(generics.ListAPIView):
|
||||||
|
model = AssetPermission
|
||||||
|
serializer_class = AssetPermissionSerializer
|
||||||
|
filterset_class = AssetPermissionFilter
|
||||||
|
search_fields = ('name',)
|
||||||
|
rbac_perms = {
|
||||||
|
'list': 'perms.view_assetpermission'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
asset_id = self.kwargs.get('pk')
|
||||||
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
|
return asset
|
||||||
|
|
||||||
|
def filter_asset_related(self, queryset):
|
||||||
|
asset = self.get_object()
|
||||||
|
nodes = asset.get_all_nodes(flat=True)
|
||||||
|
perms = queryset.filter(Q(assets=asset) | Q(nodes__in=nodes))
|
||||||
|
return perms
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
queryset = self.filter_asset_related(queryset)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermUserPermissionsListApi(BaseAssetRelatedPermissionListApi):
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
queryset = self.filter_user_related(queryset)
|
||||||
|
queryset = queryset.distinct()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def filter_user_related(self, queryset):
|
||||||
|
user = self.get_perm_user()
|
||||||
|
user_groups = user.groups.all()
|
||||||
|
perms = queryset.filter(Q(users=user) | Q(user_groups__in=user_groups))
|
||||||
|
return perms
|
||||||
|
|
||||||
|
def get_perm_user(self):
|
||||||
|
user_id = self.kwargs.get('perm_user_id')
|
||||||
|
user = get_object_or_404(User, pk=user_id)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermUserGroupPermissionsListApi(BaseAssetRelatedPermissionListApi):
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
queryset = self.filter_user_group_related(queryset)
|
||||||
|
queryset = queryset.distinct()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def filter_user_group_related(self, queryset):
|
||||||
|
user_group = self.get_perm_user_group()
|
||||||
|
perms = queryset.filter(user_groups=user_group)
|
||||||
|
return perms
|
||||||
|
|
||||||
|
def get_perm_user_group(self):
|
||||||
|
user_group_id = self.kwargs.get('perm_user_group_id')
|
||||||
|
user_group = get_object_or_404(UserGroup, pk=user_group_id)
|
||||||
|
return user_group
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
from assets.models import Web
|
||||||
|
from assets.serializers import WebSerializer
|
||||||
|
|
||||||
|
from .asset import AssetViewSet
|
||||||
|
|
||||||
|
__all__ = ['WebViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class WebViewSet(AssetViewSet):
|
||||||
|
model = Web
|
||||||
|
|
||||||
|
def get_serializer_classes(self):
|
||||||
|
serializer_classes = super().get_serializer_classes()
|
||||||
|
serializer_classes['default'] = WebSerializer
|
||||||
|
return serializer_classes
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .base import *
|
||||||
|
from .change_secret import *
|
||||||
|
from .gather_accounts import *
|
|
@ -0,0 +1,116 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status, mixins, viewsets
|
||||||
|
|
||||||
|
from orgs.mixins import generics
|
||||||
|
from assets import serializers
|
||||||
|
from assets.tasks import execute_automation
|
||||||
|
from assets.models import BaseAutomation, AutomationExecution
|
||||||
|
from common.const.choices import Trigger
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AutomationAssetsListApi', 'AutomationRemoveAssetApi',
|
||||||
|
'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi', 'AutomationExecutionViewSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationAssetsListApi(generics.ListAPIView):
|
||||||
|
serializer_class = serializers.AutomationAssetsSerializer
|
||||||
|
filter_fields = ("name", "address")
|
||||||
|
search_fields = filter_fields
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
return get_object_or_404(BaseAutomation, pk=pk)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
instance = self.get_object()
|
||||||
|
assets = instance.get_all_assets().only(
|
||||||
|
*self.serializer_class.Meta.only_fields
|
||||||
|
)
|
||||||
|
return assets
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationRemoveAssetApi(generics.RetrieveUpdateAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
|
serializer_class = serializers.UpdateAssetSerializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response({'error': serializer.errors})
|
||||||
|
|
||||||
|
assets = serializer.validated_data.get('assets')
|
||||||
|
if assets:
|
||||||
|
instance.assets.remove(*tuple(assets))
|
||||||
|
return Response({'msg': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
|
serializer_class = serializers.UpdateAssetSerializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
assets = serializer.validated_data.get('assets')
|
||||||
|
if assets:
|
||||||
|
instance.assets.add(*tuple(assets))
|
||||||
|
return Response({"msg": "ok"})
|
||||||
|
else:
|
||||||
|
return Response({"error": serializer.errors})
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
|
serializer_class = serializers.UpdateAssetSerializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
action_params = ['add', 'remove']
|
||||||
|
action = request.query_params.get('action')
|
||||||
|
if action not in action_params:
|
||||||
|
err_info = _("The parameter 'action' must be [{}]".format(','.join(action_params)))
|
||||||
|
return Response({"error": err_info})
|
||||||
|
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
nodes = serializer.validated_data.get('nodes')
|
||||||
|
if nodes:
|
||||||
|
# eg: plan.nodes.add(*tuple(assets))
|
||||||
|
getattr(instance.nodes, action)(*tuple(nodes))
|
||||||
|
return Response({"msg": "ok"})
|
||||||
|
else:
|
||||||
|
return Response({"error": serializer.errors})
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationExecutionViewSet(
|
||||||
|
mixins.CreateModelMixin, mixins.ListModelMixin,
|
||||||
|
mixins.RetrieveModelMixin, viewsets.GenericViewSet
|
||||||
|
):
|
||||||
|
search_fields = ('trigger',)
|
||||||
|
filterset_fields = ('trigger', 'automation_id')
|
||||||
|
serializer_class = serializers.AutomationExecutionSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = AutomationExecution.objects.all()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
queryset = queryset.order_by('-date_start')
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
automation = serializer.validated_data.get('automation')
|
||||||
|
tp = serializer.validated_data.get('type')
|
||||||
|
task = execute_automation.delay(
|
||||||
|
pid=automation.pk, trigger=Trigger.manual, tp=tp
|
||||||
|
)
|
||||||
|
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from rest_framework import mixins
|
||||||
|
|
||||||
|
from common.utils import get_object_or_none
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||||
|
|
||||||
|
from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
|
||||||
|
from assets import serializers
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
|
search_fields = filter_fields
|
||||||
|
ordering_fields = ('name',)
|
||||||
|
serializer_class = serializers.ChangeSecretAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
||||||
|
serializer_class = serializers.ChangeSecretRecordSerializer
|
||||||
|
filter_fields = ['asset', 'execution_id']
|
||||||
|
search_fields = ['asset__hostname']
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return ChangeSecretRecord.objects.all()
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
eid = self.request.GET.get('execution_id')
|
||||||
|
execution = get_object_or_none(AutomationExecution, pk=eid)
|
||||||
|
if execution:
|
||||||
|
queryset = queryset.filter(execution=execution)
|
||||||
|
queryset = queryset.order_by('-date_started')
|
||||||
|
return queryset
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
|
||||||
|
from assets.models import GatherAccountsAutomation
|
||||||
|
from assets import serializers
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'GatherAccountsAutomationViewSet',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
||||||
|
model = GatherAccountsAutomation
|
||||||
|
filter_fields = ('name',)
|
||||||
|
search_fields = filter_fields
|
||||||
|
ordering_fields = ('name',)
|
||||||
|
serializer_class = serializers.GatherAccountAutomationSerializer
|
|
@ -0,0 +1,34 @@
|
||||||
|
from rest_framework.mixins import ListModelMixin
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from common.drf.api import JMSGenericViewSet
|
||||||
|
from assets.serializers import CategorySerializer, TypeSerializer
|
||||||
|
from assets.const import AllTypes
|
||||||
|
|
||||||
|
__all__ = ['CategoryViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
|
||||||
|
serializer_classes = {
|
||||||
|
'default': CategorySerializer,
|
||||||
|
'types': TypeSerializer
|
||||||
|
}
|
||||||
|
permission_classes = ()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return AllTypes.categories()
|
||||||
|
|
||||||
|
@action(methods=['get'], detail=False)
|
||||||
|
def types(self, request, *args, **kwargs):
|
||||||
|
queryset = AllTypes.types()
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@action(methods=['get'], detail=False)
|
||||||
|
def constraints(self, request, *args, **kwargs):
|
||||||
|
category = request.query_params.get('category')
|
||||||
|
tp = request.query_params.get('type')
|
||||||
|
constraints = AllTypes.get_constraints(category, tp)
|
||||||
|
return Response(constraints)
|
||||||
|
|
|
@ -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,5 +1,4 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework.views import APIView, Response
|
from rest_framework.views import APIView, Response
|
||||||
|
@ -10,18 +9,17 @@ from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..models import Domain, Gateway
|
from ..models import Domain, Gateway
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||||
|
|
||||||
|
|
||||||
class DomainViewSet(OrgBulkModelViewSet):
|
class DomainViewSet(OrgBulkModelViewSet):
|
||||||
model = Domain
|
model = Domain
|
||||||
filterset_fields = ("name", )
|
filterset_fields = ("name",)
|
||||||
search_fields = filterset_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.DomainSerializer
|
serializer_class = serializers.DomainSerializer
|
||||||
ordering_fields = ('name',)
|
ordering_fields = ('name',)
|
||||||
ordering = ('name', )
|
ordering = ('name',)
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.request.query_params.get('gateway'):
|
if self.request.query_params.get('gateway'):
|
||||||
|
@ -30,27 +28,33 @@ class DomainViewSet(OrgBulkModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class GatewayViewSet(OrgBulkModelViewSet):
|
class GatewayViewSet(OrgBulkModelViewSet):
|
||||||
model = Gateway
|
perm_model = Gateway
|
||||||
filterset_fields = ("domain__name", "name", "username", "ip", "domain")
|
filterset_fields = ("domain__name", "name", "domain")
|
||||||
search_fields = ("domain__name", "name", "username", "ip")
|
search_fields = ("domain__name",)
|
||||||
serializer_class = serializers.GatewaySerializer
|
serializer_class = serializers.GatewaySerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = Domain.get_gateway_queryset()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||||
queryset = Gateway.objects.all()
|
|
||||||
object = None
|
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'POST': 'assets.test_gateway'
|
'POST': 'assets.test_gateway'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = Domain.get_gateway_queryset()
|
||||||
|
return queryset
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object(Gateway.objects.all())
|
gateway = self.get_object()
|
||||||
local_port = self.request.data.get('port') or self.object.port
|
local_port = self.request.data.get('port') or gateway.port
|
||||||
try:
|
try:
|
||||||
local_port = int(local_port)
|
local_port = int(local_port)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValidationError({'port': _('Number required')})
|
raise ValidationError({'port': _('Number required')})
|
||||||
ok, e = self.object.test_connective(local_port=local_port)
|
ok, e = gateway.test_connective(local_port=local_port)
|
||||||
if ok:
|
if ok:
|
||||||
return Response("ok")
|
return Response("ok")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -16,5 +16,5 @@ class GatheredUserViewSet(OrgModelViewSet):
|
||||||
serializer_class = GatheredUserSerializer
|
serializer_class = GatheredUserSerializer
|
||||||
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
|
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
|
||||||
|
|
||||||
filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id']
|
filterset_fields = ['asset', 'username', 'present', 'asset__address', 'asset__name', 'asset_id']
|
||||||
search_fields = ['username', 'asset__ip', 'asset__hostname']
|
search_fields = ['username', 'asset__address', 'asset__name']
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from common.utils.common import timeit
|
from rest_framework.request import Request
|
||||||
from assets.models import Node, Asset
|
|
||||||
from assets.pagination import NodeAssetTreePagination
|
from assets.models import Node, PlatformProtocol
|
||||||
from common.utils import lazyproperty
|
from assets.utils import get_node_from_request, is_query_node_all_assets
|
||||||
from assets.utils import get_node, is_query_node_all_assets
|
from common.utils import lazyproperty, timeit
|
||||||
|
|
||||||
|
|
||||||
class SerializeToTreeNodeMixin:
|
class SerializeToTreeNodeMixin:
|
||||||
|
@ -38,16 +38,11 @@ class SerializeToTreeNodeMixin:
|
||||||
]
|
]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_platform(self, asset: Asset):
|
|
||||||
default = 'file'
|
|
||||||
icon = {'windows', 'linux'}
|
|
||||||
platform = asset.platform_base.lower()
|
|
||||||
if platform in icon:
|
|
||||||
return platform
|
|
||||||
return default
|
|
||||||
|
|
||||||
@timeit
|
@timeit
|
||||||
def serialize_assets(self, assets, node_key=None):
|
def serialize_assets(self, assets, node_key=None):
|
||||||
|
sftp_enabled_platform = PlatformProtocol.objects \
|
||||||
|
.filter(name='ssh', setting__sftp_enabled=True) \
|
||||||
|
.values_list('platform', flat=True).distinct()
|
||||||
if node_key is None:
|
if node_key is None:
|
||||||
get_pid = lambda asset: getattr(asset, 'parent_key', '')
|
get_pid = lambda asset: getattr(asset, 'parent_key', '')
|
||||||
else:
|
else:
|
||||||
|
@ -56,22 +51,18 @@ class SerializeToTreeNodeMixin:
|
||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
'id': str(asset.id),
|
'id': str(asset.id),
|
||||||
'name': asset.hostname,
|
'name': asset.name,
|
||||||
'title': asset.ip,
|
'title': asset.address,
|
||||||
'pId': get_pid(asset),
|
'pId': get_pid(asset),
|
||||||
'isParent': False,
|
'isParent': False,
|
||||||
'open': False,
|
'open': False,
|
||||||
'iconSkin': self.get_platform(asset),
|
'iconSkin': asset.type,
|
||||||
'chkDisabled': not asset.is_active,
|
'chkDisabled': not asset.is_active,
|
||||||
'meta': {
|
'meta': {
|
||||||
'type': 'asset',
|
'type': 'asset',
|
||||||
'data': {
|
'data': {
|
||||||
'id': asset.id,
|
'org_name': asset.org_name,
|
||||||
'hostname': asset.hostname,
|
'sftp': asset.platform_id in sftp_enabled_platform,
|
||||||
'ip': asset.ip,
|
|
||||||
'protocols': asset.protocols_as_list,
|
|
||||||
'platform': asset.platform_base,
|
|
||||||
'org_name': asset.org_name
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +71,8 @@ class SerializeToTreeNodeMixin:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class FilterAssetByNodeMixin:
|
class NodeFilterMixin:
|
||||||
pagination_class = NodeAssetTreePagination
|
request: Request
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def is_query_node_all_assets(self):
|
def is_query_node_all_assets(self):
|
||||||
|
@ -89,4 +80,4 @@ class FilterAssetByNodeMixin:
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def node(self):
|
def node(self):
|
||||||
return get_node(self.request)
|
return get_node_from_request(self.request)
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from collections import namedtuple, defaultdict
|
from collections import namedtuple, defaultdict
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.shortcuts import get_object_or_404, Http404
|
|
||||||
from django.db.models.signals import m2m_changed
|
from django.db.models.signals import m2m_changed
|
||||||
|
|
||||||
from common.const.http import POST
|
from common.const.http import POST
|
||||||
|
@ -15,7 +16,7 @@ from common.exceptions import SomeoneIsDoingThis
|
||||||
from common.const.signals import PRE_REMOVE, POST_REMOVE
|
from common.const.signals import PRE_REMOVE, POST_REMOVE
|
||||||
from common.mixins.api import SuggestionMixin
|
from common.mixins.api import SuggestionMixin
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
from common.utils import get_logger, get_object_or_none
|
from common.utils import get_logger
|
||||||
from common.tree import TreeNodeSerializer
|
from common.tree import TreeNodeSerializer
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from orgs.mixins import generics
|
from orgs.mixins import generics
|
||||||
|
@ -27,6 +28,7 @@ from ..tasks import (
|
||||||
check_node_assets_amount_task
|
check_node_assets_amount_task
|
||||||
)
|
)
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
from ..const import AllTypes
|
||||||
from .mixin import SerializeToTreeNodeMixin
|
from .mixin import SerializeToTreeNodeMixin
|
||||||
from assets.locks import NodeAddChildrenLock
|
from assets.locks import NodeAddChildrenLock
|
||||||
|
|
||||||
|
@ -34,9 +36,8 @@ logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
|
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
|
||||||
'NodeAddChildrenApi', 'NodeListAsTreeApi',
|
'NodeAddChildrenApi', 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi',
|
||||||
'NodeChildrenAsTreeApi',
|
'NodeTaskCreateApi', 'CategoryTreeApi',
|
||||||
'NodeTaskCreateApi',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,12 +201,26 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
|
||||||
if not self.instance or not include_assets:
|
if not self.instance or not include_assets:
|
||||||
return []
|
return []
|
||||||
assets = self.instance.get_assets().only(
|
assets = self.instance.get_assets().only(
|
||||||
"id", "hostname", "ip", "os", "platform_id",
|
"id", "name", "address", "platform_id",
|
||||||
"org_id", "protocols", "is_active",
|
"org_id", "is_active",
|
||||||
).prefetch_related('platform')
|
).prefetch_related('platform')
|
||||||
return self.serialize_assets(assets, self.instance.key)
|
return self.serialize_assets(assets, self.instance.key)
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
|
||||||
|
serializer_class = TreeNodeSerializer
|
||||||
|
|
||||||
|
def check_permissions(self, request):
|
||||||
|
if not request.user.has_perm('assets.view_asset'):
|
||||||
|
raise PermissionDenied
|
||||||
|
return True
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
nodes = AllTypes.to_tree_nodes()
|
||||||
|
serializer = self.get_serializer(nodes, many=True)
|
||||||
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class NodeAssetsApi(generics.ListAPIView):
|
class NodeAssetsApi(generics.ListAPIView):
|
||||||
serializer_class = serializers.AssetSerializer
|
serializer_class = serializers.AssetSerializer
|
||||||
|
|
||||||
|
@ -324,7 +339,7 @@ class NodeTaskCreateApi(generics.CreateAPIView):
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
node_id = self.kwargs.get('pk')
|
node_id = self.kwargs.get('pk')
|
||||||
node = get_object_or_none(self.model, id=node_id)
|
node = get_object_or_404(self.model, id=node_id)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -346,8 +361,6 @@ class NodeTaskCreateApi(generics.CreateAPIView):
|
||||||
task = self.refresh_nodes_cache()
|
task = self.refresh_nodes_cache()
|
||||||
self.set_serializer_data(serializer, task)
|
self.set_serializer_data(serializer, task)
|
||||||
return
|
return
|
||||||
if node is None:
|
|
||||||
raise Http404()
|
|
||||||
if action == "refresh":
|
if action == "refresh":
|
||||||
task = update_node_assets_hardware_info_manual.delay(node)
|
task = update_node_assets_hardware_info_manual.delay(node)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
from common.drf.api import JMSModelViewSet
|
||||||
|
from common.drf.serializers import GroupedChoiceSerializer
|
||||||
|
from assets.models import Platform
|
||||||
|
from assets.serializers import PlatformSerializer
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['AssetPlatformViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPlatformViewSet(JMSModelViewSet):
|
||||||
|
queryset = Platform.objects.all()
|
||||||
|
serializer_classes = {
|
||||||
|
'default': PlatformSerializer,
|
||||||
|
'categories': GroupedChoiceSerializer
|
||||||
|
}
|
||||||
|
filterset_fields = ['name', 'category', 'type']
|
||||||
|
search_fields = ['name']
|
||||||
|
rbac_perms = {
|
||||||
|
'categories': 'assets.view_platform',
|
||||||
|
'type_constraints': 'assets.view_platform',
|
||||||
|
'ops_methods': 'assets.view_platform'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
pk = self.kwargs.get('pk', '')
|
||||||
|
if pk.isnumeric():
|
||||||
|
return super().get_object()
|
||||||
|
return self.get_queryset().get(name=pk)
|
||||||
|
|
||||||
|
def check_object_permissions(self, request, obj):
|
||||||
|
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
||||||
|
self.permission_denied(
|
||||||
|
request, message={"detail": "Internal platform"}
|
||||||
|
)
|
||||||
|
return super().check_object_permissions(request, obj)
|
|
@ -1,253 +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 ..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=SystemUser.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')
|
|
||||||
node_id = self.request.query_params.get('node_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,
|
|
||||||
node_id=node_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()
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue