mirror of https://github.com/jumpserver/jumpserver
commit
f00a650366
|
@ -17,7 +17,6 @@ dump.rdb
|
||||||
.idea/
|
.idea/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
config.py
|
config.py
|
||||||
migrations/
|
|
||||||
*.log
|
*.log
|
||||||
host_rsa_key
|
host_rsa_key
|
||||||
*.bat
|
*.bat
|
||||||
|
@ -33,3 +32,4 @@ celerybeat-schedule.db
|
||||||
data/static
|
data/static
|
||||||
docs/_build/
|
docs/_build/
|
||||||
xpack
|
xpack
|
||||||
|
logs/*
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
FROM registry.fit2cloud.com/public/python:v3
|
||||||
|
MAINTAINER Jumpserver Team <ibuler@qq.com>
|
||||||
|
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
RUN useradd jumpserver
|
||||||
|
|
||||||
|
COPY ./requirements /tmp/requirements
|
||||||
|
|
||||||
|
RUN yum -y install epel-release && cd /tmp/requirements && \
|
||||||
|
yum -y install $(cat rpm_requirements.txt)
|
||||||
|
|
||||||
|
RUN cd /tmp/requirements && pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . /opt/jumpserver
|
||||||
|
COPY config_docker.py /opt/jumpserver/config.py
|
||||||
|
VOLUME /opt/jumpserver/data
|
||||||
|
VOLUME /opt/jumpserver/logs
|
||||||
|
|
||||||
|
ENV LANG=zh_CN.UTF-8
|
||||||
|
ENV LC_ALL=zh_CN.UTF-8
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
|
@ -2,4 +2,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "1.4.4"
|
__version__ = "1.4.5"
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django.views.generic.detail import SingleObjectMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
||||||
from ..models import Domain, Gateway
|
from ..models import Domain, Gateway
|
||||||
from ..utils import test_gateway_connectability
|
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object(Gateway.objects.all())
|
self.object = self.get_object(Gateway.objects.all())
|
||||||
ok, e = test_gateway_connectability(self.object)
|
ok, e = self.object.test_connective()
|
||||||
if ok:
|
if ok:
|
||||||
return Response("ok")
|
return Response("ok")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -142,14 +142,14 @@ class AssetBulkUpdateForm(OrgModelForm):
|
||||||
if k in changed_fields}
|
if k in changed_fields}
|
||||||
assets = cleaned_data.pop('assets')
|
assets = cleaned_data.pop('assets')
|
||||||
labels = cleaned_data.pop('labels', [])
|
labels = cleaned_data.pop('labels', [])
|
||||||
nodes = cleaned_data.pop('nodes')
|
nodes = cleaned_data.pop('nodes', None)
|
||||||
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
|
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
|
||||||
assets.update(**cleaned_data)
|
assets.update(**cleaned_data)
|
||||||
|
|
||||||
if labels:
|
if labels:
|
||||||
for label in labels:
|
for asset in assets:
|
||||||
label.assets.add(*tuple(assets))
|
asset.labels.set(labels)
|
||||||
if nodes:
|
if nodes:
|
||||||
for node in nodes:
|
for asset in assets:
|
||||||
node.assets.add(*tuple(assets))
|
asset.nodes.set(nodes)
|
||||||
return assets
|
return assets
|
||||||
|
|
|
@ -36,6 +36,10 @@ class DomainForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
||||||
|
protocol = forms.ChoiceField(
|
||||||
|
choices=[Gateway.PROTOCOL_CHOICES[0]],
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
password_field = self.fields.get('password')
|
password_field = self.fields.get('password')
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-05 10:07
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adminuser',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='asset',
|
||||||
|
options={'verbose_name': 'Asset'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='assetgroup',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='cluster',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='systemuser',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-09 15:31
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import assets.models.asset
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0002_auto_20180105_1807'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cluster',
|
||||||
|
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-25 04:18
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0003_auto_20180109_2331'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='assetgroup',
|
||||||
|
name='created_by',
|
||||||
|
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-26 08:37
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0004_auto_20180125_1218'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Label',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||||
|
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
|
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'assets_label',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='label',
|
||||||
|
unique_together=set([('name', 'value')]),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='labels',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-30 07:02
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0005_auto_20180126_1637'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cabinet_no',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cabinet_pos',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='env',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='remote_card_ip',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='status',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='type',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,60 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-02-25 10:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import assets.models.asset
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0006_auto_20180130_1502'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Node',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||||
|
('value', models.CharField(max_length=128, unique=True, verbose_name='Value')),
|
||||||
|
('child_mark', models.IntegerField(default=0)),
|
||||||
|
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cluster',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='groups',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='cluster',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='admin_user',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='nodes',
|
||||||
|
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='nodes',
|
||||||
|
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-03-06 10:04
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0007_auto_20180225_1815'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='created_by',
|
||||||
|
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='platform',
|
||||||
|
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='created_by',
|
||||||
|
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-03-07 04:12
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0008_auto_20180306_1804'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-03-07 09:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0009_auto_20180307_1212'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-03-26 01:57
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import assets.models.utils
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0010_auto_20180307_1749'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Domain',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, 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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Gateway',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||||
|
('username', models.CharField(max_length=128, verbose_name='Username')),
|
||||||
|
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||||
|
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||||
|
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||||
|
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||||
|
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||||
|
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
|
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='domain',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-04 05:02
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0011_auto_20180326_0957'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='domain',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-11 03:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0012_auto_20180404_1302'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='assets',
|
||||||
|
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='sudo',
|
||||||
|
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-27 04:45
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0013_auto_20180411_1135'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-05-10 04:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0014_auto_20180427_1245'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-05-11 04:03
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0015_auto_20180510_1235'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-07-02 06:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||||
|
asset_model = apps.get_model("assets", "Asset")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0016_auto_20180511_1203'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='login_mode',
|
||||||
|
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='platform',
|
||||||
|
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_win_to_ssh_protocol),
|
||||||
|
]
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Generated by Django 2.0.7 on 2018-08-07 03:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0017_auto_20180702_1415'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='domain',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='label',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='hostname',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='adminuser',
|
||||||
|
unique_together={('name', 'org_id')},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='asset',
|
||||||
|
unique_together={('org_id', 'hostname')},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='gateway',
|
||||||
|
unique_together={('name', 'org_id')},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='systemuser',
|
||||||
|
unique_together={('name', 'org_id')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.0.7 on 2018-08-16 05:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0018_auto_20180807_1116'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cpu_vcpus',
|
||||||
|
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='label',
|
||||||
|
unique_together={('name', 'value', 'org_id')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 2.0.7 on 2018-08-16 08:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0019_auto_20180816_1320'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='domain',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='label',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 2.1 on 2018-09-03 03:32
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0020_auto_20180816_1652'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='domain',
|
||||||
|
options={'verbose_name': 'Domain'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='gateway',
|
||||||
|
options={'verbose_name': 'Gateway'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='node',
|
||||||
|
options={'verbose_name': 'Node'},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 2.1.1 on 2018-10-12 09:17
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0021_auto_20180903_1132'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CommandFilter',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=64, verbose_name='Name')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CommandFilterRule',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
|
||||||
|
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||||
|
('content', models.TextField(help_text='One line one command', max_length=1024, verbose_name='Content')),
|
||||||
|
('action', models.IntegerField(choices=[(0, 'Deny'), (1, 'Allow')], default=0, verbose_name='Action')),
|
||||||
|
('comment', models.CharField(blank=True, default='', max_length=64, verbose_name='Comment')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
|
||||||
|
('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='assets.CommandFilter', verbose_name='Filter')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('priority', 'action'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='cmd_filters',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.CommandFilter', verbose_name='Command filter'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.1.1 on 2018-10-16 08:50
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0022_auto_20181012_1717'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='commandfilterrule',
|
||||||
|
options={'ordering': ('-priority', 'action')},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='commandfilterrule',
|
||||||
|
name='priority',
|
||||||
|
field=models.IntegerField(default=50, help_text='1-100, the higher will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='priority',
|
||||||
|
field=models.IntegerField(default=20, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,3 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from .user import *
|
from .user import *
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .cluster import *
|
from .cluster import *
|
||||||
|
|
|
@ -145,6 +145,13 @@ class Asset(OrgModelMixin):
|
||||||
return True, ''
|
return True, ''
|
||||||
return False, warning
|
return False, warning
|
||||||
|
|
||||||
|
def support_ansible(self):
|
||||||
|
if self.platform in ("Windows", "Windows2016", "Other"):
|
||||||
|
return False
|
||||||
|
if self.protocol != 'ssh':
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def is_unixlike(self):
|
def is_unixlike(self):
|
||||||
if self.platform not in ("Windows", "Windows2016"):
|
if self.platform not in ("Windows", "Windows2016"):
|
||||||
return True
|
return True
|
||||||
|
@ -257,7 +264,8 @@ class Asset(OrgModelMixin):
|
||||||
from random import seed, choice
|
from random import seed, choice
|
||||||
import forgery_py
|
import forgery_py
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
|
from .node import Node
|
||||||
|
nodes = list(Node.objects.all())
|
||||||
seed()
|
seed()
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
ip = [str(i) for i in random.sample(range(255), 4)]
|
ip = [str(i) for i in random.sample(range(255), 4)]
|
||||||
|
@ -268,6 +276,11 @@ class Asset(OrgModelMixin):
|
||||||
created_by='Fake')
|
created_by='Fake')
|
||||||
try:
|
try:
|
||||||
asset.save()
|
asset.save()
|
||||||
|
if nodes and len(nodes) > 3:
|
||||||
|
_nodes = random.sample(nodes, 3)
|
||||||
|
else:
|
||||||
|
_nodes = [Node.default_node()]
|
||||||
|
asset.nodes.set(_nodes)
|
||||||
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
|
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
|
||||||
logger.debug('Generate fake asset : %s' % asset.ip)
|
logger.debug('Generate fake asset : %s' % asset.ip)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
|
|
@ -105,6 +105,9 @@ class AssetUser(OrgModelMixin):
|
||||||
if update_fields:
|
if update_fields:
|
||||||
self.save(update_fields=update_fields)
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
|
def get_auth(self, asset=None):
|
||||||
|
pass
|
||||||
|
|
||||||
def clear_auth(self):
|
def clear_auth(self):
|
||||||
self._password = ''
|
self._password = ''
|
||||||
self._private_key = ''
|
self._private_key = ''
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
|
import re
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
|
@ -35,7 +36,7 @@ class CommandFilterRule(OrgModelMixin):
|
||||||
(TYPE_COMMAND, _('Command')),
|
(TYPE_COMMAND, _('Command')),
|
||||||
)
|
)
|
||||||
|
|
||||||
ACTION_DENY, ACTION_ALLOW = range(2)
|
ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3)
|
||||||
ACTION_CHOICES = (
|
ACTION_CHOICES = (
|
||||||
(ACTION_DENY, _('Deny')),
|
(ACTION_DENY, _('Deny')),
|
||||||
(ACTION_ALLOW, _('Allow')),
|
(ACTION_ALLOW, _('Allow')),
|
||||||
|
@ -53,8 +54,34 @@ class CommandFilterRule(OrgModelMixin):
|
||||||
date_updated = models.DateTimeField(auto_now=True)
|
date_updated = models.DateTimeField(auto_now=True)
|
||||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||||
|
|
||||||
|
__pattern = None
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('-priority', 'action')
|
ordering = ('-priority', 'action')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _pattern(self):
|
||||||
|
if self.__pattern:
|
||||||
|
return self.__pattern
|
||||||
|
if self.type == 'command':
|
||||||
|
regex = []
|
||||||
|
for cmd in self.content.split('\r\n'):
|
||||||
|
cmd = cmd.replace(' ', '\s+')
|
||||||
|
regex.append(r'\b{0}\b'.format(cmd))
|
||||||
|
self.__pattern = re.compile(r'{}'.format('|'.join(regex)))
|
||||||
|
else:
|
||||||
|
self.__pattern = re.compile(r'{0}'.format(self.content))
|
||||||
|
return self.__pattern
|
||||||
|
|
||||||
|
def match(self, data):
|
||||||
|
found = self._pattern.search(data)
|
||||||
|
if not found:
|
||||||
|
return self.ACTION_UNKNOWN, ''
|
||||||
|
|
||||||
|
if self.action == self.ACTION_ALLOW:
|
||||||
|
return self.ACTION_ALLOW, found.group()
|
||||||
|
else:
|
||||||
|
return self.ACTION_DENY, found.group()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} % {}'.format(self.type, self.content)
|
return '{} % {}'.format(self.type, self.content)
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
import uuid
|
import uuid
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
import paramiko
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -57,3 +59,37 @@ class Gateway(AssetUser):
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [('name', 'org_id')]
|
unique_together = [('name', 'org_id')]
|
||||||
verbose_name = _("Gateway")
|
verbose_name = _("Gateway")
|
||||||
|
|
||||||
|
def test_connective(self):
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
proxy = paramiko.SSHClient()
|
||||||
|
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
|
||||||
|
try:
|
||||||
|
proxy.connect(self.ip, port=self.port,
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
pkey=self.private_key_obj)
|
||||||
|
except(paramiko.AuthenticationException,
|
||||||
|
paramiko.BadAuthenticationType,
|
||||||
|
paramiko.SSHException) as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
sock = proxy.get_transport().open_channel(
|
||||||
|
'direct-tcpip', ('127.0.0.1', self.port), ('127.0.0.1', 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.connect("127.0.0.1", port=self.port,
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
key_filename=self.private_key_file,
|
||||||
|
sock=sock,
|
||||||
|
timeout=5)
|
||||||
|
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
|
||||||
|
paramiko.AuthenticationException, TimeoutError) as e:
|
||||||
|
return False, str(e)
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
return True, None
|
||||||
|
|
|
@ -17,7 +17,8 @@ class Label(OrgModelMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category"))
|
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"))
|
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||||
date_created = models.DateTimeField(
|
date_created = models.DateTimeField(
|
||||||
|
|
|
@ -203,17 +203,14 @@ class Node(OrgModelMixin):
|
||||||
# 如果使用current_org 在set_current_org时会死循环
|
# 如果使用current_org 在set_current_org时会死循环
|
||||||
_current_org = get_current_org()
|
_current_org = get_current_org()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if _current_org.is_root():
|
if not _current_org.is_real():
|
||||||
key = '0'
|
return cls.default_node()
|
||||||
elif _current_org.is_default():
|
set_current_org(Organization.root())
|
||||||
key = '1'
|
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||||
else:
|
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or ['1']
|
||||||
set_current_org(Organization.root())
|
key = max([int(k) for k in org_nodes_roots_keys])
|
||||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
key = str(key + 1) if key != 0 else '2'
|
||||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or ['1']
|
set_current_org(_current_org)
|
||||||
key = max([int(k) for k in org_nodes_roots_keys])
|
|
||||||
key = str(key + 1) if key != 0 else '2'
|
|
||||||
set_current_org(_current_org)
|
|
||||||
root = cls.objects.create(key=key, value=_current_org.name)
|
root = cls.objects.create(key=key, value=_current_org.name)
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,15 @@ class SystemUser(AssetUser):
|
||||||
).distinct()
|
).distinct()
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
def is_command_can_run(self, command):
|
||||||
|
for rule in self.cmd_filter_rules:
|
||||||
|
action, matched_cmd = rule.match(command)
|
||||||
|
if action == rule.ACTION_ALLOW:
|
||||||
|
return True, None
|
||||||
|
elif action == rule.ACTION_DENY:
|
||||||
|
return False, matched_cmd
|
||||||
|
return True, None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_system_user_by_id_or_cached(cls, sid):
|
def get_system_user_by_id_or_cached(cls, sid):
|
||||||
cached = cache.get(cls.cache_key.format(sid))
|
cached = cache.get(cls.cache_key.format(sid))
|
||||||
|
|
|
@ -23,7 +23,6 @@ class DomainSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class GatewaySerializer(serializers.ModelSerializer):
|
class GatewaySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Gateway
|
model = Gateway
|
||||||
fields = [
|
fields = [
|
||||||
|
|
|
@ -68,6 +68,8 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_assets_amount(obj):
|
def get_assets_amount(obj):
|
||||||
|
if hasattr(obj, 'assets_amount'):
|
||||||
|
return obj.assets_amount
|
||||||
return obj.get_all_assets().count()
|
return obj.get_all_assets().count()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -86,7 +88,7 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all())
|
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Node
|
model = Node
|
||||||
|
|
|
@ -58,7 +58,7 @@ def on_system_user_nodes_change(sender, instance=None, **kwargs):
|
||||||
def on_system_user_assets_change(sender, instance=None, **kwargs):
|
def on_system_user_assets_change(sender, instance=None, **kwargs):
|
||||||
if instance and kwargs["action"] == "post_add":
|
if instance and kwargs["action"] == "post_add":
|
||||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
push_system_user_to_assets(instance, assets)
|
push_system_user_to_assets.delay(instance, assets)
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
|
|
|
@ -4,14 +4,15 @@ import re
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from ops.celery import app as celery_app
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from common.utils import get_object_or_none, capacity_convert, \
|
from common.utils import capacity_convert, \
|
||||||
sum_capacity, encrypt_password, get_logger
|
sum_capacity, encrypt_password, get_logger
|
||||||
from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \
|
from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \
|
||||||
after_app_ready_start
|
after_app_ready_start
|
||||||
from ops.celery import app as celery_app
|
from orgs.utils import set_to_root_org
|
||||||
|
|
||||||
from .models import SystemUser, AdminUser, Asset
|
from .models import SystemUser, AdminUser, Asset
|
||||||
from . import const
|
from . import const
|
||||||
|
@ -20,34 +21,34 @@ from . import const
|
||||||
FORKS = 10
|
FORKS = 10
|
||||||
TIMEOUT = 60
|
TIMEOUT = 60
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
CACHE_MAX_TIME = 60*60*60
|
CACHE_MAX_TIME = 60*60*2
|
||||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
||||||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
|
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def set_assets_hardware_info(result, **kwargs):
|
def set_assets_hardware_info(assets, result, **kwargs):
|
||||||
"""
|
"""
|
||||||
Using ops task run result, to update asset info
|
Using ops task run result, to update asset info
|
||||||
|
|
||||||
@shared_task must be exit, because we using it as a task callback, is must
|
@shared_task must be exit, because we using it as a task callback, is must
|
||||||
be a celery task also
|
be a celery task also
|
||||||
|
:param assets:
|
||||||
:param result:
|
:param result:
|
||||||
:param kwargs: {task_name: ""}
|
:param kwargs: {task_name: ""}
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
result_raw = result[0]
|
result_raw = result[0]
|
||||||
assets_updated = []
|
assets_updated = []
|
||||||
for hostname, info in result_raw.get('ok', {}).items():
|
success_result = result_raw.get('ok', {})
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
hostname = asset.hostname
|
||||||
|
info = success_result.get(hostname, {})
|
||||||
info = info.get('setup', {}).get('ansible_facts', {})
|
info = info.get('setup', {}).get('ansible_facts', {})
|
||||||
if not info:
|
if not info:
|
||||||
logger.error("Get asset info failed: {}".format(hostname))
|
logger.error(_("Get asset info failed: {}").format(hostname))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
asset = Asset.objects.get_object_by_fullname(hostname)
|
|
||||||
if not asset:
|
|
||||||
continue
|
|
||||||
|
|
||||||
___vendor = info.get('ansible_system_vendor', 'Unknown')
|
___vendor = info.get('ansible_system_vendor', 'Unknown')
|
||||||
___model = info.get('ansible_product_name', 'Unknown')
|
___model = info.get('ansible_product_name', 'Unknown')
|
||||||
___sn = info.get('ansible_product_serial', 'Unknown')
|
___sn = info.get('ansible_product_serial', 'Unknown')
|
||||||
|
@ -94,34 +95,43 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
if task_name is None:
|
if task_name is None:
|
||||||
task_name = _("Update some assets hardware info")
|
task_name = _("Update some assets hardware info")
|
||||||
# task_name = _("更新资产硬件信息")
|
|
||||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||||
hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
hosts = []
|
||||||
if not hostname_list:
|
for asset in assets:
|
||||||
logger.info("Not hosts get, may be asset is not active or not unixlike platform")
|
if not asset.is_active:
|
||||||
|
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
if not asset.support_ansible():
|
||||||
|
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
hosts.append(asset)
|
||||||
|
if not hosts:
|
||||||
|
logger.info(_("No assets matched, stop task"))
|
||||||
return {}
|
return {}
|
||||||
|
created_by = str(assets[0].org_id)
|
||||||
task, created = update_or_create_ansible_task(
|
task, created = update_or_create_ansible_task(
|
||||||
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||||
)
|
)
|
||||||
result = task.run()
|
result = task.run()
|
||||||
# Todo: may be somewhere using
|
# Todo: may be somewhere using
|
||||||
# Manual run callback function
|
# Manual run callback function
|
||||||
set_assets_hardware_info(result)
|
set_assets_hardware_info(assets, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def update_asset_hardware_info_manual(asset):
|
def update_asset_hardware_info_manual(asset):
|
||||||
task_name = _("Update asset hardware info")
|
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||||
# task_name = _("更新资产硬件信息")
|
# task_name = _("更新资产硬件信息")
|
||||||
return update_assets_hardware_info_util([asset], task_name=task_name)
|
return update_assets_hardware_info_util(
|
||||||
|
[asset], task_name=task_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task
|
@shared_task
|
||||||
@register_as_period_task(interval=3600)
|
|
||||||
@after_app_ready_start
|
|
||||||
@after_app_shutdown_clean
|
|
||||||
def update_assets_hardware_info_period():
|
def update_assets_hardware_info_period():
|
||||||
"""
|
"""
|
||||||
Update asset hardware period task
|
Update asset hardware period task
|
||||||
|
@ -132,25 +142,28 @@ def update_assets_hardware_info_period():
|
||||||
return
|
return
|
||||||
|
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
|
from orgs.models import Organization
|
||||||
|
orgs = Organization.objects.all().values_list('id', flat=True)
|
||||||
|
orgs.append('')
|
||||||
task_name = _("Update assets hardware info period")
|
task_name = _("Update assets hardware info period")
|
||||||
# task_name = _("定期更新资产硬件信息")
|
# for org_id in orgs:
|
||||||
hostname_list = [
|
# org_id = str(org_id)
|
||||||
asset.fullname for asset in Asset.objects.all()
|
# hostname_list = [
|
||||||
if asset.is_active and asset.is_unixlike()
|
# asset for asset in Asset.objects.all()
|
||||||
]
|
# if asset.is_active and asset.is_unixlike()
|
||||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
# ]
|
||||||
|
# tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||||
# Only create, schedule by celery beat
|
#
|
||||||
update_or_create_ansible_task(
|
# # Only create, schedule by celery beat
|
||||||
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
# update_or_create_ansible_task(
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
# task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||||
interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
|
# options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||||
)
|
# interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
## ADMIN USER CONNECTIVE ##
|
## ADMIN USER CONNECTIVE ##
|
||||||
|
|
||||||
@shared_task
|
|
||||||
def set_admin_user_connectability_info(result, **kwargs):
|
def set_admin_user_connectability_info(result, **kwargs):
|
||||||
admin_user = kwargs.get("admin_user")
|
admin_user = kwargs.get("admin_user")
|
||||||
task_name = kwargs.get("task_name")
|
task_name = kwargs.get("task_name")
|
||||||
|
@ -182,36 +195,39 @@ def test_admin_user_connectability_util(admin_user, task_name):
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
|
|
||||||
assets = admin_user.get_related_assets()
|
assets = admin_user.get_related_assets()
|
||||||
hosts = [asset.fullname for asset in assets
|
hosts = []
|
||||||
if asset.is_active and asset.is_unixlike()]
|
for asset in assets:
|
||||||
|
if not asset.is_active:
|
||||||
|
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
if not asset.support_ansible():
|
||||||
|
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
hosts.append(asset)
|
||||||
if not hosts:
|
if not hosts:
|
||||||
return
|
logger.info(_("No assets matched, stop task"))
|
||||||
|
return {}
|
||||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||||
task, created = update_or_create_ansible_task(
|
task, created = update_or_create_ansible_task(
|
||||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
options=const.TASK_OPTIONS, run_as_admin=True, created_by=admin_user.org_id,
|
||||||
)
|
)
|
||||||
result = task.run()
|
result = task.run()
|
||||||
set_admin_user_connectability_info(result, admin_user=admin_user.name)
|
set_admin_user_connectability_info(result, admin_user=admin_user.name)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task
|
@shared_task
|
||||||
@register_as_period_task(interval=3600)
|
@register_as_period_task(interval=3600)
|
||||||
@after_app_ready_start
|
|
||||||
@after_app_shutdown_clean
|
|
||||||
def test_admin_user_connectability_period():
|
def test_admin_user_connectability_period():
|
||||||
"""
|
"""
|
||||||
A period task that update the ansible task period
|
A period task that update the ansible task period
|
||||||
"""
|
"""
|
||||||
if PERIOD_TASK != "on":
|
|
||||||
logger.debug("Period task disabled, test admin user connectability pass")
|
|
||||||
return
|
|
||||||
|
|
||||||
admin_users = AdminUser.objects.all()
|
admin_users = AdminUser.objects.all()
|
||||||
for admin_user in admin_users:
|
for admin_user in admin_users:
|
||||||
task_name = _("Test admin user connectability period: {}".format(admin_user.name))
|
task_name = _("Test admin user connectability period: {}").format(admin_user.name)
|
||||||
# task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name))
|
|
||||||
test_admin_user_connectability_util(admin_user, task_name)
|
test_admin_user_connectability_util(admin_user, task_name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -229,14 +245,25 @@ def test_asset_connectability_util(assets, task_name=None):
|
||||||
if task_name is None:
|
if task_name is None:
|
||||||
task_name = _("Test assets connectability")
|
task_name = _("Test assets connectability")
|
||||||
# task_name = _("测试资产可连接性")
|
# task_name = _("测试资产可连接性")
|
||||||
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
hosts = []
|
||||||
|
for asset in assets:
|
||||||
|
if not asset.is_active:
|
||||||
|
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
if not asset.support_ansible():
|
||||||
|
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
hosts.append(asset)
|
||||||
if not hosts:
|
if not hosts:
|
||||||
logger.info("No hosts, passed")
|
logger.info(_("No assets, task stop"))
|
||||||
return {}
|
return {}
|
||||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||||
|
created_by = assets[0].org_id
|
||||||
task, created = update_or_create_ansible_task(
|
task, created = update_or_create_ansible_task(
|
||||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
|
||||||
)
|
)
|
||||||
result = task.run()
|
result = task.run()
|
||||||
summary = result[1]
|
summary = result[1]
|
||||||
|
@ -250,7 +277,8 @@ def test_asset_connectability_util(assets, task_name=None):
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def test_asset_connectability_manual(asset):
|
def test_asset_connectability_manual(asset):
|
||||||
summary = test_asset_connectability_util([asset])
|
task_name = _("Test assets connectability: {}").format(asset)
|
||||||
|
summary = test_asset_connectability_util([asset], task_name=task_name)
|
||||||
|
|
||||||
if summary.get('dark'):
|
if summary.get('dark'):
|
||||||
return False, summary['dark']
|
return False, summary['dark']
|
||||||
|
@ -267,7 +295,7 @@ def set_system_user_connectablity_info(result, **kwargs):
|
||||||
system_user = kwargs.get("system_user")
|
system_user = kwargs.get("system_user")
|
||||||
if system_user is None:
|
if system_user is None:
|
||||||
system_user = task_name.split(":")[-1]
|
system_user = task_name.split(":")[-1]
|
||||||
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(system_user)
|
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(str(system_user.id))
|
||||||
cache.set(cache_key, summary, CACHE_MAX_TIME)
|
cache.set(cache_key, summary, CACHE_MAX_TIME)
|
||||||
|
|
||||||
|
|
||||||
|
@ -281,19 +309,28 @@ def test_system_user_connectability_util(system_user, assets, task_name):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
# assets = system_user.get_assets()
|
hosts = []
|
||||||
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
|
||||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||||
|
for asset in assets:
|
||||||
|
if not asset.is_active:
|
||||||
|
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
if not asset.support_ansible():
|
||||||
|
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||||
|
logger.info(msg)
|
||||||
|
continue
|
||||||
|
hosts.append(asset)
|
||||||
if not hosts:
|
if not hosts:
|
||||||
logger.info("No hosts, passed")
|
logger.info(_("No assets matched, stop task"))
|
||||||
return {}
|
return {}
|
||||||
task, created = update_or_create_ansible_task(
|
task, created = update_or_create_ansible_task(
|
||||||
task_name, hosts=hosts, tasks=tasks, pattern='all',
|
task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||||
options=const.TASK_OPTIONS,
|
options=const.TASK_OPTIONS,
|
||||||
run_as=system_user.name, created_by="System",
|
run_as=system_user, created_by=system_user.org_id,
|
||||||
)
|
)
|
||||||
result = task.run()
|
result = task.run()
|
||||||
set_system_user_connectablity_info(result, system_user=system_user.name)
|
set_system_user_connectablity_info(result, system_user=system_user)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -313,17 +350,13 @@ def test_system_user_connectability_a_asset(system_user, asset):
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
@register_as_period_task(interval=3600)
|
|
||||||
@after_app_ready_start
|
|
||||||
@after_app_shutdown_clean
|
|
||||||
def test_system_user_connectability_period():
|
def test_system_user_connectability_period():
|
||||||
if PERIOD_TASK != "on":
|
if PERIOD_TASK != "on":
|
||||||
logger.debug("Period task disabled, test system user connectability pass")
|
logger.debug("Period task disabled, test system user connectability pass")
|
||||||
return
|
return
|
||||||
|
|
||||||
system_users = SystemUser.objects.all()
|
system_users = SystemUser.objects.all()
|
||||||
for system_user in system_users:
|
for system_user in system_users:
|
||||||
task_name = _("Test system user connectability period: {}".format(system_user))
|
task_name = _("Test system user connectability period: {}").format(system_user)
|
||||||
# task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
# task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
||||||
test_system_user_connectability_util(system_user, task_name)
|
test_system_user_connectability_util(system_user, task_name)
|
||||||
|
|
||||||
|
@ -374,28 +407,33 @@ def get_push_system_user_tasks(system_user):
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def push_system_user_util(system_users, assets, task_name):
|
def push_system_user_util(system_user, assets, task_name):
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
tasks = []
|
if not system_user.is_need_push():
|
||||||
for system_user in system_users:
|
msg = _("Push system user task skip, auto push not enable or "
|
||||||
if not system_user.is_need_push():
|
"protocol is not ssh: {}").format(system_user.name)
|
||||||
msg = "push system user `{}` passed, may be not auto push or ssh " \
|
logger.info(msg)
|
||||||
"protocol is not ssh".format(system_user.name)
|
return
|
||||||
|
|
||||||
|
tasks = get_push_system_user_tasks(system_user)
|
||||||
|
hosts = []
|
||||||
|
for asset in assets:
|
||||||
|
if not asset.is_active:
|
||||||
|
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
continue
|
continue
|
||||||
tasks.extend(get_push_system_user_tasks(system_user))
|
if not asset.support_ansible():
|
||||||
|
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||||
if not tasks:
|
logger.info(msg)
|
||||||
logger.info("Not tasks, passed")
|
continue
|
||||||
return {}
|
hosts.append(asset)
|
||||||
|
|
||||||
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
|
||||||
if not hosts:
|
if not hosts:
|
||||||
logger.info("Not hosts, passed")
|
logger.info(_("No assets matched, stop task"))
|
||||||
return {}
|
return {}
|
||||||
task, created = update_or_create_ansible_task(
|
task, created = update_or_create_ansible_task(
|
||||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System'
|
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||||
|
created_by=system_user.org_id,
|
||||||
)
|
)
|
||||||
return task.run()
|
return task.run()
|
||||||
|
|
||||||
|
@ -403,24 +441,22 @@ def push_system_user_util(system_users, assets, task_name):
|
||||||
@shared_task
|
@shared_task
|
||||||
def push_system_user_to_assets_manual(system_user):
|
def push_system_user_to_assets_manual(system_user):
|
||||||
assets = system_user.get_assets()
|
assets = system_user.get_assets()
|
||||||
# task_name = "推送系统用户到入资产: {}".format(system_user.name)
|
|
||||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||||
return push_system_user_util([system_user], assets, task_name=task_name)
|
return push_system_user_util(system_user, assets, task_name=task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def push_system_user_a_asset_manual(system_user, asset):
|
def push_system_user_a_asset_manual(system_user, asset):
|
||||||
task_name = _("Push system users to asset: {} => {}").format(
|
task_name = _("Push system users to asset: {} => {}").format(
|
||||||
system_user.name, asset.fullname
|
system_user.name, asset
|
||||||
)
|
)
|
||||||
return push_system_user_util([system_user], [asset], task_name=task_name)
|
return push_system_user_util(system_user, [asset], task_name=task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def push_system_user_to_assets(system_user, assets):
|
def push_system_user_to_assets(system_user, assets):
|
||||||
# task_name = _("推送系统用户到入资产: {}").format(system_user.name)
|
|
||||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||||
return push_system_user_util.delay([system_user], assets, task_name)
|
return push_system_user_util(system_user, assets, task_name)
|
||||||
|
|
||||||
|
|
||||||
# @shared_task
|
# @shared_task
|
||||||
|
|
|
@ -86,6 +86,9 @@ $(document).ready(function () {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
templateSelection: format
|
templateSelection: format
|
||||||
});
|
});
|
||||||
|
$('#id_nodes.select2').select2({
|
||||||
|
closeOnSelect: false
|
||||||
|
});
|
||||||
$("#id_protocol").change(function (){
|
$("#id_protocol").change(function (){
|
||||||
var protocol = $("#id_protocol option:selected").text();
|
var protocol = $("#id_protocol option:selected").text();
|
||||||
var port = 22;
|
var port = 22;
|
||||||
|
|
|
@ -159,7 +159,7 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if asset.is_unixlike %}
|
{% if asset.protocol == 'ssh' %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Refresh hardware' %}:</td>
|
<td>{% trans 'Refresh hardware' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -452,7 +452,7 @@ $(document).ready(function(){
|
||||||
$.each(rows, function (index, obj) {
|
$.each(rows, function (index, obj) {
|
||||||
assets.push(obj.id)
|
assets.push(obj.id)
|
||||||
});
|
});
|
||||||
var _node_id = current_node ? current_node : null;
|
var _node_id = current_node ? current_node.node_id : null;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{% url "assets:asset-export" %}",
|
url: "{% url "assets:asset-export" %}",
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
@ -32,9 +32,7 @@ $(document).ready(function () {
|
||||||
})
|
})
|
||||||
.on('click', '#btn_asset_modal_confirm', function () {
|
.on('click', '#btn_asset_modal_confirm', function () {
|
||||||
var assets = asset_table2.selected;
|
var assets = asset_table2.selected;
|
||||||
$.each(assets, function (id, data) {
|
$('.select2').val(assets).trigger('change');
|
||||||
$('.select2').val(assets).trigger('change');
|
|
||||||
});
|
|
||||||
$("#asset_list_modal").modal('hide');
|
$("#asset_list_modal").modal('hide');
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,12 +35,7 @@ $(document).ready(function () {
|
||||||
})
|
})
|
||||||
.on('click', '#btn_asset_modal_confirm', function () {
|
.on('click', '#btn_asset_modal_confirm', function () {
|
||||||
var assets = asset_table2.selected;
|
var assets = asset_table2.selected;
|
||||||
$('.select2 option:selected').each(function (i, data) {
|
$('#id_assets').val(assets).trigger('change');
|
||||||
assets.push($(data).attr('value'))
|
|
||||||
});
|
|
||||||
$.each(assets, function (id, data) {
|
|
||||||
$('.select2').val(assets).trigger('change');
|
|
||||||
});
|
|
||||||
$("#asset_list_modal").modal('hide');
|
$("#asset_list_modal").modal('hide');
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -71,7 +71,6 @@ function initTable() {
|
||||||
} else {
|
} else {
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
console.log("init table")
|
|
||||||
url = "{% url 'api-perms:my-assets' %}";
|
url = "{% url 'api-perms:my-assets' %}";
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#user_assets_table'),
|
ele: $('#user_assets_table'),
|
||||||
|
@ -108,7 +107,8 @@ function initTable() {
|
||||||
|
|
||||||
function onSelected(event, treeNode) {
|
function onSelected(event, treeNode) {
|
||||||
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
|
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
|
||||||
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
var node_id = treeNode.meta.node.id;
|
||||||
|
url = url.replace("{{ DEFAULT_PK }}", node_id);
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
asset_table.ajax.url(url);
|
asset_table.ajax.url(url);
|
||||||
asset_table.ajax.reload();
|
asset_table.ajax.reload();
|
||||||
|
@ -131,21 +131,10 @@ function initTree() {
|
||||||
};
|
};
|
||||||
|
|
||||||
var zNodes = [];
|
var zNodes = [];
|
||||||
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
|
$.get("{% url 'api-perms:my-nodes-assets-as-tree' %}?show_assets=0", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
|
||||||
value["node_id"] = value["id"];
|
|
||||||
value["id"] = value["tree_id"];
|
|
||||||
if (value["tree_id"] !== value["tree_parent"]) {
|
|
||||||
value["pId"] = value["tree_parent"];
|
|
||||||
}
|
|
||||||
value["isParent"] = value["is_node"];
|
|
||||||
value['name'] = value['value'];
|
|
||||||
});
|
|
||||||
zNodes = data;
|
zNodes = data;
|
||||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||||
var root = zTree.getNodes()[0];
|
|
||||||
zTree.expandNode(root);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,11 @@ from .models import Asset, SystemUser, Label
|
||||||
|
|
||||||
|
|
||||||
def get_assets_by_id_list(id_list):
|
def get_assets_by_id_list(id_list):
|
||||||
return Asset.objects.filter(id__in=id_list)
|
return Asset.objects.filter(id__in=id_list).filter(is_active=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_users_by_id_list(id_list):
|
||||||
|
return SystemUser.objects.filter(id__in=id_list)
|
||||||
|
|
||||||
|
|
||||||
def get_assets_by_fullname_list(hostname_list):
|
def get_assets_by_fullname_list(hostname_list):
|
||||||
|
@ -21,6 +25,11 @@ def get_system_user_by_name(name):
|
||||||
return system_user
|
return system_user
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_user_by_id(id):
|
||||||
|
system_user = get_object_or_none(SystemUser, id=id)
|
||||||
|
return system_user
|
||||||
|
|
||||||
|
|
||||||
class LabelFilter:
|
class LabelFilter:
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
queryset = super().filter_queryset(queryset)
|
queryset = super().filter_queryset(queryset)
|
||||||
|
@ -40,44 +49,3 @@ class LabelFilter:
|
||||||
for kwargs in conditions:
|
for kwargs in conditions:
|
||||||
queryset = queryset.filter(**kwargs)
|
queryset = queryset.filter(**kwargs)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
def test_gateway_connectability(gateway):
|
|
||||||
"""
|
|
||||||
Test system cant connect his assets or not.
|
|
||||||
:param gateway:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
client = paramiko.SSHClient()
|
|
||||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
proxy = paramiko.SSHClient()
|
|
||||||
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
|
|
||||||
try:
|
|
||||||
proxy.connect(gateway.ip, gateway.port,
|
|
||||||
username=gateway.username,
|
|
||||||
password=gateway.password,
|
|
||||||
pkey=gateway.private_key_obj)
|
|
||||||
except(paramiko.AuthenticationException,
|
|
||||||
paramiko.BadAuthenticationType,
|
|
||||||
SSHException) as e:
|
|
||||||
return False, str(e)
|
|
||||||
|
|
||||||
sock = proxy.get_transport().open_channel(
|
|
||||||
'direct-tcpip', ('127.0.0.1', gateway.port), ('127.0.0.1', 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.connect("127.0.0.1", port=gateway.port,
|
|
||||||
username=gateway.username,
|
|
||||||
password=gateway.password,
|
|
||||||
key_filename=gateway.private_key_file,
|
|
||||||
sock=sock,
|
|
||||||
timeout=5
|
|
||||||
)
|
|
||||||
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
|
|
||||||
paramiko.AuthenticationException, TimeoutError) as e:
|
|
||||||
return False, str(e)
|
|
||||||
finally:
|
|
||||||
client.close()
|
|
||||||
return True, None
|
|
||||||
|
|
|
@ -216,6 +216,7 @@ class AssetExportView(LoginRequiredMixin, View):
|
||||||
return HttpResponse('Json object not valid', status=400)
|
return HttpResponse('Json object not valid', status=400)
|
||||||
|
|
||||||
if not assets_id:
|
if not assets_id:
|
||||||
|
print(node_id)
|
||||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
||||||
assets = node.get_all_assets()
|
assets = node.get_all_assets()
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
|
@ -277,7 +278,8 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
v = ''
|
v = ''
|
||||||
elif k == 'domain':
|
elif k == 'domain':
|
||||||
v = get_object_or_none(Domain, name=v)
|
v = get_object_or_none(Domain, name=v)
|
||||||
|
elif k == 'platform':
|
||||||
|
v = v.lower().capitalize()
|
||||||
if v != '':
|
if v != '':
|
||||||
asset_dict[k] = v
|
asset_dict[k] = v
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-06 04:30
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FTPLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('user', models.CharField(max_length=128, verbose_name='User')),
|
||||||
|
('remote_addr', models.CharField(blank=True, max_length=15, null=True, verbose_name='Remote addr')),
|
||||||
|
('asset', models.CharField(max_length=1024, verbose_name='Asset')),
|
||||||
|
('system_user', models.CharField(max_length=128, verbose_name='System user')),
|
||||||
|
('operate', models.CharField(max_length=16, verbose_name='Operate')),
|
||||||
|
('filename', models.CharField(max_length=1024, verbose_name='Filename')),
|
||||||
|
('is_success', models.BooleanField(default=True, verbose_name='Success')),
|
||||||
|
('date_start', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.0.7 on 2018-08-07 03:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('audits', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ftplog',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.0.7 on 2018-08-16 08:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('audits', '0002_ftplog_org_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ftplog',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Generated by Django 2.1 on 2018-09-03 03:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0014_auto_20180816_1652'),
|
||||||
|
('audits', '0003_auto_20180816_1652'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OperateLog',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('user', models.CharField(max_length=128, verbose_name='User')),
|
||||||
|
('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete')], max_length=16, verbose_name='Action')),
|
||||||
|
('resource_type', models.CharField(max_length=64, verbose_name='Resource Type')),
|
||||||
|
('resource', models.CharField(max_length=128, verbose_name='Resource')),
|
||||||
|
('remote_addr', models.CharField(blank=True, max_length=15, null=True, verbose_name='Remote addr')),
|
||||||
|
('datetime', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PasswordChangeLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('user', models.CharField(max_length=128, verbose_name='User')),
|
||||||
|
('change_by', models.CharField(max_length=128, verbose_name='Change by')),
|
||||||
|
('remote_addr', models.CharField(blank=True, max_length=15, null=True, verbose_name='Remote addr')),
|
||||||
|
('datetime', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserLoginLog',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
},
|
||||||
|
bases=('users.loginlog',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -13,4 +13,5 @@ urlpatterns = [
|
||||||
path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
|
path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
|
||||||
path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'),
|
path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'),
|
||||||
path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'),
|
path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'),
|
||||||
|
path('command-execution-log/', views.CommandExecutionListView.as_view(), name='command-execution-log-list'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,6 +7,8 @@ from common.mixins import DatetimeSearchMixin
|
||||||
from common.permissions import AdminUserRequiredMixin
|
from common.permissions import AdminUserRequiredMixin
|
||||||
|
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
|
from ops.views import CommandExecutionListView as UserCommandExecutionListView
|
||||||
|
from users.models import User
|
||||||
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
|
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,7 +124,10 @@ class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListVie
|
||||||
date_from = date_to = None
|
date_from = date_to = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.queryset = super().get_queryset()
|
users = current_org.get_org_users()
|
||||||
|
self.queryset = super().get_queryset().filter(
|
||||||
|
user__in=[user.__str__() for user in users]
|
||||||
|
)
|
||||||
self.user = self.request.GET.get('user')
|
self.user = self.request.GET.get('user')
|
||||||
|
|
||||||
filter_kwargs = dict()
|
filter_kwargs = dict()
|
||||||
|
@ -184,7 +189,7 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Users'),
|
'app': _('Audits'),
|
||||||
'action': _('Login log'),
|
'action': _('Login log'),
|
||||||
'date_from': self.date_from,
|
'date_from': self.date_from,
|
||||||
'date_to': self.date_to,
|
'date_to': self.date_to,
|
||||||
|
@ -193,4 +198,35 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||||
'user_list': self.get_org_users(),
|
'user_list': self.get_org_users(),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionListView(UserCommandExecutionListView):
|
||||||
|
user_id = None
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = self._get_queryset()
|
||||||
|
self.user_id = self.request.GET.get('user')
|
||||||
|
org_users = self.get_user_list()
|
||||||
|
if self.user_id:
|
||||||
|
queryset = queryset.filter(user=self.user_id)
|
||||||
|
else:
|
||||||
|
queryset = queryset.filter(user__in=org_users)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_user_list(self):
|
||||||
|
users = current_org.get_org_users()
|
||||||
|
return users
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Audits'),
|
||||||
|
'action': _('Command execution list'),
|
||||||
|
'date_from': self.date_from,
|
||||||
|
'date_to': self.date_to,
|
||||||
|
'user_list': self.get_user_list(),
|
||||||
|
'keyword': self.keyword,
|
||||||
|
'user_id': self.user_id,
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
# coding:utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
import ldap
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||||
|
from django_auth_ldap.backend import _LDAPUser, LDAPBackend
|
||||||
|
from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
|
||||||
|
|
||||||
|
logger = _LDAPConfig.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPAuthorizationBackend(LDAPBackend):
|
||||||
|
"""
|
||||||
|
Override this class to override _LDAPUser to LDAPUser
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
||||||
|
if password or self.settings.PERMIT_EMPTY_PASSWORD:
|
||||||
|
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
||||||
|
user = self.authenticate_ldap_user(ldap_user, password)
|
||||||
|
else:
|
||||||
|
logger.debug('Rejecting empty password for {}'.format(username))
|
||||||
|
user = None
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
user = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = self.get_user_model().objects.get(pk=user_id)
|
||||||
|
LDAPUser(self, user=user) # This sets user.ldap_user
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_group_permissions(self, user, obj=None):
|
||||||
|
if not hasattr(user, 'ldap_user') and self.settings.AUTHORIZE_ALL_USERS:
|
||||||
|
LDAPUser(self, user=user) # This sets user.ldap_user
|
||||||
|
|
||||||
|
if hasattr(user, 'ldap_user'):
|
||||||
|
permissions = user.ldap_user.get_group_permissions()
|
||||||
|
else:
|
||||||
|
permissions = set()
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
|
||||||
|
def populate_user(self, username):
|
||||||
|
ldap_user = LDAPUser(self, username=username)
|
||||||
|
user = ldap_user.populate_user()
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPUser(_LDAPUser):
|
||||||
|
|
||||||
|
def _search_for_user_dn(self):
|
||||||
|
"""
|
||||||
|
This method was overridden because the AUTH_LDAP_USER_SEARCH
|
||||||
|
configuration in the settings.py file
|
||||||
|
is configured with a `lambda` problem value
|
||||||
|
"""
|
||||||
|
|
||||||
|
user_search_union = [
|
||||||
|
LDAPSearch(
|
||||||
|
USER_SEARCH, ldap.SCOPE_SUBTREE,
|
||||||
|
settings.AUTH_LDAP_SEARCH_FILTER
|
||||||
|
)
|
||||||
|
for USER_SEARCH in str(settings.AUTH_LDAP_SEARCH_OU).split("|")
|
||||||
|
]
|
||||||
|
|
||||||
|
search = LDAPSearchUnion(*user_search_union)
|
||||||
|
if search is None:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance.'
|
||||||
|
)
|
||||||
|
|
||||||
|
results = search.execute(self.connection, {'user': self._username})
|
||||||
|
if results is not None and len(results) == 1:
|
||||||
|
(user_dn, self._user_attrs) = next(iter(results))
|
||||||
|
else:
|
||||||
|
user_dn = None
|
||||||
|
|
||||||
|
return user_dn
|
||||||
|
|
||||||
|
def _populate_user_from_attributes(self):
|
||||||
|
super()._populate_user_from_attributes()
|
||||||
|
if not hasattr(self._user, 'email') or '@' not in self._user.email:
|
||||||
|
email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX)
|
||||||
|
setattr(self._user, 'email', email)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from django.http.request import QueryDict
|
from django.http.request import QueryDict
|
||||||
from django.contrib.auth.signals import user_logged_out
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.contrib.auth.signals import user_logged_out
|
||||||
|
from django_auth_ldap.backend import populate_user
|
||||||
from .openid import client
|
from .openid import client
|
||||||
from .signals import post_create_openid_user
|
from .signals import post_create_openid_user
|
||||||
|
|
||||||
|
@ -31,3 +32,9 @@ def on_post_create_openid_user(sender, user=None, **kwargs):
|
||||||
user.source = user.SOURCE_OPENID
|
user.source = user.SOURCE_OPENID
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(populate_user)
|
||||||
|
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
|
||||||
|
if user and user.name != 'admin':
|
||||||
|
user.source = user.SOURCE_LDAP
|
||||||
|
user.save()
|
||||||
|
|
|
@ -168,13 +168,16 @@ class DjangoSettingsAPI(APIView):
|
||||||
return Response("Not in debug mode")
|
return Response("Not in debug mode")
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
for k, v in settings.__dict__.items():
|
for i in [settings, getattr(settings, '_wrapped')]:
|
||||||
if k and k.isupper():
|
if not i:
|
||||||
try:
|
continue
|
||||||
json.dumps(v)
|
for k, v in i.__dict__.items():
|
||||||
data[k] = v
|
if k and k.isupper():
|
||||||
except (json.JSONDecodeError, TypeError):
|
try:
|
||||||
data[k] = str(v)
|
json.dumps(v)
|
||||||
|
data[k] = v
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
data[k] = str(v)
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,29 +4,28 @@ import json
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from .models import Setting, common_settings
|
from .models import Setting, settings
|
||||||
from .fields import FormDictField, FormEncryptCharField, \
|
from .fields import FormDictField, FormEncryptCharField, \
|
||||||
FormEncryptMixin, FormEncryptDictField
|
FormEncryptMixin
|
||||||
|
|
||||||
|
|
||||||
class BaseForm(forms.Form):
|
class BaseForm(forms.Form):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for name, field in self.fields.items():
|
for name, field in self.fields.items():
|
||||||
db_value = getattr(common_settings, name)
|
value = getattr(settings, name, None)
|
||||||
django_value = getattr(settings, name) if hasattr(settings, name) else None
|
# django_value = getattr(settings, name) if hasattr(settings, name) else None
|
||||||
|
|
||||||
if db_value is None and django_value is None:
|
if value is None: # and django_value is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if db_value is False or db_value:
|
if value is not None:
|
||||||
if isinstance(db_value, dict):
|
if isinstance(value, dict):
|
||||||
db_value = json.dumps(db_value)
|
value = json.dumps(value)
|
||||||
initial_value = db_value
|
initial_value = value
|
||||||
elif django_value is False or django_value:
|
# elif django_value is False or django_value:
|
||||||
initial_value = django_value
|
# initial_value = django_value
|
||||||
else:
|
else:
|
||||||
initial_value = ''
|
initial_value = ''
|
||||||
field.initial = initial_value
|
field.initial = initial_value
|
||||||
|
@ -44,7 +43,7 @@ class BaseForm(forms.Form):
|
||||||
field = self.fields[name]
|
field = self.fields[name]
|
||||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||||
continue
|
continue
|
||||||
if value == getattr(common_settings, name):
|
if value == getattr(settings, name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
encrypted = True if isinstance(field, FormEncryptMixin) else False
|
encrypted = True if isinstance(field, FormEncryptMixin) else False
|
||||||
|
@ -70,7 +69,7 @@ class BasicSettingForm(BaseForm):
|
||||||
)
|
)
|
||||||
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
||||||
max_length=1024, label=_("Email Subject Prefix"),
|
max_length=1024, label=_("Email Subject Prefix"),
|
||||||
initial="[Jumpserver] "
|
help_text=_("Tips: Some word will be intercept by mail provider")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,21 +97,21 @@ class EmailSettingForm(BaseForm):
|
||||||
|
|
||||||
class LDAPSettingForm(BaseForm):
|
class LDAPSettingForm(BaseForm):
|
||||||
AUTH_LDAP_SERVER_URI = forms.CharField(
|
AUTH_LDAP_SERVER_URI = forms.CharField(
|
||||||
label=_("LDAP server"), initial='ldap://localhost:389'
|
label=_("LDAP server"),
|
||||||
)
|
)
|
||||||
AUTH_LDAP_BIND_DN = forms.CharField(
|
AUTH_LDAP_BIND_DN = forms.CharField(
|
||||||
label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org'
|
label=_("Bind DN"),
|
||||||
)
|
)
|
||||||
AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField(
|
AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField(
|
||||||
label=_("Password"), initial='',
|
label=_("Password"),
|
||||||
widget=forms.PasswordInput, required=False
|
widget=forms.PasswordInput, required=False
|
||||||
)
|
)
|
||||||
AUTH_LDAP_SEARCH_OU = forms.CharField(
|
AUTH_LDAP_SEARCH_OU = forms.CharField(
|
||||||
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org',
|
label=_("User OU"),
|
||||||
help_text=_("Use | split User OUs")
|
help_text=_("Use | split User OUs")
|
||||||
)
|
)
|
||||||
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
|
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
|
||||||
label=_("User search filter"), initial='(cn=%(user)s)',
|
label=_("User search filter"),
|
||||||
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
|
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
|
||||||
)
|
)
|
||||||
AUTH_LDAP_USER_ATTR_MAP = FormDictField(
|
AUTH_LDAP_USER_ATTR_MAP = FormDictField(
|
||||||
|
@ -120,14 +119,14 @@ class LDAPSettingForm(BaseForm):
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"User attr map present how to map LDAP user attr to jumpserver, "
|
"User attr map present how to map LDAP user attr to jumpserver, "
|
||||||
"username,name,email is jumpserver attr"
|
"username,name,email is jumpserver attr"
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||||
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
AUTH_LDAP_START_TLS = forms.BooleanField(
|
AUTH_LDAP_START_TLS = forms.BooleanField(
|
||||||
label=_("Use SSL"), initial=False, required=False
|
label=_("Use SSL"), required=False
|
||||||
)
|
)
|
||||||
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False, required=False)
|
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False)
|
||||||
|
|
||||||
|
|
||||||
class TerminalSettingForm(BaseForm):
|
class TerminalSettingForm(BaseForm):
|
||||||
|
@ -173,10 +172,11 @@ class SecuritySettingForm(BaseForm):
|
||||||
initial=30, min_value=5,
|
initial=30, min_value=5,
|
||||||
label=_("No logon interval"),
|
label=_("No logon interval"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Tip :(unit/minute) if the user has failed to log in for a limited "
|
"Tip: (unit/minute) if the user has failed to log in for a limited "
|
||||||
"number of times, no login is allowed during this time interval."
|
"number of times, no login is allowed during this time interval."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# ssh max idle time
|
||||||
SECURITY_MAX_IDLE_TIME = forms.IntegerField(
|
SECURITY_MAX_IDLE_TIME = forms.IntegerField(
|
||||||
initial=30, required=False,
|
initial=30, required=False,
|
||||||
label=_("Connection max idle time"),
|
label=_("Connection max idle time"),
|
||||||
|
@ -185,6 +185,18 @@ class SecuritySettingForm(BaseForm):
|
||||||
'Unit: minute'
|
'Unit: minute'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
# password expiration time
|
||||||
|
SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField(
|
||||||
|
initial=9999, label=_("Password expiration time"),
|
||||||
|
min_value=1,
|
||||||
|
help_text=_(
|
||||||
|
"Tip: (unit: day) "
|
||||||
|
"If the user does not update the password during the time, "
|
||||||
|
"the user password will expire failure;"
|
||||||
|
"The password expiration reminder mail will be automatic sent to the user "
|
||||||
|
"by system within 5 days (daily) before the password expires"
|
||||||
|
)
|
||||||
|
)
|
||||||
# min length
|
# min length
|
||||||
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
|
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
|
||||||
initial=6, label=_("Password minimum length"),
|
initial=6, label=_("Password minimum length"),
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-11 05:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.manager
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Settings',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||||
|
('value', models.TextField(verbose_name='Value')),
|
||||||
|
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
|
||||||
|
('comment', models.TextField(verbose_name='Comment')),
|
||||||
|
],
|
||||||
|
managers=[
|
||||||
|
('configs', django.db.models.manager.Manager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-11 06:07
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('common', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Settings',
|
||||||
|
new_name='Setting',
|
||||||
|
),
|
||||||
|
migrations.AlterModelManagers(
|
||||||
|
name='setting',
|
||||||
|
managers=[
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='setting',
|
||||||
|
table='settings',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-01-22 03:54
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('common', '0002_auto_20180111_1407'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='setting',
|
||||||
|
name='category',
|
||||||
|
field=models.CharField(default='default', max_length=128),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.1 on 2018-09-03 03:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('common', '0003_setting_category'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='setting',
|
||||||
|
name='encrypted',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,11 +1,10 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import ldap
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db.utils import ProgrammingError, OperationalError
|
from django.db.utils import ProgrammingError, OperationalError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
|
||||||
|
|
||||||
from .utils import get_signer
|
from .utils import get_signer
|
||||||
|
|
||||||
|
@ -40,11 +39,7 @@ class Setting(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
instances = self.__class__.objects.filter(name=item)
|
return cache.get(item)
|
||||||
if len(instances) == 1:
|
|
||||||
return instances[0].cleaned_value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cleaned_value(self):
|
def cleaned_value(self):
|
||||||
|
@ -69,6 +64,11 @@ class Setting(models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def save_storage(cls, name, data):
|
def save_storage(cls, name, data):
|
||||||
|
"""
|
||||||
|
:param name: TERMINAL_REPLAY_STORAGE or TERMINAL_COMMAND_STORAGE
|
||||||
|
:param data: {}
|
||||||
|
:return: Setting object
|
||||||
|
"""
|
||||||
obj = cls.objects.filter(name=name).first()
|
obj = cls.objects.filter(name=name).first()
|
||||||
if not obj:
|
if not obj:
|
||||||
obj = cls()
|
obj = cls()
|
||||||
|
@ -84,7 +84,14 @@ class Setting(models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_storage(cls, name, storage_name):
|
def delete_storage(cls, name, storage_name):
|
||||||
obj = cls.objects.get(name=name)
|
"""
|
||||||
|
:param name: TERMINAL_REPLAY_STORAGE or TERMINAL_COMMAND_STORAGE
|
||||||
|
:param storage_name: ""
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
obj = cls.objects.filter(name=name).first()
|
||||||
|
if not obj:
|
||||||
|
return False
|
||||||
value = obj.cleaned_value
|
value = obj.cleaned_value
|
||||||
value.pop(storage_name, '')
|
value.pop(storage_name, '')
|
||||||
obj.cleaned_value = value
|
obj.cleaned_value = value
|
||||||
|
@ -102,22 +109,15 @@ class Setting(models.Model):
|
||||||
|
|
||||||
def refresh_setting(self):
|
def refresh_setting(self):
|
||||||
setattr(settings, self.name, self.cleaned_value)
|
setattr(settings, self.name, self.cleaned_value)
|
||||||
|
|
||||||
if self.name == "AUTH_LDAP":
|
if self.name == "AUTH_LDAP":
|
||||||
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
||||||
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
old_setting = settings.AUTHENTICATION_BACKENDS
|
||||||
|
old_setting.insert(0, settings.AUTH_LDAP_BACKEND)
|
||||||
|
settings.AUTHENTICATION_BACKENDS = old_setting
|
||||||
elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
||||||
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
|
old_setting = settings.AUTHENTICATION_BACKENDS
|
||||||
|
old_setting.remove(settings.AUTH_LDAP_BACKEND)
|
||||||
if self.name == "AUTH_LDAP_SEARCH_FILTER":
|
settings.AUTHENTICATION_BACKENDS = old_setting
|
||||||
settings.AUTH_LDAP_USER_SEARCH_UNION = [
|
|
||||||
LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, settings.AUTH_LDAP_SEARCH_FILTER)
|
|
||||||
for USER_SEARCH in str(settings.AUTH_LDAP_SEARCH_OU).split("|")
|
|
||||||
]
|
|
||||||
settings.AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*settings.AUTH_LDAP_USER_SEARCH_UNION)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "settings"
|
db_table = "settings"
|
||||||
|
|
||||||
|
|
||||||
common_settings = Setting()
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ from rest_framework import permissions
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.http.response import HttpResponseForbidden
|
from django.http.response import HttpResponseForbidden
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
|
|
||||||
|
@ -96,3 +97,12 @@ class SuperUserRequiredMixin(UserPassesTestMixin):
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
if self.request.user.is_authenticated and self.request.user.is_superuser:
|
if self.request.user.is_authenticated and self.request.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class WithBootstrapToken(permissions.BasePermission):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
authorization = request.META.get('HTTP_AUTHORIZATION', '')
|
||||||
|
if not authorization:
|
||||||
|
return False
|
||||||
|
request_bootstrap_token = authorization.split()[-1]
|
||||||
|
return settings.BOOTSTRAP_TOKEN == request_bootstrap_token
|
||||||
|
|
|
@ -4,4 +4,3 @@
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
|
|
||||||
django_ready = Signal()
|
django_ready = Signal()
|
||||||
ldap_auth_enable = Signal(providing_args=["enabled"])
|
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
#
|
#
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import post_save, pre_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
from django.conf import settings
|
from django.conf import LazySettings, empty
|
||||||
from django.db.utils import ProgrammingError, OperationalError
|
from django.db.utils import ProgrammingError, OperationalError
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
from jumpserver.utils import current_request
|
from jumpserver.utils import current_request
|
||||||
from .models import Setting
|
from .models import Setting
|
||||||
from .utils import get_logger
|
from .utils import get_logger
|
||||||
from .signals import django_ready, ldap_auth_enable
|
from .signals import django_ready
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
@ -25,27 +26,47 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
||||||
def refresh_all_settings_on_django_ready(sender, **kwargs):
|
def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||||
logger.debug("Receive django ready signal")
|
logger.debug("Receive django ready signal")
|
||||||
logger.debug(" - fresh all settings")
|
logger.debug(" - fresh all settings")
|
||||||
|
CACHE_KEY_PREFIX = '_SETTING_'
|
||||||
|
|
||||||
|
def monkey_patch_getattr(self, name):
|
||||||
|
key = CACHE_KEY_PREFIX + name
|
||||||
|
cached = cache.get(key)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup(name)
|
||||||
|
val = getattr(self._wrapped, name)
|
||||||
|
# self.__dict__[name] = val # Never set it
|
||||||
|
return val
|
||||||
|
|
||||||
|
def monkey_patch_setattr(self, name, value):
|
||||||
|
key = CACHE_KEY_PREFIX + name
|
||||||
|
cache.set(key, value, None)
|
||||||
|
if name == '_wrapped':
|
||||||
|
self.__dict__.clear()
|
||||||
|
else:
|
||||||
|
self.__dict__.pop(name, None)
|
||||||
|
super(LazySettings, self).__setattr__(name, value)
|
||||||
|
|
||||||
|
def monkey_patch_delattr(self, name):
|
||||||
|
super(LazySettings, self).__delattr__(name)
|
||||||
|
self.__dict__.pop(name, None)
|
||||||
|
key = CACHE_KEY_PREFIX + name
|
||||||
|
cache.delete(key)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
LazySettings.__getattr__ = monkey_patch_getattr
|
||||||
|
LazySettings.__setattr__ = monkey_patch_setattr
|
||||||
|
LazySettings.__delattr__ = monkey_patch_delattr
|
||||||
Setting.refresh_all_settings()
|
Setting.refresh_all_settings()
|
||||||
except (ProgrammingError, OperationalError):
|
except (ProgrammingError, OperationalError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@receiver(ldap_auth_enable, dispatch_uid="my_unique_identifier")
|
|
||||||
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
|
|
||||||
if enabled:
|
|
||||||
logger.debug("Enable LDAP auth")
|
|
||||||
if settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
|
||||||
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.debug("Disable LDAP auth")
|
|
||||||
if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
|
||||||
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, dispatch_uid="my_unique_identifier")
|
@receiver(pre_save, dispatch_uid="my_unique_identifier")
|
||||||
def on_create_set_created_by(sender, instance=None, **kwargs):
|
def on_create_set_created_by(sender, instance=None, **kwargs):
|
||||||
|
if getattr(instance, '_ignore_auto_created_by', False) is True:
|
||||||
|
return
|
||||||
if hasattr(instance, 'created_by') and not instance.created_by:
|
if hasattr(instance, 'created_by') and not instance.created_by:
|
||||||
if current_request and current_request.user.is_authenticated:
|
if current_request and current_request.user.is_authenticated:
|
||||||
instance.created_by = current_request.user.name
|
instance.created_by = current_request.user.name
|
||||||
|
|
|
@ -3,7 +3,6 @@ from django.conf import settings
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from .utils import get_logger
|
from .utils import get_logger
|
||||||
from .models import Setting
|
from .models import Setting
|
||||||
from common.models import common_settings
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -23,13 +22,9 @@ def send_mail_async(*args, **kwargs):
|
||||||
Example:
|
Example:
|
||||||
send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None)
|
send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None)
|
||||||
"""
|
"""
|
||||||
configs = Setting.objects.filter(name__startswith='EMAIL')
|
|
||||||
for config in configs:
|
|
||||||
setattr(settings, config.name, config.cleaned_value)
|
|
||||||
|
|
||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
args = list(args)
|
args = list(args)
|
||||||
args[0] = common_settings.EMAIL_SUBJECT_PREFIX + args[0]
|
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
|
||||||
args.insert(2, settings.EMAIL_HOST_USER)
|
args.insert(2, settings.EMAIL_HOST_USER)
|
||||||
args = tuple(args)
|
args = tuple(args)
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<h3>{% trans "User login settings" %}</h3>
|
<h3>{% trans "Security setting" %}</h3>
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% if forloop.counter == 5 %}
|
{% if forloop.counter == 6 %}
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans "Password check rule" %}</h3>
|
<h3>{% trans "Password check rule" %}</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -87,12 +87,12 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ name }}</td>
|
<td>{{ name }}</td>
|
||||||
<td>{{ setting.TYPE }}</td>
|
<td>{{ setting.TYPE }}</td>
|
||||||
<td><a class="btn btn-xs btn-danger m-l-xs btn-del-command" data-name="{{ name }}">{% trans 'Delete' %}</a></td>
|
<td><a class="btn btn-xs btn-danger m-l-xs btn-del-command" {% if setting.TYPE == 'server' and name == 'default' %} disabled {% endif %} data-name="{{ name }}">{% trans 'Delete' %}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<a href="{% url 'common:command-storage-create' %}" class="btn btn-primary">{% trans 'Add' %}</a>
|
<a href="{% url 'common:command-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a>
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans "Replay storage" %}</h3>
|
<h3>{% trans "Replay storage" %}</h3>
|
||||||
|
@ -109,12 +109,12 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ name }}</td>
|
<td>{{ name }}</td>
|
||||||
<td>{{ setting.TYPE }}</td>
|
<td>{{ setting.TYPE }}</td>
|
||||||
<td><a class="btn btn-xs btn-danger m-l-xs btn-del-replay" data-name="{{ name }}">{% trans 'Delete' %}</a></td>
|
<td><a class="btn btn-xs btn-danger m-l-xs btn-del-replay" {% if setting.TYPE == 'server' and name == 'default' %} disabled {% endif %} data-name="{{ name }}">{% trans 'Delete' %}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<a href="{% url 'common:replay-storage-create' %}" class="btn btn-primary">{% trans 'Add' %}</a>
|
<a href="{% url 'common:replay-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a>
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -151,7 +151,7 @@ function deleteStorage($this, the_url){
|
||||||
toastr.success("{% trans 'Delete succeed' %}");
|
toastr.success("{% trans 'Delete succeed' %}");
|
||||||
};
|
};
|
||||||
var error = function(){
|
var error = function(){
|
||||||
toastr.error("{% trans 'Delete failed' %}}");
|
toastr.error("{% trans 'Delete failed' %}");
|
||||||
};
|
};
|
||||||
ajaxAPI(the_url, JSON.stringify(data), success, error, method);
|
ajaxAPI(the_url, JSON.stringify(data), success, error, method);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,3 +111,13 @@ def sort(data):
|
||||||
@register.filter
|
@register.filter
|
||||||
def subtract(value, arg):
|
def subtract(value, arg):
|
||||||
return value - arg
|
return value - arg
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def state_show(state):
|
||||||
|
success = '<i class ="fa fa-check text-navy"> </i>'
|
||||||
|
failed = '<i class ="fa fa-times text-danger"> </i>'
|
||||||
|
if state:
|
||||||
|
return success
|
||||||
|
else:
|
||||||
|
return failed
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class TreeNode:
|
||||||
|
id = ""
|
||||||
|
name = ""
|
||||||
|
comment = ""
|
||||||
|
title = ""
|
||||||
|
isParent = False
|
||||||
|
pId = ""
|
||||||
|
open = False
|
||||||
|
iconSkin = ""
|
||||||
|
meta = {}
|
||||||
|
|
||||||
|
_tree = None
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def root(cls):
|
||||||
|
return cls(id="#", name='Root', title='Root', isParent=True, open=True)
|
||||||
|
|
||||||
|
def get_parent(self):
|
||||||
|
return self._tree.get_node(self.pId)
|
||||||
|
|
||||||
|
def get_parents(self):
|
||||||
|
parent = self.get_parent()
|
||||||
|
if parent == self._tree.root:
|
||||||
|
return []
|
||||||
|
parents = [parent]
|
||||||
|
parents.extend(parent.get_parents())
|
||||||
|
return parents
|
||||||
|
|
||||||
|
def add_child(self, child):
|
||||||
|
self._tree.add_node(child, self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '<{}: {}>'.format(self.id, self.name)
|
||||||
|
|
||||||
|
__repr__ = __str__
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if self.isParent and not other.isParent:
|
||||||
|
return False
|
||||||
|
return self.id > other.id
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.id == other.id
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if self.isParent and not other.isParent:
|
||||||
|
return True
|
||||||
|
return self.id < other.id
|
||||||
|
|
||||||
|
|
||||||
|
class Tree:
|
||||||
|
def __init__(self):
|
||||||
|
self.nodes = {}
|
||||||
|
self.root = TreeNode.root()
|
||||||
|
self.root._tree = self
|
||||||
|
|
||||||
|
def add_node(self, node, parent=None):
|
||||||
|
node._tree = self
|
||||||
|
|
||||||
|
if not parent:
|
||||||
|
parent = self.root
|
||||||
|
if parent.id not in self.nodes and parent != self.root:
|
||||||
|
raise ValueError("Parent not in tree")
|
||||||
|
elif node in parent.get_parents():
|
||||||
|
raise ValueError("Parent must not be node parent")
|
||||||
|
node.pId = parent.id
|
||||||
|
parent.isParent = True
|
||||||
|
self.nodes[node.id] = node
|
||||||
|
|
||||||
|
def get_nodes(self):
|
||||||
|
return sorted(self.nodes.values())
|
||||||
|
|
||||||
|
def get_node(self, tid):
|
||||||
|
return self.nodes.get(tid) or TreeNode.root()
|
||||||
|
|
||||||
|
|
||||||
|
class TreeNodeSerializer(serializers.Serializer):
|
||||||
|
id = serializers.CharField(max_length=128)
|
||||||
|
name = serializers.CharField(max_length=128)
|
||||||
|
title = serializers.CharField(max_length=128)
|
||||||
|
pId = serializers.CharField(max_length=128)
|
||||||
|
isParent = serializers.BooleanField(default=False)
|
||||||
|
open = serializers.BooleanField(default=False)
|
||||||
|
iconSkin = serializers.CharField(max_length=128, allow_blank=True)
|
||||||
|
meta = serializers.JSONField()
|
|
@ -13,5 +13,5 @@ urlpatterns = [
|
||||||
path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'),
|
path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'),
|
||||||
path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'),
|
path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'),
|
||||||
path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
|
path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
|
||||||
# path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -37,8 +37,7 @@ def reverse(view_name, urlconf=None, args=None, kwargs=None,
|
||||||
kwargs=kwargs, current_app=current_app)
|
kwargs=kwargs, current_app=current_app)
|
||||||
|
|
||||||
if external:
|
if external:
|
||||||
from common.models import common_settings
|
site_url = settings.SITE_URL
|
||||||
site_url = common_settings.SITE_URL or settings.SITE_URL
|
|
||||||
url = site_url.strip('/') + url
|
url = site_url.strip('/') + url
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
@ -389,53 +388,18 @@ def get_request_ip(request):
|
||||||
return login_ip
|
return login_ip
|
||||||
|
|
||||||
|
|
||||||
def get_command_storage_or_create_default_storage():
|
def get_command_storage_setting():
|
||||||
from common.models import common_settings, Setting
|
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
||||||
name = 'TERMINAL_COMMAND_STORAGE'
|
value = settings.TERMINAL_COMMAND_STORAGE
|
||||||
default = {'default': {'TYPE': 'server'}}
|
value.update(default)
|
||||||
try:
|
return value
|
||||||
command_storage = common_settings.TERMINAL_COMMAND_STORAGE
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
if command_storage is None:
|
|
||||||
obj = Setting()
|
|
||||||
obj.name = name
|
|
||||||
obj.encrypted = True
|
|
||||||
obj.cleaned_value = default
|
|
||||||
obj.save()
|
|
||||||
if isinstance(command_storage, dict) and not command_storage:
|
|
||||||
obj = Setting.objects.get(name=name)
|
|
||||||
value = obj.cleaned_value
|
|
||||||
value.update(default)
|
|
||||||
obj.cleaned_value = value
|
|
||||||
obj.save()
|
|
||||||
command_storage = common_settings.TERMINAL_COMMAND_STORAGE
|
|
||||||
return command_storage
|
|
||||||
|
|
||||||
|
|
||||||
def get_replay_storage_or_create_default_storage():
|
def get_replay_storage_setting():
|
||||||
from common.models import common_settings, Setting
|
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
|
||||||
name = 'TERMINAL_REPLAY_STORAGE'
|
value = settings.TERMINAL_REPLAY_STORAGE
|
||||||
default = {'default': {'TYPE': 'server'}}
|
value.update(default)
|
||||||
try:
|
return value
|
||||||
replay_storage = common_settings.TERMINAL_REPLAY_STORAGE
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
if replay_storage is None:
|
|
||||||
obj = Setting()
|
|
||||||
obj.name = name
|
|
||||||
obj.encrypted = True
|
|
||||||
obj.cleaned_value = default
|
|
||||||
obj.save()
|
|
||||||
replay_storage = common_settings.TERMINAL_REPLAY_STORAGE
|
|
||||||
if isinstance(replay_storage, dict) and not replay_storage:
|
|
||||||
obj = Setting.objects.get(name=name)
|
|
||||||
value = obj.cleaned_value
|
|
||||||
value.update(default)
|
|
||||||
obj.cleaned_value = value
|
|
||||||
obj.save()
|
|
||||||
replay_storage = common_settings.TERMINAL_REPLAY_STORAGE
|
|
||||||
return replay_storage
|
|
||||||
|
|
||||||
|
|
||||||
class TeeObj:
|
class TeeObj:
|
||||||
|
|
|
@ -2,13 +2,10 @@ from django.views.generic import TemplateView
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from common.models import common_settings
|
|
||||||
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
|
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
|
||||||
TerminalSettingForm, SecuritySettingForm
|
TerminalSettingForm, SecuritySettingForm
|
||||||
from common.permissions import SuperUserRequiredMixin
|
from common.permissions import SuperUserRequiredMixin
|
||||||
from .signals import ldap_auth_enable
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +26,7 @@ class BasicSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
form = self.form_class(request.POST)
|
form = self.form_class(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
msg = _("Update setting successfully, please restart program")
|
msg = _("Update setting successfully")
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect('settings:basic-setting')
|
return redirect('settings:basic-setting')
|
||||||
else:
|
else:
|
||||||
|
@ -55,7 +52,7 @@ class EmailSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
form = self.form_class(request.POST)
|
form = self.form_class(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
msg = _("Update setting successfully, please restart program")
|
msg = _("Update setting successfully")
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect('settings:email-setting')
|
return redirect('settings:email-setting')
|
||||||
else:
|
else:
|
||||||
|
@ -81,9 +78,7 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
form = self.form_class(request.POST)
|
form = self.form_class(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
if "AUTH_LDAP" in form.cleaned_data:
|
msg = _("Update setting successfully")
|
||||||
ldap_auth_enable.send(sender=self.__class__, enabled=form.cleaned_data["AUTH_LDAP"])
|
|
||||||
msg = _("Update setting successfully, please restart program")
|
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect('settings:ldap-setting')
|
return redirect('settings:ldap-setting')
|
||||||
else:
|
else:
|
||||||
|
@ -97,8 +92,8 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
template_name = "common/terminal_setting.html"
|
template_name = "common/terminal_setting.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
command_storage = utils.get_command_storage_or_create_default_storage()
|
command_storage = utils.get_command_storage_setting()
|
||||||
replay_storage = utils.get_replay_storage_or_create_default_storage()
|
replay_storage = utils.get_replay_storage_setting()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'app': _('Settings'),
|
'app': _('Settings'),
|
||||||
|
@ -114,7 +109,7 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
form = self.form_class(request.POST)
|
form = self.form_class(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
msg = _("Update setting successfully, please restart program")
|
msg = _("Update setting successfully")
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect('settings:terminal-setting')
|
return redirect('settings:terminal-setting')
|
||||||
else:
|
else:
|
||||||
|
@ -164,7 +159,7 @@ class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
form = self.form_class(request.POST)
|
form = self.form_class(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
msg = _("Update setting successfully, please restart program")
|
msg = _("Update setting successfully")
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect('settings:security-setting')
|
return redirect('settings:security-setting')
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import errno
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def import_string(dotted_path):
|
||||||
|
try:
|
||||||
|
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||||
|
except ValueError as err:
|
||||||
|
raise ImportError("%s doesn't look like a module path" % dotted_path) from err
|
||||||
|
|
||||||
|
module = import_module(module_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return getattr(module, class_name)
|
||||||
|
except AttributeError as err:
|
||||||
|
raise ImportError('Module "%s" does not define a "%s" attribute/class' % (
|
||||||
|
module_path, class_name)
|
||||||
|
) from err
|
||||||
|
|
||||||
|
|
||||||
|
class Config(dict):
|
||||||
|
"""Works exactly like a dict but provides ways to fill it from files
|
||||||
|
or special dictionaries. There are two common patterns to populate the
|
||||||
|
config.
|
||||||
|
|
||||||
|
Either you can fill the config from a config file::
|
||||||
|
|
||||||
|
app.config.from_pyfile('yourconfig.cfg')
|
||||||
|
|
||||||
|
Or alternatively you can define the configuration options in the
|
||||||
|
module that calls :meth:`from_object` or provide an import path to
|
||||||
|
a module that should be loaded. It is also possible to tell it to
|
||||||
|
use the same module and with that provide the configuration values
|
||||||
|
just before the call::
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
SECRET_KEY = 'development key'
|
||||||
|
app.config.from_object(__name__)
|
||||||
|
|
||||||
|
In both cases (loading from any Python file or loading from modules),
|
||||||
|
only uppercase keys are added to the config. This makes it possible to use
|
||||||
|
lowercase values in the config file for temporary values that are not added
|
||||||
|
to the config or to define the config keys in the same file that implements
|
||||||
|
the application.
|
||||||
|
|
||||||
|
Probably the most interesting way to load configurations is from an
|
||||||
|
environment variable pointing to a file::
|
||||||
|
|
||||||
|
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||||
|
|
||||||
|
In this case before launching the application you have to set this
|
||||||
|
environment variable to the file you want to use. On Linux and OS X
|
||||||
|
use the export statement::
|
||||||
|
|
||||||
|
export YOURAPPLICATION_SETTINGS='/path/to/config/file'
|
||||||
|
|
||||||
|
On windows use `set` instead.
|
||||||
|
|
||||||
|
:param root_path: path to which files are read relative from. When the
|
||||||
|
config object is created by the application, this is
|
||||||
|
the application's :attr:`~flask.Flask.root_path`.
|
||||||
|
:param defaults: an optional dictionary of default values
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, root_path=None, defaults=None):
|
||||||
|
self.defaults = defaults or {}
|
||||||
|
self.root_path = root_path
|
||||||
|
super().__init__({})
|
||||||
|
|
||||||
|
def from_envvar(self, variable_name, silent=False):
|
||||||
|
"""Loads a configuration from an environment variable pointing to
|
||||||
|
a configuration file. This is basically just a shortcut with nicer
|
||||||
|
error messages for this line of code::
|
||||||
|
|
||||||
|
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
|
||||||
|
|
||||||
|
:param variable_name: name of the environment variable
|
||||||
|
:param silent: set to ``True`` if you want silent failure for missing
|
||||||
|
files.
|
||||||
|
:return: bool. ``True`` if able to load config, ``False`` otherwise.
|
||||||
|
"""
|
||||||
|
rv = os.environ.get(variable_name)
|
||||||
|
if not rv:
|
||||||
|
if silent:
|
||||||
|
return False
|
||||||
|
raise RuntimeError('The environment variable %r is not set '
|
||||||
|
'and as such configuration could not be '
|
||||||
|
'loaded. Set this variable and make it '
|
||||||
|
'point to a configuration file' %
|
||||||
|
variable_name)
|
||||||
|
return self.from_pyfile(rv, silent=silent)
|
||||||
|
|
||||||
|
def from_pyfile(self, filename, silent=False):
|
||||||
|
"""Updates the values in the config from a Python file. This function
|
||||||
|
behaves as if the file was imported as module with the
|
||||||
|
:meth:`from_object` function.
|
||||||
|
|
||||||
|
:param filename: the filename of the config. This can either be an
|
||||||
|
absolute filename or a filename relative to the
|
||||||
|
root path.
|
||||||
|
:param silent: set to ``True`` if you want silent failure for missing
|
||||||
|
files.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
`silent` parameter.
|
||||||
|
"""
|
||||||
|
if self.root_path:
|
||||||
|
filename = os.path.join(self.root_path, filename)
|
||||||
|
d = types.ModuleType('config')
|
||||||
|
d.__file__ = filename
|
||||||
|
try:
|
||||||
|
with open(filename, mode='rb') as config_file:
|
||||||
|
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
||||||
|
except IOError as e:
|
||||||
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
|
return False
|
||||||
|
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
||||||
|
raise
|
||||||
|
self.from_object(d)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def from_object(self, obj):
|
||||||
|
"""Updates the values from the given object. An object can be of one
|
||||||
|
of the following two types:
|
||||||
|
|
||||||
|
- a string: in this case the object with that name will be imported
|
||||||
|
- an actual object reference: that object is used directly
|
||||||
|
|
||||||
|
Objects are usually either modules or classes. :meth:`from_object`
|
||||||
|
loads only the uppercase attributes of the module/class. A ``dict``
|
||||||
|
object will not work with :meth:`from_object` because the keys of a
|
||||||
|
``dict`` are not attributes of the ``dict`` class.
|
||||||
|
|
||||||
|
Example of module-based configuration::
|
||||||
|
|
||||||
|
app.config.from_object('yourapplication.default_config')
|
||||||
|
from yourapplication import default_config
|
||||||
|
app.config.from_object(default_config)
|
||||||
|
|
||||||
|
You should not use this function to load the actual configuration but
|
||||||
|
rather configuration defaults. The actual config should be loaded
|
||||||
|
with :meth:`from_pyfile` and ideally from a location not within the
|
||||||
|
package because the package might be installed system wide.
|
||||||
|
|
||||||
|
See :ref:`config-dev-prod` for an example of class-based configuration
|
||||||
|
using :meth:`from_object`.
|
||||||
|
|
||||||
|
:param obj: an import name or object
|
||||||
|
"""
|
||||||
|
if isinstance(obj, str):
|
||||||
|
obj = import_string(obj)
|
||||||
|
for key in dir(obj):
|
||||||
|
if key.isupper():
|
||||||
|
self[key] = getattr(obj, key)
|
||||||
|
|
||||||
|
def from_json(self, filename, silent=False):
|
||||||
|
"""Updates the values in the config from a JSON file. This function
|
||||||
|
behaves as if the JSON object was a dictionary and passed to the
|
||||||
|
:meth:`from_mapping` function.
|
||||||
|
|
||||||
|
:param filename: the filename of the JSON file. This can either be an
|
||||||
|
absolute filename or a filename relative to the
|
||||||
|
root path.
|
||||||
|
:param silent: set to ``True`` if you want silent failure for missing
|
||||||
|
files.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
if self.root_path:
|
||||||
|
filename = os.path.join(self.root_path, filename)
|
||||||
|
try:
|
||||||
|
with open(filename) as json_file:
|
||||||
|
obj = json.loads(json_file.read())
|
||||||
|
except IOError as e:
|
||||||
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
|
return False
|
||||||
|
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
||||||
|
raise
|
||||||
|
return self.from_mapping(obj)
|
||||||
|
|
||||||
|
def from_yaml(self, filename, silent=False):
|
||||||
|
if self.root_path:
|
||||||
|
filename = os.path.join(self.root_path, filename)
|
||||||
|
try:
|
||||||
|
with open(filename) as json_file:
|
||||||
|
obj = yaml.load(json_file)
|
||||||
|
except IOError as e:
|
||||||
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
|
return False
|
||||||
|
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
||||||
|
raise
|
||||||
|
return self.from_mapping(obj)
|
||||||
|
|
||||||
|
def from_mapping(self, *mapping, **kwargs):
|
||||||
|
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||||
|
keys.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
mappings = []
|
||||||
|
if len(mapping) == 1:
|
||||||
|
if hasattr(mapping[0], 'items'):
|
||||||
|
mappings.append(mapping[0].items())
|
||||||
|
else:
|
||||||
|
mappings.append(mapping[0])
|
||||||
|
elif len(mapping) > 1:
|
||||||
|
raise TypeError(
|
||||||
|
'expected at most 1 positional argument, got %d' % len(mapping)
|
||||||
|
)
|
||||||
|
mappings.append(kwargs.items())
|
||||||
|
for mapping in mappings:
|
||||||
|
for (key, value) in mapping:
|
||||||
|
if key.isupper():
|
||||||
|
self[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
|
||||||
|
"""Returns a dictionary containing a subset of configuration options
|
||||||
|
that match the specified namespace/prefix. Example usage::
|
||||||
|
|
||||||
|
app.config['IMAGE_STORE_TYPE'] = 'fs'
|
||||||
|
app.config['IMAGE_STORE_PATH'] = '/var/app/images'
|
||||||
|
app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
|
||||||
|
image_store_config = app.config.get_namespace('IMAGE_STORE_')
|
||||||
|
|
||||||
|
The resulting dictionary `image_store_config` would look like::
|
||||||
|
|
||||||
|
{
|
||||||
|
'types': 'fs',
|
||||||
|
'path': '/var/app/images',
|
||||||
|
'base_url': 'http://img.website.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
This is often useful when configuration options map directly to
|
||||||
|
keyword arguments in functions or class constructors.
|
||||||
|
|
||||||
|
:param namespace: a configuration namespace
|
||||||
|
:param lowercase: a flag indicating if the keys of the resulting
|
||||||
|
dictionary should be lowercase
|
||||||
|
:param trim_namespace: a flag indicating if the keys of the resulting
|
||||||
|
dictionary should not include the namespace
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
rv = {}
|
||||||
|
for k, v in self.items():
|
||||||
|
if not k.startswith(namespace):
|
||||||
|
continue
|
||||||
|
if trim_namespace:
|
||||||
|
key = k[len(namespace):]
|
||||||
|
else:
|
||||||
|
key = k
|
||||||
|
if lowercase:
|
||||||
|
key = key.lower()
|
||||||
|
rv[key] = v
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
try:
|
||||||
|
value = super().__getitem__(item)
|
||||||
|
except KeyError:
|
||||||
|
value = None
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
value = os.environ.get(item, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
return self.defaults.get(item)
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return self.__getitem__(item)
|
||||||
|
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'SECRET_KEY': '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x',
|
||||||
|
'BOOTSTRAP_TOKEN': 'PleaseChangeMe',
|
||||||
|
'DEBUG': True,
|
||||||
|
'SITE_URL': 'http://localhost',
|
||||||
|
'LOG_LEVEL': 'DEBUG',
|
||||||
|
'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'),
|
||||||
|
'DB_ENGINE': 'mysql',
|
||||||
|
'DB_NAME': 'jumpserver',
|
||||||
|
'DB_HOST': '127.0.0.1',
|
||||||
|
'DB_PORT': 3306,
|
||||||
|
'DB_USER': 'root',
|
||||||
|
'DB_PASSWORD': '',
|
||||||
|
'REDIS_HOST': '127.0.0.1',
|
||||||
|
'REDIS_PORT': 6379,
|
||||||
|
'REDIS_PASSWORD': '',
|
||||||
|
'REDIS_DB_CELERY': 3,
|
||||||
|
'REDIS_DB_CACHE': 4,
|
||||||
|
'CAPTCHA_TEST_MODE': None,
|
||||||
|
'TOKEN_EXPIRATION': 3600,
|
||||||
|
'DISPLAY_PER_PAGE': 25,
|
||||||
|
'DEFAULT_EXPIRED_YEARS': 70,
|
||||||
|
'SESSION_COOKIE_DOMAIN': None,
|
||||||
|
'CSRF_COOKIE_DOMAIN': None,
|
||||||
|
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||||
|
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||||
|
'AUTH_OPENID': False,
|
||||||
|
'EMAIL_SUFFIX': 'jumpserver.org'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_user_config():
|
||||||
|
sys.path.insert(0, PROJECT_DIR)
|
||||||
|
config = Config(PROJECT_DIR, defaults)
|
||||||
|
try:
|
||||||
|
from config import config as c
|
||||||
|
config.from_object(c)
|
||||||
|
except ImportError:
|
||||||
|
msg = """
|
||||||
|
|
||||||
|
Error: No config file found.
|
||||||
|
|
||||||
|
You can run `cp config_example.py config.py`, and edit it.
|
||||||
|
"""
|
||||||
|
raise ImportError(msg)
|
||||||
|
return config
|
|
@ -14,27 +14,15 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import ldap
|
import ldap
|
||||||
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
# from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
from .conf import load_user_config
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||||
|
CONFIG = load_user_config()
|
||||||
sys.path.append(PROJECT_DIR)
|
|
||||||
|
|
||||||
# Import project config setting
|
|
||||||
try:
|
|
||||||
from config import config as CONFIG
|
|
||||||
except ImportError:
|
|
||||||
msg = """
|
|
||||||
|
|
||||||
Error: No config file found.
|
|
||||||
|
|
||||||
You can run `cp config_example.py config.py`, and edit it.
|
|
||||||
"""
|
|
||||||
raise ImportError(msg)
|
|
||||||
# CONFIG = type('_', (), {'__getattr__': lambda arg1, arg2: None})()
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
||||||
|
@ -42,16 +30,19 @@ except ImportError:
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = CONFIG.SECRET_KEY
|
SECRET_KEY = CONFIG.SECRET_KEY
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the token secret, remove it if all coco, guacamole ok
|
||||||
|
BOOTSTRAP_TOKEN = CONFIG.BOOTSTRAP_TOKEN
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = CONFIG.DEBUG or False
|
DEBUG = CONFIG.DEBUG
|
||||||
|
|
||||||
# Absolute url for some case, for example email link
|
# Absolute url for some case, for example email link
|
||||||
SITE_URL = CONFIG.SITE_URL or 'http://localhost'
|
SITE_URL = CONFIG.SITE_URL
|
||||||
|
|
||||||
# LOG LEVEL
|
# LOG LEVEL
|
||||||
LOG_LEVEL = 'DEBUG' if DEBUG else CONFIG.LOG_LEVEL or 'WARNING'
|
LOG_LEVEL = CONFIG.LOG_LEVEL
|
||||||
|
|
||||||
ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS or []
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
@ -152,9 +143,10 @@ TEMPLATES = [
|
||||||
LOGIN_REDIRECT_URL = reverse_lazy('index')
|
LOGIN_REDIRECT_URL = reverse_lazy('index')
|
||||||
LOGIN_URL = reverse_lazy('users:login')
|
LOGIN_URL = reverse_lazy('users:login')
|
||||||
|
|
||||||
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN or None
|
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN
|
||||||
CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN or None
|
CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN
|
||||||
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE or 3600 * 24
|
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE
|
||||||
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
||||||
|
|
||||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||||
# Database
|
# Database
|
||||||
|
@ -317,13 +309,13 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/'
|
||||||
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]
|
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]
|
||||||
|
|
||||||
# Email config
|
# Email config
|
||||||
EMAIL_HOST = CONFIG.EMAIL_HOST
|
EMAIL_HOST = 'smtp.jumpserver.org'
|
||||||
EMAIL_PORT = CONFIG.EMAIL_PORT
|
EMAIL_PORT = 25
|
||||||
EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER
|
EMAIL_HOST_USER = 'noreply@jumpserver.org'
|
||||||
EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD
|
EMAIL_HOST_PASSWORD = ''
|
||||||
EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL
|
EMAIL_USE_SSL = False
|
||||||
EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS
|
EMAIL_USE_TLS = False
|
||||||
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX or ''
|
EMAIL_SUBJECT_PREFIX = '[JMS] '
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
|
@ -362,31 +354,29 @@ AUTH_USER_MODEL = 'users.User'
|
||||||
FILE_UPLOAD_PERMISSIONS = 0o644
|
FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
|
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
|
||||||
|
|
||||||
|
# OTP settings
|
||||||
|
OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME
|
||||||
|
|
||||||
# Auth LDAP settings
|
# Auth LDAP settings
|
||||||
AUTH_LDAP = CONFIG.AUTH_LDAP
|
AUTH_LDAP = False
|
||||||
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
|
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
|
||||||
AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN
|
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'
|
||||||
AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD
|
AUTH_LDAP_BIND_PASSWORD = ''
|
||||||
AUTH_LDAP_SEARCH_OU = CONFIG.AUTH_LDAP_SEARCH_OU
|
AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org'
|
||||||
AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER
|
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
|
||||||
AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS
|
AUTH_LDAP_START_TLS = False
|
||||||
AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP
|
AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"}
|
||||||
AUTH_LDAP_USER_SEARCH_UNION = [
|
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||||
LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER)
|
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
for USER_SEARCH in str(AUTH_LDAP_SEARCH_OU).split("|")
|
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||||
]
|
# AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*AUTH_LDAP_USER_SEARCH_UNION)
|
# )
|
||||||
AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
|
||||||
AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
|
||||||
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
|
||||||
AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
|
||||||
)
|
|
||||||
AUTH_LDAP_CONNECTION_OPTIONS = {
|
AUTH_LDAP_CONNECTION_OPTIONS = {
|
||||||
ldap.OPT_TIMEOUT: 5
|
ldap.OPT_TIMEOUT: 5
|
||||||
}
|
}
|
||||||
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 1
|
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 1
|
||||||
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||||
AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
AUTH_LDAP_BACKEND = 'authentication.ldap.backends.LDAPAuthorizationBackend'
|
||||||
|
|
||||||
if AUTH_LDAP:
|
if AUTH_LDAP:
|
||||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||||
|
@ -411,10 +401,10 @@ if AUTH_OPENID:
|
||||||
|
|
||||||
# Celery using redis as broker
|
# Celery using redis as broker
|
||||||
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
||||||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
'password': CONFIG.REDIS_PASSWORD,
|
||||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
'host': CONFIG.REDIS_HOST,
|
||||||
'port': CONFIG.REDIS_PORT or 6379,
|
'port': CONFIG.REDIS_PORT,
|
||||||
'db':CONFIG.REDIS_DB_CELERY_BROKER or 3,
|
'db': CONFIG.REDIS_DB_CELERY,
|
||||||
}
|
}
|
||||||
CELERY_TASK_SERIALIZER = 'pickle'
|
CELERY_TASK_SERIALIZER = 'pickle'
|
||||||
CELERY_RESULT_SERIALIZER = 'pickle'
|
CELERY_RESULT_SERIALIZER = 'pickle'
|
||||||
|
@ -436,10 +426,10 @@ CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'redis_cache.RedisCache',
|
'BACKEND': 'redis_cache.RedisCache',
|
||||||
'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
||||||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
'password': CONFIG.REDIS_PASSWORD,
|
||||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
'host': CONFIG.REDIS_HOST,
|
||||||
'port': CONFIG.REDIS_PORT or 6379,
|
'port': CONFIG.REDIS_PORT,
|
||||||
'db': CONFIG.REDIS_DB_CACHE or 4,
|
'db': CONFIG.REDIS_DB_CACHE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,27 +444,45 @@ COMMAND_STORAGE = {
|
||||||
'ENGINE': 'terminal.backends.command.db',
|
'ENGINE': 'terminal.backends.command.db',
|
||||||
}
|
}
|
||||||
|
|
||||||
TERMINAL_COMMAND_STORAGE = {
|
DEFAULT_TERMINAL_COMMAND_STORAGE = {
|
||||||
"default": {
|
"default": {
|
||||||
"TYPE": "server",
|
"TYPE": "server",
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
TERMINAL_COMMAND_STORAGE = {
|
||||||
# 'ali-es': {
|
# 'ali-es': {
|
||||||
# 'TYPE': 'elasticsearch',
|
# 'TYPE': 'elasticsearch',
|
||||||
# 'HOSTS': ['http://elastic:changeme@localhost:9200'],
|
# 'HOSTS': ['http://elastic:changeme@localhost:9200'],
|
||||||
# },
|
# },
|
||||||
}
|
}
|
||||||
|
|
||||||
TERMINAL_REPLAY_STORAGE = {
|
DEFAULT_TERMINAL_REPLAY_STORAGE = {
|
||||||
"default": {
|
"default": {
|
||||||
"TYPE": "server",
|
"TYPE": "server",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TERMINAL_REPLAY_STORAGE = {
|
||||||
|
}
|
||||||
|
|
||||||
DEFAULT_PASSWORD_MIN_LENGTH = 6
|
SECURITY_MFA_AUTH = False
|
||||||
DEFAULT_LOGIN_LIMIT_COUNT = 7
|
SECURITY_LOGIN_LIMIT_COUNT = 7
|
||||||
DEFAULT_LOGIN_LIMIT_TIME = 30 # Unit: minute
|
SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute
|
||||||
DEFAULT_SECURITY_MAX_IDLE_TIME = 30 # Unit: minute
|
SECURITY_MAX_IDLE_TIME = 30 # Unit: minute
|
||||||
|
SECURITY_PASSWORD_EXPIRATION_TIME = 9999 # Unit: day
|
||||||
|
SECURITY_PASSWORD_MIN_LENGTH = 6 # Unit: bit
|
||||||
|
SECURITY_PASSWORD_UPPER_CASE = False
|
||||||
|
SECURITY_PASSWORD_LOWER_CASE = False
|
||||||
|
SECURITY_PASSWORD_NUMBER = False
|
||||||
|
SECURITY_PASSWORD_SPECIAL_CHAR = False
|
||||||
|
SECURITY_PASSWORD_RULES = [
|
||||||
|
'SECURITY_PASSWORD_MIN_LENGTH',
|
||||||
|
'SECURITY_PASSWORD_UPPER_CASE',
|
||||||
|
'SECURITY_PASSWORD_LOWER_CASE',
|
||||||
|
'SECURITY_PASSWORD_NUMBER',
|
||||||
|
'SECURITY_PASSWORD_SPECIAL_CHAR'
|
||||||
|
]
|
||||||
|
|
||||||
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||||
BOOTSTRAP3 = {
|
BOOTSTRAP3 = {
|
||||||
|
@ -486,16 +494,20 @@ BOOTSTRAP3 = {
|
||||||
'success_css_class': '',
|
'success_css_class': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION
|
||||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
|
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
|
||||||
DEFAULT_EXPIRED_YEARS = 70
|
DEFAULT_EXPIRED_YEARS = 70
|
||||||
USER_GUIDE_URL = ""
|
USER_GUIDE_URL = ""
|
||||||
|
|
||||||
|
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
|
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.swagger.CustomSwaggerAutoSchema',
|
||||||
'SECURITY_DEFINITIONS': {
|
'SECURITY_DEFINITIONS': {
|
||||||
'basic': {
|
'basic': {
|
||||||
'type': 'basic'
|
'type': 'basic'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Default email suffix
|
||||||
|
EMAIL_SUFFIX = CONFIG.EMAIL_SUFFIX
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
from drf_yasg.inspectors import SwaggerAutoSchema
|
||||||
|
|
||||||
|
from rest_framework import permissions
|
||||||
|
from drf_yasg.views import get_schema_view
|
||||||
|
from drf_yasg import openapi
|
||||||
|
|
||||||
|
|
||||||
|
class CustomSwaggerAutoSchema(SwaggerAutoSchema):
|
||||||
|
def get_tags(self, operation_keys):
|
||||||
|
if len(operation_keys) > 2 and operation_keys[1].startswith('v'):
|
||||||
|
return [operation_keys[2]]
|
||||||
|
return super().get_tags(operation_keys)
|
||||||
|
|
||||||
|
|
||||||
|
def get_swagger_view(version='v1'):
|
||||||
|
from .urls import api_v1_patterns, api_v2_patterns
|
||||||
|
if version == "v2":
|
||||||
|
patterns = api_v2_patterns
|
||||||
|
else:
|
||||||
|
patterns = api_v1_patterns
|
||||||
|
schema_view = get_schema_view(
|
||||||
|
openapi.Info(
|
||||||
|
title="Jumpserver API Docs",
|
||||||
|
default_version=version,
|
||||||
|
description="Jumpserver Restful api docs",
|
||||||
|
terms_of_service="https://www.jumpserver.org",
|
||||||
|
contact=openapi.Contact(email="support@fit2cloud.com"),
|
||||||
|
license=openapi.License(name="GPLv2 License"),
|
||||||
|
),
|
||||||
|
public=True,
|
||||||
|
patterns=patterns,
|
||||||
|
permission_classes=(permissions.AllowAny,),
|
||||||
|
)
|
||||||
|
return schema_view
|
||||||
|
|
|
@ -1,70 +1,34 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import re
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.urls import path, include, re_path
|
from django.urls import path, include, re_path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from django.views.i18n import JavaScriptCatalog
|
||||||
from rest_framework.response import Response
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.utils.encoding import iri_to_uri
|
|
||||||
from rest_framework import permissions
|
|
||||||
from drf_yasg.views import get_schema_view
|
|
||||||
from drf_yasg import openapi
|
|
||||||
|
|
||||||
from .views import IndexView, LunaView, I18NView
|
from .views import IndexView, LunaView, I18NView
|
||||||
|
from .swagger import get_swagger_view
|
||||||
schema_view = get_schema_view(
|
|
||||||
openapi.Info(
|
|
||||||
title="Jumpserver API Docs",
|
|
||||||
default_version='v1',
|
|
||||||
description="Jumpserver Restful api docs",
|
|
||||||
terms_of_service="https://www.jumpserver.org",
|
|
||||||
contact=openapi.Contact(email="support@fit2cloud.com"),
|
|
||||||
license=openapi.License(name="GPLv2 License"),
|
|
||||||
),
|
|
||||||
public=True,
|
|
||||||
permission_classes=(permissions.AllowAny,),
|
|
||||||
)
|
|
||||||
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
|
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseTemporaryRedirect(HttpResponse):
|
api_v1_patterns = [
|
||||||
status_code = 307
|
path('api/', include([
|
||||||
|
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
|
||||||
|
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||||
|
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||||
|
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||||
|
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
|
||||||
|
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||||
|
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||||
|
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
||||||
|
]))
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, redirect_to):
|
api_v2_patterns = [
|
||||||
HttpResponse.__init__(self)
|
path('api/', include([
|
||||||
self['Location'] = iri_to_uri(redirect_to)
|
path('terminal/v2/', include('terminal.urls.api_urls_v2', namespace='api-terminal-v2')),
|
||||||
|
path('users/v2/', include('users.urls.api_urls_v2', namespace='api-users-v2')),
|
||||||
|
]))
|
||||||
@csrf_exempt
|
|
||||||
def redirect_format_api(request, *args, **kwargs):
|
|
||||||
_path, query = request.path, request.GET.urlencode()
|
|
||||||
matched = api_url_pattern.match(_path)
|
|
||||||
if matched:
|
|
||||||
version, app, extra = matched.groups()
|
|
||||||
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
|
|
||||||
"app": app, "version": version, "extra": extra,
|
|
||||||
"query": query
|
|
||||||
})
|
|
||||||
return HttpResponseTemporaryRedirect(_path)
|
|
||||||
else:
|
|
||||||
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
|
||||||
|
|
||||||
|
|
||||||
v1_api_patterns = [
|
|
||||||
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
|
|
||||||
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
|
|
||||||
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
|
|
||||||
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
|
||||||
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
|
|
||||||
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
|
||||||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
|
||||||
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
app_view_patterns = [
|
app_view_patterns = [
|
||||||
|
@ -78,6 +42,7 @@ app_view_patterns = [
|
||||||
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if settings.XPACK_ENABLED:
|
if settings.XPACK_ENABLED:
|
||||||
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
|
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
|
||||||
|
|
||||||
|
@ -87,12 +52,13 @@ js_i18n_patterns = i18n_patterns(
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', IndexView.as_view(), name='index'),
|
path('', IndexView.as_view(), name='index'),
|
||||||
|
path('', include(api_v2_patterns)),
|
||||||
|
path('', include(api_v1_patterns)),
|
||||||
path('luna/', LunaView.as_view(), name='luna-error'),
|
path('luna/', LunaView.as_view(), name='luna-error'),
|
||||||
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
|
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
|
||||||
path('settings/', include('common.urls.view_urls', namespace='settings')),
|
path('settings/', include('common.urls.view_urls', namespace='settings')),
|
||||||
path('common/', include('common.urls.view_urls', namespace='common')),
|
path('common/', include('common.urls.view_urls', namespace='common')),
|
||||||
path('api/v1/', redirect_format_api),
|
# path('api/v2/', include(api_v2_patterns)),
|
||||||
path('api/', include(v1_api_patterns)),
|
|
||||||
|
|
||||||
# External apps url
|
# External apps url
|
||||||
path('captcha/', include('captcha.urls')),
|
path('captcha/', include('captcha.urls')),
|
||||||
|
@ -104,7 +70,13 @@ urlpatterns += js_i18n_patterns
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
re_path('swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
re_path('^swagger(?P<format>\.json|\.yaml)$',
|
||||||
path('docs/', schema_view.with_ui('swagger', cache_timeout=None), name="docs"),
|
get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
|
||||||
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='redoc'),
|
path('docs/', get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"),
|
||||||
|
path('redoc/', get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'),
|
||||||
|
|
||||||
|
re_path('^v2/swagger(?P<format>\.json|\.yaml)$',
|
||||||
|
get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
|
||||||
|
path('docs/v2/', get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"),
|
||||||
|
path('redoc/v2/', get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -8,6 +9,10 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils.encoding import iri_to_uri
|
||||||
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
|
@ -188,3 +193,29 @@ class I18NView(View):
|
||||||
response = HttpResponseRedirect(referer_url)
|
response = HttpResponseRedirect(referer_url)
|
||||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
|
||||||
|
|
||||||
|
|
||||||
|
class HttpResponseTemporaryRedirect(HttpResponse):
|
||||||
|
status_code = 307
|
||||||
|
|
||||||
|
def __init__(self, redirect_to):
|
||||||
|
HttpResponse.__init__(self)
|
||||||
|
self['Location'] = iri_to_uri(redirect_to)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def redirect_format_api(request, *args, **kwargs):
|
||||||
|
_path, query = request.path, request.GET.urlencode()
|
||||||
|
matched = api_url_pattern.match(_path)
|
||||||
|
if matched:
|
||||||
|
version, app, extra = matched.groups()
|
||||||
|
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
|
||||||
|
"app": app, "version": version, "extra": extra,
|
||||||
|
"query": query
|
||||||
|
})
|
||||||
|
return HttpResponseTemporaryRedirect(_path)
|
||||||
|
else:
|
||||||
|
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-08-08 14:48+0800\n"
|
"POT-Creation-Date: 2018-11-21 19:14+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -17,58 +17,58 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:158
|
#: static/js/jumpserver.js:168
|
||||||
msgid "Update is successful!"
|
msgid "Update is successful!"
|
||||||
msgstr "更新成功"
|
msgstr "更新成功"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:160
|
#: static/js/jumpserver.js:170
|
||||||
msgid "An unknown error occurred while updating.."
|
msgid "An unknown error occurred while updating.."
|
||||||
msgstr "更新时发生未知错误"
|
msgstr "更新时发生未知错误"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:205 static/js/jumpserver.js:247
|
#: static/js/jumpserver.js:236 static/js/jumpserver.js:273
|
||||||
#: static/js/jumpserver.js:252
|
#: static/js/jumpserver.js:276
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "错误"
|
msgstr "错误"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:205
|
#: static/js/jumpserver.js:236
|
||||||
msgid "Being used by the asset, please unbind the asset first."
|
msgid "Being used by the asset, please unbind the asset first."
|
||||||
msgstr "正在被资产使用中,请先解除资产绑定"
|
msgstr "正在被资产使用中,请先解除资产绑定"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:212 static/js/jumpserver.js:260
|
#: static/js/jumpserver.js:242 static/js/jumpserver.js:283
|
||||||
msgid "Delete the success"
|
msgid "Delete the success"
|
||||||
msgstr "删除成功"
|
msgstr "删除成功"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:219
|
#: static/js/jumpserver.js:248
|
||||||
msgid "Are you sure about deleting it?"
|
msgid "Are you sure about deleting it?"
|
||||||
msgstr "你确定删除吗 ?"
|
msgstr "你确定删除吗 ?"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:224 static/js/jumpserver.js:273
|
#: static/js/jumpserver.js:252 static/js/jumpserver.js:293
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:227 static/js/jumpserver.js:276
|
#: static/js/jumpserver.js:254 static/js/jumpserver.js:295
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "确认"
|
msgstr "确认"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:247
|
#: static/js/jumpserver.js:273
|
||||||
msgid ""
|
msgid ""
|
||||||
"The organization contains undeleted information. Please try again after "
|
"The organization contains undeleted information. Please try again after "
|
||||||
"deleting"
|
"deleting"
|
||||||
msgstr "组织中包含未删除信息,请删除后重试"
|
msgstr "组织中包含未删除信息,请删除后重试"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:252
|
#: static/js/jumpserver.js:276
|
||||||
msgid ""
|
msgid ""
|
||||||
"Do not perform this operation under this organization. Try again after "
|
"Do not perform this operation under this organization. Try again after "
|
||||||
"switching to another organization"
|
"switching to another organization"
|
||||||
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
|
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:267
|
#: static/js/jumpserver.js:289
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please ensure that the following information in the organization has been "
|
"Please ensure that the following information in the organization has been "
|
||||||
"deleted"
|
"deleted"
|
||||||
msgstr "请确保组织内的以下信息已删除"
|
msgstr "请确保组织内的以下信息已删除"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:269
|
#: static/js/jumpserver.js:290
|
||||||
msgid ""
|
msgid ""
|
||||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||||
"Labels、Asset permission"
|
"Labels、Asset permission"
|
||||||
|
@ -76,32 +76,52 @@ msgstr ""
|
||||||
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
|
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
|
||||||
"规则"
|
"规则"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:311
|
#: static/js/jumpserver.js:329
|
||||||
msgid "Loading ..."
|
msgid "Loading ..."
|
||||||
msgstr "加载中 ..."
|
msgstr "加载中 ..."
|
||||||
|
|
||||||
#: static/js/jumpserver.js:313
|
#: static/js/jumpserver.js:330
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "搜索"
|
msgstr "搜索"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:317
|
#: static/js/jumpserver.js:333
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgid "Selected item %d"
|
msgid "Selected item %d"
|
||||||
msgstr "选中 %d 项"
|
msgstr "选中 %d 项"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:322
|
#: static/js/jumpserver.js:337
|
||||||
msgid "Per page _MENU_"
|
msgid "Per page _MENU_"
|
||||||
msgstr "每页 _MENU_"
|
msgstr "每页 _MENU_"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:324
|
#: static/js/jumpserver.js:338
|
||||||
msgid ""
|
msgid ""
|
||||||
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
||||||
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
|
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:328
|
#: static/js/jumpserver.js:341
|
||||||
msgid "No match"
|
msgid "No match"
|
||||||
msgstr "没有匹配项"
|
msgstr "没有匹配项"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:330
|
#: static/js/jumpserver.js:342
|
||||||
msgid "No record"
|
msgid "No record"
|
||||||
msgstr "没有记录"
|
msgstr "没有记录"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:701
|
||||||
|
msgid "Password minimum length {N} bits"
|
||||||
|
msgstr "密码最小长度 {N} 位"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:702
|
||||||
|
msgid "Must contain capital letters"
|
||||||
|
msgstr "必须包含大写字母"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:703
|
||||||
|
msgid "Must contain lowercase letters"
|
||||||
|
msgstr "必须包含小写字母"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:704
|
||||||
|
msgid "Must contain numeric characters"
|
||||||
|
msgstr "必须包含数字字符"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:705
|
||||||
|
msgid "Must contain special characters"
|
||||||
|
msgstr "必须包含特殊字符"
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
import sys
|
import datetime
|
||||||
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from ansible import constants as C
|
||||||
from ansible.plugins.callback import CallbackBase
|
from ansible.plugins.callback import CallbackBase
|
||||||
from ansible.plugins.callback.default import CallbackModule
|
from ansible.plugins.callback.default import CallbackModule
|
||||||
|
from ansible.plugins.callback.minimal import CallbackModule as CMDCallBackModule
|
||||||
from .display import TeeObj
|
|
||||||
|
|
||||||
|
|
||||||
class AdHocResultCallback(CallbackModule):
|
class CallbackMixin:
|
||||||
"""
|
def __init__(self, display=None):
|
||||||
Task result Callback
|
|
||||||
"""
|
|
||||||
def __init__(self, display=None, options=None, file_obj=None):
|
|
||||||
# result_raw example: {
|
# result_raw example: {
|
||||||
# "ok": {"hostname": {"task_name": {},...},..},
|
# "ok": {"hostname": {"task_name": {},...},..},
|
||||||
# "failed": {"hostname": {"task_name": {}..}, ..},
|
# "failed": {"hostname": {"task_name": {}..}, ..},
|
||||||
|
@ -20,71 +19,138 @@ class AdHocResultCallback(CallbackModule):
|
||||||
# "skipped": {"hostname": {"task_name": {}, ..}, ..},
|
# "skipped": {"hostname": {"task_name": {}, ..}, ..},
|
||||||
# }
|
# }
|
||||||
# results_summary example: {
|
# results_summary example: {
|
||||||
# "contacted": {"hostname",...},
|
# "contacted": {"hostname": {"task_name": {}}, "hostname": {}},
|
||||||
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
|
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
|
||||||
|
# "success": True
|
||||||
# }
|
# }
|
||||||
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
|
self.results_raw = dict(
|
||||||
self.results_summary = dict(contacted=[], dark={})
|
ok=defaultdict(dict),
|
||||||
|
failed=defaultdict(dict),
|
||||||
|
unreachable=defaultdict(dict),
|
||||||
|
skippe=defaultdict(dict),
|
||||||
|
)
|
||||||
|
self.results_summary = dict(
|
||||||
|
contacted=defaultdict(dict),
|
||||||
|
dark=defaultdict(dict),
|
||||||
|
success=True
|
||||||
|
)
|
||||||
|
self.results = {
|
||||||
|
'raw': self.results_raw,
|
||||||
|
'summary': self.results_summary,
|
||||||
|
}
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if file_obj is not None:
|
if display:
|
||||||
sys.stdout = TeeObj(file_obj)
|
self._display = display
|
||||||
|
self._display.columns = 79
|
||||||
|
|
||||||
def gather_result(self, t, res):
|
def display(self, msg):
|
||||||
self._clean_results(res._result, res._task.action)
|
self._display.display(msg)
|
||||||
host = res._host.get_name()
|
|
||||||
task_name = res.task_name
|
|
||||||
task_result = res._result
|
|
||||||
|
|
||||||
if self.results_raw[t].get(host):
|
def gather_result(self, t, result):
|
||||||
self.results_raw[t][host][task_name] = task_result
|
self._clean_results(result._result, result._task.action)
|
||||||
else:
|
host = result._host.get_name()
|
||||||
self.results_raw[t][host] = {task_name: task_result}
|
task_name = result.task_name
|
||||||
|
task_result = result._result
|
||||||
|
|
||||||
|
self.results_raw[t][host][task_name] = task_result
|
||||||
self.clean_result(t, host, task_name, task_result)
|
self.clean_result(t, host, task_name, task_result)
|
||||||
|
|
||||||
|
|
||||||
|
class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
|
||||||
|
"""
|
||||||
|
Task result Callback
|
||||||
|
"""
|
||||||
def clean_result(self, t, host, task_name, task_result):
|
def clean_result(self, t, host, task_name, task_result):
|
||||||
contacted = self.results_summary["contacted"]
|
contacted = self.results_summary["contacted"]
|
||||||
dark = self.results_summary["dark"]
|
dark = self.results_summary["dark"]
|
||||||
if t in ("ok", "skipped") and host not in dark:
|
|
||||||
if host not in contacted:
|
if task_result.get('rc') is not None:
|
||||||
contacted.append(host)
|
cmd = task_result.get('cmd')
|
||||||
else:
|
if isinstance(cmd, list):
|
||||||
if dark.get(host):
|
cmd = " ".join(cmd)
|
||||||
dark[host][task_name] = task_result.values
|
|
||||||
else:
|
else:
|
||||||
dark[host] = {task_name: task_result}
|
cmd = str(cmd)
|
||||||
if host in contacted:
|
detail = {
|
||||||
contacted.remove(host)
|
'cmd': cmd,
|
||||||
|
'stderr': task_result.get('stderr'),
|
||||||
|
'stdout': task_result.get('stdout'),
|
||||||
|
'rc': task_result.get('rc'),
|
||||||
|
'delta': task_result.get('delta'),
|
||||||
|
'msg': task_result.get('msg', '')
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
detail = {
|
||||||
|
"changed": task_result.get('changed', False),
|
||||||
|
"msg": task_result.get('msg', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if t in ("ok", "skipped"):
|
||||||
|
contacted[host][task_name] = detail
|
||||||
|
else:
|
||||||
|
dark[host][task_name] = detail
|
||||||
|
|
||||||
def v2_runner_on_failed(self, result, ignore_errors=False):
|
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||||
|
self.results_summary['success'] = False
|
||||||
self.gather_result("failed", result)
|
self.gather_result("failed", result)
|
||||||
super().v2_runner_on_failed(result, ignore_errors=ignore_errors)
|
|
||||||
|
if result._task.action in C.MODULE_NO_JSON:
|
||||||
|
CMDCallBackModule.v2_runner_on_failed(self,
|
||||||
|
result, ignore_errors=ignore_errors
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
super().v2_runner_on_failed(
|
||||||
|
result, ignore_errors=ignore_errors
|
||||||
|
)
|
||||||
|
|
||||||
def v2_runner_on_ok(self, result):
|
def v2_runner_on_ok(self, result):
|
||||||
self.gather_result("ok", result)
|
self.gather_result("ok", result)
|
||||||
super().v2_runner_on_ok(result)
|
if result._task.action in C.MODULE_NO_JSON:
|
||||||
|
CMDCallBackModule.v2_runner_on_ok(self, result)
|
||||||
|
else:
|
||||||
|
super().v2_runner_on_ok(result)
|
||||||
|
|
||||||
def v2_runner_on_skipped(self, result):
|
def v2_runner_on_skipped(self, result):
|
||||||
self.gather_result("skipped", result)
|
self.gather_result("skipped", result)
|
||||||
super().v2_runner_on_skipped(result)
|
super().v2_runner_on_skipped(result)
|
||||||
|
|
||||||
def v2_runner_on_unreachable(self, result):
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
self.results_summary['success'] = False
|
||||||
self.gather_result("unreachable", result)
|
self.gather_result("unreachable", result)
|
||||||
super().v2_runner_on_unreachable(result)
|
super().v2_runner_on_unreachable(result)
|
||||||
|
|
||||||
|
def on_playbook_start(self, name):
|
||||||
|
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
self.display(
|
||||||
|
"{} Start task: {}\r\n".format(date_start, name)
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_playbook_end(self, name):
|
||||||
|
date_finished = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
self.display(
|
||||||
|
"{} Task finish\r\n".format(date_finished)
|
||||||
|
)
|
||||||
|
|
||||||
|
def display_skipped_hosts(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def display_ok_hosts(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CommandResultCallback(AdHocResultCallback):
|
class CommandResultCallback(AdHocResultCallback):
|
||||||
"""
|
"""
|
||||||
Command result callback
|
Command result callback
|
||||||
|
|
||||||
|
results_command: {
|
||||||
|
"cmd": "",
|
||||||
|
"stderr": "",
|
||||||
|
"stdout": "",
|
||||||
|
"rc": 0,
|
||||||
|
"delta": 0:0:0.123
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
def __init__(self, display=None):
|
def __init__(self, display=None, **kwargs):
|
||||||
# results_command: {
|
|
||||||
# "cmd": "",
|
|
||||||
# "stderr": "",
|
|
||||||
# "stdout": "",
|
|
||||||
# "rc": 0,
|
|
||||||
# "delta": 0:0:0.123
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
self.results_command = dict()
|
self.results_command = dict()
|
||||||
super().__init__(display)
|
super().__init__(display)
|
||||||
|
|
||||||
|
@ -92,6 +158,43 @@ class CommandResultCallback(AdHocResultCallback):
|
||||||
super().gather_result(t, res)
|
super().gather_result(t, res)
|
||||||
self.gather_cmd(t, res)
|
self.gather_cmd(t, res)
|
||||||
|
|
||||||
|
def v2_playbook_on_play_start(self, play):
|
||||||
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
msg = '$ {} ({})'.format(play.name, now)
|
||||||
|
self._play = play
|
||||||
|
self._display.banner(msg)
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
self.results_summary['success'] = False
|
||||||
|
self.gather_result("unreachable", result)
|
||||||
|
msg = result._result.get("msg")
|
||||||
|
if not msg:
|
||||||
|
msg = json.dumps(result._result, indent=4)
|
||||||
|
self._display.display("%s | FAILED! => \n%s" % (
|
||||||
|
result._host.get_name(),
|
||||||
|
msg,
|
||||||
|
), color=C.COLOR_ERROR)
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||||
|
self.results_summary['success'] = False
|
||||||
|
self.gather_result("failed", result)
|
||||||
|
msg = result._result.get("msg", '')
|
||||||
|
stderr = result._result.get("stderr")
|
||||||
|
if stderr:
|
||||||
|
msg += '\n' + stderr
|
||||||
|
module_stdout = result._result.get("module_stdout")
|
||||||
|
if module_stdout:
|
||||||
|
msg += '\n' + module_stdout
|
||||||
|
if not msg:
|
||||||
|
msg = json.dumps(result._result, indent=4)
|
||||||
|
self._display.display("%s | FAILED! => \n%s" % (
|
||||||
|
result._host.get_name(),
|
||||||
|
msg,
|
||||||
|
), color=C.COLOR_ERROR)
|
||||||
|
|
||||||
|
def _print_task_banner(self, task):
|
||||||
|
pass
|
||||||
|
|
||||||
def gather_cmd(self, t, res):
|
def gather_cmd(self, t, res):
|
||||||
host = res._host.get_name()
|
host = res._host.get_name()
|
||||||
cmd = {}
|
cmd = {}
|
||||||
|
|
|
@ -17,7 +17,7 @@ from common.utils import get_logger
|
||||||
from .exceptions import AnsibleError
|
from .exceptions import AnsibleError
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["AdHocRunner", "PlayBookRunner"]
|
__all__ = ["AdHocRunner", "PlayBookRunner", "CommandRunner"]
|
||||||
C.HOST_KEY_CHECKING = False
|
C.HOST_KEY_CHECKING = False
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ def get_default_options():
|
||||||
listtasks=False,
|
listtasks=False,
|
||||||
listhosts=False,
|
listhosts=False,
|
||||||
syntax=False,
|
syntax=False,
|
||||||
timeout=60,
|
timeout=30,
|
||||||
connection='ssh',
|
connection='ssh',
|
||||||
module_path='',
|
module_path='',
|
||||||
forks=10,
|
forks=10,
|
||||||
|
@ -135,6 +135,7 @@ class AdHocRunner:
|
||||||
loader_class = DataLoader
|
loader_class = DataLoader
|
||||||
variable_manager_class = VariableManager
|
variable_manager_class = VariableManager
|
||||||
default_options = get_default_options()
|
default_options = get_default_options()
|
||||||
|
command_modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell')
|
||||||
|
|
||||||
def __init__(self, inventory, options=None):
|
def __init__(self, inventory, options=None):
|
||||||
self.options = self.update_options(options)
|
self.options = self.update_options(options)
|
||||||
|
@ -145,7 +146,7 @@ class AdHocRunner:
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_result_callback(self, file_obj=None):
|
def get_result_callback(self, file_obj=None):
|
||||||
return self.__class__.results_callback_class(file_obj=file_obj)
|
return self.__class__.results_callback_class()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_module_args(module_name, module_args=''):
|
def check_module_args(module_name, module_args=''):
|
||||||
|
@ -163,10 +164,28 @@ class AdHocRunner:
|
||||||
"pattern: %s dose not match any hosts." % pattern
|
"pattern: %s dose not match any hosts." % pattern
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean_args(self, module, args):
|
||||||
|
if module not in self.command_modules_choices:
|
||||||
|
return args
|
||||||
|
if isinstance(args, str):
|
||||||
|
if args.startswith('executable='):
|
||||||
|
_args = args.split(' ')
|
||||||
|
executable, command = _args[0].split('=')[1], ' '.join(_args[1:])
|
||||||
|
args = {'executable': executable, '_raw_params': command}
|
||||||
|
else:
|
||||||
|
args = {'_raw_params': args}
|
||||||
|
return args
|
||||||
|
else:
|
||||||
|
return args
|
||||||
|
|
||||||
def clean_tasks(self, tasks):
|
def clean_tasks(self, tasks):
|
||||||
cleaned_tasks = []
|
cleaned_tasks = []
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
self.check_module_args(task['action']['module'], task['action'].get('args'))
|
module = task['action']['module']
|
||||||
|
args = task['action'].get('args')
|
||||||
|
cleaned_args = self.clean_args(module, args)
|
||||||
|
task['action']['args'] = cleaned_args
|
||||||
|
self.check_module_args(module, cleaned_args)
|
||||||
cleaned_tasks.append(task)
|
cleaned_tasks.append(task)
|
||||||
return cleaned_tasks
|
return cleaned_tasks
|
||||||
|
|
||||||
|
@ -177,17 +196,16 @@ class AdHocRunner:
|
||||||
options = self.__class__.default_options
|
options = self.__class__.default_options
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no', file_obj=None):
|
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
|
||||||
"""
|
"""
|
||||||
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
|
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
|
||||||
:param pattern: all, *, or others
|
:param pattern: all, *, or others
|
||||||
:param play_name: The play name
|
:param play_name: The play name
|
||||||
:param gather_facts:
|
:param gather_facts:
|
||||||
:param file_obj: logging to file_obj
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.check_pattern(pattern)
|
self.check_pattern(pattern)
|
||||||
self.results_callback = self.get_result_callback(file_obj)
|
self.results_callback = self.get_result_callback()
|
||||||
cleaned_tasks = self.clean_tasks(tasks)
|
cleaned_tasks = self.clean_tasks(tasks)
|
||||||
|
|
||||||
play_source = dict(
|
play_source = dict(
|
||||||
|
@ -211,10 +229,6 @@ class AdHocRunner:
|
||||||
stdout_callback=self.results_callback,
|
stdout_callback=self.results_callback,
|
||||||
passwords=self.options.passwords,
|
passwords=self.options.passwords,
|
||||||
)
|
)
|
||||||
print("Get matched hosts: {}".format(
|
|
||||||
self.inventory.get_matched_hosts(pattern)
|
|
||||||
))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tqm.run(play)
|
tqm.run(play)
|
||||||
return self.results_callback
|
return self.results_callback
|
||||||
|
@ -229,16 +243,12 @@ class CommandRunner(AdHocRunner):
|
||||||
results_callback_class = CommandResultCallback
|
results_callback_class = CommandResultCallback
|
||||||
modules_choices = ('shell', 'raw', 'command', 'script')
|
modules_choices = ('shell', 'raw', 'command', 'script')
|
||||||
|
|
||||||
def execute(self, cmd, pattern, module=None):
|
def execute(self, cmd, pattern, module='shell'):
|
||||||
if module and module not in self.modules_choices:
|
if module and module not in self.modules_choices:
|
||||||
raise AnsibleError("Module should in {}".format(self.modules_choices))
|
raise AnsibleError("Module should in {}".format(self.modules_choices))
|
||||||
else:
|
|
||||||
module = "shell"
|
|
||||||
|
|
||||||
tasks = [
|
tasks = [
|
||||||
{"action": {"module": module, "args": cmd}}
|
{"action": {"module": module, "args": cmd}}
|
||||||
]
|
]
|
||||||
hosts = self.inventory.get_hosts(pattern=pattern)
|
return self.run(tasks, pattern, play_name=cmd)
|
||||||
name = "Run command {} on {}".format(cmd, ", ".join([host.name for host in hosts]))
|
|
||||||
return self.run(tasks, pattern, play_name=name)
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from .adhoc import *
|
||||||
|
from .celery import *
|
||||||
|
from .command import *
|
|
@ -1,31 +1,37 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# -*- coding: utf-8 -*-
|
||||||
import uuid
|
#
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from rest_framework import viewsets, generics
|
from rest_framework import viewsets, generics
|
||||||
from rest_framework.views import Response
|
from rest_framework.views import Response
|
||||||
|
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
|
from orgs.utils import current_org
|
||||||
from .serializers import TaskSerializer, AdHocSerializer, \
|
from ..models import Task, AdHoc, AdHocRunHistory
|
||||||
|
from ..serializers import TaskSerializer, AdHocSerializer, \
|
||||||
AdHocRunHistorySerializer
|
AdHocRunHistorySerializer
|
||||||
from .tasks import run_ansible_task
|
from ..tasks import run_ansible_task
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TaskViewSet(viewsets.ModelViewSet):
|
class TaskViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Task.objects.all()
|
queryset = Task.objects.all()
|
||||||
serializer_class = TaskSerializer
|
serializer_class = TaskSerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
label = None
|
|
||||||
help_text = ''
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
if current_org:
|
||||||
|
queryset = queryset.filter(created_by=current_org.id)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class TaskRun(generics.RetrieveAPIView):
|
class TaskRun(generics.RetrieveAPIView):
|
||||||
queryset = Task.objects.all()
|
queryset = Task.objects.all()
|
||||||
serializer_class = TaskViewSet
|
# serializer_class = TaskViewSet
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
@ -47,7 +53,7 @@ class AdHocViewSet(viewsets.ModelViewSet):
|
||||||
return self.queryset
|
return self.queryset
|
||||||
|
|
||||||
|
|
||||||
class AdHocRunHistorySet(viewsets.ModelViewSet):
|
class AdHocRunHistoryViewSet(viewsets.ModelViewSet):
|
||||||
queryset = AdHocRunHistory.objects.all()
|
queryset = AdHocRunHistory.objects.all()
|
||||||
serializer_class = AdHocRunHistorySerializer
|
serializer_class = AdHocRunHistorySerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
@ -66,28 +72,6 @@ class AdHocRunHistorySet(viewsets.ModelViewSet):
|
||||||
return self.queryset
|
return self.queryset
|
||||||
|
|
||||||
|
|
||||||
class CeleryTaskLogApi(generics.RetrieveAPIView):
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
buff_size = 1024 * 10
|
|
||||||
end = False
|
|
||||||
queryset = CeleryTask.objects.all()
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
|
||||||
task = self.get_object()
|
|
||||||
log_path = task.full_log_path
|
|
||||||
|
|
||||||
if not log_path or not os.path.isfile(log_path):
|
|
||||||
return Response({"data": _("Waiting ...")}, status=203)
|
|
||||||
|
|
||||||
with open(log_path, 'r') as f:
|
|
||||||
offset = cache.get(mark, 0)
|
|
||||||
f.seek(offset)
|
|
||||||
data = f.read(self.buff_size).replace('\n', '\r\n')
|
|
||||||
mark = str(uuid.uuid4())
|
|
||||||
cache.set(mark, f.tell(), 5)
|
|
||||||
|
|
||||||
if data == '' and task.is_finished():
|
|
||||||
self.end = True
|
|
||||||
return Response({"data": data, 'end': self.end, 'mark': mark})
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import uuid
|
||||||
|
import os
|
||||||
|
|
||||||
|
from celery.result import AsyncResult
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework.views import Response
|
||||||
|
|
||||||
|
from common.permissions import IsOrgAdmin, IsValidUser
|
||||||
|
from ..models import CeleryTask
|
||||||
|
from ..serializers import CeleryResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['CeleryTaskLogApi', 'CeleryResultApi']
|
||||||
|
|
||||||
|
|
||||||
|
class CeleryTaskLogApi(generics.RetrieveAPIView):
|
||||||
|
permission_classes = (IsValidUser,)
|
||||||
|
buff_size = 1024 * 10
|
||||||
|
end = False
|
||||||
|
queryset = CeleryTask.objects.all()
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
||||||
|
task = self.get_object()
|
||||||
|
log_path = task.full_log_path
|
||||||
|
|
||||||
|
if not log_path or not os.path.isfile(log_path):
|
||||||
|
return Response({"data": _("Waiting ...")}, status=203)
|
||||||
|
|
||||||
|
with open(log_path, 'r') as f:
|
||||||
|
offset = cache.get(mark, 0)
|
||||||
|
f.seek(offset)
|
||||||
|
data = f.read(self.buff_size).replace('\n', '\r\n')
|
||||||
|
mark = str(uuid.uuid4())
|
||||||
|
cache.set(mark, f.tell(), 5)
|
||||||
|
|
||||||
|
if data == '' and task.is_finished():
|
||||||
|
self.end = True
|
||||||
|
return Response({"data": data, 'end': self.end, 'mark': mark})
|
||||||
|
|
||||||
|
|
||||||
|
class CeleryResultApi(generics.RetrieveAPIView):
|
||||||
|
permission_classes = (IsValidUser,)
|
||||||
|
serializer_class = CeleryResultSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
return AsyncResult(pk)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from common.permissions import IsValidUser
|
||||||
|
from ..models import CommandExecution
|
||||||
|
from ..serializers import CommandExecutionSerializer
|
||||||
|
from ..tasks import run_command_execution
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = CommandExecutionSerializer
|
||||||
|
permission_classes = (IsValidUser,)
|
||||||
|
task = None
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return CommandExecution.objects.filter(
|
||||||
|
user_id=str(self.request.user.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
instance = serializer.save()
|
||||||
|
instance.user = self.request.user
|
||||||
|
instance.save()
|
||||||
|
run_command_execution.apply_async(
|
||||||
|
args=(instance.id,), task_id=str(instance.id)
|
||||||
|
)
|
|
@ -27,7 +27,6 @@ def on_app_ready(sender=None, headers=None, body=None, **kwargs):
|
||||||
if cache.get("CELERY_APP_READY", 0) == 1:
|
if cache.get("CELERY_APP_READY", 0) == 1:
|
||||||
return
|
return
|
||||||
cache.set("CELERY_APP_READY", 1, 10)
|
cache.set("CELERY_APP_READY", 1, 10)
|
||||||
logger.debug("App ready signal recv")
|
|
||||||
tasks = get_after_app_ready_tasks()
|
tasks = get_after_app_ready_tasks()
|
||||||
logger.debug("Start need start task: [{}]".format(
|
logger.debug("Start need start task: [{}]".format(
|
||||||
", ".join(tasks))
|
", ".join(tasks))
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from assets.models import SystemUser
|
||||||
|
from .models import CommandExecution
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = CommandExecution
|
||||||
|
fields = ['run_as', 'command']
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
run_as_field = self.fields.get('run_as')
|
||||||
|
run_as_field.queryset = SystemUser.objects.all()
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from .ansible.inventory import BaseInventory
|
from .ansible.inventory import BaseInventory
|
||||||
from assets.utils import get_assets_by_fullname_list, get_system_user_by_name
|
from assets.utils import get_assets_by_id_list, get_system_user_by_id
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'JMSInventory'
|
'JMSInventory'
|
||||||
|
@ -14,19 +14,18 @@ class JMSInventory(BaseInventory):
|
||||||
JMS Inventory is the manager with jumpserver assets, so you can
|
JMS Inventory is the manager with jumpserver assets, so you can
|
||||||
write you own manager, construct you inventory
|
write you own manager, construct you inventory
|
||||||
"""
|
"""
|
||||||
def __init__(self, hostname_list, run_as_admin=False, run_as=None, become_info=None):
|
def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None):
|
||||||
"""
|
"""
|
||||||
:param hostname_list: ["test1", ]
|
:param host_id_list: ["test1", ]
|
||||||
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
|
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
|
||||||
:param run_as: 是否统一使用某个系统用户去执行
|
:param run_as: 是否统一使用某个系统用户去执行
|
||||||
:param become_info: 是否become成某个用户去执行
|
:param become_info: 是否become成某个用户去执行
|
||||||
"""
|
"""
|
||||||
self.hostname_list = hostname_list
|
self.assets = assets
|
||||||
self.using_admin = run_as_admin
|
self.using_admin = run_as_admin
|
||||||
self.run_as = run_as
|
self.run_as = run_as
|
||||||
self.become_info = become_info
|
self.become_info = become_info
|
||||||
|
|
||||||
assets = self.get_jms_assets()
|
|
||||||
host_list = []
|
host_list = []
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
|
@ -43,14 +42,10 @@ class JMSInventory(BaseInventory):
|
||||||
host.update(become_info)
|
host.update(become_info)
|
||||||
super().__init__(host_list=host_list)
|
super().__init__(host_list=host_list)
|
||||||
|
|
||||||
def get_jms_assets(self):
|
|
||||||
assets = get_assets_by_fullname_list(self.hostname_list)
|
|
||||||
return assets
|
|
||||||
|
|
||||||
def convert_to_ansible(self, asset, run_as_admin=False):
|
def convert_to_ansible(self, asset, run_as_admin=False):
|
||||||
info = {
|
info = {
|
||||||
'id': asset.id,
|
'id': asset.id,
|
||||||
'hostname': asset.fullname,
|
'hostname': asset.hostname,
|
||||||
'ip': asset.ip,
|
'ip': asset.ip,
|
||||||
'port': asset.port,
|
'port': asset.port,
|
||||||
'vars': dict(),
|
'vars': dict(),
|
||||||
|
@ -75,7 +70,7 @@ class JMSInventory(BaseInventory):
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def get_run_user_info(self):
|
def get_run_user_info(self):
|
||||||
system_user = get_system_user_by_name(self.run_as)
|
system_user = self.run_as
|
||||||
if not system_user:
|
if not system_user:
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-02 09:45
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ops', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CeleryTask',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=1024)),
|
||||||
|
('status', models.CharField(choices=[('waiting', 'waiting'), ('running', 'running'), ('finished', 'finished')], max_length=128)),
|
||||||
|
('log_path', models.CharField(blank=True, max_length=256, null=True)),
|
||||||
|
('date_published', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('date_start', models.DateTimeField(null=True)),
|
||||||
|
('date_finished', models.DateTimeField(null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 2.1.4 on 2018-12-07 09:44
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0023_auto_20181016_1650'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('ops', '0002_celerytask'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CommandExecution',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('command', models.TextField(verbose_name='Command')),
|
||||||
|
('_result', models.TextField(blank=True, null=True, verbose_name='Result')),
|
||||||
|
('is_finished', models.BooleanField(default=False)),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('date_start', models.DateTimeField(null=True)),
|
||||||
|
('date_finished', models.DateTimeField(null=True)),
|
||||||
|
('hosts', models.ManyToManyField(to='assets.Asset')),
|
||||||
|
('run_as', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser')),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='run_as',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='hosts',
|
||||||
|
field=models.ManyToManyField(to='assets.Asset', verbose_name='Host'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='task',
|
||||||
|
name='created_by',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=128),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='task',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='task',
|
||||||
|
unique_together={('name', 'created_by')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.1.4 on 2018-12-07 09:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0023_auto_20181016_1650'),
|
||||||
|
('ops', '0003_auto_20181207_1744'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='run_as',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,4 +2,5 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from .adhoc import *
|
from .adhoc import *
|
||||||
from .celery import *
|
from .celery import *
|
||||||
|
from .command import *
|
||||||
|
|
|
@ -34,16 +34,17 @@ class Task(models.Model):
|
||||||
One task can have some versions of adhoc, run a task only run the latest version adhoc
|
One task can have some versions of adhoc, run a task only run the latest version adhoc
|
||||||
"""
|
"""
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
interval = models.IntegerField(verbose_name=_("Interval"), null=True, blank=True, help_text=_("Units: seconds"))
|
interval = models.IntegerField(verbose_name=_("Interval"), null=True, blank=True, help_text=_("Units: seconds"))
|
||||||
crontab = models.CharField(verbose_name=_("Crontab"), null=True, blank=True, max_length=128, help_text=_("5 * * * *"))
|
crontab = models.CharField(verbose_name=_("Crontab"), null=True, blank=True, max_length=128, help_text=_("5 * * * *"))
|
||||||
is_periodic = models.BooleanField(default=False)
|
is_periodic = models.BooleanField(default=False)
|
||||||
callback = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Callback")) # Callback must be a registered celery task
|
callback = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Callback")) # Callback must be a registered celery task
|
||||||
is_deleted = models.BooleanField(default=False)
|
is_deleted = models.BooleanField(default=False)
|
||||||
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
||||||
created_by = models.CharField(max_length=128, blank=True, null=True, default='')
|
created_by = models.CharField(max_length=128, blank=True, default='')
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
__latest_adhoc = None
|
__latest_adhoc = None
|
||||||
|
_ignore_auto_created_by = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def short_id(self):
|
def short_id(self):
|
||||||
|
@ -94,7 +95,7 @@ class Task(models.Model):
|
||||||
update_fields=None):
|
update_fields=None):
|
||||||
from ..tasks import run_ansible_task
|
from ..tasks import run_ansible_task
|
||||||
super().save(
|
super().save(
|
||||||
force_insert=force_insert, force_update=force_update,
|
force_insert=force_insert, force_update=force_update,
|
||||||
using=using, update_fields=update_fields,
|
using=using, update_fields=update_fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ class Task(models.Model):
|
||||||
crontab = self.crontab
|
crontab = self.crontab
|
||||||
|
|
||||||
tasks = {
|
tasks = {
|
||||||
self.name: {
|
self.__str__(): {
|
||||||
"task": run_ansible_task.name,
|
"task": run_ansible_task.name,
|
||||||
"interval": interval,
|
"interval": interval,
|
||||||
"crontab": crontab,
|
"crontab": crontab,
|
||||||
|
@ -119,11 +120,11 @@ class Task(models.Model):
|
||||||
}
|
}
|
||||||
create_or_update_celery_periodic_tasks(tasks)
|
create_or_update_celery_periodic_tasks(tasks)
|
||||||
else:
|
else:
|
||||||
disable_celery_periodic_task(self.name)
|
disable_celery_periodic_task(self.__str__())
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
super().delete(using=using, keep_parents=keep_parents)
|
super().delete(using=using, keep_parents=keep_parents)
|
||||||
delete_celery_periodic_task(self.name)
|
delete_celery_periodic_task(self.__str__())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schedule(self):
|
def schedule(self):
|
||||||
|
@ -133,10 +134,11 @@ class Task(models.Model):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name + '@' + str(self.created_by)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'ops_task'
|
db_table = 'ops_task'
|
||||||
|
unique_together = ('name', 'created_by')
|
||||||
get_latest_by = 'date_created'
|
get_latest_by = 'date_created'
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,8 +159,9 @@ class AdHoc(models.Model):
|
||||||
pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern'))
|
pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern'))
|
||||||
_options = models.CharField(max_length=1024, default='', verbose_name=_('Options'))
|
_options = models.CharField(max_length=1024, default='', verbose_name=_('Options'))
|
||||||
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
|
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
|
||||||
|
hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
|
||||||
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
|
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
|
||||||
run_as = models.CharField(max_length=128, default='', verbose_name=_("Run as"))
|
run_as = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE)
|
||||||
_become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
|
_become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
|
||||||
created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by'))
|
created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -174,14 +177,6 @@ class AdHoc(models.Model):
|
||||||
else:
|
else:
|
||||||
raise SyntaxError('Tasks should be a list: {}'.format(item))
|
raise SyntaxError('Tasks should be a list: {}'.format(item))
|
||||||
|
|
||||||
@property
|
|
||||||
def hosts(self):
|
|
||||||
return json.loads(self._hosts)
|
|
||||||
|
|
||||||
@hosts.setter
|
|
||||||
def hosts(self, item):
|
|
||||||
self._hosts = json.dumps(item)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inventory(self):
|
def inventory(self):
|
||||||
if self.become:
|
if self.become:
|
||||||
|
@ -194,7 +189,7 @@ class AdHoc(models.Model):
|
||||||
become_info = None
|
become_info = None
|
||||||
|
|
||||||
inventory = JMSInventory(
|
inventory = JMSInventory(
|
||||||
self.hosts, run_as_admin=self.run_as_admin,
|
self.hosts.all(), run_as_admin=self.run_as_admin,
|
||||||
run_as=self.run_as, become_info=become_info
|
run_as=self.run_as, become_info=become_info
|
||||||
)
|
)
|
||||||
return inventory
|
return inventory
|
||||||
|
@ -242,14 +237,13 @@ class AdHoc(models.Model):
|
||||||
history.timedelta = time.time() - time_start
|
history.timedelta = time.time() - time_start
|
||||||
history.save()
|
history.save()
|
||||||
|
|
||||||
def _run_only(self, file_obj=None):
|
def _run_only(self):
|
||||||
runner = AdHocRunner(self.inventory, options=self.options)
|
runner = AdHocRunner(self.inventory, options=self.options)
|
||||||
try:
|
try:
|
||||||
result = runner.run(
|
result = runner.run(
|
||||||
self.tasks,
|
self.tasks,
|
||||||
self.pattern,
|
self.pattern,
|
||||||
self.task.name,
|
self.task.name,
|
||||||
file_obj=file_obj,
|
|
||||||
)
|
)
|
||||||
return result.results_raw, result.results_summary
|
return result.results_raw, result.results_summary
|
||||||
except AnsibleError as e:
|
except AnsibleError as e:
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ugettext
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from ..ansible.runner import CommandRunner
|
||||||
|
from ..inventory import JMSInventory
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecution(models.Model):
|
||||||
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
|
hosts = models.ManyToManyField('assets.Asset')
|
||||||
|
run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE)
|
||||||
|
command = models.TextField(verbose_name=_("Command"))
|
||||||
|
_result = models.TextField(blank=True, null=True, verbose_name=_('Result'))
|
||||||
|
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True)
|
||||||
|
is_finished = models.BooleanField(default=False)
|
||||||
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
date_start = models.DateTimeField(null=True)
|
||||||
|
date_finished = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.command[:10]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inventory(self):
|
||||||
|
return JMSInventory(self.hosts.all(), run_as=self.run_as)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def result(self):
|
||||||
|
if self._result:
|
||||||
|
return json.loads(self._result)
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@result.setter
|
||||||
|
def result(self, item):
|
||||||
|
self._result = json.dumps(item)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_success(self):
|
||||||
|
if 'error' in self.result:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_hosts_names(self):
|
||||||
|
return ','.join(self.hosts.all().values_list('hostname', flat=True))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
print('-'*10 + ' ' + ugettext('Task start') + ' ' + '-'*10)
|
||||||
|
self.date_start = timezone.now()
|
||||||
|
ok, msg = self.run_as.is_command_can_run(self.command)
|
||||||
|
if ok:
|
||||||
|
runner = CommandRunner(self.inventory)
|
||||||
|
try:
|
||||||
|
result = runner.execute(self.command, 'all')
|
||||||
|
self.result = result.results_command
|
||||||
|
except Exception as e:
|
||||||
|
print("Error occur: {}".format(e))
|
||||||
|
self.result = {"error": str(e)}
|
||||||
|
else:
|
||||||
|
msg = _("Command `{}` is forbidden ........").format(self.command)
|
||||||
|
print('\033[31m' + msg + '\033[0m')
|
||||||
|
self.result = {"error": msg}
|
||||||
|
self.is_finished = True
|
||||||
|
self.date_finished = timezone.now()
|
||||||
|
self.save()
|
||||||
|
print('-'*10 + ' ' + ugettext('Task end') + ' ' + '-'*10)
|
||||||
|
return self.result
|
|
@ -1,8 +1,19 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
from .models import Task, AdHoc, AdHocRunHistory
|
from .models import Task, AdHoc, AdHocRunHistory, CommandExecution
|
||||||
|
|
||||||
|
|
||||||
|
class CeleryResultSerializer(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField()
|
||||||
|
result = serializers.JSONField()
|
||||||
|
state = serializers.CharField(max_length=16)
|
||||||
|
|
||||||
|
|
||||||
|
class CeleryTaskSerializer(serializers.Serializer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TaskSerializer(serializers.ModelSerializer):
|
class TaskSerializer(serializers.ModelSerializer):
|
||||||
|
@ -51,3 +62,23 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer):
|
||||||
fields = super().get_field_names(declared_fields, info)
|
fields = super().get_field_names(declared_fields, info)
|
||||||
fields.extend(['summary', 'short_id'])
|
fields.extend(['summary', 'short_id'])
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionSerializer(serializers.ModelSerializer):
|
||||||
|
result = serializers.JSONField(read_only=True)
|
||||||
|
log_url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CommandExecution
|
||||||
|
fields = [
|
||||||
|
'id', 'hosts', 'run_as', 'command', 'result', 'log_url',
|
||||||
|
'is_finished', 'date_created', 'date_finished'
|
||||||
|
]
|
||||||
|
read_only_fields = [
|
||||||
|
'id', 'result', 'is_finished', 'log_url', 'date_created',
|
||||||
|
'date_finished'
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_log_url(obj):
|
||||||
|
return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id})
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue