mirror of https://github.com/jumpserver/jumpserver
Merge branch 'dev' of bitbucket.org:jumpserver/core into dev
commit
e5953e1932
|
@ -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"]
|
|
@ -20,7 +20,7 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
|
||||||
|
|
||||||
### 功能
|
### 功能
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 开始使用
|
### 开始使用
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "1.4.3"
|
__version__ = "1.4.4"
|
||||||
|
|
|
@ -17,6 +17,7 @@ from django.db import transaction
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
from common.mixins import IDInFilterMixin
|
from common.mixins import IDInFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -37,9 +38,17 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
Admin user api set, for add,delete,update,list,retrieve resource
|
Admin user api set, for add,delete,update,list,retrieve resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
filter_fields = ("name", "username")
|
||||||
|
search_fields = filter_fields
|
||||||
queryset = AdminUser.objects.all()
|
queryset = AdminUser.objects.all()
|
||||||
serializer_class = serializers.AdminUserSerializer
|
serializer_class = serializers.AdminUserSerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset().all()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
class AdminUserAuthApi(generics.UpdateAPIView):
|
||||||
|
|
|
@ -53,14 +53,14 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||||
if show_current_asset:
|
if show_current_asset:
|
||||||
self.queryset = self.queryset.filter(
|
self.queryset = self.queryset.filter(
|
||||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||||
).distinct()
|
)
|
||||||
return
|
return
|
||||||
if show_current_asset:
|
if show_current_asset:
|
||||||
self.queryset = self.queryset.filter(nodes=node).distinct()
|
self.queryset = self.queryset.filter(nodes=node)
|
||||||
else:
|
else:
|
||||||
self.queryset = self.queryset.filter(
|
self.queryset = self.queryset.filter(
|
||||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||||
).distinct()
|
)
|
||||||
|
|
||||||
def filter_admin_user_id(self):
|
def filter_admin_user_id(self):
|
||||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
|
@ -13,14 +14,20 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterViewSet(BulkModelViewSet):
|
class CommandFilterViewSet(BulkModelViewSet):
|
||||||
|
filter_fields = ("name",)
|
||||||
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
queryset = CommandFilter.objects.all()
|
queryset = CommandFilter.objects.all()
|
||||||
serializer_class = serializers.CommandFilterSerializer
|
serializer_class = serializers.CommandFilterSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterRuleViewSet(BulkModelViewSet):
|
class CommandFilterRuleViewSet(BulkModelViewSet):
|
||||||
|
filter_fields = ("content",)
|
||||||
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.CommandFilterRuleSerializer
|
serializer_class = serializers.CommandFilterRuleSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
fpk = self.kwargs.get('filter_pk')
|
fpk = self.kwargs.get('filter_pk')
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from rest_framework.views import APIView, Response
|
from rest_framework.views import APIView, Response
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
|
@ -20,6 +21,11 @@ class DomainViewSet(BulkModelViewSet):
|
||||||
queryset = Domain.objects.all()
|
queryset = Domain.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.DomainSerializer
|
serializer_class = serializers.DomainSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset().all()
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.request.query_params.get('gateway'):
|
if self.request.query_params.get('gateway'):
|
||||||
|
@ -33,11 +39,12 @@ class DomainViewSet(BulkModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class GatewayViewSet(BulkModelViewSet):
|
class GatewayViewSet(BulkModelViewSet):
|
||||||
filter_fields = ("domain",)
|
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
queryset = Gateway.objects.all()
|
queryset = Gateway.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.GatewaySerializer
|
serializer_class = serializers.GatewaySerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
|
||||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -27,8 +28,11 @@ __all__ = ['LabelViewSet']
|
||||||
|
|
||||||
|
|
||||||
class LabelViewSet(BulkModelViewSet):
|
class LabelViewSet(BulkModelViewSet):
|
||||||
|
filter_fields = ("name", "value")
|
||||||
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.LabelSerializer
|
serializer_class = serializers.LabelSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
if request.query_params.get("distinct"):
|
if request.query_params.get("distinct"):
|
||||||
|
|
|
@ -42,9 +42,16 @@ class SystemUserViewSet(BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
System user api set, for add,delete,update,list,retrieve resource
|
System user api set, for add,delete,update,list,retrieve resource
|
||||||
"""
|
"""
|
||||||
|
filter_fields = ("name", "username")
|
||||||
|
search_fields = filter_fields
|
||||||
queryset = SystemUser.objects.all()
|
queryset = SystemUser.objects.all()
|
||||||
serializer_class = serializers.SystemUserSerializer
|
serializer_class = serializers.SystemUserSerializer
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset().all()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -219,6 +219,16 @@ class Asset(OrgModelMixin):
|
||||||
'become': self.admin_user.become_info,
|
'become': self.admin_user.become_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_node(self):
|
||||||
|
from .node import Node
|
||||||
|
fake_node = Node()
|
||||||
|
fake_node.id = self.id
|
||||||
|
fake_node.key = self.id
|
||||||
|
fake_node.value = self.hostname
|
||||||
|
fake_node.asset = self
|
||||||
|
fake_node.is_node = False
|
||||||
|
return fake_node
|
||||||
|
|
||||||
def _to_secret_json(self):
|
def _to_secret_json(self):
|
||||||
"""
|
"""
|
||||||
Ansible use it create inventory, First using asset user,
|
Ansible use it create inventory, First using asset user,
|
||||||
|
|
|
@ -92,7 +92,7 @@ class Node(OrgModelMixin):
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def get_children(self, with_self=False):
|
def get_children(self, with_self=False):
|
||||||
pattern = r'^{0}$|^{}:[0-9]+$' if with_self else r'^{}:[0-9]+$'
|
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[0-9]+$'
|
||||||
return self.__class__.objects.filter(
|
return self.__class__.objects.filter(
|
||||||
key__regex=pattern.format(self.key)
|
key__regex=pattern.format(self.key)
|
||||||
)
|
)
|
||||||
|
@ -121,10 +121,10 @@ class Node(OrgModelMixin):
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
if self.is_default_node():
|
if self.is_default_node():
|
||||||
assets = Asset.objects.filter(nodes__isnull=True)
|
assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
|
||||||
else:
|
else:
|
||||||
assets = Asset.objects.filter(nodes__id=self.id)
|
assets = Asset.objects.filter(nodes__id=self.id)
|
||||||
return assets
|
return assets.distinct()
|
||||||
|
|
||||||
def get_valid_assets(self):
|
def get_valid_assets(self):
|
||||||
return self.get_assets().valid()
|
return self.get_assets().valid()
|
||||||
|
|
|
@ -93,7 +93,7 @@ $(document).ready(function(){
|
||||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
|
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
|
||||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
|
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options)
|
||||||
})
|
})
|
||||||
|
|
||||||
.on('click', '.btn_admin_user_delete', function () {
|
.on('click', '.btn_admin_user_delete', function () {
|
||||||
|
|
|
@ -66,7 +66,7 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
|
|
|
@ -95,7 +95,7 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
|
|
|
@ -98,7 +98,7 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
|
|
|
@ -62,7 +62,7 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
|
|
|
@ -47,7 +47,7 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
|
|
|
@ -100,7 +100,7 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var zTree, asset_table;
|
var zTree, asset_table, show=0;
|
||||||
var inited = false;
|
var inited = false;
|
||||||
var url;
|
var url;
|
||||||
function initTable() {
|
function initTable() {
|
||||||
|
@ -102,7 +102,7 @@ function initTable() {
|
||||||
{data: "system_users_granted", orderable: false}
|
{data: "system_users_granted", orderable: false}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
asset_table = jumpserver.initDataTable(options);
|
asset_table = jumpserver.initServerSideDataTable(options);
|
||||||
return asset_table
|
return asset_table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +183,21 @@ $(document).ready(function () {
|
||||||
$('#asset_detail_tbody').html(trs)
|
$('#asset_detail_tbody').html(trs)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (show === 0) {
|
||||||
|
$("#split-left").hide(500, function () {
|
||||||
|
$("#split-right").attr("class", "col-lg-12");
|
||||||
|
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||||
|
show = 1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$("#split-right").attr("class", "col-lg-9");
|
||||||
|
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||||
|
$("#split-left").show(500);
|
||||||
|
show = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -277,7 +277,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
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||||
form_class = LabelForm
|
form_class = LabelForm
|
||||||
success_url = reverse_lazy('assets:label-list')
|
success_url = reverse_lazy('assets:label-list')
|
||||||
success_message = create_success_msg
|
success_message = create_success_msg
|
||||||
|
disable_name = ['draw', 'search', 'limit', 'offset', '_']
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
|
@ -45,6 +46,16 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
name = form.cleaned_data.get('name')
|
||||||
|
if name in self.disable_name:
|
||||||
|
msg = _(
|
||||||
|
'Tips: Avoid using label names reserved internally: {}'
|
||||||
|
).format(', '.join(self.disable_name))
|
||||||
|
form.add_error("name", msg)
|
||||||
|
return self.form_invalid(form)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
model = Label
|
model = Label
|
||||||
|
|
|
@ -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',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -160,8 +160,12 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
if current_org.is_default():
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
else:
|
||||||
users = self.get_org_users()
|
users = self.get_org_users()
|
||||||
queryset = super().get_queryset().filter(username__in=users)
|
queryset = super().get_queryset().filter(username__in=users)
|
||||||
|
|
||||||
self.user = self.request.GET.get('user', '')
|
self.user = self.request.GET.get('user', '')
|
||||||
self.keyword = self.request.GET.get("keyword", '')
|
self.keyword = self.request.GET.get("keyword", '')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationConfig(AppConfig):
|
||||||
|
name = 'authentication'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signals_handlers
|
||||||
|
super().ready()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from .models import Client
|
||||||
|
|
||||||
|
|
||||||
|
def new_client():
|
||||||
|
"""
|
||||||
|
:return: authentication.models.Client
|
||||||
|
"""
|
||||||
|
return Client(
|
||||||
|
server_url=settings.AUTH_OPENID_SERVER_URL,
|
||||||
|
realm_name=settings.AUTH_OPENID_REALM_NAME,
|
||||||
|
client_id=settings.AUTH_OPENID_CLIENT_ID,
|
||||||
|
client_secret=settings.AUTH_OPENID_CLIENT_SECRET
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
client = new_client()
|
|
@ -0,0 +1,90 @@
|
||||||
|
# coding:utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from . import client
|
||||||
|
from common.utils import get_logger
|
||||||
|
from authentication.openid.models import OIDT_ACCESS_TOKEN
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
BACKEND_OPENID_AUTH_CODE = \
|
||||||
|
'authentication.openid.backends.OpenIDAuthorizationCodeBackend'
|
||||||
|
|
||||||
|
|
||||||
|
class BaseOpenIDAuthorizationBackend(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def user_can_authenticate(user):
|
||||||
|
"""
|
||||||
|
Reject users with is_active=False. Custom user models that don't have
|
||||||
|
that attribute are allowed.
|
||||||
|
"""
|
||||||
|
is_active = getattr(user, 'is_active', None)
|
||||||
|
return is_active or is_active is None
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
try:
|
||||||
|
user = UserModel._default_manager.get(pk=user_id)
|
||||||
|
except UserModel.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return user if self.user_can_authenticate(user) else None
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
|
||||||
|
|
||||||
|
def authenticate(self, request, **kwargs):
|
||||||
|
logger.info('1.openid code backend')
|
||||||
|
|
||||||
|
code = kwargs.get('code')
|
||||||
|
redirect_uri = kwargs.get('redirect_uri')
|
||||||
|
|
||||||
|
if not code or not redirect_uri:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
oidt_profile = client.update_or_create_from_code(
|
||||||
|
code=code,
|
||||||
|
redirect_uri=redirect_uri
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Check openid user single logout or not with access_token
|
||||||
|
request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token
|
||||||
|
|
||||||
|
user = oidt_profile.user
|
||||||
|
|
||||||
|
return user if self.user_can_authenticate(user) else None
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend):
|
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
|
logger.info('2.openid password backend')
|
||||||
|
|
||||||
|
if not settings.AUTH_OPENID:
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif not username:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
oidt_profile = client.update_or_create_from_password(
|
||||||
|
username=username, password=password
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
user = oidt_profile.user
|
||||||
|
return user if self.user_can_authenticate(user) else None
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# coding:utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import logout
|
||||||
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
from django.contrib.auth import BACKEND_SESSION_KEY
|
||||||
|
|
||||||
|
from . import client
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .backends import BACKEND_OPENID_AUTH_CODE
|
||||||
|
from authentication.openid.models import OIDT_ACCESS_TOKEN
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
||||||
|
"""
|
||||||
|
Check openid user single logout (with access_token)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def process_request(self, request):
|
||||||
|
|
||||||
|
# Don't need openid auth if AUTH_OPENID is False
|
||||||
|
if not settings.AUTH_OPENID:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Don't need check single logout if user not authenticated
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return
|
||||||
|
|
||||||
|
elif request.session[BACKEND_SESSION_KEY] != BACKEND_OPENID_AUTH_CODE:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check openid user single logout or not with access_token
|
||||||
|
try:
|
||||||
|
client.openid_connect_client.userinfo(
|
||||||
|
token=request.session.get(OIDT_ACCESS_TOKEN))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logout(request)
|
||||||
|
logger.error(e)
|
|
@ -0,0 +1,159 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from keycloak.realm import KeycloakRealm
|
||||||
|
from keycloak.keycloak_openid import KeycloakOpenID
|
||||||
|
from ..signals import post_create_openid_user
|
||||||
|
|
||||||
|
OIDT_ACCESS_TOKEN = 'oidt_access_token'
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIDTokenProfile(object):
|
||||||
|
|
||||||
|
def __init__(self, user, access_token, refresh_token):
|
||||||
|
"""
|
||||||
|
:param user: User object
|
||||||
|
:param access_token:
|
||||||
|
:param refresh_token:
|
||||||
|
"""
|
||||||
|
self.user = user
|
||||||
|
self.access_token = access_token
|
||||||
|
self.refresh_token = refresh_token
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{}'s OpenID token profile".format(self.user.username)
|
||||||
|
|
||||||
|
|
||||||
|
class Client(object):
|
||||||
|
|
||||||
|
def __init__(self, server_url, realm_name, client_id, client_secret):
|
||||||
|
self.server_url = server_url
|
||||||
|
self.realm_name = realm_name
|
||||||
|
self.client_id = client_id
|
||||||
|
self.client_secret = client_secret
|
||||||
|
self.realm = self.new_realm()
|
||||||
|
self.openid_client = self.new_openid_client()
|
||||||
|
self.openid_connect_client = self.new_openid_connect_client()
|
||||||
|
|
||||||
|
def new_realm(self):
|
||||||
|
"""
|
||||||
|
:param authentication.openid.models.Realm realm:
|
||||||
|
:return keycloak.realm.Realm:
|
||||||
|
"""
|
||||||
|
return KeycloakRealm(
|
||||||
|
server_url=self.server_url,
|
||||||
|
realm_name=self.realm_name,
|
||||||
|
headers={}
|
||||||
|
)
|
||||||
|
|
||||||
|
def new_openid_connect_client(self):
|
||||||
|
"""
|
||||||
|
:rtype: keycloak.openid_connect.KeycloakOpenidConnect
|
||||||
|
"""
|
||||||
|
openid_connect = self.realm.open_id_connect(
|
||||||
|
client_id=self.client_id,
|
||||||
|
client_secret=self.client_secret
|
||||||
|
)
|
||||||
|
return openid_connect
|
||||||
|
|
||||||
|
def new_openid_client(self):
|
||||||
|
"""
|
||||||
|
:rtype: keycloak.keycloak_openid.KeycloakOpenID
|
||||||
|
"""
|
||||||
|
|
||||||
|
return KeycloakOpenID(
|
||||||
|
server_url='%sauth/' % self.server_url,
|
||||||
|
realm_name=self.realm_name,
|
||||||
|
client_id=self.client_id,
|
||||||
|
client_secret_key=self.client_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_or_create_from_password(self, username, password):
|
||||||
|
"""
|
||||||
|
Update or create an user based on an authentication username and password.
|
||||||
|
|
||||||
|
:param str username: authentication username
|
||||||
|
:param str password: authentication password
|
||||||
|
:return: authentication.models.OpenIDTokenProfile
|
||||||
|
"""
|
||||||
|
token_response = self.openid_client.token(
|
||||||
|
username=username, password=password
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._update_or_create(token_response=token_response)
|
||||||
|
|
||||||
|
def update_or_create_from_code(self, code, redirect_uri):
|
||||||
|
"""
|
||||||
|
Update or create an user based on an authentication code.
|
||||||
|
Response as specified in:
|
||||||
|
|
||||||
|
https://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||||
|
|
||||||
|
:param str code: authentication code
|
||||||
|
:param str redirect_uri:
|
||||||
|
:rtype: authentication.models.OpenIDTokenProfile
|
||||||
|
"""
|
||||||
|
|
||||||
|
token_response = self.openid_connect_client.authorization_code(
|
||||||
|
code=code, redirect_uri=redirect_uri)
|
||||||
|
|
||||||
|
return self._update_or_create(token_response=token_response)
|
||||||
|
|
||||||
|
def _update_or_create(self, token_response):
|
||||||
|
"""
|
||||||
|
Update or create an user based on a token response.
|
||||||
|
|
||||||
|
`token_response` contains the items returned by the OpenIDConnect Token API
|
||||||
|
end-point:
|
||||||
|
- id_token
|
||||||
|
- access_token
|
||||||
|
- expires_in
|
||||||
|
- refresh_token
|
||||||
|
- refresh_expires_in
|
||||||
|
|
||||||
|
:param dict token_response:
|
||||||
|
:rtype: authentication.openid.models.OpenIDTokenProfile
|
||||||
|
"""
|
||||||
|
|
||||||
|
userinfo = self.openid_connect_client.userinfo(
|
||||||
|
token=token_response['access_token'])
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
user, _ = get_user_model().objects.update_or_create(
|
||||||
|
username=userinfo.get('preferred_username', ''),
|
||||||
|
defaults={
|
||||||
|
'email': userinfo.get('email', ''),
|
||||||
|
'first_name': userinfo.get('given_name', ''),
|
||||||
|
'last_name': userinfo.get('family_name', '')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
oidt_profile = OpenIDTokenProfile(
|
||||||
|
user=user,
|
||||||
|
access_token=token_response['access_token'],
|
||||||
|
refresh_token=token_response['refresh_token'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
post_create_openid_user.send(sender=user.__class__, user=user)
|
||||||
|
|
||||||
|
return oidt_profile
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.client_id
|
||||||
|
|
||||||
|
|
||||||
|
class Nonce(object):
|
||||||
|
"""
|
||||||
|
The openid-login is stored in cache as a temporary object, recording the
|
||||||
|
user's redirect_uri and next_pat
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, redirect_uri, next_path):
|
||||||
|
import uuid
|
||||||
|
self.state = uuid.uuid4()
|
||||||
|
self.redirect_uri = redirect_uri
|
||||||
|
self.next_path = next_path
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.views.generic.base import RedirectView
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.http.response import (
|
||||||
|
HttpResponseBadRequest,
|
||||||
|
HttpResponseServerError,
|
||||||
|
HttpResponseRedirect
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import client
|
||||||
|
from .models import Nonce
|
||||||
|
from users.models import LoginLog
|
||||||
|
from users.tasks import write_login_log_async
|
||||||
|
from common.utils import get_request_ip
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_site_url():
|
||||||
|
return settings.BASE_SITE_URL
|
||||||
|
|
||||||
|
|
||||||
|
class LoginView(RedirectView):
|
||||||
|
|
||||||
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
nonce = Nonce(
|
||||||
|
redirect_uri=get_base_site_url() + reverse(
|
||||||
|
"authentication:openid-login-complete"),
|
||||||
|
|
||||||
|
next_path=self.request.GET.get('next')
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.set(str(nonce.state), nonce, 24*3600)
|
||||||
|
|
||||||
|
self.request.session['openid_state'] = str(nonce.state)
|
||||||
|
|
||||||
|
authorization_url = client.openid_connect_client.\
|
||||||
|
authorization_url(
|
||||||
|
redirect_uri=nonce.redirect_uri, scope='code',
|
||||||
|
state=str(nonce.state)
|
||||||
|
)
|
||||||
|
|
||||||
|
return authorization_url
|
||||||
|
|
||||||
|
|
||||||
|
class LoginCompleteView(RedirectView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if 'error' in request.GET:
|
||||||
|
return HttpResponseServerError(self.request.GET['error'])
|
||||||
|
|
||||||
|
if 'code' not in self.request.GET and 'state' not in self.request.GET:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
if self.request.GET['state'] != self.request.session['openid_state']:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
nonce = cache.get(self.request.GET['state'])
|
||||||
|
|
||||||
|
if not nonce:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
user = authenticate(
|
||||||
|
request=self.request,
|
||||||
|
code=self.request.GET['code'],
|
||||||
|
redirect_uri=nonce.redirect_uri
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.delete(str(nonce.state))
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
login(self.request, user)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': user.username,
|
||||||
|
'mfa': int(user.otp_enabled),
|
||||||
|
'reason': LoginLog.REASON_NOTHING,
|
||||||
|
'status': True
|
||||||
|
}
|
||||||
|
self.write_login_log(data)
|
||||||
|
|
||||||
|
return HttpResponseRedirect(nonce.next_path or '/')
|
||||||
|
|
||||||
|
def write_login_log(self, data):
|
||||||
|
login_ip = get_request_ip(self.request)
|
||||||
|
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||||
|
tmp_data = {
|
||||||
|
'ip': login_ip,
|
||||||
|
'type': 'W',
|
||||||
|
'user_agent': user_agent
|
||||||
|
}
|
||||||
|
data.update(tmp_data)
|
||||||
|
write_login_log_async.delay(**data)
|
|
@ -0,0 +1,4 @@
|
||||||
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
|
post_create_openid_user = Signal(providing_args=('user',))
|
|
@ -0,0 +1,33 @@
|
||||||
|
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 .openid import client
|
||||||
|
from .signals import post_create_openid_user
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(user_logged_out)
|
||||||
|
def on_user_logged_out(sender, request, user, **kwargs):
|
||||||
|
if not settings.AUTH_OPENID:
|
||||||
|
return
|
||||||
|
|
||||||
|
query = QueryDict('', mutable=True)
|
||||||
|
query.update({
|
||||||
|
'redirect_uri': settings.BASE_SITE_URL
|
||||||
|
})
|
||||||
|
|
||||||
|
openid_logout_url = "%s?%s" % (
|
||||||
|
client.openid_connect_client.get_url(
|
||||||
|
name='end_session_endpoint'),
|
||||||
|
query.urlencode()
|
||||||
|
)
|
||||||
|
|
||||||
|
request.COOKIES['next'] = openid_logout_url
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_create_openid_user)
|
||||||
|
def on_post_create_openid_user(sender, user=None, **kwargs):
|
||||||
|
if user and user.username != 'admin':
|
||||||
|
user.source = user.SOURCE_OPENID
|
||||||
|
user.save()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# coding:utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
from authentication.openid import views
|
||||||
|
|
||||||
|
app_name = 'authentication'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# openid
|
||||||
|
path('openid/login/', views.LoginView.as_view(), name='openid-login'),
|
||||||
|
path('openid/login/complete/', views.LoginCompleteView.as_view(),
|
||||||
|
name='openid-login-complete'),
|
||||||
|
|
||||||
|
# other
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
import json
|
import json
|
||||||
|
import jms_storage
|
||||||
|
|
||||||
from rest_framework.views import Response, APIView
|
from rest_framework.views import Response, APIView
|
||||||
from ldap3 import Server, Connection
|
from ldap3 import Server, Connection
|
||||||
|
@ -8,8 +11,9 @@ from django.core.mail import get_connection, send_mail
|
||||||
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 .permissions import IsOrgAdmin
|
from .permissions import IsOrgAdmin, IsSuperUser
|
||||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||||
|
from .models import Setting
|
||||||
|
|
||||||
|
|
||||||
class MailTestingAPI(APIView):
|
class MailTestingAPI(APIView):
|
||||||
|
@ -85,13 +89,89 @@ class LDAPTestingAPI(APIView):
|
||||||
return Response({"error": str(serializer.errors)}, status=401)
|
return Response({"error": str(serializer.errors)}, status=401)
|
||||||
|
|
||||||
|
|
||||||
|
class ReplayStorageCreateAPI(APIView):
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
storage_data = request.data
|
||||||
|
|
||||||
|
if storage_data.get('TYPE') == 'ceph':
|
||||||
|
port = storage_data.get('PORT')
|
||||||
|
if port.isdigit():
|
||||||
|
storage_data['PORT'] = int(storage_data.get('PORT'))
|
||||||
|
|
||||||
|
storage_name = storage_data.pop('NAME')
|
||||||
|
data = {storage_name: storage_data}
|
||||||
|
|
||||||
|
if not self.is_valid(storage_data):
|
||||||
|
return Response({"error": _("Error: Account invalid")}, status=401)
|
||||||
|
|
||||||
|
Setting.save_storage('TERMINAL_REPLAY_STORAGE', data)
|
||||||
|
return Response({"msg": _('Create succeed')}, status=200)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid(storage_data):
|
||||||
|
if storage_data.get('TYPE') == 'server':
|
||||||
|
return True
|
||||||
|
storage = jms_storage.get_object_storage(storage_data)
|
||||||
|
target = 'tests.py'
|
||||||
|
src = os.path.join(settings.BASE_DIR, 'common', target)
|
||||||
|
return storage.is_valid(src, target)
|
||||||
|
|
||||||
|
|
||||||
|
class ReplayStorageDeleteAPI(APIView):
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
storage_name = str(request.data.get('name'))
|
||||||
|
Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name)
|
||||||
|
return Response({"msg": _('Delete succeed')}, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandStorageCreateAPI(APIView):
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
storage_data = request.data
|
||||||
|
storage_name = storage_data.pop('NAME')
|
||||||
|
data = {storage_name: storage_data}
|
||||||
|
if not self.is_valid(storage_data):
|
||||||
|
return Response({"error": _("Error: Account invalid")}, status=401)
|
||||||
|
|
||||||
|
Setting.save_storage('TERMINAL_COMMAND_STORAGE', data)
|
||||||
|
return Response({"msg": _('Create succeed')}, status=200)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid(storage_data):
|
||||||
|
if storage_data.get('TYPE') == 'server':
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
storage = jms_storage.get_log_storage(storage_data)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return storage.ping()
|
||||||
|
|
||||||
|
|
||||||
|
class CommandStorageDeleteAPI(APIView):
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
storage_name = str(request.data.get('name'))
|
||||||
|
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
|
||||||
|
return Response({"msg": _('Delete succeed')}, status=200)
|
||||||
|
|
||||||
|
|
||||||
class DjangoSettingsAPI(APIView):
|
class DjangoSettingsAPI(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if not settings.DEBUG:
|
if not settings.DEBUG:
|
||||||
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 not i:
|
||||||
|
continue
|
||||||
|
for k, v in i.__dict__.items():
|
||||||
if k and k.isupper():
|
if k and k.isupper():
|
||||||
try:
|
try:
|
||||||
json.dumps(v)
|
json.dumps(v)
|
||||||
|
|
|
@ -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):
|
||||||
|
@ -135,32 +134,24 @@ class TerminalSettingForm(BaseForm):
|
||||||
('hostname', _('Hostname')),
|
('hostname', _('Hostname')),
|
||||||
('ip', _('IP')),
|
('ip', _('IP')),
|
||||||
)
|
)
|
||||||
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
|
|
||||||
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
|
|
||||||
)
|
|
||||||
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
|
|
||||||
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
|
|
||||||
)
|
|
||||||
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
|
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
|
||||||
initial=True, required=False, label=_("Password auth")
|
initial=True, required=False, label=_("Password auth")
|
||||||
)
|
)
|
||||||
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
|
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
|
||||||
initial=True, required=False, label=_("Public key auth")
|
initial=True, required=False, label=_("Public key auth")
|
||||||
)
|
)
|
||||||
TERMINAL_COMMAND_STORAGE = FormEncryptDictField(
|
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
|
||||||
label=_("Command storage"), help_text=_(
|
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
|
||||||
"Set terminal storage setting, `default` is the using as default,"
|
|
||||||
"You can set other storage and some terminal using"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
TERMINAL_REPLAY_STORAGE = FormEncryptDictField(
|
|
||||||
label=_("Replay storage"), help_text=_(
|
|
||||||
"Set replay storage setting, `default` is the using as default,"
|
|
||||||
"You can set other storage and some terminal using"
|
|
||||||
)
|
)
|
||||||
|
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
|
||||||
|
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalCommandStorage(BaseForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SecuritySettingForm(BaseForm):
|
class SecuritySettingForm(BaseForm):
|
||||||
# MFA global setting
|
# MFA global setting
|
||||||
SECURITY_MFA_AUTH = forms.BooleanField(
|
SECURITY_MFA_AUTH = forms.BooleanField(
|
||||||
|
@ -181,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"),
|
||||||
|
@ -193,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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -117,6 +117,3 @@ class DatetimeSearchMixin:
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.get_date_range()
|
self.get_date_range()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
@ -67,6 +62,32 @@ class Setting(models.Model):
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
raise ValueError("Json dump error: {}".format(str(e)))
|
raise ValueError("Json dump error: {}".format(str(e)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_storage(cls, name, data):
|
||||||
|
obj = cls.objects.filter(name=name).first()
|
||||||
|
if not obj:
|
||||||
|
obj = cls()
|
||||||
|
obj.name = name
|
||||||
|
obj.encrypted = True
|
||||||
|
obj.cleaned_value = data
|
||||||
|
else:
|
||||||
|
value = obj.cleaned_value
|
||||||
|
value.update(data)
|
||||||
|
obj.cleaned_value = value
|
||||||
|
obj.save()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_storage(cls, name, storage_name):
|
||||||
|
obj = cls.objects.filter(name=name).first()
|
||||||
|
if not obj:
|
||||||
|
return False
|
||||||
|
value = obj.cleaned_value
|
||||||
|
value.pop(storage_name, '')
|
||||||
|
obj.cleaned_value = value
|
||||||
|
obj.save()
|
||||||
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def refresh_all_settings(cls):
|
def refresh_all_settings(cls):
|
||||||
try:
|
try:
|
||||||
|
@ -78,22 +99,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,25 +26,43 @@ 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 hasattr(instance, 'created_by') and not instance.created_by:
|
if hasattr(instance, 'created_by') and not instance.created_by:
|
||||||
|
|
|
@ -22,10 +22,6 @@ 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] = settings.EMAIL_SUBJECT_PREFIX + args[0]
|
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
|
||||||
|
|
|
@ -75,32 +75,6 @@
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
})
|
|
||||||
.on("click", ".btn-test", function () {
|
|
||||||
var data = {};
|
|
||||||
var form = $("form").serializeArray();
|
|
||||||
$.each(form, function (i, field) {
|
|
||||||
data[field.name] = field.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
var the_url = "{% url 'api-common:mail-testing' %}";
|
|
||||||
|
|
||||||
function error(message) {
|
|
||||||
toastr.error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
function success(message) {
|
|
||||||
toastr.success(message.msg)
|
|
||||||
}
|
|
||||||
APIUpdateAttr({
|
|
||||||
url: the_url,
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
method: "POST",
|
|
||||||
flash_message: false,
|
|
||||||
success: success,
|
|
||||||
error: error
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
{#{% extends 'base.html' %}#}
|
||||||
|
{% extends '_base_create_update.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="ibox float-e-margins">
|
||||||
|
<div class="ibox-title">
|
||||||
|
<h5>{{ action }}</h5>
|
||||||
|
<div class="ibox-tools">
|
||||||
|
<a class="collapse-link">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
<i class="fa fa-wrench"></i>
|
||||||
|
</a>
|
||||||
|
<a class="close-link">
|
||||||
|
<i class="fa fa-times"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ibox-content">
|
||||||
|
<form action="" method="POST" class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<select id="id_type" class="selector form-control">
|
||||||
|
<option value ="server" selected="selected">server</option>
|
||||||
|
<option value ="es">es (elasticsearch)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_name" class="form-control" type="text" name="NAME" value="">
|
||||||
|
<div class="help-block">* required</div>
|
||||||
|
<div id="id_error" style="color: red;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_hosts">{% trans "Hosts" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_hosts" class="form-control" type="text" name="HOSTS" value="">
|
||||||
|
<div class="help-block">{% trans 'Tips: If there are multiple hosts, separate them with a comma (,)' %}</div>
|
||||||
|
<div class="help-block">eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{# <div class="form-group" style="display: none;" >#}
|
||||||
|
{# <label class="col-md-2 control-label" for="id_other">{% trans "Other" %}</label>#}
|
||||||
|
{# <div class="col-md-9">#}
|
||||||
|
{# <input id="id_other" class="form-control" type="text" name="OTHER" value="">#}
|
||||||
|
{# </div>#}
|
||||||
|
{# </div>#}
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_bucket">{% trans "Index" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_index" class="form-control" type="text" name="INDEX" value="jumpserver">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_doc_type">{% trans "Doc type" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_doc_type" class="form-control" type="text" name="DOC_TYPE" value="command_store">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
<a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var field_of_all, need_get_field_of_server, need_get_field_of_es;
|
||||||
|
|
||||||
|
function showField(field){
|
||||||
|
$.each(field, function(index, value){
|
||||||
|
$(value).parent('div').parent('div').css('display', '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hiddenField(field){
|
||||||
|
$.each(field, function(index, value){
|
||||||
|
$(value).parent('div').parent('div').css('display', 'none');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFieldByType(type){
|
||||||
|
|
||||||
|
if(type === 'server'){
|
||||||
|
return need_get_field_of_server
|
||||||
|
}
|
||||||
|
else if(type === 'es'){
|
||||||
|
return need_get_field_of_es
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ajaxAPI(url, data, success, error){
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
data: data,
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
success: success,
|
||||||
|
error: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var name_id = '#id_name';
|
||||||
|
var hosts_id = '#id_hosts';
|
||||||
|
{#var other_id = '#id_other';#}
|
||||||
|
var index_id = '#id_index';
|
||||||
|
var doc_type_id = '#id_doc_type';
|
||||||
|
|
||||||
|
field_of_all = [name_id, hosts_id, index_id, doc_type_id];
|
||||||
|
need_get_field_of_server = [name_id];
|
||||||
|
need_get_field_of_es = [name_id, hosts_id, index_id, doc_type_id];
|
||||||
|
})
|
||||||
|
.on('change', '.selector', function(){
|
||||||
|
var type = $('.selector').val();
|
||||||
|
console.log(type);
|
||||||
|
hiddenField(field_of_all);
|
||||||
|
var field = getFieldByType(type);
|
||||||
|
showField(field)
|
||||||
|
})
|
||||||
|
|
||||||
|
.on('click', '#id_submit_button', function(){
|
||||||
|
var type = $('.selector').val();
|
||||||
|
var field = getFieldByType(type);
|
||||||
|
var data = {'TYPE': type};
|
||||||
|
$.each(field, function(index, id_field){
|
||||||
|
var name = $(id_field).attr('name');
|
||||||
|
var value = $(id_field).val();
|
||||||
|
if(name === 'HOSTS'){
|
||||||
|
data[name] = value.split(',');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
data[name] = value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var url = "{% url 'api-common:command-storage-create' %}";
|
||||||
|
var success = function(data, textStatus) {
|
||||||
|
console.log(data, textStatus);
|
||||||
|
location = "{% url 'common:terminal-setting' %}";
|
||||||
|
};
|
||||||
|
var error = function(data, textStatus) {
|
||||||
|
var error_msg = data.responseJSON.error;
|
||||||
|
$('#id_error').html(error_msg)
|
||||||
|
};
|
||||||
|
ajaxAPI(url, JSON.stringify(data), success, error)
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,248 @@
|
||||||
|
{#{% extends 'base.html' %}#}
|
||||||
|
{% extends '_base_create_update.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="ibox float-e-margins">
|
||||||
|
<div class="ibox-title">
|
||||||
|
<h5>{{ action }}</h5>
|
||||||
|
<div class="ibox-tools">
|
||||||
|
<a class="collapse-link">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
<i class="fa fa-wrench"></i>
|
||||||
|
</a>
|
||||||
|
<a class="close-link">
|
||||||
|
<i class="fa fa-times"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ibox-content">
|
||||||
|
<form action="" method="POST" class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<select id="id_type" class="selector form-control">
|
||||||
|
<option value ="server" selected="selected">server</option>
|
||||||
|
<option value ="s3">s3</option>
|
||||||
|
<option value="oss">oss</option>
|
||||||
|
<option value ="azure">azure</option>
|
||||||
|
<option value="ceph">ceph</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_name" class="form-control" type="text" name="NAME" value="">
|
||||||
|
<div class="help-block">* required</div>
|
||||||
|
<div id="id_error" style="color: red;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_host">{% trans "Host" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_host" class="form-control" type="text" name="HOSTNAME" value="" placeholder="Host">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_port">{% trans "Port" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_port" class="form-control" type="text" name="PORT" value="" placeholder="7480">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_bucket">{% trans "Bucket" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_bucket" class="form-control" type="text" name="BUCKET" value="" placeholder="Bucket">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_access_key">{% trans "Access key" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_access_key" class="form-control" type="text" name="ACCESS_KEY" value="" placeholder="Access key">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_secret_key">{% trans "Secret key" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_secret_key" class="form-control" type="text" name="SECRET_KEY" value="", placeholder="Secret key">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_container_name">{% trans "Container name" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_container_name" class="form-control" type="text" name="CONTAINER_NAME" value="" placeholder="Container">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_account_name">{% trans "Account name" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_account_name" class="form-control" type="text" name="ACCOUNT_NAME" value="" placeholder="Account name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_account_key">{% trans "Account key" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_account_key" class="form-control" type="text" name="ACCOUNT_KEY" value="" placeholder="Account key">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_endpoint">{% trans "Endpoint" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_endpoint" class="form-control" type="text" name="ENDPOINT" value="" placeholder="Endpoint">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_endpoint_suffix">{% trans "Endpoint suffix" %}</label>
|
||||||
|
{# <div class="col-md-9">#}
|
||||||
|
{# <input id="id_endpoint_suffix" class="form-control" type="text" name="ENDPOINT_SUFFIX" value="">#}
|
||||||
|
{# <div class="help-block">{% trans '' %}</div>#}
|
||||||
|
{# </div>#}
|
||||||
|
<div class="col-md-9">
|
||||||
|
<select id="id_endpoint_suffix" name="ENDPOINT_SUFFIX" class="endpoint-suffix-selector form-control">
|
||||||
|
<option value="core.chinacloudapi.cn" selected="selected">core.chinacloudapi.cn</option>
|
||||||
|
<option value="core.windows.net">core.windows.net</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: none;" >
|
||||||
|
<label class="col-md-2 control-label" for="id_region">{% trans "Region" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="id_region" class="form-control" type="text" name="REGION" value="" placeholder="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
<a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var field_of_all, need_get_field_of_server, need_get_field_of_s3,
|
||||||
|
need_get_field_of_oss, need_get_field_of_azure, need_get_field_of_ceph;
|
||||||
|
|
||||||
|
function showField(field){
|
||||||
|
$.each(field, function(index, value){
|
||||||
|
$(value).parent('div').parent('div').css('display', '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hiddenField(field){
|
||||||
|
$.each(field, function(index, value){
|
||||||
|
$(value).parent('div').parent('div').css('display', 'none');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFieldByType(type){
|
||||||
|
|
||||||
|
if(type === 'server'){
|
||||||
|
return need_get_field_of_server
|
||||||
|
}
|
||||||
|
else if(type === 's3'){
|
||||||
|
return need_get_field_of_s3
|
||||||
|
}
|
||||||
|
else if(type === 'oss'){
|
||||||
|
return need_get_field_of_oss
|
||||||
|
}
|
||||||
|
else if(type === 'azure'){
|
||||||
|
return need_get_field_of_azure
|
||||||
|
}
|
||||||
|
else if(type === 'ceph'){
|
||||||
|
return need_get_field_of_ceph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ajaxAPI(url, data, success, error){
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
data: data,
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
success: success,
|
||||||
|
error: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var name_id = '#id_name';
|
||||||
|
var host_id = '#id_host';
|
||||||
|
var port_id = '#id_port';
|
||||||
|
var bucket_id = '#id_bucket';
|
||||||
|
var access_key_id = '#id_access_key';
|
||||||
|
var secret_key_id = '#id_secret_key';
|
||||||
|
var container_name_id = '#id_container_name';
|
||||||
|
var account_name_id = '#id_account_name';
|
||||||
|
var account_key_id = '#id_account_key';
|
||||||
|
var endpoint_id = '#id_endpoint';
|
||||||
|
var endpoint_suffix_id = '#id_endpoint_suffix';
|
||||||
|
var region_id = '#id_region';
|
||||||
|
|
||||||
|
field_of_all = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, container_name_id, account_name_id, account_key_id, endpoint_id, endpoint_suffix_id, region_id];
|
||||||
|
need_get_field_of_server = [name_id];
|
||||||
|
need_get_field_of_s3 = [name_id, bucket_id, access_key_id, secret_key_id, region_id];
|
||||||
|
need_get_field_of_oss = [name_id, bucket_id, access_key_id, secret_key_id, endpoint_id];
|
||||||
|
need_get_field_of_azure = [name_id, container_name_id, account_name_id, account_key_id, endpoint_suffix_id];
|
||||||
|
need_get_field_of_ceph = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, region_id];
|
||||||
|
})
|
||||||
|
.on('change', '.selector', function(){
|
||||||
|
var type = $('.selector').val();
|
||||||
|
console.log(type);
|
||||||
|
hiddenField(field_of_all);
|
||||||
|
var field = getFieldByType(type);
|
||||||
|
showField(field)
|
||||||
|
})
|
||||||
|
|
||||||
|
.on('click', '#id_submit_button', function(){
|
||||||
|
var type = $('.selector').val();
|
||||||
|
var field = getFieldByType(type);
|
||||||
|
var data = {'TYPE': type};
|
||||||
|
$.each(field, function(index, id_field){
|
||||||
|
var name = $(id_field).attr('name');
|
||||||
|
data[name] = $(id_field).val();
|
||||||
|
});
|
||||||
|
var url = "{% url 'api-common:replay-storage-create' %}";
|
||||||
|
var success = function(data, textStatus) {
|
||||||
|
location = "{% url 'common:terminal-setting' %}";
|
||||||
|
};
|
||||||
|
var error = function(data, textStatus) {
|
||||||
|
var error_msg = data.responseJSON.error;
|
||||||
|
$('#id_error').html(error_msg)
|
||||||
|
};
|
||||||
|
ajaxAPI(url, JSON.stringify(data), success, error)
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -63,6 +63,14 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
<button id="submit_button" class="btn btn-primary"
|
||||||
|
type="submit">{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
||||||
<h3>{% trans "Command storage" %}</h3>
|
<h3>{% trans "Command storage" %}</h3>
|
||||||
|
@ -71,6 +79,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Name' %}</th>
|
<th>{% trans 'Name' %}</th>
|
||||||
<th>{% trans 'Type' %}</th>
|
<th>{% trans 'Type' %}</th>
|
||||||
|
<th>{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -78,10 +87,13 @@
|
||||||
<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>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<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>
|
||||||
<table class="table table-hover " id="task-history-list-table">
|
<table class="table table-hover " id="task-history-list-table">
|
||||||
|
@ -89,6 +101,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Name' %}</th>
|
<th>{% trans 'Name' %}</th>
|
||||||
<th>{% trans 'Type' %}</th>
|
<th>{% trans 'Type' %}</th>
|
||||||
|
<th>{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -96,18 +109,14 @@
|
||||||
<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>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<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>
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-4 col-sm-offset-2">
|
|
||||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
|
||||||
<button id="submit_button" class="btn btn-primary"
|
|
||||||
type="submit">{% trans 'Submit' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,40 +125,63 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
|
||||||
})
|
|
||||||
.on("click", ".btn-test", function () {
|
|
||||||
var data = {};
|
|
||||||
var form = $("form").serializeArray();
|
|
||||||
$.each(form, function (i, field) {
|
|
||||||
data[field.name] = field.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
var the_url = "{% url 'api-common:ldap-testing' %}";
|
function ajaxAPI(url, data, success, error, method){
|
||||||
|
$.ajax({
|
||||||
function error(message) {
|
url: url,
|
||||||
toastr.error(message)
|
data: data,
|
||||||
}
|
method: method,
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
function success(message) {
|
|
||||||
toastr.success(message.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
APIUpdateAttr({
|
|
||||||
url: the_url,
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
method: "POST",
|
|
||||||
flash_message: false,
|
|
||||||
success: success,
|
success: success,
|
||||||
error: error
|
error: error
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.on('click', '', function () {
|
}
|
||||||
|
|
||||||
})
|
function deleteStorage($this, the_url){
|
||||||
</script>
|
var name = $this.data('name');
|
||||||
|
function doDelete(){
|
||||||
|
console.log('delete storage');
|
||||||
|
var data = {"name": name};
|
||||||
|
var method = 'POST';
|
||||||
|
var success = function(){
|
||||||
|
$this.parent().parent().remove();
|
||||||
|
toastr.success("{% trans 'Delete succeed' %}");
|
||||||
|
};
|
||||||
|
var error = function(){
|
||||||
|
toastr.error("{% trans 'Delete failed' %}");
|
||||||
|
};
|
||||||
|
ajaxAPI(the_url, JSON.stringify(data), success, error, method);
|
||||||
|
}
|
||||||
|
swal({
|
||||||
|
title: "{% trans 'Are you sure about deleting it?' %}",
|
||||||
|
text: " [" + name + "] ",
|
||||||
|
type: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: "{% trans 'Cancel' %}",
|
||||||
|
confirmButtonColor: "#ed5565",
|
||||||
|
confirmButtonText: "{% trans 'Confirm' %}",
|
||||||
|
closeOnConfirm: true
|
||||||
|
}, function () {
|
||||||
|
doDelete()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
})
|
||||||
|
.on('click', '.btn-del-replay', function(){
|
||||||
|
var $this = $(this);
|
||||||
|
var the_url = "{% url 'api-common:replay-storage-delete' %}";
|
||||||
|
deleteStorage($this, the_url);
|
||||||
|
})
|
||||||
|
.on('click', '.btn-del-command', function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var the_url = "{% url 'api-common:command-storage-delete' %}";
|
||||||
|
deleteStorage($this, the_url)
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -9,5 +9,9 @@ app_name = 'common'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
|
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
|
||||||
path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
|
path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
|
||||||
# path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'),
|
||||||
|
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/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
|
||||||
|
path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -11,5 +11,7 @@ urlpatterns = [
|
||||||
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
||||||
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
|
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
|
||||||
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
|
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
|
||||||
|
url(r'^terminal/replay-storage/create$', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'),
|
||||||
|
url(r'^terminal/command-storage/create$', views.CommandStorageCreateView.as_view(), name='command-storage-create'),
|
||||||
url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
|
url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -37,7 +37,8 @@ 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:
|
||||||
url = settings.SITE_URL.strip('/') + url
|
site_url = settings.SITE_URL
|
||||||
|
url = site_url.strip('/') + url
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
@ -387,6 +388,20 @@ def get_request_ip(request):
|
||||||
return login_ip
|
return login_ip
|
||||||
|
|
||||||
|
|
||||||
|
def get_command_storage_setting():
|
||||||
|
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
||||||
|
value = settings.TERMINAL_COMMAND_STORAGE
|
||||||
|
value.update(default)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_replay_storage_setting():
|
||||||
|
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
|
||||||
|
value = settings.TERMINAL_REPLAY_STORAGE
|
||||||
|
value.update(default)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class TeeObj:
|
class TeeObj:
|
||||||
origin_stdout = sys.stdout
|
origin_stdout = sys.stdout
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,11 @@ 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 .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
|
||||||
|
|
||||||
|
|
||||||
class BasicSettingView(SuperUserRequiredMixin, TemplateView):
|
class BasicSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
|
@ -27,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:
|
||||||
|
@ -79,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:
|
||||||
|
@ -95,14 +92,15 @@ 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 = settings.TERMINAL_COMMAND_STORAGE
|
command_storage = utils.get_command_storage_setting()
|
||||||
replay_storage = settings.TERMINAL_REPLAY_STORAGE
|
replay_storage = utils.get_replay_storage_setting()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'app': _('Settings'),
|
'app': _('Settings'),
|
||||||
'action': _('Terminal setting'),
|
'action': _('Terminal setting'),
|
||||||
'form': self.form_class(),
|
'form': self.form_class(),
|
||||||
'replay_storage': replay_storage,
|
'replay_storage': replay_storage,
|
||||||
'command_storage': command_storage,
|
'command_storage': command_storage
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
@ -111,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:
|
||||||
|
@ -120,6 +118,30 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
return render(request, self.template_name, context)
|
return render(request, self.template_name, context)
|
||||||
|
|
||||||
|
|
||||||
|
class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView):
|
||||||
|
template_name = 'common/replay_storage_create.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Settings'),
|
||||||
|
'action': _('Create replay storage')
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView):
|
||||||
|
template_name = 'common/command_storage_create.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Settings'),
|
||||||
|
'action': _('Create command storage')
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
|
class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
|
||||||
form_class = SecuritySettingForm
|
form_class = SecuritySettingForm
|
||||||
template_name = "common/security_setting.html"
|
template_name = "common/security_setting.html"
|
||||||
|
@ -137,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,331 @@
|
||||||
|
#!/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_BROKER': 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,
|
||||||
|
'AUTH_OPENID': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
|
@ -17,24 +17,12 @@ 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
|
||||||
|
|
||||||
|
@ -64,6 +55,7 @@ INSTALLED_APPS = [
|
||||||
'common.apps.CommonConfig',
|
'common.apps.CommonConfig',
|
||||||
'terminal.apps.TerminalConfig',
|
'terminal.apps.TerminalConfig',
|
||||||
'audits.apps.AuditsConfig',
|
'audits.apps.AuditsConfig',
|
||||||
|
'authentication.apps.AuthenticationConfig', # authentication
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_swagger',
|
'rest_framework_swagger',
|
||||||
'drf_yasg',
|
'drf_yasg',
|
||||||
|
@ -94,6 +86,7 @@ MIDDLEWARE = [
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'authentication.openid.middleware.OpenIDAuthenticationMiddleware', # openid
|
||||||
'jumpserver.middleware.TimezoneMiddleware',
|
'jumpserver.middleware.TimezoneMiddleware',
|
||||||
'jumpserver.middleware.DemoMiddleware',
|
'jumpserver.middleware.DemoMiddleware',
|
||||||
'jumpserver.middleware.RequestMiddleware',
|
'jumpserver.middleware.RequestMiddleware',
|
||||||
|
@ -150,9 +143,9 @@ 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
|
||||||
|
|
||||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||||
# Database
|
# Database
|
||||||
|
@ -315,13 +308,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,
|
||||||
|
@ -361,19 +354,19 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
|
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
|
||||||
|
|
||||||
# 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_USER_SEARCH_UNION = lambda: [
|
||||||
LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER)
|
LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER)
|
||||||
for USER_SEARCH in str(AUTH_LDAP_SEARCH_OU).split("|")
|
for USER_SEARCH in str(AUTH_LDAP_SEARCH_OU).split("|")
|
||||||
]
|
]
|
||||||
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*AUTH_LDAP_USER_SEARCH_UNION)
|
AUTH_LDAP_USER_SEARCH = lambda: LDAPSearchUnion(*AUTH_LDAP_USER_SEARCH_UNION())
|
||||||
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_GROUP_SEARCH = LDAPSearch(
|
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||||
|
@ -389,12 +382,30 @@ AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
||||||
if AUTH_LDAP:
|
if AUTH_LDAP:
|
||||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||||
|
|
||||||
|
# openid
|
||||||
|
# Auth OpenID settings
|
||||||
|
BASE_SITE_URL = CONFIG.BASE_SITE_URL
|
||||||
|
AUTH_OPENID = CONFIG.AUTH_OPENID
|
||||||
|
AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL
|
||||||
|
AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME
|
||||||
|
AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID
|
||||||
|
AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET
|
||||||
|
AUTH_OPENID_BACKENDS = [
|
||||||
|
'authentication.openid.backends.OpenIDAuthorizationPasswordBackend',
|
||||||
|
'authentication.openid.backends.OpenIDAuthorizationCodeBackend',
|
||||||
|
]
|
||||||
|
|
||||||
|
if AUTH_OPENID:
|
||||||
|
LOGIN_URL = reverse_lazy("authentication:openid-login")
|
||||||
|
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[0])
|
||||||
|
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[1])
|
||||||
|
|
||||||
# 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 if CONFIG.REDIS_PASSWORD else '',
|
||||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
||||||
'port': CONFIG.REDIS_PORT or 6379,
|
'port': CONFIG.REDIS_PORT or 6379,
|
||||||
'db':CONFIG.REDIS_DB_CELERY_BROKER or 3,
|
'db': CONFIG.REDIS_DB_CELERY_BROKER or 3,
|
||||||
}
|
}
|
||||||
CELERY_TASK_SERIALIZER = 'pickle'
|
CELERY_TASK_SERIALIZER = 'pickle'
|
||||||
CELERY_RESULT_SERIALIZER = 'pickle'
|
CELERY_RESULT_SERIALIZER = 'pickle'
|
||||||
|
@ -416,10 +427,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -434,27 +445,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 = {
|
||||||
|
@ -466,16 +495,18 @@ 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'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,62 +1,18 @@
|
||||||
# ~*~ 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([
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
v1_api_patterns = [
|
|
||||||
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
|
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
|
||||||
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
|
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||||
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
|
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||||
|
@ -65,6 +21,14 @@ v1_api_patterns = [
|
||||||
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||||
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
||||||
|
]))
|
||||||
|
]
|
||||||
|
|
||||||
|
api_v2_patterns = [
|
||||||
|
path('api/', include([
|
||||||
|
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')),
|
||||||
|
]))
|
||||||
]
|
]
|
||||||
|
|
||||||
app_view_patterns = [
|
app_view_patterns = [
|
||||||
|
@ -75,8 +39,10 @@ app_view_patterns = [
|
||||||
path('ops/', include('ops.urls.view_urls', namespace='ops')),
|
path('ops/', include('ops.urls.view_urls', namespace='ops')),
|
||||||
path('audits/', include('audits.urls.view_urls', namespace='audits')),
|
path('audits/', include('audits.urls.view_urls', namespace='audits')),
|
||||||
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
||||||
|
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')))
|
||||||
|
|
||||||
|
@ -86,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')),
|
||||||
|
@ -103,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 "必须包含特殊字符"
|
||||||
|
|
|
@ -19,13 +19,13 @@ 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
|
# label = None
|
||||||
help_text = ''
|
# help_text = ''
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
|
@ -50,7 +50,7 @@ class JMSInventory(BaseInventory):
|
||||||
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.hostname,
|
'hostname': asset.fullname,
|
||||||
'ip': asset.ip,
|
'ip': asset.ip,
|
||||||
'port': asset.port,
|
'port': asset.port,
|
||||||
'vars': dict(),
|
'vars': dict(),
|
||||||
|
|
|
@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView
|
||||||
|
|
||||||
from common.mixins import DatetimeSearchMixin
|
from common.mixins import DatetimeSearchMixin
|
||||||
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
|
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
|
||||||
from common.permissions import SuperUserRequiredMixin
|
from common.permissions import SuperUserRequiredMixin, AdminUserRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView):
|
class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||||
|
@ -121,6 +121,6 @@ class AdHocHistoryDetailView(SuperUserRequiredMixin, DetailView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CeleryTaskLogView(SuperUserRequiredMixin, DetailView):
|
class CeleryTaskLogView(AdminUserRequiredMixin, DetailView):
|
||||||
template_name = 'ops/celery_task_log.html'
|
template_name = 'ops/celery_task_log.html'
|
||||||
model = CeleryTask
|
model = CeleryTask
|
||||||
|
|
|
@ -1,14 +1,68 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework import viewsets
|
from rest_framework import status
|
||||||
|
from rest_framework.views import Response
|
||||||
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
|
||||||
from common.permissions import IsSuperUserOrAppUser
|
from common.permissions import IsSuperUserOrAppUser
|
||||||
from .models import Organization
|
from .models import Organization
|
||||||
from .serializers import OrgSerializer
|
from .serializers import OrgSerializer, OrgReadSerializer, \
|
||||||
|
OrgMembershipUserSerializer, OrgMembershipAdminSerializer
|
||||||
|
from users.models import User, UserGroup
|
||||||
|
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||||
|
from perms.models import AssetPermission
|
||||||
|
from orgs.utils import current_org
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .mixins import OrgMembershipModelViewSetMixin
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class OrgViewSet(viewsets.ModelViewSet):
|
class OrgViewSet(BulkModelViewSet):
|
||||||
queryset = Organization.objects.all()
|
queryset = Organization.objects.all()
|
||||||
serializer_class = OrgSerializer
|
serializer_class = OrgSerializer
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
org = None
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action in ('list', 'retrieve'):
|
||||||
|
return OrgReadSerializer
|
||||||
|
else:
|
||||||
|
return super().get_serializer_class()
|
||||||
|
|
||||||
|
def get_data_from_model(self, model):
|
||||||
|
if model == User:
|
||||||
|
data = model.objects.filter(orgs__id=self.org.id)
|
||||||
|
else:
|
||||||
|
data = model.objects.filter(org_id=self.org.id)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
self.org = self.get_object()
|
||||||
|
models = [
|
||||||
|
User, UserGroup,
|
||||||
|
Asset, Domain, AdminUser, SystemUser, Label,
|
||||||
|
AssetPermission,
|
||||||
|
]
|
||||||
|
for model in models:
|
||||||
|
data = self.get_data_from_model(model)
|
||||||
|
if data:
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
if str(current_org) == str(self.org):
|
||||||
|
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
self.org.delete()
|
||||||
|
return Response({'msg': True}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class OrgMembershipAdminsViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
|
||||||
|
serializer_class = OrgMembershipAdminSerializer
|
||||||
|
membership_class = Organization.admins.through
|
||||||
|
permission_classes = (IsSuperUserOrAppUser, )
|
||||||
|
|
||||||
|
|
||||||
|
class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
|
||||||
|
serializer_class = OrgMembershipUserSerializer
|
||||||
|
membership_class = Organization.users.through
|
||||||
|
permission_classes = (IsSuperUserOrAppUser, )
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.0.7 on 2018-08-07 03:16
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Organization',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||||
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||||
|
('admins', models.ManyToManyField(blank=True, related_name='admin_orgs', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('users', models.ManyToManyField(blank=True, related_name='orgs', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue