Merge pull request #9250 from jumpserver/v3

v3 to dev
This commit is contained in:
老广 2022-12-28 13:26:25 +08:00 committed by GitHub
commit ce0632f49b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
284 changed files with 20975 additions and 3445 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ dump.rdb
.cache/ .cache/
.idea/ .idea/
.vscode/ .vscode/
.fleet/
db.sqlite3 db.sqlite3
config.py config.py
config.yml config.yml

View File

@ -1,4 +1,4 @@
FROM python:3.8-slim as stage-build FROM python:3.9-slim as stage-build
ARG TARGETARCH ARG TARGETARCH
ARG VERSION ARG VERSION
@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . . ADD . .
RUN cd utils && bash -ixeu build.sh RUN cd utils && bash -ixeu build.sh
FROM python:3.8-slim FROM python:3.9-slim
ARG TARGETARCH ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com> MAINTAINER JumpServer Team <ibuler@qq.com>
@ -18,7 +18,6 @@ ARG BUILD_DEPENDENCIES=" \
pkg-config" pkg-config"
ARG DEPENDENCIES=" \ ARG DEPENDENCIES=" \
default-libmysqlclient-dev \
freetds-dev \ freetds-dev \
libpq-dev \ libpq-dev \
libffi-dev \ libffi-dev \
@ -28,21 +27,20 @@ ARG DEPENDENCIES=" \
libxml2-dev \ libxml2-dev \
libxmlsec1-dev \ libxmlsec1-dev \
libxmlsec1-openssl \ libxmlsec1-openssl \
libaio-dev \ libaio-dev"
openssh-client \
sshpass"
ARG TOOLS=" \ ARG TOOLS=" \
ca-certificates \ ca-certificates \
curl \ curl \
default-libmysqlclient-dev \
default-mysql-client \ default-mysql-client \
iputils-ping \
locales \ locales \
openssh-client \
procps \ procps \
redis-tools \ sshpass \
telnet \ telnet \
vim \
unzip \ unzip \
vim \
wget" wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn ARG APT_MIRROR=http://mirrors.ustc.edu.cn
@ -82,6 +80,8 @@ ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
ARG DEBUG
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \ set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \ && pip config set global.index-url ${PIP_MIRROR} \

10
Dockerfile-ee Normal file
View File

@ -0,0 +1,10 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM jumpserver/core:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
WORKDIR /opt/jumpserver
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip install -r requirements/requirements_xpack.txt

View File

@ -1,4 +1,4 @@
FROM python:3.8-slim as stage-build FROM python:3.9-slim as stage-build
ARG TARGETARCH ARG TARGETARCH
ARG VERSION ARG VERSION
@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . . ADD . .
RUN cd utils && bash -ixeu build.sh RUN cd utils && bash -ixeu build.sh
FROM python:3.8-slim FROM python:3.9-slim
ARG TARGETARCH ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com> MAINTAINER JumpServer Team <ibuler@qq.com>
@ -18,7 +18,6 @@ ARG BUILD_DEPENDENCIES=" \
pkg-config" pkg-config"
ARG DEPENDENCIES=" \ ARG DEPENDENCIES=" \
default-libmysqlclient-dev \
freetds-dev \ freetds-dev \
libpq-dev \ libpq-dev \
libffi-dev \ libffi-dev \
@ -28,21 +27,20 @@ ARG DEPENDENCIES=" \
libxml2-dev \ libxml2-dev \
libxmlsec1-dev \ libxmlsec1-dev \
libxmlsec1-openssl \ libxmlsec1-openssl \
libaio-dev \ libaio-dev"
openssh-client \
sshpass"
ARG TOOLS=" \ ARG TOOLS=" \
ca-certificates \ ca-certificates \
curl \ curl \
default-libmysqlclient-dev \
default-mysql-client \ default-mysql-client \
iputils-ping \
locales \ locales \
netcat \ openssh-client \
redis-server \ procps \
sshpass \
telnet \ telnet \
vim \
unzip \ unzip \
vim \
wget" wget"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
@ -69,13 +67,15 @@ ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
ARG DEBUG
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \ set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \ && pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \ && pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \ && pip install --upgrade setuptools wheel \
&& pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \ && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl \
&& pip install $(grep 'PyNaCl' requirements/requirements.txt) \ && pip install $(grep 'PyNaCl' requirements/requirements.txt) \
&& GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \ && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \

View File

@ -28,6 +28,7 @@ class Migration(migrations.Migration):
('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command', ('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command',
max_length=16, verbose_name='Type')), max_length=16, verbose_name='Type')),
('content', models.TextField(help_text='One line one command', verbose_name='Content')), ('content', models.TextField(help_text='One line one command', verbose_name='Content')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')), ('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')),
], ],
options={ options={
@ -57,7 +58,8 @@ class Migration(migrations.Migration):
('assets', models.JSONField(verbose_name='Asset')), ('assets', models.JSONField(verbose_name='Asset')),
('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')), ('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')),
( (
'reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), 'reviewers',
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
], ],
options={ options={
'verbose_name': 'Command acl', 'verbose_name': 'Command acl',

View File

@ -1,18 +1,33 @@
# Generated by Django 3.2.14 on 2022-12-02 04:25 # Generated by Django 3.2.14 on 2022-12-02 04:25
from django.db import migrations, models from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('acls', '0007_auto_20221202_1048'), ('acls', '0007_auto_20221202_1048'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AlterModelOptions(
model_name='commandgroup', name='commandgroup',
name='comment', options={'verbose_name': 'Command group'},
field=models.TextField(blank=True, verbose_name='Comment'), ),
migrations.RenameField(
model_name='commandfilteracl',
old_name='commands',
new_name='command_groups',
),
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'},
), ),
] ]

View File

@ -1,22 +0,0 @@
# 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',
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.14 on 2022-12-20 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('acls', '0008_commandgroup_comment'),
]
operations = [
migrations.AddField(
model_name='commandfilteracl',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='loginacl',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='loginassetacl',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='commandfilteracl',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='commandgroup',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='commandgroup',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='loginacl',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='loginassetacl',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
]

View File

@ -1,25 +0,0 @@
# 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'},
),
]

View File

@ -3,7 +3,7 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.mixins import CommonModelMixin from common.db.models import JMSBaseModel
from common.utils import contains_ip from common.utils import contains_ip
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
@ -58,7 +58,7 @@ class UserAssetAccountACLQuerySet(BaseACLQuerySet):
def filter_account(self, username): def filter_account(self, username):
q = Q(accounts__username_group__contains=username) | \ q = Q(accounts__username_group__contains=username) | \
Q(accounts__username_group__contains='*') Q(accounts__username_group__contains='*')
return self.filter(q) return self.filter(q)
@ -67,7 +67,7 @@ class ACLManager(models.Manager):
return self.get_queryset().valid() return self.get_queryset().valid()
class BaseACL(CommonModelMixin): class BaseACL(JMSBaseModel):
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
priority = models.IntegerField( priority = models.IntegerField(
default=50, verbose_name=_("Priority"), default=50, verbose_name=_("Priority"),
@ -77,7 +77,6 @@ class BaseACL(CommonModelMixin):
action = models.CharField(max_length=64, default=ActionChoices.reject, verbose_name=_('Action')) action = models.CharField(max_length=64, default=ActionChoices.reject, verbose_name=_('Action'))
reviewers = models.ManyToManyField('users.User', blank=True, verbose_name=_("Reviewers")) 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'))
ActionChoices = ActionChoices ActionChoices = ActionChoices
objects = ACLManager.from_queryset(BaseACLQuerySet)() objects = ACLManager.from_queryset(BaseACLQuerySet)()

View File

@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty, get_logger from common.utils import lazyproperty, get_logger
from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import JMSOrgBaseModel
from .base import UserAssetAccountBaseACL from .base import UserAssetAccountBaseACL
logger = get_logger(__file__) logger = get_logger(__file__)
@ -26,7 +25,6 @@ class CommandGroup(JMSOrgBaseModel):
) )
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case'))
comment = models.TextField(blank=True, verbose_name=_("Comment"))
TypeChoices = TypeChoices TypeChoices = TypeChoices

View File

@ -1,17 +1,17 @@
# Generated by Django 3.1.12 on 2021-08-26 09:07 # Generated by Django 3.1.12 on 2021-08-26 09:07
import assets.models.base import uuid
import common.db.fields
from django.conf import settings
import django.core.validators import django.core.validators
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import simple_history.models import simple_history.models
import uuid from django.conf import settings
from django.db import migrations, models
import common.db.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0076_delete_assetuser'), ('assets', '0076_delete_assetuser'),
@ -22,14 +22,19 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='HistoricalAccount', name='HistoricalAccount',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')],
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), verbose_name='Username')),
('password',
common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key',
common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')),
('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
@ -37,10 +42,17 @@ class Migration(migrations.Migration):
('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField()), ('history_date', models.DateTimeField()),
('history_change_reason', models.CharField(max_length=100, null=True)), ('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), ('history_type',
('app', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='applications.application', verbose_name='Database')), models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), ('app', models.ForeignKey(blank=True, db_constraint=False, null=True,
('systemuser', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.systemuser', verbose_name='System user')), on_delete=django.db.models.deletion.DO_NOTHING, related_name='+',
to='applications.application', verbose_name='Database')),
('history_user',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+',
to=settings.AUTH_USER_MODEL)),
('systemuser', models.ForeignKey(blank=True, db_constraint=False, null=True,
on_delete=django.db.models.deletion.DO_NOTHING, related_name='+',
to='assets.systemuser', verbose_name='System user')),
], ],
options={ options={
'verbose_name': 'historical Account', 'verbose_name': 'historical Account',
@ -52,20 +64,28 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Account', name='Account',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('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)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')],
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), verbose_name='Username')),
('password',
common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key',
common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
('version', models.IntegerField(default=1, verbose_name='Version')), ('version', models.IntegerField(default=1, verbose_name='Version')),
('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='applications.application', verbose_name='Database')), ('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
('systemuser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', verbose_name='System user')), to='applications.application', verbose_name='Database')),
('systemuser',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser',
verbose_name='System user')),
], ],
options={ options={
'verbose_name': 'Account', 'verbose_name': 'Account',

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.14 on 2022-12-20 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('applications', '0026_auto_20220817_1716'),
]
operations = [
migrations.AddField(
model_name='application',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='application',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AlterField(
model_name='application',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
]

View File

@ -1,12 +1,11 @@
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 common.db.models import JMSBaseModel
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
class Application(CommonModelMixin, OrgModelMixin): class Application(JMSBaseModel, OrgModelMixin):
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
category = models.CharField( category = models.CharField(
max_length=16, verbose_name=_('Category') max_length=16, verbose_name=_('Category')
@ -15,9 +14,6 @@ class Application(CommonModelMixin, OrgModelMixin):
max_length=16, verbose_name=_('Type') max_length=16, verbose_name=_('Type')
) )
attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) attrs = models.JSONField(default=dict, verbose_name=_('Attrs'))
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
class Meta: class Meta:
verbose_name = _('Application') verbose_name = _('Application')

View File

@ -1,11 +1,11 @@
from .mixin import *
from .category import *
from .platform import *
from .asset import *
from .label import *
from .account import * from .account import *
from .node import * from .asset import *
from .domain import *
from .automations import * from .automations import *
from .gathered_user import * from .category import *
from .domain import *
from .favorite_asset import * from .favorite_asset import *
from .label import *
from .mixin import *
from .node import *
from .platform import *
from .tree import *

View File

@ -1,19 +1,22 @@
from django.shortcuts import get_object_or_404
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView, ListAPIView from rest_framework.generics import CreateAPIView, ListAPIView
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet from assets import serializers
from rbac.permissions import RBACPermission from assets.filters import AccountFilterSet
from assets.models import Account, Asset
from assets.tasks import verify_accounts_connectivity
from authentication.const import ConfirmType
from common.mixins import RecordViewLogMixin from common.mixins import RecordViewLogMixin
from common.permissions import UserConfirmation from common.permissions import UserConfirmation
from authentication.const import ConfirmType from orgs.mixins.api import OrgBulkModelViewSet
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'] __all__ = [
'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI'
]
from rbac.permissions import RBACPermission
class AccountViewSet(OrgBulkModelViewSet): class AccountViewSet(OrgBulkModelViewSet):
@ -30,10 +33,18 @@ class AccountViewSet(OrgBulkModelViewSet):
'su_from_accounts': 'assets.view_account', 'su_from_accounts': 'assets.view_account',
} }
@action(methods=['get'], detail=True, url_path='su-from-accounts') @action(methods=['get'], detail=False, url_path='su-from-accounts')
def su_from_accounts(self, request, *args, **kwargs): def su_from_accounts(self, request, *args, **kwargs):
account = super().get_object() account_id = request.query_params.get('account')
accounts = account.get_su_from_accounts() asset_id = request.query_params.get('asset')
if account_id:
account = get_object_or_404(Account, pk=account_id)
accounts = account.get_su_from_accounts()
elif asset_id:
asset = get_object_or_404(Asset, pk=asset_id)
accounts = asset.accounts.all()
else:
accounts = []
serializer = serializers.AccountSerializer(accounts, many=True) serializer = serializers.AccountSerializer(accounts, many=True)
return Response(data=serializer.data) return Response(data=serializer.data)
@ -54,8 +65,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
'default': serializers.AccountSecretSerializer, 'default': serializers.AccountSecretSerializer,
} }
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']
# Todo: 记得打开 permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
rbac_perms = { rbac_perms = {
'list': 'assets.view_accountsecret', 'list': 'assets.view_accountsecret',
'retrieve': 'assets.view_accountsecret', 'retrieve': 'assets.view_accountsecret',
@ -66,8 +76,7 @@ class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
model = Account.history.model model = Account.history.model
serializer_class = serializers.AccountHistorySerializer serializer_class = serializers.AccountHistorySerializer
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']
# Todo: 记得打开 permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
rbac_perms = { rbac_perms = {
'list': 'assets.view_accountsecret', 'list': 'assets.view_accountsecret',
} }
@ -102,4 +111,5 @@ class AccountTaskCreateAPI(CreateAPIView):
def get_exception_handler(self): def get_exception_handler(self):
def handler(e, context): def handler(e, context):
return Response({"error": str(e)}, status=400) return Response({"error": str(e)}, status=400)
return handler return handler

View File

@ -1,9 +1,6 @@
from assets import serializers from assets import serializers
from assets.models import AccountTemplate from assets.models import AccountTemplate
from rbac.permissions import RBACPermission
from authentication.const import ConfirmType
from common.mixins import RecordViewLogMixin from common.mixins import RecordViewLogMixin
from common.permissions import UserConfirmation
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet

View File

@ -30,18 +30,23 @@ __all__ = [
class AssetFilterSet(BaseFilterSet): class AssetFilterSet(BaseFilterSet):
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact") type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = django_filters.CharFilter( category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
field_name="platform__category", lookup_expr="exact" platform = django_filters.CharFilter(method='filter_platform')
)
hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact")
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
"id", "name", "address", "is_active", "id", "name", "address", "is_active",
"type", "category", "hostname" "type", "category", "platform"
] ]
@staticmethod
def filter_platform(queryset, name, value):
if value.isdigit():
return queryset.filter(platform_id=value)
else:
return queryset.filter(platform__name=value)
class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
""" """
@ -55,8 +60,9 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
ordering = ("name",) ordering = ("name",)
serializer_classes = ( serializer_classes = (
("default", serializers.AssetSerializer), ("default", serializers.AssetSerializer),
("suggestion", serializers.MiniAssetSerializer), ("retrieve", serializers.AssetDetailSerializer),
("platform", serializers.PlatformSerializer), ("platform", serializers.PlatformSerializer),
("suggestion", serializers.MiniAssetSerializer),
("gateways", serializers.GatewaySerializer), ("gateways", serializers.GatewaySerializer),
) )
rbac_perms = ( rbac_perms = (

View File

@ -1,4 +1,4 @@
from assets.models import Cloud from assets.models import Cloud, Asset
from assets.serializers import CloudSerializer from assets.serializers import CloudSerializer
from .asset import AssetViewSet from .asset import AssetViewSet
@ -8,6 +8,7 @@ __all__ = ['CloudViewSet']
class CloudViewSet(AssetViewSet): class CloudViewSet(AssetViewSet):
model = Cloud model = Cloud
perm_model = Asset
def get_serializer_classes(self): def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes() serializer_classes = super().get_serializer_classes()

View File

@ -1,4 +1,4 @@
from assets.models import Database from assets.models import Database, Asset
from assets.serializers import DatabaseSerializer from assets.serializers import DatabaseSerializer
from .asset import AssetViewSet from .asset import AssetViewSet
@ -8,6 +8,7 @@ __all__ = ['DatabaseViewSet']
class DatabaseViewSet(AssetViewSet): class DatabaseViewSet(AssetViewSet):
model = Database model = Database
perm_model = Asset
def get_serializer_classes(self): def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes() serializer_classes = super().get_serializer_classes()

View File

@ -1,6 +1,5 @@
from assets.serializers import DeviceSerializer from assets.serializers import DeviceSerializer
from assets.models import Device from assets.models import Device, Asset
from .asset import AssetViewSet from .asset import AssetViewSet
__all__ = ['DeviceViewSet'] __all__ = ['DeviceViewSet']
@ -8,6 +7,7 @@ __all__ = ['DeviceViewSet']
class DeviceViewSet(AssetViewSet): class DeviceViewSet(AssetViewSet):
model = Device model = Device
perm_model = Asset
def get_serializer_classes(self): def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes() serializer_classes = super().get_serializer_classes()

View File

@ -1,4 +1,4 @@
from assets.models import Host from assets.models import Host, Asset
from assets.serializers import HostSerializer from assets.serializers import HostSerializer
from .asset import AssetViewSet from .asset import AssetViewSet
@ -7,6 +7,7 @@ __all__ = ['HostViewSet']
class HostViewSet(AssetViewSet): class HostViewSet(AssetViewSet):
model = Host model = Host
perm_model = Asset
def get_serializer_classes(self): def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes() serializer_classes = super().get_serializer_classes()

View File

@ -1,4 +1,4 @@
from assets.models import Web from assets.models import Web, Asset
from assets.serializers import WebSerializer from assets.serializers import WebSerializer
from .asset import AssetViewSet from .asset import AssetViewSet
@ -8,6 +8,7 @@ __all__ = ['WebViewSet']
class WebViewSet(AssetViewSet): class WebViewSet(AssetViewSet):
model = Web model = Web
perm_model = Asset
def get_serializer_classes(self): def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes() serializer_classes = super().get_serializer_classes()

View File

@ -1,17 +1,18 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.response import Response
from rest_framework import status, mixins, viewsets from rest_framework import status, mixins, viewsets
from rest_framework.response import Response
from orgs.mixins import generics
from assets import serializers from assets import serializers
from assets.tasks import execute_automation
from assets.models import BaseAutomation, AutomationExecution from assets.models import BaseAutomation, AutomationExecution
from assets.tasks import execute_automation
from common.const.choices import Trigger from common.const.choices import Trigger
from orgs.mixins import generics
__all__ = [ __all__ = [
'AutomationAssetsListApi', 'AutomationRemoveAssetApi', 'AutomationAssetsListApi', 'AutomationRemoveAssetApi',
'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi', 'AutomationExecutionViewSet' 'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi',
'AutomationExecutionViewSet',
] ]

View File

@ -3,14 +3,15 @@
from rest_framework import mixins from rest_framework import mixins
from assets import serializers
from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
from common.utils import get_object_or_none from common.utils import get_object_or_none
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from .base import AutomationExecutionViewSet
from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
from assets import serializers
__all__ = [ __all__ = [
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet' 'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet',
'ChangSecretExecutionViewSet'
] ]
@ -38,3 +39,11 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
queryset = queryset.filter(execution=execution) queryset = queryset.filter(execution=execution)
queryset = queryset.order_by('-date_started') queryset = queryset.order_by('-date_started')
return queryset return queryset
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (
("list", "assets.view_changesecretexecution"),
("retrieve", "assets.view_changesecretexecution"),
("create", "assets.add_changesecretexecution"),
)

View File

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from orgs.mixins.api import OrgBulkModelViewSet
from assets.models import GatherAccountsAutomation
from assets import serializers from assets import serializers
from assets.models import GatherAccountsAutomation
from orgs.mixins.api import OrgBulkModelViewSet
from .base import AutomationExecutionViewSet
__all__ = [ __all__ = [
'GatherAccountsAutomationViewSet', 'GatherAccountsAutomationViewSet', 'GatherAccountsExecutionViewSet'
] ]
@ -16,3 +16,11 @@ class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
search_fields = filter_fields search_fields = filter_fields
ordering_fields = ('name',) ordering_fields = ('name',)
serializer_class = serializers.GatherAccountAutomationSerializer serializer_class = serializers.GatherAccountAutomationSerializer
class GatherAccountsExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (
("list", "assets.view_gatheraccountsexecution"),
("retrieve", "assets.view_gatheraccountsexecution"),
("create", "assets.add_gatheraccountsexecution"),
)

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
#
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
__all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
model = GatheredUser
serializer_class = GatheredUserSerializer
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filterset_fields = ['asset', 'username', 'present', 'asset__address', 'asset__name', 'asset_id']
search_fields = ['username', 'asset__address', 'asset__name']

View File

@ -61,6 +61,7 @@ class SerializeToTreeNodeMixin:
'meta': { 'meta': {
'type': 'asset', 'type': 'asset',
'data': { 'data': {
'platform_type': asset.platform.type,
'org_name': asset.org_name, 'org_name': asset.org_name,
'sftp': asset.platform_id in sftp_enabled_platform, 'sftp': asset.platform_id in sftp_enabled_platform,
}, },

View File

@ -1,43 +1,37 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from functools import partial
from collections import namedtuple, defaultdict from collections import namedtuple, defaultdict
from django.core.exceptions import PermissionDenied from functools import partial
from rest_framework import status
from rest_framework.generics import get_object_or_404
from rest_framework.serializers import ValidationError
from rest_framework.response import Response
from rest_framework.decorators import action
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.utils.translation import ugettext_lazy as _
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from common.const.http import POST
from common.exceptions import SomeoneIsDoingThis
from common.const.signals import PRE_REMOVE, POST_REMOVE
from common.mixins.api import SuggestionMixin
from assets.models import Asset from assets.models import Asset
from common.const.http import POST
from common.const.signals import PRE_REMOVE, POST_REMOVE
from common.exceptions import SomeoneIsDoingThis
from common.mixins.api import SuggestionMixin
from common.utils import get_logger from common.utils import get_logger
from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org from orgs.utils import current_org
from .. import serializers
from ..models import Node from ..models import Node
from ..tasks import ( from ..tasks import (
update_node_assets_hardware_info_manual, update_node_assets_hardware_info_manual,
test_node_assets_connectivity_manual, test_node_assets_connectivity_manual,
check_node_assets_amount_task check_node_assets_amount_task
) )
from .. import serializers
from ..const import AllTypes
from .mixin import SerializeToTreeNodeMixin
from assets.locks import NodeAddChildrenLock
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', 'NodeViewSet', 'NodeAssetsApi', 'NodeAddAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
'NodeAddChildrenApi', 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi', 'NodeAddChildrenApi', 'NodeTaskCreateApi',
'NodeTaskCreateApi', 'CategoryTreeApi',
] ]
@ -74,153 +68,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet):
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)
class NodeListAsTreeApi(generics.ListAPIView):
"""
获取节点列表树
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
serializer_class = TreeNodeSerializer
@staticmethod
def to_tree_queryset(queryset):
queryset = [node.as_tree_node() for node in queryset]
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.to_tree_queryset(queryset)
return queryset
class NodeChildrenApi(generics.ListCreateAPIView):
serializer_class = serializers.NodeSerializer
search_fields = ('value',)
instance = None
is_initial = False
def initial(self, request, *args, **kwargs):
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
with NodeAddChildrenLock(self.instance):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
serializer.instance = node
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key")
if not pk and not key:
self.is_initial = True
if current_org.is_root():
node = None
else:
node = Node.org_root()
return node
if pk:
node = get_object_or_404(Node, pk=pk)
else:
node = get_object_or_404(Node, key=key)
return node
def get_org_root_queryset(self, query_all):
if query_all:
return Node.objects.all()
else:
return Node.org_root_nodes()
def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all"
if self.is_initial and current_org.is_root():
return self.get_org_root_queryset(query_all)
if self.is_initial:
with_self = True
else:
with_self = False
if not self.instance:
return Node.objects.none()
if query_all:
queryset = self.instance.get_all_children(with_self=with_self)
else:
queryset = self.instance.get_children(with_self=with_self)
return queryset
class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
"""
节点子节点作为树返回
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
def filter_queryset(self, queryset):
if not self.request.GET.get('search'):
return queryset
queryset = super().filter_queryset(queryset)
queryset = self.model.get_ancestor_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
nodes = self.filter_queryset(self.get_queryset()).order_by('value')
nodes = self.serialize_nodes(nodes, with_asset_amount=True)
assets = self.get_assets()
data = [*nodes, *assets]
return Response(data=data)
def get_assets(self):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not self.instance or not include_assets:
return []
assets = self.instance.get_assets().only(
"id", "name", "address", "platform_id",
"org_id", "is_active",
).prefetch_related('platform')
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

View File

@ -1,10 +1,10 @@
from jumpserver.utils import has_valid_xpack_license
from common.drf.api import JMSModelViewSet from common.drf.api import JMSModelViewSet
from common.drf.serializers import GroupedChoiceSerializer from common.drf.serializers import GroupedChoiceSerializer
from assets.models import Platform from assets.models import Platform
from assets.const import AllTypes
from assets.serializers import PlatformSerializer from assets.serializers import PlatformSerializer
__all__ = ['AssetPlatformViewSet'] __all__ = ['AssetPlatformViewSet']
@ -22,6 +22,11 @@ class AssetPlatformViewSet(JMSModelViewSet):
'ops_methods': 'assets.view_platform' 'ops_methods': 'assets.view_platform'
} }
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(type__in=AllTypes.get_types())
return queryset
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk', '') pk = self.kwargs.get('pk', '')
if pk.isnumeric(): if pk.isnumeric():

153
apps/assets/api/tree.py Normal file
View File

@ -0,0 +1,153 @@
# ~*~ coding: utf-8 ~*~
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from assets.locks import NodeAddChildrenLock
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from orgs.mixins import generics
from orgs.utils import current_org
from .mixin import SerializeToTreeNodeMixin
from .. import serializers
from ..const import AllTypes
from ..models import Node, Platform, Asset
logger = get_logger(__file__)
__all__ = [
'NodeChildrenApi',
'NodeChildrenAsTreeApi',
'CategoryTreeApi',
]
class NodeChildrenApi(generics.ListCreateAPIView):
"""
节点的增删改查
"""
serializer_class = serializers.NodeSerializer
search_fields = ('value',)
instance = None
is_initial = False
def initial(self, request, *args, **kwargs):
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
with NodeAddChildrenLock(self.instance):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
serializer.instance = node
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key")
if not pk and not key:
self.is_initial = True
if current_org.is_root():
node = None
else:
node = Node.org_root()
return node
if pk:
node = get_object_or_404(Node, pk=pk)
else:
node = get_object_or_404(Node, key=key)
return node
def get_org_root_queryset(self, query_all):
if query_all:
return Node.objects.all()
else:
return Node.org_root_nodes()
def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all"
if self.is_initial and current_org.is_root():
return self.get_org_root_queryset(query_all)
if self.is_initial:
with_self = True
else:
with_self = False
if not self.instance:
return Node.objects.none()
if query_all:
queryset = self.instance.get_all_children(with_self=with_self)
else:
queryset = self.instance.get_children(with_self=with_self)
return queryset
class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
"""
节点子节点作为树返回
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
def filter_queryset(self, queryset):
if not self.request.GET.get('search'):
return queryset
queryset = super().filter_queryset(queryset)
queryset = self.model.get_ancestor_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
nodes = self.filter_queryset(self.get_queryset()).order_by('value')
nodes = self.serialize_nodes(nodes, with_asset_amount=True)
assets = self.get_assets_as_node()
data = [*nodes, *assets]
return Response(data=data)
def get_assets_as_node(self):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not self.instance or not include_assets:
return []
assets = self.instance.get_assets_for_tree()
return self.serialize_assets(assets, self.instance.key)
class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
serializer_class = TreeNodeSerializer
rbac_perms = {
'GET': 'assets.view_asset',
'list': 'assets.view_asset',
}
def get_assets(self):
key = self.request.query_params.get('key')
platform = Platform.objects.filter(id=key).first()
if not platform:
return []
assets = Asset.objects.filter(platform=platform).prefetch_related('platform')
return self.serialize_assets(assets, key)
def list(self, request, *args, **kwargs):
include_asset = self.request.query_params.get('assets', '0') == '1'
if include_asset and self.request.query_params.get('key'):
nodes = self.get_assets()
else:
nodes = AllTypes.to_tree_nodes(include_asset)
return Response(data=nodes)

View File

@ -49,23 +49,6 @@ class BaseAccountHandler:
header_fields[field] = str(v.label) header_fields[field] = str(v.label)
return header_fields return header_fields
@staticmethod
def load_auth(tp, value, system_user):
if value:
return value
if system_user:
return getattr(system_user, tp, '')
return ''
@classmethod
def replace_auth(cls, account, system_user_dict):
system_user = system_user_dict.get(account.systemuser_id)
account.username = cls.load_auth('username', account.username, system_user)
account.password = cls.load_auth('password', account.password, system_user)
account.private_key = cls.load_auth('private_key', account.private_key, system_user)
account.public_key = cls.load_auth('public_key', account.public_key, system_user)
return account
@classmethod @classmethod
def create_row(cls, data, header_fields): def create_row(cls, data, header_fields):
data = cls.unpack_data(data) data = cls.unpack_data(data)
@ -94,30 +77,30 @@ class AssetAccountHandler(BaseAccountHandler):
return filename return filename
@classmethod @classmethod
def create_data_map(cls, categories: list): def create_data_map(cls, types: list):
data_map = defaultdict(list) data_map = defaultdict(list)
# TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作 # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
qs = Account.objects.filter( qs = Account.objects.filter(
asset__platform__type__in=categories asset__platform__type__in=types
).annotate(category=F('asset__platform__type')) ).annotate(type=F('asset__platform__type'))
print(qs, categories)
if not qs.exists(): if not qs.exists():
return data_map return data_map
category_dict = {} type_dict = {}
for i in AllTypes.grouped_choices_to_objs(): for i in AllTypes.grouped_choices_to_objs():
for j in i['children']: for j in i['children']:
category_dict[j['value']] = j['display_name'] type_dict[j['value']] = j['display_name']
header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first()))
account_category_map = defaultdict(list) account_type_map = defaultdict(list)
for account in qs: for account in qs:
account_category_map[account.category].append(account) account_type_map[account.type].append(account)
data_map = {} data_map = {}
for category, accounts in account_category_map.items(): for tp, accounts in account_type_map.items():
sheet_name = category_dict.get(category, category) sheet_name = type_dict.get(tp, tp)
data = AccountSecretSerializer(accounts, many=True).data data = AccountSecretSerializer(accounts, many=True).data
data_map.update(cls.add_rows(data, header_fields, sheet_name)) data_map.update(cls.add_rows(data, header_fields, sheet_name))
@ -140,9 +123,9 @@ class AccountBackupHandler:
# Print task start date # Print task start date
time_start = time.time() time_start = time.time()
files = [] files = []
categories = self.execution.categories types = self.execution.types
data_map = AssetAccountHandler.create_data_map(categories) data_map = AssetAccountHandler.create_data_map(types)
if not data_map: if not data_map:
return files return files

View File

@ -1,19 +1,19 @@
import os import os
import yaml
import shutil import shutil
from hashlib import md5
from copy import deepcopy
from socket import gethostname
from collections import defaultdict from collections import defaultdict
from copy import deepcopy
from hashlib import md5
from socket import gethostname
import yaml
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from common.utils import get_logger
from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj
from assets.const import SecretType
from assets.automations.methods import platform_automation_methods from assets.automations.methods import platform_automation_methods
from assets.const import SecretType
from common.utils import get_logger, lazyproperty
from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
logger = get_logger(__name__) logger = get_logger(__name__)
@ -93,7 +93,7 @@ class BasePlaybookManager:
def get_assets_group_by_platform(self): def get_assets_group_by_platform(self):
return self.automation.all_assets_group_by_platform() return self.automation.all_assets_group_by_platform()
@property @lazyproperty
def runtime_dir(self): def runtime_dir(self):
ansible_dir = settings.ANSIBLE_DIR ansible_dir = settings.ANSIBLE_DIR
dir_name = '{}_{}'.format(self.automation.name.replace(' ', '_'), self.execution.id) dir_name = '{}_{}'.format(self.automation.name.replace(' ', '_'), self.execution.id)
@ -105,11 +105,6 @@ class BasePlaybookManager:
os.makedirs(path, exist_ok=True, mode=0o755) os.makedirs(path, exist_ok=True, mode=0o755)
return path return path
def prepare_playbook_dir(self):
for d in [self.runtime_dir]:
if not os.path.exists(d):
os.makedirs(d, exist_ok=True, mode=0o755)
def host_callback(self, host, automation=None, **kwargs): def host_callback(self, host, automation=None, **kwargs):
enabled_attr = '{}_enabled'.format(self.__class__.method_type()) enabled_attr = '{}_enabled'.format(self.__class__.method_type())
method_attr = '{}_method'.format(self.__class__.method_type()) method_attr = '{}_method'.format(self.__class__.method_type())

View File

@ -1,8 +1,9 @@
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from assets.const import AutomationTypes from assets.const import AutomationTypes, Source
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from .filter import GatherAccountsFilter from .filter import GatherAccountsFilter
from ...models import GatheredUser
from ..base.manager import BasePlaybookManager from ..base.manager import BasePlaybookManager
logger = get_logger(__name__) logger = get_logger(__name__)
@ -26,20 +27,33 @@ class GatherAccountsManager(BasePlaybookManager):
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result) result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
return result return result
@staticmethod
def bulk_create_accounts(asset, result):
account_objs = []
account_model = asset.accounts.model
account_usernames = set(asset.accounts.values_list('username', flat=True))
with tmp_to_org(asset.org_id):
accounts_dict = {}
for username, data in result.items():
comment = ''
d = {'asset': asset, 'username': username, 'name': username, 'source': Source.COLLECTED}
if data.get('date'):
comment += f"{_('Date last login')}: {data['date']}\n "
if data.get('address'):
comment += f"{_('IP last login')}: {data['address'][:32]}"
d['comment'] = comment
accounts_dict[username] = d
for username, data in accounts_dict.items():
if username in account_usernames:
continue
account_objs.append(account_model(**data))
account_model.objects.bulk_create(account_objs)
def on_host_success(self, host, result): def on_host_success(self, host, result):
info = result.get('debug', {}).get('res', {}).get('info', {}) info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host) asset = self.host_asset_mapper.get(host)
org_id = asset.org_id
if asset and info: if asset and info:
result = self.filter_success_result(host, info) result = self.filter_success_result(host, info)
with tmp_to_org(org_id): self.bulk_create_accounts(asset, result)
GatheredUser.objects.filter(asset=asset, present=True).update(present=False)
for username, data in result.items():
defaults = {'asset': asset, 'present': True, 'username': username}
if data.get('date'):
defaults['date_last_login'] = data['date']
if data.get('address'):
defaults['ip_last_login'] = data['address'][:32]
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
else: else:
logger.error("Not found info".format(host)) logger.error("Not found info".format(host))

View File

@ -1,16 +0,0 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Add user account.username
mongodb_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
db: "{{ jms_asset.specific.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"

View File

@ -1,6 +0,0 @@
id: push_account_mongodb
name: Push account from MongoDB
category: database
type:
- mongodb
method: push_account

View File

@ -1,15 +0,0 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Add user account.username
community.mysql.mysql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"

View File

@ -1,6 +0,0 @@
id: push_account_mysql
name: Push account from MySQL
category: database
type:
- mysql
method: push_account

View File

@ -1,16 +0,0 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Add user account.username
oracle_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"

View File

@ -1,6 +0,0 @@
id: push_account_oracle
name: Push account from Oracle
category: database
type:
- oracle
method: push_account

View File

@ -1,16 +0,0 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Add user account.username
community.postgresql.postgresql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.specific.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"

View File

@ -1,6 +0,0 @@
id: push_account_postgresql
name: Push account for PostgreSQL
category: database
type:
- postgresql
method: push_account

View File

@ -1,19 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: Add user account.username
ansible.builtin.user:
name: "{{ account.username }}"
- name: Set account.username password
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
when: secret_type == "password"
- name: Set account.username SSH key
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
when: secret_type == "ssh_key"

View File

@ -1,7 +0,0 @@
id: push_account_posix
name: Push posix account
category: host
type:
- linux
- unix
method: push_account

View File

@ -1,13 +0,0 @@
- hosts: windows
gather_facts: yes
tasks:
- name: Add user account.username
ansible.windows.win_user:
vars:
fullname: "{{ account.username }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
state: present
password_expired: no
update_password: always
password_never_expires: yes

View File

@ -1,7 +0,0 @@
id: push_account_windows
name: Push account windows
version: 1
method: push_account
category: host
type:
- windows

View File

@ -13,3 +13,14 @@ class SecretType(TextChoices):
SSH_KEY = 'ssh_key', _('SSH key') SSH_KEY = 'ssh_key', _('SSH key')
ACCESS_KEY = 'access_key', _('Access key') ACCESS_KEY = 'access_key', _('Access key')
TOKEN = 'token', _('Token') TOKEN = 'token', _('Token')
class AliasAccount(TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
class Source(TextChoices):
LOCAL = 'local', _('Local')
COLLECTED = 'collected', _('Collected')

View File

@ -12,7 +12,7 @@ DEFAULT_PASSWORD_RULES = {
class AutomationTypes(TextChoices): class AutomationTypes(TextChoices):
ping = 'ping', _('Ping') ping = 'ping', _('Ping')
gather_facts = 'gather_facts', _('Gather facts') gather_facts = 'gather_facts', _('Gather facts')
push_account = 'push_account', _('Create account') push_account = 'push_account', _('Push account')
change_secret = 'change_secret', _('Change secret') change_secret = 'change_secret', _('Change secret')
verify_account = 'verify_account', _('Verify account') verify_account = 'verify_account', _('Verify account')
gather_accounts = 'gather_accounts', _('Gather accounts') gather_accounts = 'gather_accounts', _('Gather accounts')
@ -20,8 +20,9 @@ class AutomationTypes(TextChoices):
@classmethod @classmethod
def get_type_model(cls, tp): def get_type_model(cls, tp):
from assets.models import ( from assets.models import (
PingAutomation, GatherFactsAutomation, PushAccountAutomation, PingAutomation, GatherFactsAutomation,
ChangeSecretAutomation, VerifyAccountAutomation, GatherAccountsAutomation, PushAccountAutomation, ChangeSecretAutomation,
VerifyAccountAutomation, GatherAccountsAutomation,
) )
type_model_dict = { type_model_dict = {
cls.ping: PingAutomation, cls.ping: PingAutomation,

View File

@ -1,5 +1,6 @@
from django.db.models import TextChoices from django.db.models import TextChoices
from jumpserver.utils import has_valid_xpack_license
from .protocol import Protocol from .protocol import Protocol
@ -53,3 +54,25 @@ class BaseType(TextChoices):
@classmethod @classmethod
def internal_platforms(cls): def internal_platforms(cls):
raise NotImplementedError raise NotImplementedError
@classmethod
def get_community_types(cls):
raise NotImplementedError
@classmethod
def get_types(cls):
tps = [tp for tp in cls]
if not has_valid_xpack_license():
tps = cls.get_community_types()
return tps
@classmethod
def get_choices(cls):
tps = cls.get_types()
cls_choices = cls.choices
return [
choice for choice in cls_choices
if choice[0] in tps
]

View File

@ -25,7 +25,6 @@ class CloudTypes(BaseType):
'gather_facts_enabled': False, 'gather_facts_enabled': False,
'verify_account_enabled': False, 'verify_account_enabled': False,
'change_secret_enabled': False, 'change_secret_enabled': False,
'push_account_enabled': False,
'gather_accounts_enabled': False, 'gather_accounts_enabled': False,
} }
} }
@ -49,3 +48,9 @@ class CloudTypes(BaseType):
cls.PRIVATE: [{'name': 'Vmware-vSphere'}], cls.PRIVATE: [{'name': 'Vmware-vSphere'}],
cls.K8S: [{'name': 'Kubernetes'}], cls.K8S: [{'name': 'Kubernetes'}],
} }
@classmethod
def get_community_types(cls):
return [
cls.K8S, cls.PUBLIC, cls.PRIVATE
]

View File

@ -1,4 +1,3 @@
from .base import BaseType from .base import BaseType
@ -34,7 +33,6 @@ class DatabaseTypes(BaseType):
'gather_accounts_enabled': True, 'gather_accounts_enabled': True,
'verify_account_enabled': True, 'verify_account_enabled': True,
'change_secret_enabled': True, 'change_secret_enabled': True,
'push_account_enabled': True,
} }
} }
return constrains return constrains
@ -62,3 +60,8 @@ class DatabaseTypes(BaseType):
cls.REDIS: [{'name': 'Redis'}], cls.REDIS: [{'name': 'Redis'}],
} }
@classmethod
def get_community_types(cls):
return [
cls.MYSQL, cls.MARIADB, cls.MONGODB, cls.REDIS
]

View File

@ -40,7 +40,6 @@ class DeviceTypes(BaseType):
'gather_accounts_enabled': False, 'gather_accounts_enabled': False,
'verify_account_enabled': False, 'verify_account_enabled': False,
'change_secret_enabled': False, 'change_secret_enabled': False,
'push_account_enabled': False,
} }
} }
@ -52,3 +51,9 @@ class DeviceTypes(BaseType):
cls.ROUTER: [], cls.ROUTER: [],
cls.FIREWALL: [] cls.FIREWALL: []
} }
@classmethod
def get_community_types(cls):
return [
cls.GENERAL, cls.SWITCH, cls.ROUTER, cls.FIREWALL
]

View File

@ -34,7 +34,7 @@ class HostTypes(BaseType):
def _get_protocol_constrains(cls) -> dict: def _get_protocol_constrains(cls) -> dict:
return { return {
'*': { '*': {
'choices': ['ssh', 'telnet', 'vnc', 'rdp'] 'choices': ['ssh', 'telnet', 'vnc', 'rdp']
}, },
cls.WINDOWS: { cls.WINDOWS: {
'choices': ['rdp', 'ssh', 'vnc'] 'choices': ['rdp', 'ssh', 'vnc']
@ -54,7 +54,6 @@ class HostTypes(BaseType):
'gather_accounts_enabled': True, 'gather_accounts_enabled': True,
'verify_account_enabled': True, 'verify_account_enabled': True,
'change_secret_enabled': True, 'change_secret_enabled': True,
'push_account_enabled': True,
}, },
cls.WINDOWS: { cls.WINDOWS: {
'ansible_config': { 'ansible_config': {
@ -76,7 +75,6 @@ class HostTypes(BaseType):
{'name': 'macOS'}, {'name': 'macOS'},
{'name': 'BSD'}, {'name': 'BSD'},
{'name': 'AIX', 'automation': { {'name': 'AIX', 'automation': {
'push_account_method': 'push_account_aix',
'change_secret_method': 'push_secret_aix' 'change_secret_method': 'push_secret_aix'
}} }}
], ],
@ -97,7 +95,7 @@ class HostTypes(BaseType):
{ {
'name': 'RemoteAppHost', 'name': 'RemoteAppHost',
'_protocols': ['rdp', 'ssh'], '_protocols': ['rdp', 'ssh'],
'protocols_setting': { 'protocols_setting': {
'ssh': { 'ssh': {
'required': True 'required': True
} }
@ -106,3 +104,9 @@ class HostTypes(BaseType):
], ],
cls.OTHER_HOST: [] cls.OTHER_HOST: []
} }
@classmethod
def get_community_types(cls) -> list:
return [
cls.LINUX, cls.UNIX, cls.WINDOWS, cls.OTHER_HOST
]

View File

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from common.db.models import ChoicesMixin from common.db.models import ChoicesMixin
__all__ = ['Protocol'] __all__ = ['Protocol']
@ -102,9 +103,9 @@ class Protocol(ChoicesMixin, models.TextChoices):
'port': 80, 'port': 80,
'secret_types': ['password'], 'secret_types': ['password'],
'setting': { 'setting': {
'username_selector': 'input[type=text]', 'username_selector': 'name=username',
'password_selector': 'input[type=password]', 'password_selector': 'name=password',
'submit_selector': 'button[type=submit]', 'submit_selector': 'id=longin_button',
} }
}, },
} }
@ -112,7 +113,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
@classmethod @classmethod
def settings(cls): def settings(cls):
return { return {
**cls.device_protocols(), **cls.device_protocols(),
**cls.database_protocols(), **cls.database_protocols(),
**cls.cloud_protocols() **cls.cloud_protocols()
} }

View File

@ -1,14 +1,15 @@
from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from common.db.models import ChoicesMixin from django.utils.translation import gettext as _
from common.tree import TreeNode
from common.db.models import ChoicesMixin
from .category import Category from .category import Category
from .host import HostTypes
from .device import DeviceTypes
from .database import DatabaseTypes
from .web import WebTypes
from .cloud import CloudTypes from .cloud import CloudTypes
from .database import DatabaseTypes
from .device import DeviceTypes
from .host import HostTypes
from .web import WebTypes
class AllTypes(ChoicesMixin): class AllTypes(ChoicesMixin):
@ -54,7 +55,7 @@ class AllTypes(ChoicesMixin):
item_name = item.replace('_enabled', '') item_name = item.replace('_enabled', '')
methods = filter_platform_methods(category, tp, item_name) methods = filter_platform_methods(category, tp, item_name)
methods = [{'name': m['name'], 'id': m['id']} for m in methods] methods = [{'name': m['name'], 'id': m['id']} for m in methods]
automation_methods[item_name+'_methods'] = methods automation_methods[item_name + '_methods'] = methods
automation.update(automation_methods) automation.update(automation_methods)
constraints['automation'] = automation constraints['automation'] = automation
return constraints return constraints
@ -62,14 +63,18 @@ class AllTypes(ChoicesMixin):
@classmethod @classmethod
def types(cls, with_constraints=True): def types(cls, with_constraints=True):
types = [] types = []
for category, tps in cls.category_types(): for category, type_cls in cls.category_types():
tps = type_cls.get_types()
types.extend([cls.serialize_type(category, tp, with_constraints) for tp in tps]) types.extend([cls.serialize_type(category, tp, with_constraints) for tp in tps])
return types return types
@classmethod @classmethod
def categories(cls, with_constraints=True): def categories(cls, with_constraints=True):
categories = [] categories = []
for category, tps in cls.category_types(): for category, type_cls in cls.category_types():
tps = type_cls.get_types()
if not tps:
continue
category_data = { category_data = {
'value': category.value, 'value': category.value,
'label': category.label, 'label': category.label,
@ -121,30 +126,82 @@ class AllTypes(ChoicesMixin):
(Category.CLOUD, CloudTypes) (Category.CLOUD, CloudTypes)
) )
@classmethod
def get_types(cls):
tps = []
for i in dict(cls.category_types()).values():
tps.extend(i.get_types())
return tps
@staticmethod @staticmethod
def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None):
node = TreeNode(**{ node = {
'id': choice.name, 'id': pid + '_' + choice.name,
'name': choice.label, 'name': choice.label,
'title': choice.label, 'title': choice.label,
'pId': pid, 'pId': pid,
'open': opened, 'open': opened,
'isParent': is_parent, 'isParent': is_parent,
}) }
if meta: if meta:
node.meta = meta node['meta'] = meta
return node return node
@classmethod @classmethod
def to_tree_nodes(cls): def platform_to_node(cls, p, pid, include_asset):
root = TreeNode(id='ROOT', name='类型节点', title='类型节点') node = {
'id': '{}'.format(p.id),
'name': p.name,
'title': p.name,
'pId': pid,
'isParent': include_asset,
'meta': {
'type': 'platform'
}
}
return node
@classmethod
def to_tree_nodes(cls, include_asset):
from ..models import Asset, Platform
asset_platforms = Asset.objects.all().values_list('platform_id', flat=True)
platform_count = defaultdict(int)
for platform_id in asset_platforms:
platform_count[platform_id] += 1
category_type_mapper = defaultdict(int)
platforms = Platform.objects.all()
tp_platforms = defaultdict(list)
for p in platforms:
category_type_mapper[p.category + '_' + p.type] += platform_count[p.id]
category_type_mapper[p.category] += platform_count[p.id]
tp_platforms[p.category + '_' + p.type].append(p)
root = dict(id='ROOT', name=_('All types'), title='所有类型', open=True, isParent=True)
nodes = [root] nodes = [root]
for category, types in cls.category_types(): for category, type_cls in cls.category_types():
category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'}) # Category 格式化
meta = {'type': 'category', 'category': category.value}
category_node = cls.choice_to_node(category, 'ROOT', meta=meta)
category_count = category_type_mapper.get(category, 0)
category_node['name'] += f'({category_count})'
nodes.append(category_node) nodes.append(category_node)
# Type 格式化
types = type_cls.get_types()
for tp in types: for tp in types:
tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'}) meta = {'type': 'type', 'category': category.value, '_type': tp.value}
tp_node = cls.choice_to_node(tp, category_node['id'], opened=False, meta=meta)
tp_count = category_type_mapper.get(category + '_' + tp, 0)
tp_node['name'] += f'({tp_count})'
nodes.append(tp_node) nodes.append(tp_node)
# Platform 格式化
for p in tp_platforms.get(category + '_' + tp, []):
platform_node = cls.platform_to_node(p, tp_node['id'], include_asset)
platform_node['name'] += f'({platform_count.get(p.id, 0)})'
nodes.append(platform_node)
return nodes return nodes
@classmethod @classmethod
@ -253,8 +310,3 @@ class AllTypes(ChoicesMixin):
print("\t- Update platform: {}".format(platform.name)) print("\t- Update platform: {}".format(platform.name))
platform_data = cls.get_type_default_platform(platform.category, platform.type) platform_data = cls.get_type_default_platform(platform.category, platform.type)
cls.create_or_update_by_platform_data(platform.name, platform_data) cls.create_or_update_by_platform_data(platform.name, platform_data)

View File

@ -44,3 +44,9 @@ class WebTypes(BaseType):
{'name': 'Website'}, {'name': 'Website'},
], ],
} }
@classmethod
def get_community_types(cls):
return [
cls.WEBSITE,
]

View File

@ -126,17 +126,6 @@ class LabelFilterBackend(filters.BaseFilterBackend):
return queryset return queryset
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
def filter_node_related_all(self, queryset, node):
return queryset.filter(
Q(asset__nodes__key__istartswith=f'{node.key}:') |
Q(asset__nodes__key=node.key)
).distinct()
def filter_node_related_direct(self, queryset, node):
return queryset.filter(asset__nodes__key=node.key).distinct()
class IpInFilterBackend(filters.BaseFilterBackend): class IpInFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
ips = request.query_params.get('ips') ips = request.query_params.get('ips')

View File

@ -12,7 +12,6 @@ def migrate_platform_type_to_lower(apps, *args):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('assets', '0094_auto_20220402_1736'), ('assets', '0094_auto_20220402_1736'),
] ]
@ -51,7 +50,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='platform', model_name='platform',
name='su_method', name='su_method',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Su method'),
), ),
migrations.RunPython(migrate_platform_type_to_lower) migrations.RunPython(migrate_platform_type_to_lower)
] ]

View File

@ -1,11 +1,9 @@
# Generated by Django 3.1.14 on 2022-04-26 07:54 # Generated by Django 3.1.14 on 2022-04-26 07:54
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('assets', '0095_auto_20220407_1726'), ('assets', '0095_auto_20220407_1726'),
] ]
@ -18,7 +16,8 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=32, verbose_name='Name')), ('name', models.CharField(max_length=32, verbose_name='Name')),
('port', models.IntegerField(verbose_name='Port')), ('port', models.IntegerField(verbose_name='Port')),
('setting', models.JSONField(default=dict, verbose_name='Setting')), ('setting', models.JSONField(default=dict, verbose_name='Setting')),
('platform', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.platform'),), ('platform',
models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.platform'),),
('default', models.BooleanField(default=True, verbose_name='Default')), ('default', models.BooleanField(default=True, verbose_name='Default')),
('required', models.BooleanField(default=False, verbose_name='Required')), ('required', models.BooleanField(default=False, verbose_name='Required')),
], ],
@ -32,20 +31,24 @@ class Migration(migrations.Migration):
('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')),
('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')),
('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')),
('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), ('gather_facts_method',
('push_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')),
('push_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')),
('change_secret_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), ('change_secret_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')),
('change_secret_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), ('change_secret_method',
models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')),
('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')),
('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')), ('verify_account_method',
models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')),
('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')),
('gather_accounts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), ('gather_accounts_method',
models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='platform', model_name='platform',
name='automation', name='automation',
field=models.OneToOneField(blank=True, null=True, on_delete=models.deletion.CASCADE, related_name='platform', to='assets.platformautomation', verbose_name='Automation'), field=models.OneToOneField(blank=True, null=True, on_delete=models.deletion.CASCADE,
related_name='platform', to='assets.platformautomation',
verbose_name='Automation'),
), ),
] ]

View File

@ -1,15 +1,16 @@
# Generated by Django 3.2.12 on 2022-07-11 08:59 # Generated by Django 3.2.12 on 2022-07-11 08:59
import common.db.fields import uuid
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import simple_history.models import simple_history.models
import uuid from django.conf import settings
from django.db import migrations, models
import common.db.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0098_auto_20220430_2126'), ('assets', '0098_auto_20220430_2126'),
@ -20,14 +21,19 @@ class Migration(migrations.Migration):
name='HistoricalAccount', name='HistoricalAccount',
fields=[ fields=[
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('version', models.IntegerField(default=0, verbose_name='Version'),), ('version', models.IntegerField(default=0, verbose_name='Version'),),
('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)), ('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)), ('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), ('history_type',
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+',
to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'verbose_name': 'historical Account', 'verbose_name': 'historical Account',
@ -40,43 +46,55 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Account', name='Account',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('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)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')],
default='unknown', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('version', models.IntegerField(default=0, verbose_name='Version')), ('version', models.IntegerField(default=0, verbose_name='Version')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset')), ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts',
to='assets.asset', verbose_name='Asset')),
], ],
options={ options={
'verbose_name': 'Account', 'verbose_name': 'Account',
'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], 'permissions': [('view_accountsecret', 'Can view asset account secret'),
('change_accountsecret', 'Can change asset account secret'),
('view_historyaccount', 'Can view asset history account'),
('view_historyaccountsecret', 'Can view asset history account secret')],
'unique_together': {('name', 'asset'), ('username', 'asset', 'secret_type')}, 'unique_together': {('name', 'asset'), ('username', 'asset', 'secret_type')},
}, },
), ),
migrations.AddField( migrations.AddField(
model_name='account', model_name='account',
name='su_from', name='su_from',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from'), field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to',
to='assets.account', verbose_name='Su from'),
), ),
migrations.CreateModel( migrations.CreateModel(
name='AccountTemplate', name='AccountTemplate',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('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)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),), ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),

View File

@ -75,33 +75,11 @@ class Migration(migrations.Migration):
name='updated_by', name='updated_by',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'),
), ),
migrations.AlterField(
model_name='platformautomation',
name='push_account_enabled',
field=models.BooleanField(default=False, verbose_name='Push account enabled'),
),
migrations.AlterField(
model_name='platformautomation',
name='push_account_method',
field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Push account method'),
),
migrations.AlterField( migrations.AlterField(
model_name='platformprotocol', model_name='platformprotocol',
name='default', name='default',
field=models.BooleanField(default=False, verbose_name='Default'), field=models.BooleanField(default=False, verbose_name='Default'),
), ),
migrations.CreateModel(
name='DiscoveryAccountAutomation',
fields=[
('baseautomation_ptr',
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='assets.baseautomation')),
],
options={
'verbose_name': 'Discovery account automation',
},
bases=('assets.baseautomation',),
),
migrations.CreateModel( migrations.CreateModel(
name='GatherFactsAutomation', name='GatherFactsAutomation',
fields=[ fields=[

View File

@ -0,0 +1,34 @@
# Generated by Django 3.2.14 on 2022-12-15 07:08
from django.db import migrations
def migrate_del_macos(apps, schema_editor):
db_alias = schema_editor.connection.alias
asset_model = apps.get_model('assets', 'Asset')
platform_model = apps.get_model('assets', 'Platform')
old_macos = platform_model.objects.using(db_alias).filter(
name='MacOS', type='macos'
).first()
new_macos = platform_model.objects.using(db_alias).filter(
name='macOS', type='unix'
).first()
if not old_macos or not new_macos:
return
asset_model.objects.using(db_alias).filter(
platform=old_macos
).update(platform=new_macos)
platform_model.objects.using(db_alias).filter(id=old_macos.id).delete()
class Migration(migrations.Migration):
dependencies = [
('assets', '0113_auto_20221122_2015'),
]
operations = [
migrations.RunPython(migrate_del_macos),
]

View File

@ -0,0 +1,183 @@
# Generated by Django 3.2.14 on 2022-12-20 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0114_remove_redundant_macos'),
]
operations = [
migrations.AddField(
model_name='accountbackupplan',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='baseautomation',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='changesecretrecord',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AddField(
model_name='domain',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AddField(
model_name='domain',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AddField(
model_name='domain',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='favoriteasset',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AddField(
model_name='favoriteasset',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='gathereduser',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AddField(
model_name='gathereduser',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AddField(
model_name='gathereduser',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AddField(
model_name='node',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AddField(
model_name='node',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AddField(
model_name='node',
name='date_created',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'),
),
migrations.AddField(
model_name='node',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AddField(
model_name='node',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='account',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='account',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='accountbackupplan',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AlterField(
model_name='accountbackupplan',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='accounttemplate',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='accounttemplate',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='asset',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='asset',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='baseautomation',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AlterField(
model_name='baseautomation',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
migrations.AlterField(
model_name='domain',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AlterField(
model_name='favoriteasset',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='gathereduser',
name='date_created',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'),
),
migrations.AlterField(
model_name='label',
name='comment',
field=models.TextField(blank=True, default='', verbose_name='Comment'),
),
migrations.AlterField(
model_name='label',
name='created_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='label',
name='updated_by',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.16 on 2022-12-22 11:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0115_auto_20221220_1956'),
]
operations = [
migrations.AlterModelOptions(
name='automationexecution',
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'), ('add_changesecretexection', 'Can add change secret execution'), ('view_gatheraccountsexecution', 'Can view gather accounts execution'), ('add_gatheraccountsexecution', 'Can add gather accounts execution')], 'verbose_name': 'Automation task execution'},
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.16 on 2022-12-23 07:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0116_alter_automationexecution_options'),
]
operations = [
migrations.AlterModelOptions(
name='gateway',
options={'verbose_name': 'Gateway'},
),
]

View File

@ -0,0 +1,50 @@
# Generated by Django 3.2.14 on 2022-12-27 07:04
import common.db.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0117_alter_gateway_options'),
]
operations = [
migrations.AddField(
model_name='pushaccountautomation',
name='password_rules',
field=models.JSONField(default=dict, verbose_name='Password rules'),
),
migrations.AddField(
model_name='pushaccountautomation',
name='secret',
field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret'),
),
migrations.AddField(
model_name='pushaccountautomation',
name='secret_strategy',
field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Secret strategy'),
),
migrations.AddField(
model_name='pushaccountautomation',
name='secret_type',
field=models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),
),
migrations.AddField(
model_name='pushaccountautomation',
name='ssh_key_change_strategy',
field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
),
migrations.AddField(
model_name='pushaccountautomation',
name='username',
field=models.CharField(default='', max_length=128, verbose_name='Username'),
preserve_default=False,
),
migrations.AlterField(
model_name='baseautomation',
name='type',
field=models.CharField(choices=[('ping', 'Ping'), ('gather_facts', 'Gather facts'), ('push_account', 'Push account'), ('change_secret', 'Change secret'), ('verify_account', 'Verify account'), ('gather_accounts', 'Gather accounts')], max_length=16, verbose_name='Type'),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2022-12-27 09:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0118_auto_20221227_1504'),
]
operations = [
migrations.AddField(
model_name='account',
name='source',
field=models.CharField(default='local', max_length=30, verbose_name='Source'),
),
migrations.DeleteModel(
name='GatheredUser',
),
]

View File

@ -7,7 +7,6 @@ from .gateway import *
from .domain import * from .domain import *
from .node import * from .node import *
from .utils import * from .utils import *
from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .backup import * from .backup import *

View File

@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from common.utils import lazyproperty from common.utils import lazyproperty
from ..const import AliasAccount, Source
from .base import AbsConnectivity, BaseAccount from .base import AbsConnectivity, BaseAccount
__all__ = ['Account', 'AccountTemplate'] __all__ = ['Account', 'AccountTemplate']
@ -41,11 +41,6 @@ class AccountHistoricalRecords(HistoricalRecords):
class Account(AbsConnectivity, BaseAccount): class Account(AbsConnectivity, BaseAccount):
class AliasAccount(models.TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
asset = models.ForeignKey( asset = models.ForeignKey(
'assets.Asset', related_name='accounts', 'assets.Asset', related_name='accounts',
on_delete=models.CASCADE, verbose_name=_('Asset') on_delete=models.CASCADE, verbose_name=_('Asset')
@ -56,6 +51,7 @@ class Account(AbsConnectivity, BaseAccount):
) )
version = models.IntegerField(default=0, verbose_name=_('Version')) version = models.IntegerField(default=0, verbose_name=_('Version'))
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
class Meta: class Meta:
verbose_name = _('Account') verbose_name = _('Account')
@ -74,21 +70,32 @@ class Account(AbsConnectivity, BaseAccount):
def platform(self): def platform(self):
return self.asset.platform return self.asset.platform
@lazyproperty
def alias(self):
if self.username.startswith('@'):
return self.username
return self.name
def __str__(self): def __str__(self):
return '{}'.format(self.username) return '{}'.format(self.username)
@lazyproperty
def has_secret(self):
return bool(self.secret)
@classmethod @classmethod
def get_manual_account(cls): def get_manual_account(cls):
""" @INPUT 手动登录的账号(any) """ """ @INPUT 手动登录的账号(any) """
return cls(name=cls.AliasAccount.INPUT.label, username=cls.AliasAccount.INPUT.value, secret=None) return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
@classmethod @classmethod
def get_user_account(cls, username): def get_user_account(cls, username):
""" @USER 动态用户的账号(self) """ """ @USER 动态用户的账号(self) """
return cls(name=cls.AliasAccount.USER.label, username=cls.AliasAccount.USER.value) return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value)
def get_su_from_accounts(self): def get_su_from_accounts(self):
return self.asset.accounts.exclude(id=self.id) """ 排除自己和以自己为 su-from 的账号 """
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
class AccountTemplate(BaseAccount): class AccountTemplate(BaseAccount):

View File

@ -2,18 +2,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
import logging import logging
from collections import defaultdict from collections import defaultdict
from django.db import models 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 common.utils import lazyproperty from common.utils import lazyproperty
from orgs.mixins.models import OrgManager, JMSOrgBaseModel from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from ..platform import Platform
from ..base import AbsConnectivity from ..base import AbsConnectivity
from ..platform import Platform
__all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol'] __all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -53,7 +51,7 @@ class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}' NODES_CACHE_KEY = 'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES' ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
CACHE_TIME = 3600 * 24 * 7 CACHE_TIME = 3600 * 24 * 7
id = "" id: str
_all_nodes_keys = None _all_nodes_keys = None
def get_nodes(self): def get_nodes(self):
@ -65,16 +63,29 @@ class NodesRelationMixin:
def get_all_nodes(self, flat=False): def get_all_nodes(self, flat=False):
from ..node import Node from ..node import Node
node_keys = self.get_all_node_keys()
nodes = Node.objects.filter(key__in=node_keys).distinct()
if not flat:
return nodes
node_ids = set(nodes.values_list('id', flat=True))
return node_ids
def get_all_node_keys(self):
node_keys = set() node_keys = set()
for node in self.get_nodes(): for node in self.get_nodes():
ancestor_keys = node.get_ancestor_keys(with_self=True) ancestor_keys = node.get_ancestor_keys(with_self=True)
node_keys.update(ancestor_keys) node_keys.update(ancestor_keys)
nodes = Node.objects.filter(key__in=node_keys).distinct() return node_keys
if flat:
node_ids = set(nodes.values_list('id', flat=True)) @classmethod
return node_ids def get_all_nodes_for_assets(cls, assets):
else: from ..node import Node
return nodes node_keys = set()
for asset in assets:
asset_node_keys = asset.get_all_node_keys()
node_keys.update(asset_node_keys)
nodes = Node.objects.filter(key__in=node_keys)
return nodes
class Protocol(models.Model): class Protocol(models.Model):
@ -87,7 +98,6 @@ class Protocol(models.Model):
class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
address = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) address = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
@ -97,7 +107,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
verbose_name=_("Nodes")) verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
info = models.JSONField(verbose_name='Info', default=dict, blank=True) info = models.JSONField(verbose_name='Info', default=dict, blank=True)
objects = AssetManager.from_queryset(AssetQuerySet)() objects = AssetManager.from_queryset(AssetQuerySet)()

View File

@ -5,4 +5,3 @@ from .gather_facts import *
from .change_secret import * from .change_secret import *
from .verify_account import * from .verify_account import *
from .gather_accounts import * from .gather_accounts import *
from .discovery_account import *

View File

@ -1,25 +1,24 @@
import uuid import uuid
from celery import current_task from celery import current_task
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 common.const.choices import Trigger from assets.const import AutomationTypes
from common.mixins.models import CommonModelMixin
from common.db.fields import EncryptJsonDictTextField
from orgs.mixins.models import OrgModelMixin
from ops.mixin import PeriodTaskModelMixin
from assets.models import Node, Asset from assets.models import Node, Asset
from assets.tasks import execute_automation from assets.tasks import execute_automation
from assets.const import AutomationTypes from common.const.choices import Trigger
from common.db.fields import EncryptJsonDictTextField
from ops.mixin import PeriodTaskModelMixin
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): class BaseAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
accounts = models.JSONField(default=list, verbose_name=_("Accounts")) accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type')) type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type'))
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self): def __str__(self):
return self.name + '@' + str(self.created_by) return self.name + '@' + str(self.created_by)
@ -102,6 +101,12 @@ class AutomationExecution(OrgModelMixin):
class Meta: class Meta:
verbose_name = _('Automation task execution') verbose_name = _('Automation task execution')
permissions = [
('view_changesecretexecution', _('Can view change secret execution')),
('add_changesecretexection', _('Can add change secret execution')),
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
('add_gatheraccountsexecution', _('Can add gather accounts execution')),
]
@property @property
def manager_type(self): def manager_type(self):

View File

@ -1,15 +1,15 @@
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 assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
from common.db import fields from common.db import fields
from common.db.models import JMSBaseModel from common.db.models import JMSBaseModel
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
from .base import BaseAutomation from .base import BaseAutomation
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord'] __all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
class ChangeSecretAutomation(BaseAutomation): class ChangeSecretMixin(models.Model):
secret_type = models.CharField( secret_type = models.CharField(
choices=SecretType.choices, max_length=16, choices=SecretType.choices, max_length=16,
default=SecretType.PASSWORD, verbose_name=_('Secret type') default=SecretType.PASSWORD, verbose_name=_('Secret type')
@ -24,6 +24,12 @@ class ChangeSecretAutomation(BaseAutomation):
choices=SSHKeyStrategy.choices, max_length=16, choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy') default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
) )
class Meta:
abstract = True
class ChangeSecretAutomation(BaseAutomation, ChangeSecretMixin):
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True) recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View File

@ -1,15 +0,0 @@
from django.utils.translation import ugettext_lazy as _
from .base import BaseAutomation
class DiscoveryAccountAutomation(BaseAutomation):
class Meta:
verbose_name = _("Discovery account automation")
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'type': 'discover_account'
})
return attr_json

View File

@ -1,12 +1,16 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from assets.const import AutomationTypes from assets.const import AutomationTypes
from .base import BaseAutomation from .base import BaseAutomation
from .change_secret import ChangeSecretMixin
__all__ = ['PushAccountAutomation'] __all__ = ['PushAccountAutomation']
class PushAccountAutomation(BaseAutomation): class PushAccountAutomation(BaseAutomation, ChangeSecretMixin):
accounts = None
username = models.CharField(max_length=128, verbose_name=_('Username'))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.type = AutomationTypes.push_account self.type = AutomationTypes.push_account

View File

@ -7,26 +7,23 @@ from celery import current_task
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 orgs.mixins.models import OrgModelMixin
from ops.mixin import PeriodTaskModelMixin
from common.utils import get_logger
from common.const.choices import Trigger from common.const.choices import Trigger
from common.db.encoder import ModelJSONFieldEncoder from common.db.encoder import ModelJSONFieldEncoder
from common.mixins.models import CommonModelMixin from common.utils import get_logger
from ops.mixin import PeriodTaskModelMixin
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution'] __all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution']
logger = get_logger(__file__) logger = get_logger(__file__)
class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
types = models.JSONField(default=list) types = models.JSONField(default=list)
recipients = models.ManyToManyField( recipients = models.ManyToManyField(
'users.User', related_name='recipient_escape_route_plans', blank=True, 'users.User', related_name='recipient_escape_route_plans', blank=True,
verbose_name=_("Recipient") verbose_name=_("Recipient")
) )
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self): def __str__(self):
return f'{self.name}({self.org_id})' return f'{self.name}({self.org_id})'

View File

@ -69,8 +69,6 @@ class BaseAccount(JMSOrgBaseModel):
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)() objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)()

View File

@ -7,11 +7,6 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from users.models import User, UserGroup
from applications.models import Application
from ..models import SystemUser, Asset, Node
from common.utils import lazyproperty, get_logger, get_object_or_none
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
logger = get_logger(__file__) logger = get_logger(__file__)

View File

@ -1,14 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
import random import random
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 common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import JMSOrgBaseModel
from .gateway import Gateway from .gateway import Gateway
logger = get_logger(__file__) logger = get_logger(__file__)
@ -16,11 +14,8 @@ logger = get_logger(__file__)
__all__ = ['Domain'] __all__ = ['Domain']
class Domain(OrgModelMixin): class Domain(JMSOrgBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
class Meta: class Meta:
verbose_name = _("Domain") verbose_name = _("Domain")
@ -51,5 +46,3 @@ class Domain(OrgModelMixin):
@classmethod @classmethod
def get_gateway_queryset(cls): def get_gateway_queryset(cls):
return Gateway.objects.all() return Gateway.objects.all()

View File

@ -2,13 +2,12 @@
# #
from django.db import models from django.db import models
from common.mixins.models import CommonModelMixin from common.db.models import JMSBaseModel
__all__ = ['FavoriteAsset'] __all__ = ['FavoriteAsset']
class FavoriteAsset(CommonModelMixin): class FavoriteAsset(JMSBaseModel):
user = models.ForeignKey('users.User', on_delete=models.CASCADE) user = models.ForeignKey('users.User', on_delete=models.CASCADE)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE)

View File

@ -37,6 +37,7 @@ class Gateway(Host):
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _("Gateway")
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.platform = self.default_platform() self.platform = self.default_platform()

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
__all__ = ['GatheredUser']
class GatheredUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login"))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
@property
def name(self):
return self.asset.name
@property
def ip(self):
return self.asset.address
class Meta:
verbose_name = _('GatherUser')
ordering = ['asset']
def __str__(self):
return '{}: {}'.format(self.asset.name, self.username)

View File

@ -4,6 +4,7 @@
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 common.utils import lazyproperty
from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import JMSOrgBaseModel
@ -19,7 +20,6 @@ class Label(JMSOrgBaseModel):
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
default=USER_CATEGORY, verbose_name=_("Category")) default=USER_CATEGORY, verbose_name=_("Category"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod @classmethod
def get_queryset_group_by_name(cls): def get_queryset_group_by_name(cls):
@ -27,6 +27,10 @@ class Label(JMSOrgBaseModel):
for name in names: for name in names:
yield name, cls.objects.filter(name=name) yield name, cls.objects.filter(name=name)
@lazyproperty
def asset_count(self):
return self.assets.count()
def __str__(self): def __str__(self):
return "{}:{}".format(self.name, self.value) return "{}:{}".format(self.name, self.value)

View File

@ -1,29 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import re import re
import time
import uuid
import threading import threading
import os
import time import time
import uuid import uuid
from collections import defaultdict from collections import defaultdict
from django.core.cache import cache
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q, Manager from django.db.models import Q, Manager
from django.db.utils import IntegrityError
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.db.transaction import atomic from django.db.transaction import atomic
from django.core.cache import cache from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from common.utils.lock import DistributedLock
from common.utils.common import timeit
from common.db.models import output_as_string from common.db.models import output_as_string
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins.models import OrgModelMixin, OrgManager from common.utils.lock import DistributedLock
from orgs.utils import get_current_org, tmp_to_org, tmp_to_root_org from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from orgs.models import Organization from orgs.models import Organization
from orgs.utils import get_current_org, tmp_to_org, tmp_to_root_org
__all__ = ['Node', 'FamilyMixin', 'compute_parent_key', 'NodeQuerySet'] __all__ = ['Node', 'FamilyMixin', 'compute_parent_key', 'NodeQuerySet']
logger = get_logger(__name__) logger = get_logger(__name__)
@ -178,9 +173,7 @@ class FamilyMixin:
return parent_keys return parent_keys
def get_ancestor_keys(self, with_self=False): def get_ancestor_keys(self, with_self=False):
return self.get_node_ancestor_keys( return self.get_node_ancestor_keys(self.key, with_self=with_self)
self.key, with_self=with_self
)
@property @property
def ancestors(self): def ancestors(self):
@ -437,6 +430,12 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin):
assets = Asset.objects.filter(nodes=self) assets = Asset.objects.filter(nodes=self)
return assets.distinct() return assets.distinct()
def get_assets_for_tree(self):
return self.get_assets().only(
"id", "name", "address", "platform_id",
"org_id", "is_active"
).prefetch_related('platform')
def get_valid_assets(self): def get_valid_assets(self):
return self.get_assets().valid() return self.get_assets().valid()
@ -547,7 +546,7 @@ class SomeNodesMixin:
return root_nodes return root_nodes
class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin): class Node(JMSOrgBaseModel, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1' key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value")) value = models.CharField(max_length=128, verbose_name=_("Value"))

View File

@ -2,9 +2,8 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from assets.const import AllTypes from assets.const import AllTypes
from common.db.fields import JsonDictTextField
from assets.const import Protocol from assets.const import Protocol
from common.db.fields import JsonDictTextField
__all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation']
@ -45,8 +44,6 @@ class PlatformAutomation(models.Model):
ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method"))
gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled"))
gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method"))
push_account_enabled = models.BooleanField(default=False, verbose_name=_("Push account enabled"))
push_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Push account method"))
change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled"))
change_secret_method = models.TextField( change_secret_method = models.TextField(
max_length=32, blank=True, null=True, verbose_name=_("Change password method")) max_length=32, blank=True, null=True, verbose_name=_("Change password method"))
@ -83,7 +80,7 @@ class Platform(models.Model):
protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled"))
# 账号有关的 # 账号有关的
su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled"))
su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Su method"))
automation = models.OneToOneField(PlatformAutomation, on_delete=models.CASCADE, related_name='platform', automation = models.OneToOneField(PlatformAutomation, on_delete=models.CASCADE, related_name='platform',
blank=True, null=True, verbose_name=_("Automation")) blank=True, null=True, verbose_name=_("Automation"))

View File

@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import validate_ssh_private_key from common.utils import validate_ssh_private_key
__all__ = [ __all__ = [
'private_key_validator', 'private_key_validator',
] ]
@ -22,8 +21,6 @@ def private_key_validator(value):
def update_internal_platforms(platform_model): def update_internal_platforms(platform_model):
from assets.const import AllTypes
platforms = [ platforms = [
{'name': 'Linux', 'category': 'host', 'type': 'linux'}, {'name': 'Linux', 'category': 'host', 'type': 'linux'},
{'name': 'BSD', 'category': 'host', 'type': 'unix'}, {'name': 'BSD', 'category': 'host', 'type': 'unix'},
@ -32,7 +29,6 @@ def update_internal_platforms(platform_model):
{'name': 'Windows', 'category': 'host', 'type': 'unix'}, {'name': 'Windows', 'category': 'host', 'type': 'unix'},
{ {
'name': 'AIX', 'category': 'host', 'type': 'unix', 'name': 'AIX', 'category': 'host', 'type': 'unix',
'push_account_method': 'create_account_aix',
'change_secret_method': 'change_secret_aix', 'change_secret_method': 'change_secret_aix',
}, },
{'name': 'Windows', 'category': 'host', 'type': 'windows'}, {'name': 'Windows', 'category': 'host', 'type': 'windows'},

View File

@ -6,7 +6,6 @@ from .label import *
from .node import * from .node import *
from .gateway import * from .gateway import *
from .domain import * from .domain import *
from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .platform import * from .platform import *

View File

@ -1,15 +1,15 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.drf.serializers import SecretReadableMixin from assets.const import SecretType, Source
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
from assets.tasks import push_accounts_to_assets
from assets.models import Account, AccountTemplate, Asset from assets.models import Account, AccountTemplate, Asset
from assets.tasks import push_accounts_to_assets
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
from common.drf.serializers import SecretReadableMixin, BulkModelSerializer
from .base import BaseAccountSerializer from .base import BaseAccountSerializer
from assets.const import SecretType
class AccountSerializerCreateMixin(serializers.ModelSerializer): class AccountSerializerCreateMixin(BulkModelSerializer):
template = serializers.UUIDField( template = serializers.UUIDField(
required=False, allow_null=True, write_only=True, required=False, allow_null=True, write_only=True,
label=_('Account template') label=_('Account template')
@ -53,35 +53,47 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer):
return instance return instance
class AccountAssetSerializer(serializers.ModelSerializer):
platform = ObjectRelatedField(read_only=True)
class Meta:
model = Asset
fields = ['id', 'name', 'address', 'platform']
def to_internal_value(self, data):
if isinstance(data, dict):
i = data.get('id')
else:
i = data
try:
return Asset.objects.get(id=i)
except Asset.DoesNotExist:
raise serializers.ValidationError(_('Asset not found'))
class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
asset = ObjectRelatedField( asset = AccountAssetSerializer(label=_('Asset'))
required=False, queryset=Asset.objects, source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id')
)
su_from = ObjectRelatedField( su_from = ObjectRelatedField(
required=False, queryset=Account.objects, required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
label=_('Account'), attrs=('id', 'name', 'username') label=_('Su from'), attrs=('id', 'name', 'username')
) )
class Meta(BaseAccountSerializer.Meta): class Meta(BaseAccountSerializer.Meta):
model = Account model = Account
fields = BaseAccountSerializer.Meta.fields \ fields = BaseAccountSerializer.Meta.fields \
+ ['su_from', 'version', 'asset'] \ + ['su_from', 'version', 'asset'] \
+ ['template', 'push_now'] + ['template', 'push_now', 'source']
extra_kwargs = { extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs, **BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False, 'allow_null': True}, 'name': {'required': False, 'allow_null': True},
} }
def __init__(self, *args, data=None, **kwargs): def validate_name(self, value):
super().__init__(*args, data=data, **kwargs) if not value:
if data and 'name' not in data: value = self.initial_data.get('username')
username = data.get('username') return value
if username is not None:
data['name'] = username
if hasattr(self, 'initial_data') and \
not getattr(self, 'initial_data', None):
delattr(self, 'initial_data')
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):

View File

@ -34,7 +34,6 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer): class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
trigger = LabeledChoiceField(choices=Trigger.choices, label=_('Trigger mode'))
class Meta: class Meta:
model = AccountBackupPlanExecution model = AccountBackupPlanExecution

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from assets.models import BaseAccount from assets.models import BaseAccount
from assets.serializers.base import AuthValidateMixin from assets.serializers.base import AuthValidateMixin
@ -9,6 +10,8 @@ __all__ = ['BaseAccountSerializer']
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta: class Meta:
model = BaseAccount model = BaseAccount
fields_mini = ['id', 'name', 'username'] fields_mini = ['id', 'name', 'username']

View File

@ -1,5 +1,5 @@
from common.drf.serializers import SecretReadableMixin
from assets.models import AccountTemplate from assets.models import AccountTemplate
from common.drf.serializers import SecretReadableMixin
from .base import BaseAccountSerializer from .base import BaseAccountSerializer

View File

@ -1,21 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.db.transaction import atomic
from django.db.models import F from django.db.models import F
from django.db.transaction import atomic
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.drf.serializers import WritableNestedModelSerializer
from common.drf.fields import LabeledChoiceField, ObjectRelatedField from common.drf.fields import LabeledChoiceField, ObjectRelatedField
from orgs.mixins.serializers import OrgResourceSerializerMixin from common.drf.serializers import WritableNestedModelSerializer
from orgs.mixins.serializers import BulkOrgResourceSerializerMixin
from ..account import AccountSerializer from ..account import AccountSerializer
from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
from ...const import Category, AllTypes from ...const import Category, AllTypes
from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
__all__ = [ __all__ = [
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer', 'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer',
'AssetDetailSerializer',
] ]
@ -58,7 +59,7 @@ class AssetAccountSerializer(AccountSerializer):
fields = fields_mini + fields_write_only fields = fields_mini + fields_write_only
class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer): class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSerializer):
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain'), allow_null=True) domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain'), allow_null=True)
@ -66,49 +67,26 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) accounts = AssetAccountSerializer(many=True, required=False, label=_('Account'))
enabled_info = serializers.SerializerMethodField()
class Meta: class Meta:
model = Asset model = Asset
fields_mini = ['id', 'name', 'address', 'enabled_info'] fields_mini = ['id', 'name', 'address']
fields_small = fields_mini + ['is_active', 'comment'] fields_small = fields_mini + ['is_active', 'comment']
fields_fk = ['domain', 'platform', 'platform'] fields_fk = ['domain', 'platform', 'platform']
fields_m2m = [ fields_m2m = [
'nodes', 'labels', 'protocols', 'accounts', 'nodes_display', 'nodes', 'labels', 'protocols', 'accounts', 'nodes_display',
] ]
read_only_fields = [ read_only_fields = [
'category', 'type', 'specific', 'info', 'category', 'type', 'info',
'connectivity', 'date_verified', 'created_by', 'connectivity', 'date_verified',
'date_created' 'created_by', 'date_created'
] ]
fields = fields_small + fields_fk + fields_m2m + read_only_fields fields = fields_small + fields_fk + fields_m2m + read_only_fields
extra_kwargs = { extra_kwargs = {
'name': {'label': _("Name")}, 'name': {'label': _("Name")},
'address': {'label': _('Address')}, 'address': {'label': _('Address')},
} 'nodes_display': {'label': _('Node path')},
def get_field_names(self, declared_fields, info):
names = super().get_field_names(declared_fields, info)
if self.__class__.__name__ != 'AssetSerializer':
names.remove('specific')
return names
@staticmethod
def get_enabled_info(obj):
platform = obj.platform
automation = platform.automation
return {
'su_enabled': platform.su_enabled,
'ping_enabled': automation.ping_enabled,
'domain_enabled': platform.domain_enabled,
'ansible_enabled': automation.ansible_enabled,
'protocols_enabled': platform.protocols_enabled,
'gather_facts_enabled': automation.gather_facts_enabled,
'push_account_enabled': automation.push_account_enabled,
'change_secret_enabled': automation.change_secret_enabled,
'verify_account_enabled': automation.verify_account_enabled,
'gather_accounts_enabled': automation.gather_accounts_enabled,
} }
@classmethod @classmethod
@ -117,7 +95,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
queryset = queryset.prefetch_related('domain', 'platform', 'protocols') \ queryset = queryset.prefetch_related('domain', 'platform', 'protocols') \
.annotate(category=F("platform__category")) \ .annotate(category=F("platform__category")) \
.annotate(type=F("platform__type")) .annotate(type=F("platform__type"))
queryset = queryset.prefetch_related('nodes', 'labels') queryset = queryset.prefetch_related('nodes', 'labels', 'accounts')
return queryset return queryset
def perform_nodes_display_create(self, instance, nodes_display): def perform_nodes_display_create(self, instance, nodes_display):
@ -188,6 +166,30 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
return instance return instance
class AssetDetailSerializer(AssetSerializer):
accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts'))
enabled_info = serializers.SerializerMethodField()
class Meta(AssetSerializer.Meta):
fields = AssetSerializer.Meta.fields + ['accounts', 'enabled_info', 'info', 'specific']
@staticmethod
def get_enabled_info(obj):
platform = obj.platform
automation = platform.automation
return {
'su_enabled': platform.su_enabled,
'ping_enabled': automation.ping_enabled,
'domain_enabled': platform.domain_enabled,
'ansible_enabled': automation.ansible_enabled,
'protocols_enabled': platform.protocols_enabled,
'gather_facts_enabled': automation.gather_facts_enabled,
'change_secret_enabled': automation.change_secret_enabled,
'verify_account_enabled': automation.verify_account_enabled,
'gather_accounts_enabled': automation.gather_accounts_enabled,
}
class MiniAssetSerializer(serializers.ModelSerializer): class MiniAssetSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Asset model = Asset

View File

@ -1,4 +1,3 @@
from assets.models import Web from assets.models import Web
from .common import AssetSerializer from .common import AssetSerializer
@ -19,12 +18,12 @@ class WebSerializer(AssetSerializer):
'label': 'URL' 'label': 'URL'
}, },
'username_selector': { 'username_selector': {
'default': 'input[type=text]' 'default': 'name=username'
}, },
'password_selector': { 'password_selector': {
'default': 'input[type=password]' 'default': 'name=password'
}, },
'submit_selector': { 'submit_selector': {
'default': 'button[type=submit]', 'default': 'id=longin_button',
}, },
} }

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from common.drf.fields import ObjectRelatedField
from ..models import GatheredUser, Asset
class GatheredUserSerializer(OrgResourceModelSerializerMixin):
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
class Meta:
model = GatheredUser
fields_mini = ['id']
fields_small = fields_mini + [
'username', 'ip_last_login', 'present', 'name',
'date_last_login', 'date_created', 'date_updated'
]
fields_fk = ['asset', 'ip']
fields = fields_small + fields_fk
read_only_fields = fields
extra_kwargs = {
'name': {'label': _("Hostname")},
'ip': {'label': 'IP'},
}

View File

@ -1,25 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers from django.db.models import Count
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import Label from ..models import Label
class LabelSerializer(BulkOrgResourceModelSerializer): class LabelSerializer(BulkOrgResourceModelSerializer):
asset_count = serializers.SerializerMethodField(label=_("Assets amount")) asset_count = serializers.ReadOnlyField(label=_("Assets amount"))
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display'))
class Meta: class Meta:
model = Label model = Label
fields_mini = ['id', 'name'] fields_mini = ['id', 'name']
fields_small = fields_mini + [ fields_small = fields_mini + [
'value', 'category', 'category_display', 'value', 'category', 'is_active',
'is_active', 'date_created', 'comment',
'date_created',
'comment',
] ]
fields_m2m = ['asset_count', 'assets'] fields_m2m = ['asset_count', 'assets']
fields = fields_small + fields_m2m fields = fields_small + fields_m2m
@ -30,14 +27,10 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
'assets': {'required': False, 'label': _('Asset')} 'assets': {'required': False, 'label': _('Asset')}
} }
@staticmethod @classmethod
def get_asset_count(obj): def setup_eager_loading(cls, queryset):
return obj.assets.count() queryset = queryset.annotate(asset_count=Count('assets'))
return queryset
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['get_category_display'])
return fields
class LabelDistinctSerializer(BulkOrgResourceModelSerializer): class LabelDistinctSerializer(BulkOrgResourceModelSerializer):

View File

@ -25,7 +25,7 @@ class ProtocolSettingSerializer(serializers.Serializer):
sftp_home = serializers.CharField(default="/tmp", label=_("SFTP home")) sftp_home = serializers.CharField(default="/tmp", label=_("SFTP home"))
# HTTP # HTTP
auto_fill = serializers.BooleanField(default=False, label=_("Auto fill")) autofile = serializers.BooleanField(default=False, label=_("Autofill"))
username_selector = serializers.CharField( username_selector = serializers.CharField(
default="", allow_blank=True, label=_("Username selector") default="", allow_blank=True, label=_("Username selector")
) )
@ -38,37 +38,26 @@ class ProtocolSettingSerializer(serializers.Serializer):
class PlatformAutomationSerializer(serializers.ModelSerializer): class PlatformAutomationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PlatformAutomation model = PlatformAutomation
fields = [ fields = [
"id", "id",
"ansible_enabled", "ansible_enabled", "ansible_config",
"ansible_config", "ping_enabled", "ping_method",
"ping_enabled", "gather_facts_enabled", "gather_facts_method",
"ping_method", "change_secret_enabled", "change_secret_method",
"gather_facts_enabled", "verify_account_enabled", "verify_account_method",
"gather_facts_method", "gather_accounts_enabled", "gather_accounts_method",
"push_account_enabled",
"push_account_method",
"change_secret_enabled",
"change_secret_method",
"verify_account_enabled",
"verify_account_method",
"gather_accounts_enabled",
"gather_accounts_method",
] ]
extra_kwargs = { extra_kwargs = {
"ping_enabled": {"label": "启用资产探测"}, "ping_enabled": {"label": "启用资产探测"},
"ping_method": {"label": "探测方式"}, "ping_method": {"label": "资产探测方式"},
"gather_facts_enabled": {"label": "启用收集信息"}, "gather_facts_enabled": {"label": "收集资产信息"},
"gather_facts_method": {"label": "收集信息方式"}, "gather_facts_method": {"label": "收集信息方式"},
"verify_account_enabled": {"label": "启用校验账号"}, "verify_account_enabled": {"label": "启用校验账号"},
"verify_account_method": {"label": "校验账号方式"}, "verify_account_method": {"label": "校验账号方式"},
"push_account_enabled": {"label": "启用推送账号"},
"push_account_method": {"label": "推送账号方式"},
"change_secret_enabled": {"label": "启用账号改密"}, "change_secret_enabled": {"label": "启用账号改密"},
"change_secret_method": {"label": "账号创建改密方式"}, "change_secret_method": {"label": "账号改密方式"},
"gather_accounts_enabled": {"label": "启用账号收集"}, "gather_accounts_enabled": {"label": "启用账号收集"},
"gather_accounts_method": {"label": "收集账号方式"}, "gather_accounts_method": {"label": "收集账号方式"},
} }
@ -81,13 +70,8 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PlatformProtocol model = PlatformProtocol
fields = [ fields = [
"id", "id", "name", "port", "primary",
"name", "default", "required", "secret_types",
"port",
"primary",
"default",
"required",
"secret_types",
"setting", "setting",
] ]
@ -113,17 +97,12 @@ class PlatformSerializer(WritableNestedModelSerializer):
model = Platform model = Platform
fields_mini = ["id", "name", "internal"] fields_mini = ["id", "name", "internal"]
fields_small = fields_mini + [ fields_small = fields_mini + [
"category", "category", "type", "charset",
"type",
"charset",
] ]
fields = fields_small + [ fields = fields_small + [
"protocols_enabled", "protocols_enabled", "protocols",
"protocols", "domain_enabled", "su_enabled",
"domain_enabled", "su_method", "automation",
"su_enabled",
"su_method",
"automation",
"comment", "comment",
] ]
extra_kwargs = { extra_kwargs = {

View File

@ -103,6 +103,7 @@ def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs):
# m2m_model.objects.bulk_create(to_create) # m2m_model.objects.bulk_create(to_create)
# #
RELATED_NODE_IDS = '_related_node_ids' RELATED_NODE_IDS = '_related_node_ids'

View File

@ -20,13 +20,9 @@ logger = get_logger(__file__)
# ------------------------------------ # ------------------------------------
def get_node_assets_mapping_for_memory_pub_sub():
return RedisPubSub('fm.node_all_asset_ids_memory_mapping')
class NodeAssetsMappingForMemoryPubSub(LazyObject): class NodeAssetsMappingForMemoryPubSub(LazyObject):
def _setup(self): def _setup(self):
self._wrapped = get_node_assets_mapping_for_memory_pub_sub() self._wrapped = RedisPubSub('fm.node_all_asset_ids_memory_mapping')
node_assets_mapping_for_memory_pub_sub = NodeAssetsMappingForMemoryPubSub() node_assets_mapping_for_memory_pub_sub = NodeAssetsMappingForMemoryPubSub()

View File

@ -23,13 +23,13 @@ router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domains', api.DomainViewSet, 'domain') router.register(r'domains', api.DomainViewSet, 'domain')
router.register(r'gateways', api.GatewayViewSet, 'gateway') router.register(r'gateways', api.GatewayViewSet, 'gateway')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation') router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation')
router.register(r'automation-executions', api.AutomationExecutionViewSet, 'automation-execution') router.register(r'change-secret-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
router.register(r'gather-account-executions', api.GatherAccountsExecutionViewSet, 'gather-account-execution')
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record') router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record')
router.register(r'gather-account-automations', api.GatherAccountsAutomationViewSet, 'gather-account-automation') router.register(r'gather-account-automations', api.GatherAccountsAutomationViewSet, 'gather-account-automation')
@ -50,7 +50,6 @@ urlpatterns = [
name='account-secret-history'), name='account-secret-history'),
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'), path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'), path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),

View File

@ -0,0 +1,2 @@
from .k8s import *
from .node import *

Some files were not shown because too many files have changed in this diff Show More