mirror of https://github.com/jumpserver/jumpserver
commit
ce0632f49b
|
@ -16,6 +16,7 @@ dump.rdb
|
|||
.cache/
|
||||
.idea/
|
||||
.vscode/
|
||||
.fleet/
|
||||
db.sqlite3
|
||||
config.py
|
||||
config.yml
|
||||
|
|
18
Dockerfile
18
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.8-slim as stage-build
|
||||
FROM python:3.9-slim as stage-build
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG VERSION
|
||||
|
@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
|
|||
ADD . .
|
||||
RUN cd utils && bash -ixeu build.sh
|
||||
|
||||
FROM python:3.8-slim
|
||||
FROM python:3.9-slim
|
||||
ARG TARGETARCH
|
||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
||||
|
||||
|
@ -18,7 +18,6 @@ ARG BUILD_DEPENDENCIES=" \
|
|||
pkg-config"
|
||||
|
||||
ARG DEPENDENCIES=" \
|
||||
default-libmysqlclient-dev \
|
||||
freetds-dev \
|
||||
libpq-dev \
|
||||
libffi-dev \
|
||||
|
@ -28,21 +27,20 @@ ARG DEPENDENCIES=" \
|
|||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
libaio-dev \
|
||||
openssh-client \
|
||||
sshpass"
|
||||
libaio-dev"
|
||||
|
||||
ARG TOOLS=" \
|
||||
ca-certificates \
|
||||
curl \
|
||||
default-libmysqlclient-dev \
|
||||
default-mysql-client \
|
||||
iputils-ping \
|
||||
locales \
|
||||
openssh-client \
|
||||
procps \
|
||||
redis-tools \
|
||||
sshpass \
|
||||
telnet \
|
||||
vim \
|
||||
unzip \
|
||||
vim \
|
||||
wget"
|
||||
|
||||
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
|
||||
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
|
||||
|
||||
ARG DEBUG
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
set -ex \
|
||||
&& pip config set global.index-url ${PIP_MIRROR} \
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.8-slim as stage-build
|
||||
FROM python:3.9-slim as stage-build
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG VERSION
|
||||
|
@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
|
|||
ADD . .
|
||||
RUN cd utils && bash -ixeu build.sh
|
||||
|
||||
FROM python:3.8-slim
|
||||
FROM python:3.9-slim
|
||||
ARG TARGETARCH
|
||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
||||
|
||||
|
@ -18,7 +18,6 @@ ARG BUILD_DEPENDENCIES=" \
|
|||
pkg-config"
|
||||
|
||||
ARG DEPENDENCIES=" \
|
||||
default-libmysqlclient-dev \
|
||||
freetds-dev \
|
||||
libpq-dev \
|
||||
libffi-dev \
|
||||
|
@ -28,21 +27,20 @@ ARG DEPENDENCIES=" \
|
|||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
libaio-dev \
|
||||
openssh-client \
|
||||
sshpass"
|
||||
libaio-dev"
|
||||
|
||||
ARG TOOLS=" \
|
||||
ca-certificates \
|
||||
curl \
|
||||
default-libmysqlclient-dev \
|
||||
default-mysql-client \
|
||||
iputils-ping \
|
||||
locales \
|
||||
netcat \
|
||||
redis-server \
|
||||
openssh-client \
|
||||
procps \
|
||||
sshpass \
|
||||
telnet \
|
||||
vim \
|
||||
unzip \
|
||||
vim \
|
||||
wget"
|
||||
|
||||
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
|
||||
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
|
||||
|
||||
ARG DEBUG
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
set -ex \
|
||||
&& pip config set global.index-url ${PIP_MIRROR} \
|
||||
&& pip install --upgrade pip \
|
||||
&& 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/greenlet/greenlet-1.1.2-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-cp39-cp39-linux_loongarch64.whl \
|
||||
&& pip install $(grep 'PyNaCl' requirements/requirements.txt) \
|
||||
&& GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \
|
||||
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
||||
|
|
|
@ -28,6 +28,7 @@ class Migration(migrations.Migration):
|
|||
('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command',
|
||||
max_length=16, verbose_name='Type')),
|
||||
('content', models.TextField(help_text='One line one command', verbose_name='Content')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')),
|
||||
],
|
||||
options={
|
||||
|
@ -57,7 +58,8 @@ class Migration(migrations.Migration):
|
|||
('assets', models.JSONField(verbose_name='Asset')),
|
||||
('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')),
|
||||
(
|
||||
'reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||
'reviewers',
|
||||
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Command acl',
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
# 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):
|
||||
|
||||
dependencies = [
|
||||
('acls', '0007_auto_20221202_1048'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='commandgroup',
|
||||
name='comment',
|
||||
field=models.TextField(blank=True, verbose_name='Comment'),
|
||||
migrations.AlterModelOptions(
|
||||
name='commandgroup',
|
||||
options={'verbose_name': 'Command group'},
|
||||
),
|
||||
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'},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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'},
|
||||
),
|
||||
]
|
|
@ -3,7 +3,7 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.mixins import CommonModelMixin
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.utils import contains_ip
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
|
@ -58,7 +58,7 @@ class UserAssetAccountACLQuerySet(BaseACLQuerySet):
|
|||
|
||||
def filter_account(self, username):
|
||||
q = Q(accounts__username_group__contains=username) | \
|
||||
Q(accounts__username_group__contains='*')
|
||||
Q(accounts__username_group__contains='*')
|
||||
return self.filter(q)
|
||||
|
||||
|
||||
|
@ -67,7 +67,7 @@ class ACLManager(models.Manager):
|
|||
return self.get_queryset().valid()
|
||||
|
||||
|
||||
class BaseACL(CommonModelMixin):
|
||||
class BaseACL(JMSBaseModel):
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
priority = models.IntegerField(
|
||||
default=50, verbose_name=_("Priority"),
|
||||
|
@ -77,7 +77,6 @@ class BaseACL(CommonModelMixin):
|
|||
action = models.CharField(max_length=64, default=ActionChoices.reject, verbose_name=_('Action'))
|
||||
reviewers = models.ManyToManyField('users.User', blank=True, verbose_name=_("Reviewers"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
ActionChoices = ActionChoices
|
||||
objects = ACLManager.from_queryset(BaseACLQuerySet)()
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from common.utils import lazyproperty, get_logger
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
|
||||
from .base import UserAssetAccountBaseACL
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -26,7 +25,6 @@ class CommandGroup(JMSOrgBaseModel):
|
|||
)
|
||||
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case'))
|
||||
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
||||
|
||||
TypeChoices = TypeChoices
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# Generated by Django 3.1.12 on 2021-08-26 09:07
|
||||
|
||||
import assets.models.base
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
import uuid
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0076_delete_assetuser'),
|
||||
|
@ -22,14 +22,19 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='HistoricalAccount',
|
||||
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)),
|
||||
('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')),
|
||||
('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')),
|
||||
('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')),
|
||||
('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')),
|
||||
('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_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),
|
||||
('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_date', models.DateTimeField()),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('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')),
|
||||
('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')),
|
||||
('history_type',
|
||||
models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('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')),
|
||||
('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={
|
||||
'verbose_name': 'historical Account',
|
||||
|
@ -52,20 +64,28 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Account',
|
||||
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)),
|
||||
('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')),
|
||||
('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')),
|
||||
('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')),
|
||||
('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')),
|
||||
('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_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('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')),
|
||||
('systemuser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', verbose_name='System user')),
|
||||
('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||
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={
|
||||
'verbose_name': 'Account',
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -1,12 +1,11 @@
|
|||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db.models import JMSBaseModel
|
||||
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'))
|
||||
category = models.CharField(
|
||||
max_length=16, verbose_name=_('Category')
|
||||
|
@ -15,9 +14,6 @@ class Application(CommonModelMixin, OrgModelMixin):
|
|||
max_length=16, verbose_name=_('Type')
|
||||
)
|
||||
attrs = models.JSONField(default=dict, verbose_name=_('Attrs'))
|
||||
comment = models.TextField(
|
||||
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Application')
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from .mixin import *
|
||||
from .category import *
|
||||
from .platform import *
|
||||
from .asset import *
|
||||
from .label import *
|
||||
from .account import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .asset import *
|
||||
from .automations import *
|
||||
from .gathered_user import *
|
||||
from .category import *
|
||||
from .domain import *
|
||||
from .favorite_asset import *
|
||||
from .label import *
|
||||
from .mixin import *
|
||||
from .node import *
|
||||
from .platform import *
|
||||
from .tree import *
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.generics import CreateAPIView, ListAPIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rbac.permissions import RBACPermission
|
||||
|
||||
from assets import serializers
|
||||
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.permissions import UserConfirmation
|
||||
from authentication.const import ConfirmType
|
||||
from assets.models import Account
|
||||
from assets.filters import AccountFilterSet
|
||||
from assets.tasks import verify_accounts_connectivity
|
||||
from assets import serializers
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
__all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI']
|
||||
__all__ = [
|
||||
'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI'
|
||||
]
|
||||
|
||||
from rbac.permissions import RBACPermission
|
||||
|
||||
|
||||
class AccountViewSet(OrgBulkModelViewSet):
|
||||
|
@ -30,10 +33,18 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||
'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):
|
||||
account = super().get_object()
|
||||
accounts = account.get_su_from_accounts()
|
||||
account_id = request.query_params.get('account')
|
||||
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)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
|
@ -54,8 +65,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
|||
'default': serializers.AccountSecretSerializer,
|
||||
}
|
||||
http_method_names = ['get', 'options']
|
||||
# Todo: 记得打开
|
||||
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||
rbac_perms = {
|
||||
'list': 'assets.view_accountsecret',
|
||||
'retrieve': 'assets.view_accountsecret',
|
||||
|
@ -66,8 +76,7 @@ class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
|
|||
model = Account.history.model
|
||||
serializer_class = serializers.AccountHistorySerializer
|
||||
http_method_names = ['get', 'options']
|
||||
# Todo: 记得打开
|
||||
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||
rbac_perms = {
|
||||
'list': 'assets.view_accountsecret',
|
||||
}
|
||||
|
@ -102,4 +111,5 @@ class AccountTaskCreateAPI(CreateAPIView):
|
|||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
return Response({"error": str(e)}, status=400)
|
||||
|
||||
return handler
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
from assets import serializers
|
||||
from assets.models import AccountTemplate
|
||||
from rbac.permissions import RBACPermission
|
||||
from authentication.const import ConfirmType
|
||||
from common.mixins import RecordViewLogMixin
|
||||
from common.permissions import UserConfirmation
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
|
||||
|
|
|
@ -30,18 +30,23 @@ __all__ = [
|
|||
|
||||
class AssetFilterSet(BaseFilterSet):
|
||||
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
|
||||
category = django_filters.CharFilter(
|
||||
field_name="platform__category", lookup_expr="exact"
|
||||
)
|
||||
hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact")
|
||||
category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
|
||||
platform = django_filters.CharFilter(method='filter_platform')
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
"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):
|
||||
"""
|
||||
|
@ -55,8 +60,9 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
|||
ordering = ("name",)
|
||||
serializer_classes = (
|
||||
("default", serializers.AssetSerializer),
|
||||
("suggestion", serializers.MiniAssetSerializer),
|
||||
("retrieve", serializers.AssetDetailSerializer),
|
||||
("platform", serializers.PlatformSerializer),
|
||||
("suggestion", serializers.MiniAssetSerializer),
|
||||
("gateways", serializers.GatewaySerializer),
|
||||
)
|
||||
rbac_perms = (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from assets.models import Cloud
|
||||
from assets.models import Cloud, Asset
|
||||
from assets.serializers import CloudSerializer
|
||||
|
||||
from .asset import AssetViewSet
|
||||
|
@ -8,6 +8,7 @@ __all__ = ['CloudViewSet']
|
|||
|
||||
class CloudViewSet(AssetViewSet):
|
||||
model = Cloud
|
||||
perm_model = Asset
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from assets.models import Database
|
||||
from assets.models import Database, Asset
|
||||
from assets.serializers import DatabaseSerializer
|
||||
|
||||
from .asset import AssetViewSet
|
||||
|
@ -8,6 +8,7 @@ __all__ = ['DatabaseViewSet']
|
|||
|
||||
class DatabaseViewSet(AssetViewSet):
|
||||
model = Database
|
||||
perm_model = Asset
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from assets.serializers import DeviceSerializer
|
||||
from assets.models import Device
|
||||
from assets.models import Device, Asset
|
||||
from .asset import AssetViewSet
|
||||
|
||||
__all__ = ['DeviceViewSet']
|
||||
|
@ -8,6 +7,7 @@ __all__ = ['DeviceViewSet']
|
|||
|
||||
class DeviceViewSet(AssetViewSet):
|
||||
model = Device
|
||||
perm_model = Asset
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from assets.models import Host
|
||||
from assets.models import Host, Asset
|
||||
from assets.serializers import HostSerializer
|
||||
from .asset import AssetViewSet
|
||||
|
||||
|
@ -7,6 +7,7 @@ __all__ = ['HostViewSet']
|
|||
|
||||
class HostViewSet(AssetViewSet):
|
||||
model = Host
|
||||
perm_model = Asset
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from assets.models import Web
|
||||
from assets.models import Web, Asset
|
||||
from assets.serializers import WebSerializer
|
||||
|
||||
from .asset import AssetViewSet
|
||||
|
@ -8,6 +8,7 @@ __all__ = ['WebViewSet']
|
|||
|
||||
class WebViewSet(AssetViewSet):
|
||||
model = Web
|
||||
perm_model = Asset
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status, mixins, viewsets
|
||||
from rest_framework.response import Response
|
||||
|
||||
from orgs.mixins import generics
|
||||
from assets import serializers
|
||||
from assets.tasks import execute_automation
|
||||
from assets.models import BaseAutomation, AutomationExecution
|
||||
from assets.tasks import execute_automation
|
||||
from common.const.choices import Trigger
|
||||
from orgs.mixins import generics
|
||||
|
||||
__all__ = [
|
||||
'AutomationAssetsListApi', 'AutomationRemoveAssetApi',
|
||||
'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi', 'AutomationExecutionViewSet'
|
||||
'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi',
|
||||
'AutomationExecutionViewSet',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
|
||||
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 orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||
|
||||
from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
|
||||
from assets import serializers
|
||||
from .base import AutomationExecutionViewSet
|
||||
|
||||
__all__ = [
|
||||
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet'
|
||||
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet',
|
||||
'ChangSecretExecutionViewSet'
|
||||
]
|
||||
|
||||
|
||||
|
@ -38,3 +39,11 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
|||
queryset = queryset.filter(execution=execution)
|
||||
queryset = queryset.order_by('-date_started')
|
||||
return queryset
|
||||
|
||||
|
||||
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
||||
rbac_perms = (
|
||||
("list", "assets.view_changesecretexecution"),
|
||||
("retrieve", "assets.view_changesecretexecution"),
|
||||
("create", "assets.add_changesecretexecution"),
|
||||
)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
from assets.models import GatherAccountsAutomation
|
||||
from assets import serializers
|
||||
from assets.models import GatherAccountsAutomation
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from .base import AutomationExecutionViewSet
|
||||
|
||||
__all__ = [
|
||||
'GatherAccountsAutomationViewSet',
|
||||
'GatherAccountsAutomationViewSet', 'GatherAccountsExecutionViewSet'
|
||||
]
|
||||
|
||||
|
||||
|
@ -16,3 +16,11 @@ class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
|||
search_fields = filter_fields
|
||||
ordering_fields = ('name',)
|
||||
serializer_class = serializers.GatherAccountAutomationSerializer
|
||||
|
||||
|
||||
class GatherAccountsExecutionViewSet(AutomationExecutionViewSet):
|
||||
rbac_perms = (
|
||||
("list", "assets.view_gatheraccountsexecution"),
|
||||
("retrieve", "assets.view_gatheraccountsexecution"),
|
||||
("create", "assets.add_gatheraccountsexecution"),
|
||||
)
|
||||
|
|
|
@ -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']
|
|
@ -61,6 +61,7 @@ class SerializeToTreeNodeMixin:
|
|||
'meta': {
|
||||
'type': 'asset',
|
||||
'data': {
|
||||
'platform_type': asset.platform.type,
|
||||
'org_name': asset.org_name,
|
||||
'sftp': asset.platform_id in sftp_enabled_platform,
|
||||
},
|
||||
|
|
|
@ -1,43 +1,37 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from functools import partial
|
||||
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.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 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.tree import TreeNodeSerializer
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.utils import current_org
|
||||
from .. import serializers
|
||||
from ..models import Node
|
||||
from ..tasks import (
|
||||
update_node_assets_hardware_info_manual,
|
||||
test_node_assets_connectivity_manual,
|
||||
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__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
|
||||
'NodeAddChildrenApi', 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi',
|
||||
'NodeTaskCreateApi', 'CategoryTreeApi',
|
||||
'NodeViewSet', 'NodeAssetsApi', 'NodeAddAssetsApi',
|
||||
'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi',
|
||||
'NodeAddChildrenApi', 'NodeTaskCreateApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -74,153 +68,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet):
|
|||
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):
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
from jumpserver.utils import has_valid_xpack_license
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from common.drf.serializers import GroupedChoiceSerializer
|
||||
from assets.models import Platform
|
||||
from assets.const import AllTypes
|
||||
from assets.serializers import PlatformSerializer
|
||||
|
||||
|
||||
__all__ = ['AssetPlatformViewSet']
|
||||
|
||||
|
||||
|
@ -22,6 +22,11 @@ class AssetPlatformViewSet(JMSModelViewSet):
|
|||
'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):
|
||||
pk = self.kwargs.get('pk', '')
|
||||
if pk.isnumeric():
|
||||
|
|
|
@ -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)
|
|
@ -49,23 +49,6 @@ class BaseAccountHandler:
|
|||
header_fields[field] = str(v.label)
|
||||
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
|
||||
def create_row(cls, data, header_fields):
|
||||
data = cls.unpack_data(data)
|
||||
|
@ -94,30 +77,30 @@ class AssetAccountHandler(BaseAccountHandler):
|
|||
return filename
|
||||
|
||||
@classmethod
|
||||
def create_data_map(cls, categories: list):
|
||||
def create_data_map(cls, types: list):
|
||||
data_map = defaultdict(list)
|
||||
|
||||
# TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
|
||||
qs = Account.objects.filter(
|
||||
asset__platform__type__in=categories
|
||||
).annotate(category=F('asset__platform__type'))
|
||||
print(qs, categories)
|
||||
asset__platform__type__in=types
|
||||
).annotate(type=F('asset__platform__type'))
|
||||
|
||||
if not qs.exists():
|
||||
return data_map
|
||||
|
||||
category_dict = {}
|
||||
type_dict = {}
|
||||
for i in AllTypes.grouped_choices_to_objs():
|
||||
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()))
|
||||
account_category_map = defaultdict(list)
|
||||
account_type_map = defaultdict(list)
|
||||
for account in qs:
|
||||
account_category_map[account.category].append(account)
|
||||
account_type_map[account.type].append(account)
|
||||
|
||||
data_map = {}
|
||||
for category, accounts in account_category_map.items():
|
||||
sheet_name = category_dict.get(category, category)
|
||||
for tp, accounts in account_type_map.items():
|
||||
sheet_name = type_dict.get(tp, tp)
|
||||
data = AccountSecretSerializer(accounts, many=True).data
|
||||
data_map.update(cls.add_rows(data, header_fields, sheet_name))
|
||||
|
||||
|
@ -140,9 +123,9 @@ class AccountBackupHandler:
|
|||
# Print task start date
|
||||
time_start = time.time()
|
||||
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:
|
||||
return files
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import os
|
||||
import yaml
|
||||
import shutil
|
||||
from hashlib import md5
|
||||
from copy import deepcopy
|
||||
from socket import gethostname
|
||||
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.utils import timezone
|
||||
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.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
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -93,7 +93,7 @@ class BasePlaybookManager:
|
|||
def get_assets_group_by_platform(self):
|
||||
return self.automation.all_assets_group_by_platform()
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def runtime_dir(self):
|
||||
ansible_dir = settings.ANSIBLE_DIR
|
||||
dir_name = '{}_{}'.format(self.automation.name.replace(' ', '_'), self.execution.id)
|
||||
|
@ -105,11 +105,6 @@ class BasePlaybookManager:
|
|||
os.makedirs(path, exist_ok=True, mode=0o755)
|
||||
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):
|
||||
enabled_attr = '{}_enabled'.format(self.__class__.method_type())
|
||||
method_attr = '{}_method'.format(self.__class__.method_type())
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
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 .filter import GatherAccountsFilter
|
||||
from ...models import GatheredUser
|
||||
from ..base.manager import BasePlaybookManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -26,20 +27,33 @@ class GatherAccountsManager(BasePlaybookManager):
|
|||
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, 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):
|
||||
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||
asset = self.host_asset_mapper.get(host)
|
||||
org_id = asset.org_id
|
||||
if asset and info:
|
||||
result = self.filter_success_result(host, info)
|
||||
with tmp_to_org(org_id):
|
||||
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)
|
||||
self.bulk_create_accounts(asset, result)
|
||||
else:
|
||||
logger.error("Not found info".format(host))
|
||||
|
|
|
@ -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 }}"
|
|
@ -1,6 +0,0 @@
|
|||
id: push_account_mongodb
|
||||
name: Push account from MongoDB
|
||||
category: database
|
||||
type:
|
||||
- mongodb
|
||||
method: push_account
|
|
@ -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: "%"
|
|
@ -1,6 +0,0 @@
|
|||
id: push_account_mysql
|
||||
name: Push account from MySQL
|
||||
category: database
|
||||
type:
|
||||
- mysql
|
||||
method: push_account
|
|
@ -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 }}"
|
|
@ -1,6 +0,0 @@
|
|||
id: push_account_oracle
|
||||
name: Push account from Oracle
|
||||
category: database
|
||||
type:
|
||||
- oracle
|
||||
method: push_account
|
|
@ -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 }}"
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
id: push_account_postgresql
|
||||
name: Push account for PostgreSQL
|
||||
category: database
|
||||
type:
|
||||
- postgresql
|
||||
method: push_account
|
|
@ -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"
|
|
@ -1,7 +0,0 @@
|
|||
id: push_account_posix
|
||||
name: Push posix account
|
||||
category: host
|
||||
type:
|
||||
- linux
|
||||
- unix
|
||||
method: push_account
|
|
@ -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
|
|
@ -1,7 +0,0 @@
|
|||
id: push_account_windows
|
||||
name: Push account windows
|
||||
version: 1
|
||||
method: push_account
|
||||
category: host
|
||||
type:
|
||||
- windows
|
|
@ -13,3 +13,14 @@ class SecretType(TextChoices):
|
|||
SSH_KEY = 'ssh_key', _('SSH key')
|
||||
ACCESS_KEY = 'access_key', _('Access key')
|
||||
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')
|
||||
|
|
|
@ -12,7 +12,7 @@ DEFAULT_PASSWORD_RULES = {
|
|||
class AutomationTypes(TextChoices):
|
||||
ping = 'ping', _('Ping')
|
||||
gather_facts = 'gather_facts', _('Gather facts')
|
||||
push_account = 'push_account', _('Create account')
|
||||
push_account = 'push_account', _('Push account')
|
||||
change_secret = 'change_secret', _('Change secret')
|
||||
verify_account = 'verify_account', _('Verify account')
|
||||
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||
|
@ -20,8 +20,9 @@ class AutomationTypes(TextChoices):
|
|||
@classmethod
|
||||
def get_type_model(cls, tp):
|
||||
from assets.models import (
|
||||
PingAutomation, GatherFactsAutomation, PushAccountAutomation,
|
||||
ChangeSecretAutomation, VerifyAccountAutomation, GatherAccountsAutomation,
|
||||
PingAutomation, GatherFactsAutomation,
|
||||
PushAccountAutomation, ChangeSecretAutomation,
|
||||
VerifyAccountAutomation, GatherAccountsAutomation,
|
||||
)
|
||||
type_model_dict = {
|
||||
cls.ping: PingAutomation,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.db.models import TextChoices
|
||||
|
||||
from jumpserver.utils import has_valid_xpack_license
|
||||
from .protocol import Protocol
|
||||
|
||||
|
||||
|
@ -53,3 +54,25 @@ class BaseType(TextChoices):
|
|||
@classmethod
|
||||
def internal_platforms(cls):
|
||||
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
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ class CloudTypes(BaseType):
|
|||
'gather_facts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'push_account_enabled': False,
|
||||
'gather_accounts_enabled': False,
|
||||
}
|
||||
}
|
||||
|
@ -49,3 +48,9 @@ class CloudTypes(BaseType):
|
|||
cls.PRIVATE: [{'name': 'Vmware-vSphere'}],
|
||||
cls.K8S: [{'name': 'Kubernetes'}],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_community_types(cls):
|
||||
return [
|
||||
cls.K8S, cls.PUBLIC, cls.PRIVATE
|
||||
]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from .base import BaseType
|
||||
|
||||
|
||||
|
@ -34,7 +33,6 @@ class DatabaseTypes(BaseType):
|
|||
'gather_accounts_enabled': True,
|
||||
'verify_account_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'push_account_enabled': True,
|
||||
}
|
||||
}
|
||||
return constrains
|
||||
|
@ -62,3 +60,8 @@ class DatabaseTypes(BaseType):
|
|||
cls.REDIS: [{'name': 'Redis'}],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_community_types(cls):
|
||||
return [
|
||||
cls.MYSQL, cls.MARIADB, cls.MONGODB, cls.REDIS
|
||||
]
|
||||
|
|
|
@ -40,7 +40,6 @@ class DeviceTypes(BaseType):
|
|||
'gather_accounts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'push_account_enabled': False,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,3 +51,9 @@ class DeviceTypes(BaseType):
|
|||
cls.ROUTER: [],
|
||||
cls.FIREWALL: []
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_community_types(cls):
|
||||
return [
|
||||
cls.GENERAL, cls.SWITCH, cls.ROUTER, cls.FIREWALL
|
||||
]
|
||||
|
|
|
@ -34,7 +34,7 @@ class HostTypes(BaseType):
|
|||
def _get_protocol_constrains(cls) -> dict:
|
||||
return {
|
||||
'*': {
|
||||
'choices': ['ssh', 'telnet', 'vnc', 'rdp']
|
||||
'choices': ['ssh', 'telnet', 'vnc', 'rdp']
|
||||
},
|
||||
cls.WINDOWS: {
|
||||
'choices': ['rdp', 'ssh', 'vnc']
|
||||
|
@ -54,7 +54,6 @@ class HostTypes(BaseType):
|
|||
'gather_accounts_enabled': True,
|
||||
'verify_account_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'push_account_enabled': True,
|
||||
},
|
||||
cls.WINDOWS: {
|
||||
'ansible_config': {
|
||||
|
@ -76,7 +75,6 @@ class HostTypes(BaseType):
|
|||
{'name': 'macOS'},
|
||||
{'name': 'BSD'},
|
||||
{'name': 'AIX', 'automation': {
|
||||
'push_account_method': 'push_account_aix',
|
||||
'change_secret_method': 'push_secret_aix'
|
||||
}}
|
||||
],
|
||||
|
@ -97,7 +95,7 @@ class HostTypes(BaseType):
|
|||
{
|
||||
'name': 'RemoteAppHost',
|
||||
'_protocols': ['rdp', 'ssh'],
|
||||
'protocols_setting': {
|
||||
'protocols_setting': {
|
||||
'ssh': {
|
||||
'required': True
|
||||
}
|
||||
|
@ -106,3 +104,9 @@ class HostTypes(BaseType):
|
|||
],
|
||||
cls.OTHER_HOST: []
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_community_types(cls) -> list:
|
||||
return [
|
||||
cls.LINUX, cls.UNIX, cls.WINDOWS, cls.OTHER_HOST
|
||||
]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.db import models
|
||||
|
||||
from common.db.models import ChoicesMixin
|
||||
|
||||
__all__ = ['Protocol']
|
||||
|
@ -102,9 +103,9 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
|||
'port': 80,
|
||||
'secret_types': ['password'],
|
||||
'setting': {
|
||||
'username_selector': 'input[type=text]',
|
||||
'password_selector': 'input[type=password]',
|
||||
'submit_selector': 'button[type=submit]',
|
||||
'username_selector': 'name=username',
|
||||
'password_selector': 'name=password',
|
||||
'submit_selector': 'id=longin_button',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
|||
@classmethod
|
||||
def settings(cls):
|
||||
return {
|
||||
**cls.device_protocols(),
|
||||
**cls.database_protocols(),
|
||||
**cls.cloud_protocols()
|
||||
**cls.device_protocols(),
|
||||
**cls.database_protocols(),
|
||||
**cls.cloud_protocols()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
|
||||
from common.db.models import ChoicesMixin
|
||||
from common.tree import TreeNode
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from common.db.models import ChoicesMixin
|
||||
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 .database import DatabaseTypes
|
||||
from .device import DeviceTypes
|
||||
from .host import HostTypes
|
||||
from .web import WebTypes
|
||||
|
||||
|
||||
class AllTypes(ChoicesMixin):
|
||||
|
@ -54,7 +55,7 @@ class AllTypes(ChoicesMixin):
|
|||
item_name = item.replace('_enabled', '')
|
||||
methods = filter_platform_methods(category, tp, item_name)
|
||||
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)
|
||||
constraints['automation'] = automation
|
||||
return constraints
|
||||
|
@ -62,14 +63,18 @@ class AllTypes(ChoicesMixin):
|
|||
@classmethod
|
||||
def types(cls, with_constraints=True):
|
||||
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])
|
||||
return types
|
||||
|
||||
@classmethod
|
||||
def categories(cls, with_constraints=True):
|
||||
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 = {
|
||||
'value': category.value,
|
||||
'label': category.label,
|
||||
|
@ -121,30 +126,82 @@ class AllTypes(ChoicesMixin):
|
|||
(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
|
||||
def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None):
|
||||
node = TreeNode(**{
|
||||
'id': choice.name,
|
||||
node = {
|
||||
'id': pid + '_' + choice.name,
|
||||
'name': choice.label,
|
||||
'title': choice.label,
|
||||
'pId': pid,
|
||||
'open': opened,
|
||||
'isParent': is_parent,
|
||||
})
|
||||
}
|
||||
if meta:
|
||||
node.meta = meta
|
||||
node['meta'] = meta
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def to_tree_nodes(cls):
|
||||
root = TreeNode(id='ROOT', name='类型节点', title='类型节点')
|
||||
def platform_to_node(cls, p, pid, include_asset):
|
||||
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]
|
||||
for category, types in cls.category_types():
|
||||
category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'})
|
||||
for category, type_cls in cls.category_types():
|
||||
# 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)
|
||||
|
||||
# Type 格式化
|
||||
types = type_cls.get_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)
|
||||
|
||||
# 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
|
||||
|
||||
@classmethod
|
||||
|
@ -253,8 +310,3 @@ class AllTypes(ChoicesMixin):
|
|||
print("\t- Update platform: {}".format(platform.name))
|
||||
platform_data = cls.get_type_default_platform(platform.category, platform.type)
|
||||
cls.create_or_update_by_platform_data(platform.name, platform_data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -44,3 +44,9 @@ class WebTypes(BaseType):
|
|||
{'name': 'Website'},
|
||||
],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_community_types(cls):
|
||||
return [
|
||||
cls.WEBSITE,
|
||||
]
|
||||
|
|
|
@ -126,17 +126,6 @@ class LabelFilterBackend(filters.BaseFilterBackend):
|
|||
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):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ips = request.query_params.get('ips')
|
||||
|
|
|
@ -12,7 +12,6 @@ def migrate_platform_type_to_lower(apps, *args):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0094_auto_20220402_1736'),
|
||||
]
|
||||
|
@ -51,7 +50,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='platform',
|
||||
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)
|
||||
]
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
# Generated by Django 3.1.14 on 2022-04-26 07:54
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0095_auto_20220407_1726'),
|
||||
]
|
||||
|
@ -18,7 +16,8 @@ class Migration(migrations.Migration):
|
|||
('name', models.CharField(max_length=32, verbose_name='Name')),
|
||||
('port', models.IntegerField(verbose_name='Port')),
|
||||
('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')),
|
||||
('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_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_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')),
|
||||
('push_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')),
|
||||
('push_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')),
|
||||
('gather_facts_method',
|
||||
models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')),
|
||||
('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_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_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(
|
||||
model_name='platform',
|
||||
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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
# Generated by Django 3.2.12 on 2022-07-11 08:59
|
||||
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0098_auto_20220430_2126'),
|
||||
|
@ -20,14 +21,19 @@ class Migration(migrations.Migration):
|
|||
name='HistoricalAccount',
|
||||
fields=[
|
||||
('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')),
|
||||
('version', models.IntegerField(default=0, verbose_name='Version'),),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', 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)),
|
||||
('history_type',
|
||||
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={
|
||||
'verbose_name': 'historical Account',
|
||||
|
@ -40,43 +46,55 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Account',
|
||||
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)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('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')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')),
|
||||
('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')),
|
||||
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
||||
('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={
|
||||
'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')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='account',
|
||||
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(
|
||||
name='AccountTemplate',
|
||||
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)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('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')),
|
||||
('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_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
|
|
|
@ -75,33 +75,11 @@ class Migration(migrations.Migration):
|
|||
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(
|
||||
model_name='platformprotocol',
|
||||
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(
|
||||
name='GatherFactsAutomation',
|
||||
fields=[
|
||||
|
|
|
@ -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),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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'},
|
||||
),
|
||||
]
|
|
@ -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'},
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -7,7 +7,6 @@ from .gateway import *
|
|||
from .domain import *
|
||||
from .node import *
|
||||
from .utils import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .account import *
|
||||
from .backup import *
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from simple_history.models import HistoricalRecords
|
||||
|
||||
from common.utils import lazyproperty
|
||||
|
||||
from ..const import AliasAccount, Source
|
||||
from .base import AbsConnectivity, BaseAccount
|
||||
|
||||
__all__ = ['Account', 'AccountTemplate']
|
||||
|
@ -41,11 +41,6 @@ class AccountHistoricalRecords(HistoricalRecords):
|
|||
|
||||
|
||||
class Account(AbsConnectivity, BaseAccount):
|
||||
class AliasAccount(models.TextChoices):
|
||||
ALL = '@ALL', _('All')
|
||||
INPUT = '@INPUT', _('Manual input')
|
||||
USER = '@USER', _('Dynamic user')
|
||||
|
||||
asset = models.ForeignKey(
|
||||
'assets.Asset', related_name='accounts',
|
||||
on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||
|
@ -56,6 +51,7 @@ class Account(AbsConnectivity, BaseAccount):
|
|||
)
|
||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
|
||||
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Account')
|
||||
|
@ -74,21 +70,32 @@ class Account(AbsConnectivity, BaseAccount):
|
|||
def platform(self):
|
||||
return self.asset.platform
|
||||
|
||||
@lazyproperty
|
||||
def alias(self):
|
||||
if self.username.startswith('@'):
|
||||
return self.username
|
||||
return self.name
|
||||
|
||||
def __str__(self):
|
||||
return '{}'.format(self.username)
|
||||
|
||||
@lazyproperty
|
||||
def has_secret(self):
|
||||
return bool(self.secret)
|
||||
|
||||
@classmethod
|
||||
def get_manual_account(cls):
|
||||
""" @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
|
||||
def get_user_account(cls, username):
|
||||
""" @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):
|
||||
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):
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import OrgManager, JMSOrgBaseModel
|
||||
from ..platform import Platform
|
||||
from ..base import AbsConnectivity
|
||||
from ..platform import Platform
|
||||
|
||||
__all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -53,7 +51,7 @@ class NodesRelationMixin:
|
|||
NODES_CACHE_KEY = 'ASSET_NODES_{}'
|
||||
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
|
||||
CACHE_TIME = 3600 * 24 * 7
|
||||
id = ""
|
||||
id: str
|
||||
_all_nodes_keys = None
|
||||
|
||||
def get_nodes(self):
|
||||
|
@ -65,16 +63,29 @@ class NodesRelationMixin:
|
|||
|
||||
def get_all_nodes(self, flat=False):
|
||||
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()
|
||||
for node in self.get_nodes():
|
||||
ancestor_keys = node.get_ancestor_keys(with_self=True)
|
||||
node_keys.update(ancestor_keys)
|
||||
nodes = Node.objects.filter(key__in=node_keys).distinct()
|
||||
if flat:
|
||||
node_ids = set(nodes.values_list('id', flat=True))
|
||||
return node_ids
|
||||
else:
|
||||
return nodes
|
||||
return node_keys
|
||||
|
||||
@classmethod
|
||||
def get_all_nodes_for_assets(cls, assets):
|
||||
from ..node import Node
|
||||
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):
|
||||
|
@ -87,7 +98,6 @@ class Protocol(models.Model):
|
|||
|
||||
|
||||
class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
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')
|
||||
|
@ -97,7 +107,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
|
|||
verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
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)
|
||||
|
||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||
|
|
|
@ -5,4 +5,3 @@ from .gather_facts import *
|
|||
from .change_secret import *
|
||||
from .verify_account import *
|
||||
from .gather_accounts import *
|
||||
from .discovery_account import *
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
import uuid
|
||||
|
||||
from celery import current_task
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.const.choices import Trigger
|
||||
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.const import AutomationTypes
|
||||
from assets.models import Node, Asset
|
||||
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"))
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type'))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name + '@' + str(self.created_by)
|
||||
|
@ -102,6 +101,12 @@ class AutomationExecution(OrgModelMixin):
|
|||
|
||||
class Meta:
|
||||
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
|
||||
def manager_type(self):
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||
from common.db import fields
|
||||
from common.db.models import JMSBaseModel
|
||||
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||
from .base import BaseAutomation
|
||||
|
||||
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord']
|
||||
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
|
||||
|
||||
|
||||
class ChangeSecretAutomation(BaseAutomation):
|
||||
class ChangeSecretMixin(models.Model):
|
||||
secret_type = models.CharField(
|
||||
choices=SecretType.choices, max_length=16,
|
||||
default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
||||
|
@ -24,6 +24,12 @@ class ChangeSecretAutomation(BaseAutomation):
|
|||
choices=SSHKeyStrategy.choices, max_length=16,
|
||||
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)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
|
|
@ -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
|
|
@ -1,12 +1,16 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from assets.const import AutomationTypes
|
||||
from .base import BaseAutomation
|
||||
from .change_secret import ChangeSecretMixin
|
||||
|
||||
__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):
|
||||
self.type = AutomationTypes.push_account
|
||||
|
|
|
@ -7,26 +7,23 @@ from celery import current_task
|
|||
from django.db import models
|
||||
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.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']
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||
types = models.JSONField(default=list)
|
||||
recipients = models.ManyToManyField(
|
||||
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
||||
verbose_name=_("Recipient")
|
||||
)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}({self.org_id})'
|
||||
|
|
|
@ -69,8 +69,6 @@ class BaseAccount(JMSOrgBaseModel):
|
|||
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
||||
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)()
|
||||
|
||||
|
|
|
@ -7,11 +7,6 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
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
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
import random
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
from .gateway import Gateway
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -16,11 +14,8 @@ logger = get_logger(__file__)
|
|||
__all__ = ['Domain']
|
||||
|
||||
|
||||
class Domain(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
class Domain(JMSOrgBaseModel):
|
||||
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:
|
||||
verbose_name = _("Domain")
|
||||
|
@ -51,5 +46,3 @@ class Domain(OrgModelMixin):
|
|||
@classmethod
|
||||
def get_gateway_queryset(cls):
|
||||
return Gateway.objects.all()
|
||||
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
#
|
||||
from django.db import models
|
||||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
|
||||
from common.db.models import JMSBaseModel
|
||||
|
||||
__all__ = ['FavoriteAsset']
|
||||
|
||||
|
||||
class FavoriteAsset(CommonModelMixin):
|
||||
class FavoriteAsset(JMSBaseModel):
|
||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE)
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ class Gateway(Host):
|
|||
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = _("Gateway")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.platform = self.default_platform()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
|
||||
|
||||
|
@ -19,7 +20,6 @@ class Label(JMSOrgBaseModel):
|
|||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
|
||||
default=USER_CATEGORY, verbose_name=_("Category"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
|
||||
@classmethod
|
||||
def get_queryset_group_by_name(cls):
|
||||
|
@ -27,6 +27,10 @@ class Label(JMSOrgBaseModel):
|
|||
for name in names:
|
||||
yield name, cls.objects.filter(name=name)
|
||||
|
||||
@lazyproperty
|
||||
def asset_count(self):
|
||||
return self.assets.count()
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{}".format(self.name, self.value)
|
||||
|
||||
|
|
|
@ -1,29 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
import time
|
||||
import uuid
|
||||
import threading
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db import models, transaction
|
||||
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.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.utils import get_logger
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from orgs.utils import get_current_org, tmp_to_org, tmp_to_root_org
|
||||
from common.utils.lock import DistributedLock
|
||||
from orgs.mixins.models import OrgManager, JMSOrgBaseModel
|
||||
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']
|
||||
logger = get_logger(__name__)
|
||||
|
@ -178,9 +173,7 @@ class FamilyMixin:
|
|||
return parent_keys
|
||||
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
return self.get_node_ancestor_keys(
|
||||
self.key, with_self=with_self
|
||||
)
|
||||
return self.get_node_ancestor_keys(self.key, with_self=with_self)
|
||||
|
||||
@property
|
||||
def ancestors(self):
|
||||
|
@ -437,6 +430,12 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin):
|
|||
assets = Asset.objects.filter(nodes=self)
|
||||
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):
|
||||
return self.get_assets().valid()
|
||||
|
||||
|
@ -547,7 +546,7 @@ class SomeNodesMixin:
|
|||
return root_nodes
|
||||
|
||||
|
||||
class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
||||
class Node(JMSOrgBaseModel, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
|
|
|
@ -2,9 +2,8 @@ from django.db import models
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from assets.const import AllTypes
|
||||
from common.db.fields import JsonDictTextField
|
||||
|
||||
from assets.const import Protocol
|
||||
from common.db.fields import JsonDictTextField
|
||||
|
||||
__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"))
|
||||
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"))
|
||||
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_method = models.TextField(
|
||||
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"))
|
||||
# 账号有关的
|
||||
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',
|
||||
blank=True, null=True, verbose_name=_("Automation"))
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from common.utils import validate_ssh_private_key
|
||||
|
||||
|
||||
__all__ = [
|
||||
'private_key_validator',
|
||||
]
|
||||
|
@ -22,8 +21,6 @@ def private_key_validator(value):
|
|||
|
||||
|
||||
def update_internal_platforms(platform_model):
|
||||
from assets.const import AllTypes
|
||||
|
||||
platforms = [
|
||||
{'name': 'Linux', 'category': 'host', 'type': 'linux'},
|
||||
{'name': 'BSD', 'category': 'host', 'type': 'unix'},
|
||||
|
@ -32,7 +29,6 @@ def update_internal_platforms(platform_model):
|
|||
{'name': 'Windows', 'category': 'host', 'type': 'unix'},
|
||||
{
|
||||
'name': 'AIX', 'category': 'host', 'type': 'unix',
|
||||
'push_account_method': 'create_account_aix',
|
||||
'change_secret_method': 'change_secret_aix',
|
||||
},
|
||||
{'name': 'Windows', 'category': 'host', 'type': 'windows'},
|
||||
|
|
|
@ -6,7 +6,6 @@ from .label import *
|
|||
from .node import *
|
||||
from .gateway import *
|
||||
from .domain import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .account import *
|
||||
from .platform import *
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.drf.serializers import SecretReadableMixin
|
||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from assets.tasks import push_accounts_to_assets
|
||||
from assets.const import SecretType, Source
|
||||
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 assets.const import SecretType
|
||||
|
||||
|
||||
class AccountSerializerCreateMixin(serializers.ModelSerializer):
|
||||
class AccountSerializerCreateMixin(BulkModelSerializer):
|
||||
template = serializers.UUIDField(
|
||||
required=False, allow_null=True, write_only=True,
|
||||
label=_('Account template')
|
||||
|
@ -53,35 +53,47 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer):
|
|||
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):
|
||||
asset = ObjectRelatedField(
|
||||
required=False, queryset=Asset.objects,
|
||||
label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id')
|
||||
)
|
||||
asset = AccountAssetSerializer(label=_('Asset'))
|
||||
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
|
||||
su_from = ObjectRelatedField(
|
||||
required=False, queryset=Account.objects,
|
||||
label=_('Account'), attrs=('id', 'name', 'username')
|
||||
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
||||
label=_('Su from'), attrs=('id', 'name', 'username')
|
||||
)
|
||||
|
||||
class Meta(BaseAccountSerializer.Meta):
|
||||
model = Account
|
||||
fields = BaseAccountSerializer.Meta.fields \
|
||||
+ ['su_from', 'version', 'asset'] \
|
||||
+ ['template', 'push_now']
|
||||
+ ['su_from', 'version', 'asset'] \
|
||||
+ ['template', 'push_now', 'source']
|
||||
extra_kwargs = {
|
||||
**BaseAccountSerializer.Meta.extra_kwargs,
|
||||
'name': {'required': False, 'allow_null': True},
|
||||
}
|
||||
|
||||
def __init__(self, *args, data=None, **kwargs):
|
||||
super().__init__(*args, data=data, **kwargs)
|
||||
if data and 'name' not in data:
|
||||
username = data.get('username')
|
||||
if username is not None:
|
||||
data['name'] = username
|
||||
if hasattr(self, 'initial_data') and \
|
||||
not getattr(self, 'initial_data', None):
|
||||
delattr(self, 'initial_data')
|
||||
def validate_name(self, value):
|
||||
if not value:
|
||||
value = self.initial_data.get('username')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
|
|
|
@ -34,7 +34,6 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode
|
|||
|
||||
|
||||
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
|
||||
trigger = LabeledChoiceField(choices=Trigger.choices, label=_('Trigger mode'))
|
||||
|
||||
class Meta:
|
||||
model = AccountBackupPlanExecution
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import BaseAccount
|
||||
from assets.serializers.base import AuthValidateMixin
|
||||
|
@ -9,6 +10,8 @@ __all__ = ['BaseAccountSerializer']
|
|||
|
||||
|
||||
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
||||
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = BaseAccount
|
||||
fields_mini = ['id', 'name', 'username']
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from common.drf.serializers import SecretReadableMixin
|
||||
from assets.models import AccountTemplate
|
||||
from common.drf.serializers import SecretReadableMixin
|
||||
from .base import BaseAccountSerializer
|
||||
|
||||
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
# -*- 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.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 orgs.mixins.serializers import OrgResourceSerializerMixin
|
||||
from common.drf.serializers import WritableNestedModelSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceSerializerMixin
|
||||
from ..account import AccountSerializer
|
||||
from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
|
||||
from ...const import Category, AllTypes
|
||||
from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
|
||||
'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer',
|
||||
'AssetDetailSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -58,7 +59,7 @@ class AssetAccountSerializer(AccountSerializer):
|
|||
fields = fields_mini + fields_write_only
|
||||
|
||||
|
||||
class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer):
|
||||
class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSerializer):
|
||||
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
|
||||
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
|
||||
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'))
|
||||
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
|
||||
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
|
||||
accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts'))
|
||||
enabled_info = serializers.SerializerMethodField()
|
||||
accounts = AssetAccountSerializer(many=True, required=False, label=_('Account'))
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields_mini = ['id', 'name', 'address', 'enabled_info']
|
||||
fields_mini = ['id', 'name', 'address']
|
||||
fields_small = fields_mini + ['is_active', 'comment']
|
||||
fields_fk = ['domain', 'platform', 'platform']
|
||||
fields_m2m = [
|
||||
'nodes', 'labels', 'protocols', 'accounts', 'nodes_display',
|
||||
]
|
||||
read_only_fields = [
|
||||
'category', 'type', 'specific', 'info',
|
||||
'connectivity', 'date_verified', 'created_by',
|
||||
'date_created'
|
||||
'category', 'type', 'info',
|
||||
'connectivity', 'date_verified',
|
||||
'created_by', 'date_created'
|
||||
]
|
||||
fields = fields_small + fields_fk + fields_m2m + read_only_fields
|
||||
extra_kwargs = {
|
||||
'name': {'label': _("Name")},
|
||||
'address': {'label': _('Address')},
|
||||
}
|
||||
|
||||
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,
|
||||
'nodes_display': {'label': _('Node path')},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -117,7 +95,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
|
|||
queryset = queryset.prefetch_related('domain', 'platform', 'protocols') \
|
||||
.annotate(category=F("platform__category")) \
|
||||
.annotate(type=F("platform__type"))
|
||||
queryset = queryset.prefetch_related('nodes', 'labels')
|
||||
queryset = queryset.prefetch_related('nodes', 'labels', 'accounts')
|
||||
return queryset
|
||||
|
||||
def perform_nodes_display_create(self, instance, nodes_display):
|
||||
|
@ -188,6 +166,30 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
|
|||
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 Meta:
|
||||
model = Asset
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from assets.models import Web
|
||||
from .common import AssetSerializer
|
||||
|
||||
|
@ -19,12 +18,12 @@ class WebSerializer(AssetSerializer):
|
|||
'label': 'URL'
|
||||
},
|
||||
'username_selector': {
|
||||
'default': 'input[type=text]'
|
||||
'default': 'name=username'
|
||||
},
|
||||
'password_selector': {
|
||||
'default': 'input[type=password]'
|
||||
'default': 'name=password'
|
||||
},
|
||||
'submit_selector': {
|
||||
'default': 'button[type=submit]',
|
||||
'default': 'id=longin_button',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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'},
|
||||
}
|
|
@ -1,25 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from django.db.models import Count
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
from ..models import Label
|
||||
|
||||
|
||||
class LabelSerializer(BulkOrgResourceModelSerializer):
|
||||
asset_count = serializers.SerializerMethodField(label=_("Assets amount"))
|
||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display'))
|
||||
asset_count = serializers.ReadOnlyField(label=_("Assets amount"))
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = fields_mini + [
|
||||
'value', 'category', 'category_display',
|
||||
'is_active',
|
||||
'date_created',
|
||||
'comment',
|
||||
'value', 'category', 'is_active',
|
||||
'date_created', 'comment',
|
||||
]
|
||||
fields_m2m = ['asset_count', 'assets']
|
||||
fields = fields_small + fields_m2m
|
||||
|
@ -30,14 +27,10 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
|
|||
'assets': {'required': False, 'label': _('Asset')}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_asset_count(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend(['get_category_display'])
|
||||
return fields
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
queryset = queryset.annotate(asset_count=Count('assets'))
|
||||
return queryset
|
||||
|
||||
|
||||
class LabelDistinctSerializer(BulkOrgResourceModelSerializer):
|
||||
|
|
|
@ -25,7 +25,7 @@ class ProtocolSettingSerializer(serializers.Serializer):
|
|||
sftp_home = serializers.CharField(default="/tmp", label=_("SFTP home"))
|
||||
|
||||
# HTTP
|
||||
auto_fill = serializers.BooleanField(default=False, label=_("Auto fill"))
|
||||
autofile = serializers.BooleanField(default=False, label=_("Autofill"))
|
||||
username_selector = serializers.CharField(
|
||||
default="", allow_blank=True, label=_("Username selector")
|
||||
)
|
||||
|
@ -38,37 +38,26 @@ class ProtocolSettingSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class PlatformAutomationSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PlatformAutomation
|
||||
fields = [
|
||||
"id",
|
||||
"ansible_enabled",
|
||||
"ansible_config",
|
||||
"ping_enabled",
|
||||
"ping_method",
|
||||
"gather_facts_enabled",
|
||||
"gather_facts_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",
|
||||
"ansible_enabled", "ansible_config",
|
||||
"ping_enabled", "ping_method",
|
||||
"gather_facts_enabled", "gather_facts_method",
|
||||
"change_secret_enabled", "change_secret_method",
|
||||
"verify_account_enabled", "verify_account_method",
|
||||
"gather_accounts_enabled", "gather_accounts_method",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"ping_enabled": {"label": "启用资产探测"},
|
||||
"ping_method": {"label": "探测方式"},
|
||||
"gather_facts_enabled": {"label": "启用收集信息"},
|
||||
"ping_method": {"label": "资产探测方式"},
|
||||
"gather_facts_enabled": {"label": "收集资产信息"},
|
||||
"gather_facts_method": {"label": "收集信息方式"},
|
||||
"verify_account_enabled": {"label": "启用校验账号"},
|
||||
"verify_account_method": {"label": "校验账号方式"},
|
||||
"push_account_enabled": {"label": "启用推送账号"},
|
||||
"push_account_method": {"label": "推送账号方式"},
|
||||
"change_secret_enabled": {"label": "启用账号改密"},
|
||||
"change_secret_method": {"label": "账号创建改密方式"},
|
||||
"change_secret_method": {"label": "账号改密方式"},
|
||||
"gather_accounts_enabled": {"label": "启用账号收集"},
|
||||
"gather_accounts_method": {"label": "收集账号方式"},
|
||||
}
|
||||
|
@ -81,13 +70,8 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = PlatformProtocol
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"port",
|
||||
"primary",
|
||||
"default",
|
||||
"required",
|
||||
"secret_types",
|
||||
"id", "name", "port", "primary",
|
||||
"default", "required", "secret_types",
|
||||
"setting",
|
||||
]
|
||||
|
||||
|
@ -113,17 +97,12 @@ class PlatformSerializer(WritableNestedModelSerializer):
|
|||
model = Platform
|
||||
fields_mini = ["id", "name", "internal"]
|
||||
fields_small = fields_mini + [
|
||||
"category",
|
||||
"type",
|
||||
"charset",
|
||||
"category", "type", "charset",
|
||||
]
|
||||
fields = fields_small + [
|
||||
"protocols_enabled",
|
||||
"protocols",
|
||||
"domain_enabled",
|
||||
"su_enabled",
|
||||
"su_method",
|
||||
"automation",
|
||||
"protocols_enabled", "protocols",
|
||||
"domain_enabled", "su_enabled",
|
||||
"su_method", "automation",
|
||||
"comment",
|
||||
]
|
||||
extra_kwargs = {
|
||||
|
|
|
@ -103,6 +103,7 @@ def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs):
|
|||
# m2m_model.objects.bulk_create(to_create)
|
||||
#
|
||||
|
||||
|
||||
RELATED_NODE_IDS = '_related_node_ids'
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
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()
|
||||
|
|
|
@ -23,13 +23,13 @@ router.register(r'labels', api.LabelViewSet, 'label')
|
|||
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
|
||||
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||
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'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'gather-account-automations', api.GatherAccountsAutomationViewSet, 'gather-account-automation')
|
||||
|
||||
|
@ -50,7 +50,6 @@ urlpatterns = [
|
|||
name='account-secret-history'),
|
||||
|
||||
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/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue