mirror of https://github.com/jumpserver/jumpserver
commit
841f707b6d
|
@ -2,8 +2,9 @@
|
|||
name: 需求建议
|
||||
about: 提出针对本项目的想法和建议
|
||||
title: "[Feature] "
|
||||
labels: 待处理, 需求
|
||||
assignees: 'ibuler'
|
||||
labels: 类型:需求
|
||||
assignees: ibuler
|
||||
|
||||
---
|
||||
|
||||
**请描述您的需求或者改进建议.**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name: Bug 提交
|
||||
about: 提交产品缺陷帮助我们更好的改进
|
||||
title: "[Bug] "
|
||||
labels: bug, 待处理
|
||||
labels: 类型:bug
|
||||
assignees: wojiushixiaobai
|
||||
|
||||
---
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name: 问题咨询
|
||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||
title: "[Question] "
|
||||
labels: 提问, 待处理
|
||||
labels: 类型:提问
|
||||
assignees: wojiushixiaobai
|
||||
|
||||
---
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from .application import *
|
||||
from .mixin import *
|
||||
from .remote_app import *
|
||||
from .database_app import *
|
||||
from .k8s_app import *
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
from .mixin import ApplicationAttrsSerializerViewMixin
|
||||
from ..hands import IsOrgAdminOrAppUser
|
||||
from .. import models, serializers
|
||||
|
||||
__all__ = [
|
||||
'ApplicationViewSet',
|
||||
]
|
||||
|
||||
|
||||
class ApplicationViewSet(ApplicationAttrsSerializerViewMixin, OrgBulkModelViewSet):
|
||||
model = models.Application
|
||||
filter_fields = ('name', 'type', 'category')
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.ApplicationSerializer
|
|
@ -0,0 +1,95 @@
|
|||
from common.exceptions import JMSException
|
||||
from .. import models
|
||||
|
||||
|
||||
class ApplicationAttrsSerializerViewMixin:
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = super().get_serializer_class()
|
||||
app_type = self.request.query_params.get('type')
|
||||
app_category = self.request.query_params.get('category')
|
||||
type_options = list(dict(models.Category.get_all_type_serializer_mapper()).keys())
|
||||
category_options = list(dict(models.Category.get_category_serializer_mapper()).keys())
|
||||
|
||||
# ListAPIView 没有 action 属性
|
||||
# 不使用method属性,因为options请求时为method为post
|
||||
action = getattr(self, 'action', 'list')
|
||||
|
||||
if app_type and app_type not in type_options:
|
||||
raise JMSException(
|
||||
'Invalid query parameter `type`, select from the following options: {}'
|
||||
''.format(type_options)
|
||||
)
|
||||
if app_category and app_category not in category_options:
|
||||
raise JMSException(
|
||||
'Invalid query parameter `category`, select from the following options: {}'
|
||||
''.format(category_options)
|
||||
)
|
||||
|
||||
if action in [
|
||||
'create', 'update', 'partial_update', 'bulk_update', 'partial_bulk_update'
|
||||
] and not app_type:
|
||||
# action: create / update
|
||||
raise JMSException(
|
||||
'The `{}` action must take the `type` query parameter'.format(action)
|
||||
)
|
||||
|
||||
if app_type:
|
||||
# action: create / update / list / retrieve / metadata
|
||||
attrs_cls = models.Category.get_type_serializer_cls(app_type)
|
||||
elif app_category:
|
||||
# action: list / retrieve / metadata
|
||||
attrs_cls = models.Category.get_category_serializer_cls(app_category)
|
||||
else:
|
||||
attrs_cls = models.Category.get_no_password_serializer_cls()
|
||||
return type('ApplicationDynamicSerializer', (serializer_class,), {'attrs': attrs_cls()})
|
||||
|
||||
|
||||
class SerializeApplicationToTreeNodeMixin:
|
||||
|
||||
@staticmethod
|
||||
def _serialize_db(db):
|
||||
return {
|
||||
'id': db.id,
|
||||
'name': db.name,
|
||||
'title': db.name,
|
||||
'pId': '',
|
||||
'open': False,
|
||||
'iconSkin': 'database',
|
||||
'meta': {'type': 'database_app'}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _serialize_remote_app(remote_app):
|
||||
return {
|
||||
'id': remote_app.id,
|
||||
'name': remote_app.name,
|
||||
'title': remote_app.name,
|
||||
'pId': '',
|
||||
'open': False,
|
||||
'isParent': False,
|
||||
'iconSkin': 'chrome',
|
||||
'meta': {'type': 'remote_app'}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _serialize_cloud(cloud):
|
||||
return {
|
||||
'id': cloud.id,
|
||||
'name': cloud.name,
|
||||
'title': cloud.name,
|
||||
'pId': '',
|
||||
'open': False,
|
||||
'isParent': False,
|
||||
'iconSkin': 'k8s',
|
||||
'meta': {'type': 'k8s_app'}
|
||||
}
|
||||
|
||||
def _serialize(self, application):
|
||||
method_name = f'_serialize_{application.category}'
|
||||
data = getattr(self, method_name)(application)
|
||||
return data
|
||||
|
||||
def serialize_applications(self, applications):
|
||||
data = [self._serialize(application) for application in applications]
|
||||
return data
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from common.exceptions import JMSException
|
||||
from ..hands import IsOrgAdmin, IsAppUser
|
||||
from ..models import RemoteApp
|
||||
from .. import models
|
||||
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
|
||||
|
||||
|
||||
|
@ -14,7 +15,7 @@ __all__ = [
|
|||
|
||||
|
||||
class RemoteAppViewSet(OrgBulkModelViewSet):
|
||||
model = RemoteApp
|
||||
model = models.RemoteApp
|
||||
filter_fields = ('name', 'type', 'comment')
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
@ -22,6 +23,18 @@ class RemoteAppViewSet(OrgBulkModelViewSet):
|
|||
|
||||
|
||||
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
||||
model = RemoteApp
|
||||
model = models.Application
|
||||
permission_classes = (IsAppUser, )
|
||||
serializer_class = RemoteAppConnectionInfoSerializer
|
||||
|
||||
@staticmethod
|
||||
def check_category_allowed(obj):
|
||||
if not obj.category_is_remote_app:
|
||||
raise JMSException(
|
||||
'The request instance(`{}`) is not of category `remote_app`'.format(obj.category)
|
||||
)
|
||||
|
||||
def get_object(self):
|
||||
obj = super().get_object()
|
||||
self.check_category_allowed(obj)
|
||||
return obj
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
# Generated by Django 2.2.13 on 2020-10-19 12:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_mysql.models
|
||||
import uuid
|
||||
|
||||
|
||||
CATEGORY_DB_LIST = ['mysql', 'oracle', 'postgresql', 'mariadb']
|
||||
CATEGORY_REMOTE_LIST = ['chrome', 'mysql_workbench', 'vmware_client', 'custom']
|
||||
CATEGORY_CLOUD_LIST = ['k8s']
|
||||
|
||||
CATEGORY_DB = 'db'
|
||||
CATEGORY_REMOTE = 'remote_app'
|
||||
CATEGORY_CLOUD = 'cloud'
|
||||
CATEGORY_LIST = [CATEGORY_DB, CATEGORY_REMOTE, CATEGORY_CLOUD]
|
||||
|
||||
|
||||
def get_application_category(old_app):
|
||||
_type = old_app.type
|
||||
if _type in CATEGORY_DB_LIST:
|
||||
category = CATEGORY_DB
|
||||
elif _type in CATEGORY_REMOTE_LIST:
|
||||
category = CATEGORY_REMOTE
|
||||
elif _type in CATEGORY_CLOUD_LIST:
|
||||
category = CATEGORY_CLOUD
|
||||
else:
|
||||
category = None
|
||||
return category
|
||||
|
||||
|
||||
def common_to_application_json(old_app):
|
||||
category = get_application_category(old_app)
|
||||
date_updated = old_app.date_updated if hasattr(old_app, 'date_updated') else old_app.date_created
|
||||
return {
|
||||
'id': old_app.id,
|
||||
'name': old_app.name,
|
||||
'type': old_app.type,
|
||||
'category': category,
|
||||
'comment': old_app.comment,
|
||||
'created_by': old_app.created_by,
|
||||
'date_created': old_app.date_created,
|
||||
'date_updated': date_updated,
|
||||
'org_id': old_app.org_id
|
||||
}
|
||||
|
||||
|
||||
def db_to_application_json(database):
|
||||
app_json = common_to_application_json(database)
|
||||
app_json.update({
|
||||
'attrs': {
|
||||
'host': database.host,
|
||||
'port': database.port,
|
||||
'database': database.database
|
||||
}
|
||||
})
|
||||
return app_json
|
||||
|
||||
|
||||
def remote_to_application_json(remote):
|
||||
app_json = common_to_application_json(remote)
|
||||
attrs = {
|
||||
'asset': str(remote.asset.id),
|
||||
'path': remote.path,
|
||||
}
|
||||
attrs.update(remote.params)
|
||||
app_json.update({
|
||||
'attrs': attrs
|
||||
})
|
||||
return app_json
|
||||
|
||||
|
||||
def k8s_to_application_json(k8s):
|
||||
app_json = common_to_application_json(k8s)
|
||||
app_json.update({
|
||||
'attrs': {
|
||||
'cluster': k8s.cluster
|
||||
}
|
||||
})
|
||||
return app_json
|
||||
|
||||
|
||||
def migrate_and_integrate_applications(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
database_app_model = apps.get_model("applications", "DatabaseApp")
|
||||
remote_app_model = apps.get_model("applications", "RemoteApp")
|
||||
k8s_app_model = apps.get_model("applications", "K8sApp")
|
||||
|
||||
database_apps = database_app_model.objects.using(db_alias).all()
|
||||
remote_apps = remote_app_model.objects.using(db_alias).all()
|
||||
k8s_apps = k8s_app_model.objects.using(db_alias).all()
|
||||
|
||||
database_applications = [db_to_application_json(db_app) for db_app in database_apps]
|
||||
remote_applications = [remote_to_application_json(remote_app) for remote_app in remote_apps]
|
||||
k8s_applications = [k8s_to_application_json(k8s_app) for k8s_app in k8s_apps]
|
||||
|
||||
applications_json = database_applications + remote_applications + k8s_applications
|
||||
application_model = apps.get_model("applications", "Application")
|
||||
applications = [
|
||||
application_model(**application_json)
|
||||
for application_json in applications_json
|
||||
if application_json['category'] in CATEGORY_LIST
|
||||
]
|
||||
for application in applications:
|
||||
if application_model.objects.using(db_alias).filter(name=application.name).exists():
|
||||
application.name = '{}-{}'.format(application.name, application.type)
|
||||
application.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0057_fill_node_value_assets_amount_and_parent_key'),
|
||||
('applications', '0005_k8sapp'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('category', models.CharField(choices=[('db', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Cloud')], max_length=16, verbose_name='Category')),
|
||||
('type', models.CharField(choices=[('mysql', 'MySQL'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('mariadb', 'MariaDB'), ('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom'), ('k8s', 'Kubernetes')], max_length=16, verbose_name='Type')),
|
||||
('attrs', django_mysql.models.JSONField(default=dict)),
|
||||
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||
('domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applications', to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
'unique_together': {('org_id', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_and_integrate_applications),
|
||||
]
|
|
@ -1,3 +1,4 @@
|
|||
from .application import *
|
||||
from .remote_app import *
|
||||
from .database_app import *
|
||||
from .k8s_app import *
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
from itertools import chain
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_mysql.models import JSONField, QuerySet
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.mixins import CommonModelMixin
|
||||
from common.db.models import ChoiceSet
|
||||
|
||||
|
||||
class DBType(ChoiceSet):
|
||||
mysql = 'mysql', 'MySQL'
|
||||
oracle = 'oracle', 'Oracle'
|
||||
pgsql = 'postgresql', 'PostgreSQL'
|
||||
mariadb = 'mariadb', 'MariaDB'
|
||||
|
||||
@classmethod
|
||||
def get_type_serializer_cls_mapper(cls):
|
||||
from ..serializers import database_app
|
||||
mapper = {
|
||||
cls.mysql: database_app.MySQLAttrsSerializer,
|
||||
cls.oracle: database_app.OracleAttrsSerializer,
|
||||
cls.pgsql: database_app.PostgreAttrsSerializer,
|
||||
cls.mariadb: database_app.MariaDBAttrsSerializer,
|
||||
}
|
||||
return mapper
|
||||
|
||||
|
||||
class RemoteAppType(ChoiceSet):
|
||||
chrome = 'chrome', 'Chrome'
|
||||
mysql_workbench = 'mysql_workbench', 'MySQL Workbench'
|
||||
vmware_client = 'vmware_client', 'vSphere Client'
|
||||
custom = 'custom', _('Custom')
|
||||
|
||||
@classmethod
|
||||
def get_type_serializer_cls_mapper(cls):
|
||||
from ..serializers import remote_app
|
||||
mapper = {
|
||||
cls.chrome: remote_app.ChromeAttrsSerializer,
|
||||
cls.mysql_workbench: remote_app.MySQLWorkbenchAttrsSerializer,
|
||||
cls.vmware_client: remote_app.VMwareClientAttrsSerializer,
|
||||
cls.custom: remote_app.CustomRemoteAppAttrsSeralizers,
|
||||
}
|
||||
return mapper
|
||||
|
||||
|
||||
class CloudType(ChoiceSet):
|
||||
k8s = 'k8s', 'Kubernetes'
|
||||
|
||||
@classmethod
|
||||
def get_type_serializer_cls_mapper(cls):
|
||||
from ..serializers import k8s_app
|
||||
mapper = {
|
||||
cls.k8s: k8s_app.K8sAttrsSerializer,
|
||||
}
|
||||
return mapper
|
||||
|
||||
|
||||
class Category(ChoiceSet):
|
||||
db = 'db', _('Database')
|
||||
remote_app = 'remote_app', _('Remote app')
|
||||
cloud = 'cloud', 'Cloud'
|
||||
|
||||
@classmethod
|
||||
def get_category_type_mapper(cls):
|
||||
return {
|
||||
cls.db: DBType,
|
||||
cls.remote_app: RemoteAppType,
|
||||
cls.cloud: CloudType
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_category_type_choices_mapper(cls):
|
||||
return {
|
||||
name: tp.choices
|
||||
for name, tp in cls.get_category_type_mapper().items()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_type_choices(cls, category):
|
||||
return cls.get_category_type_choices_mapper().get(category, [])
|
||||
|
||||
@classmethod
|
||||
def get_all_type_choices(cls):
|
||||
all_grouped_choices = tuple(cls.get_category_type_choices_mapper().values())
|
||||
return tuple(chain(*all_grouped_choices))
|
||||
|
||||
@classmethod
|
||||
def get_all_type_serializer_mapper(cls):
|
||||
mapper = {}
|
||||
for tp in cls.get_category_type_mapper().values():
|
||||
mapper.update(tp.get_type_serializer_cls_mapper())
|
||||
return mapper
|
||||
|
||||
@classmethod
|
||||
def get_type_serializer_cls(cls, tp):
|
||||
mapper = cls.get_all_type_serializer_mapper()
|
||||
return mapper.get(tp, None)
|
||||
|
||||
@classmethod
|
||||
def get_category_serializer_mapper(cls):
|
||||
from ..serializers import remote_app, database_app, k8s_app
|
||||
return {
|
||||
cls.db: database_app.DBAttrsSerializer,
|
||||
cls.remote_app: remote_app.RemoteAppAttrsSerializer,
|
||||
cls.cloud: k8s_app.CloudAttrsSerializer,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_category_serializer_cls(cls, cg):
|
||||
mapper = cls.get_category_serializer_mapper()
|
||||
return mapper.get(cg, None)
|
||||
|
||||
@classmethod
|
||||
def get_no_password_serializer_cls(cls):
|
||||
from ..serializers import common
|
||||
return common.NoPasswordSerializer
|
||||
|
||||
|
||||
class Application(CommonModelMixin, OrgModelMixin):
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
domain = models.ForeignKey('assets.Domain', null=True, blank=True, related_name='applications', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_('Category'))
|
||||
type = models.CharField(max_length=16, choices=Category.get_all_type_choices(), verbose_name=_('Type'))
|
||||
attrs = JSONField()
|
||||
comment = models.TextField(
|
||||
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'name')]
|
||||
ordering = ('name',)
|
||||
|
||||
def __str__(self):
|
||||
category_display = self.get_category_display()
|
||||
type_display = self.get_type_display()
|
||||
return f'{self.name}({type_display})[{category_display}]'
|
||||
|
||||
def category_is_remote_app(self):
|
||||
return self.category == Category.remote_app
|
|
@ -1,3 +1,5 @@
|
|||
from .application import *
|
||||
from .remote_app import *
|
||||
from .database_app import *
|
||||
from .k8s_app import *
|
||||
from .common import *
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
from .. import models
|
||||
|
||||
__all__ = [
|
||||
'ApplicationSerializer',
|
||||
]
|
||||
|
||||
|
||||
class ApplicationSerializer(BulkOrgResourceModelSerializer):
|
||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
|
||||
class Meta:
|
||||
model = models.Application
|
||||
fields = [
|
||||
'id', 'name', 'category', 'category_display', 'type', 'type_display', 'attrs',
|
||||
'domain', 'created_by', 'date_created', 'date_updated', 'comment'
|
||||
]
|
||||
read_only_fields = [
|
||||
'created_by', 'date_created', 'date_updated', 'get_type_display',
|
||||
]
|
||||
|
||||
def create(self, validated_data):
|
||||
attrs = validated_data.pop('attrs', {})
|
||||
instance = super().create(validated_data)
|
||||
instance.attrs = attrs
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
new_attrs = validated_data.pop('attrs', {})
|
||||
instance = super().update(instance, validated_data)
|
||||
attrs = instance.attrs
|
||||
attrs.update(new_attrs)
|
||||
instance.attrs = attrs
|
||||
instance.save()
|
||||
return instance
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class NoPasswordSerializer(serializers.JSONField):
|
||||
def to_representation(self, value):
|
||||
new_value = {}
|
||||
for k, v in value.items():
|
||||
if 'password' not in k:
|
||||
new_value[k] = v
|
||||
return new_value
|
||||
|
|
@ -1,14 +1,36 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from .. import models
|
||||
|
||||
__all__ = [
|
||||
'DatabaseAppSerializer',
|
||||
]
|
||||
|
||||
class DBAttrsSerializer(serializers.Serializer):
|
||||
host = serializers.CharField(max_length=128, label=_('Host'))
|
||||
port = serializers.IntegerField(label=_('Port'))
|
||||
database = serializers.CharField(
|
||||
max_length=128, required=False, allow_blank=True, allow_null=True, label=_('Database')
|
||||
)
|
||||
|
||||
|
||||
class MySQLAttrsSerializer(DBAttrsSerializer):
|
||||
port = serializers.IntegerField(default=3306, label=_('Port'))
|
||||
|
||||
|
||||
class PostgreAttrsSerializer(DBAttrsSerializer):
|
||||
port = serializers.IntegerField(default=5432, label=_('Port'))
|
||||
|
||||
|
||||
class OracleAttrsSerializer(DBAttrsSerializer):
|
||||
port = serializers.IntegerField(default=1521, label=_('Port'))
|
||||
|
||||
|
||||
class MariaDBAttrsSerializer(MySQLAttrsSerializer):
|
||||
pass
|
||||
|
||||
|
||||
class DatabaseAppSerializer(BulkOrgResourceModelSerializer):
|
||||
|
@ -24,3 +46,6 @@ class DatabaseAppSerializer(BulkOrgResourceModelSerializer):
|
|||
'created_by', 'date_created', 'date_updated'
|
||||
'get_type_display',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'get_type_display': {'label': _('Type for display')},
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .. import models
|
||||
|
||||
__all__ = [
|
||||
'K8sAppSerializer',
|
||||
]
|
||||
|
||||
class CloudAttrsSerializer(serializers.Serializer):
|
||||
cluster = serializers.CharField(max_length=1024, label=_('Cluster'))
|
||||
|
||||
|
||||
class K8sAttrsSerializer(CloudAttrsSerializer):
|
||||
pass
|
||||
|
||||
|
||||
class K8sAppSerializer(BulkOrgResourceModelSerializer):
|
||||
type_display = serializers.CharField(source='get_type_display', read_only=True)
|
||||
type_display = serializers.CharField(source='get_type_display', read_only=True, label=_('Type for display'))
|
||||
|
||||
class Meta:
|
||||
model = models.K8sApp
|
||||
|
|
|
@ -2,21 +2,138 @@
|
|||
#
|
||||
|
||||
import copy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.fields.serializer import CustomMetaDictField
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from assets.models import Asset
|
||||
|
||||
from .. import const
|
||||
from ..models import RemoteApp
|
||||
from ..models import RemoteApp, Category, Application
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
|
||||
]
|
||||
class CharPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
||||
|
||||
def to_internal_value(self, data):
|
||||
instance = super().to_internal_value(data)
|
||||
return str(instance.id)
|
||||
|
||||
def to_representation(self, value):
|
||||
# value is instance.id
|
||||
if self.pk_field is not None:
|
||||
return self.pk_field.to_representation(value)
|
||||
return value
|
||||
|
||||
|
||||
class RemoteAppAttrsSerializer(serializers.Serializer):
|
||||
asset_info = serializers.SerializerMethodField()
|
||||
asset = CharPrimaryKeyRelatedField(queryset=Asset.objects, required=False, label=_("Asset"))
|
||||
path = serializers.CharField(max_length=128, label=_('Application path'))
|
||||
|
||||
@staticmethod
|
||||
def get_asset_info(obj):
|
||||
asset_info = {}
|
||||
asset_id = obj.get('asset')
|
||||
if not asset_id:
|
||||
return asset_info
|
||||
try:
|
||||
asset = Asset.objects.get(id=asset_id)
|
||||
asset_info.update({
|
||||
'id': str(asset.id),
|
||||
'hostname': asset.hostname
|
||||
})
|
||||
except ObjectDoesNotExist as e:
|
||||
logger.error(e)
|
||||
return asset_info
|
||||
|
||||
|
||||
class ChromeAttrsSerializer(RemoteAppAttrsSerializer):
|
||||
REMOTE_APP_PATH = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
|
||||
path = serializers.CharField(max_length=128, label=_('Application path'), default=REMOTE_APP_PATH)
|
||||
chrome_target = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Target URL'))
|
||||
chrome_username = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Username'))
|
||||
chrome_password = serializers.CharField(max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'))
|
||||
|
||||
|
||||
class MySQLWorkbenchAttrsSerializer(RemoteAppAttrsSerializer):
|
||||
REMOTE_APP_PATH = 'C:\Program Files\MySQL\MySQL Workbench 8.0 CE\MySQLWorkbench.exe'
|
||||
path = serializers.CharField(max_length=128, label=_('Application path'), default=REMOTE_APP_PATH)
|
||||
mysql_workbench_ip = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('IP'))
|
||||
mysql_workbench_port = serializers.IntegerField(required=False, label=_('Port'))
|
||||
mysql_workbench_name = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Database'))
|
||||
mysql_workbench_username = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Username'))
|
||||
mysql_workbench_password = serializers.CharField(max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'))
|
||||
|
||||
|
||||
class VMwareClientAttrsSerializer(RemoteAppAttrsSerializer):
|
||||
REMOTE_APP_PATH = 'C:\Program Files (x86)\VMware\Infrastructure\Virtual Infrastructure Client\Launcher\VpxClient.exe'
|
||||
path = serializers.CharField(max_length=128, label=_('Application path'), default=REMOTE_APP_PATH)
|
||||
vmware_target = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Target URL'))
|
||||
vmware_username = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Username'))
|
||||
vmware_password = serializers.CharField(max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'))
|
||||
|
||||
|
||||
class CustomRemoteAppAttrsSeralizers(RemoteAppAttrsSerializer):
|
||||
custom_cmdline = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Operating parameter'))
|
||||
custom_target = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Target url'))
|
||||
custom_username = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Username'))
|
||||
custom_password = serializers.CharField(max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'))
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
||||
parameter_remote_app = serializers.SerializerMethodField()
|
||||
asset = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Application
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'parameter_remote_app',
|
||||
]
|
||||
read_only_fields = ['parameter_remote_app']
|
||||
|
||||
@staticmethod
|
||||
def get_parameters(obj):
|
||||
"""
|
||||
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
||||
"""
|
||||
serializer_cls = Category.get_type_serializer_cls(obj.type)
|
||||
fields = serializer_cls().get_fields()
|
||||
fields.pop('asset', None)
|
||||
fields_name = list(fields.keys())
|
||||
attrs = obj.attrs
|
||||
_parameters = list()
|
||||
_parameters.append(obj.type)
|
||||
for field_name in list(fields_name):
|
||||
value = attrs.get(field_name, None)
|
||||
if not value:
|
||||
continue
|
||||
if field_name == 'path':
|
||||
value = '\"%s\"' % value
|
||||
_parameters.append(str(value))
|
||||
_parameters = ' '.join(_parameters)
|
||||
return _parameters
|
||||
|
||||
def get_parameter_remote_app(self, obj):
|
||||
parameters = self.get_parameters(obj)
|
||||
parameter = {
|
||||
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
|
||||
'working_directory': '',
|
||||
'parameters': parameters,
|
||||
}
|
||||
return parameter
|
||||
|
||||
@staticmethod
|
||||
def get_asset(obj):
|
||||
return obj.attrs.get('asset')
|
||||
|
||||
|
||||
# TODO: DELETE
|
||||
class RemoteAppParamsDictField(CustomMetaDictField):
|
||||
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
|
||||
default_type = const.REMOTE_APP_TYPE_CHROME
|
||||
|
@ -24,8 +141,9 @@ class RemoteAppParamsDictField(CustomMetaDictField):
|
|||
convert_key_to_upper = False
|
||||
|
||||
|
||||
# TODO: DELETE
|
||||
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
|
||||
params = RemoteAppParamsDictField()
|
||||
params = RemoteAppParamsDictField(label=_('Parameters'))
|
||||
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
|
||||
|
||||
class Meta:
|
||||
|
@ -39,6 +157,10 @@ class RemoteAppSerializer(BulkOrgResourceModelSerializer):
|
|||
'created_by', 'date_created', 'asset_info',
|
||||
'get_type_display'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'asset_info': {'label': _('Asset info')},
|
||||
'get_type_display': {'label': _('Type for display')},
|
||||
}
|
||||
|
||||
def process_params(self, instance, validated_data):
|
||||
new_params = copy.deepcopy(validated_data.get('params', {}))
|
||||
|
@ -66,21 +188,3 @@ class RemoteAppSerializer(BulkOrgResourceModelSerializer):
|
|||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
||||
parameter_remote_app = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'parameter_remote_app',
|
||||
]
|
||||
read_only_fields = ['parameter_remote_app']
|
||||
|
||||
@staticmethod
|
||||
def get_parameter_remote_app(obj):
|
||||
parameter = {
|
||||
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
|
||||
'working_directory': '',
|
||||
'parameters': obj.parameters,
|
||||
}
|
||||
return parameter
|
||||
|
|
|
@ -10,6 +10,7 @@ from .. import api
|
|||
app_name = 'applications'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'applications', api.ApplicationViewSet, 'application')
|
||||
router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
|
||||
router.register(r'database-apps', api.DatabaseAppViewSet, 'database-app')
|
||||
router.register(r'k8s-apps', api.K8sAppViewSet, 'k8s-app')
|
||||
|
|
|
@ -94,7 +94,6 @@ class AdminUserAssetsListView(generics.ListAPIView):
|
|||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
|
|
|
@ -32,7 +32,7 @@ class AssetViewSet(FilterAssetByNodeMixin, OrgBulkModelViewSet):
|
|||
model = Asset
|
||||
filter_fields = (
|
||||
"hostname", "ip", "systemuser__id", "admin_user__id", "platform__base",
|
||||
"is_active", 'ip'
|
||||
"is_active"
|
||||
)
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from rest_framework.views import APIView, Response
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
|
@ -42,6 +44,10 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
|||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(Gateway.objects.all())
|
||||
local_port = self.request.data.get('port') or self.object.port
|
||||
try:
|
||||
local_port = int(local_port)
|
||||
except ValueError:
|
||||
raise ValidationError({'port': _('Number required')})
|
||||
ok, e = self.object.test_connective(local_port=local_port)
|
||||
if ok:
|
||||
return Response("ok")
|
||||
|
|
|
@ -19,7 +19,7 @@ from ..tasks import (
|
|||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
|
||||
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi',
|
||||
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView',
|
||||
]
|
||||
|
||||
|
||||
|
@ -125,3 +125,18 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
|||
pk = self.kwargs.get('pk', None)
|
||||
system_user = get_object_or_404(SystemUser, pk=pk)
|
||||
return system_user.cmd_filter_rules
|
||||
|
||||
|
||||
class SystemUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(SystemUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
system_user = self.get_object()
|
||||
return system_user.get_all_assets()
|
||||
|
|
|
@ -95,7 +95,7 @@ class SystemUserNodeRelationViewSet(BaseRelationViewSet):
|
|||
'id', 'node', 'systemuser',
|
||||
]
|
||||
search_fields = [
|
||||
"node__value", "systemuser__name", "systemuser_username"
|
||||
"node__value", "systemuser__name", "systemuser__username"
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 2.2.13 on 2020-10-23 03:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0057_fill_node_value_assets_amount_and_parent_key'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'ordering': ['-date_created'], 'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='comment',
|
||||
field=models.TextField(blank=True, default='', verbose_name='Comment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='commandfilterrule',
|
||||
name='content',
|
||||
field=models.TextField(help_text='One line one command', verbose_name='Content'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 2.2.13 on 2020-10-27 11:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0058_auto_20201023_1115'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc'), ('mysql', 'mysql'), ('oracle', 'oracle'), ('mariadb', 'mariadb'), ('postgresql', 'postgresql'), ('k8s', 'k8s')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='ad_domain',
|
||||
field=models.CharField(default='', max_length=256),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='ip',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,58 @@
|
|||
# Generated by Django 2.2.13 on 2020-10-26 11:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def get_node_ancestor_keys(key, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
parent_keys.append(":".join(key_list))
|
||||
key_list.pop()
|
||||
return parent_keys
|
||||
|
||||
|
||||
def migrate_nodes_value_with_slash(apps, schema_editor):
|
||||
model = apps.get_model("assets", "Node")
|
||||
db_alias = schema_editor.connection.alias
|
||||
nodes = model.objects.using(db_alias).filter(value__contains='/')
|
||||
print('')
|
||||
print("- Start migrate node value if has /")
|
||||
for i, node in enumerate(list(nodes)):
|
||||
new_value = node.value.replace('/', '|')
|
||||
print("{} start migrate node value: {} => {}".format(i, node.value, new_value))
|
||||
node.value = new_value
|
||||
node.save()
|
||||
|
||||
|
||||
def migrate_nodes_full_value(apps, schema_editor):
|
||||
model = apps.get_model("assets", "Node")
|
||||
db_alias = schema_editor.connection.alias
|
||||
nodes = model.objects.using(db_alias).all()
|
||||
print("- Start migrate node full value")
|
||||
for i, node in enumerate(list(nodes)):
|
||||
print("{} start migrate {} node full value".format(i, node.value))
|
||||
ancestor_keys = get_node_ancestor_keys(node.key, True)
|
||||
values = model.objects.filter(key__in=ancestor_keys).values_list('key', 'value')
|
||||
values = [v for k, v in sorted(values, key=lambda x: len(x[0]))]
|
||||
node.full_value = '/' + '/'.join(values)
|
||||
node.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0059_auto_20201027_1905'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='full_value',
|
||||
field=models.CharField(default='', max_length=4096, verbose_name='Full value'),
|
||||
),
|
||||
migrations.RunPython(migrate_nodes_value_with_slash),
|
||||
migrations.RunPython(migrate_nodes_full_value)
|
||||
]
|
|
@ -227,7 +227,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||
org_objects = AssetOrgManager.from_queryset(AssetQuerySet)()
|
||||
|
@ -313,6 +313,12 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
}
|
||||
return info
|
||||
|
||||
def nodes_display(self):
|
||||
names = []
|
||||
for n in self.nodes.all():
|
||||
names.append(n.full_value)
|
||||
return names
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
fake_node = Node()
|
||||
|
@ -355,3 +361,4 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
class Meta:
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
ordering = ['-date_created']
|
||||
|
|
|
@ -52,7 +52,7 @@ class CommandFilterRule(OrgModelMixin):
|
|||
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
|
||||
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the higher will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
|
||||
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
|
|
@ -9,6 +9,7 @@ import paramiko
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils.strings import no_special_chars
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from .base import BaseUser
|
||||
|
||||
|
@ -47,7 +48,7 @@ class Gateway(BaseUser):
|
|||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=PROTOCOL_SSH, verbose_name=_("Protocol"))
|
||||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||
|
@ -64,8 +65,8 @@ class Gateway(BaseUser):
|
|||
def test_connective(self, local_port=None):
|
||||
if local_port is None:
|
||||
local_port = self.port
|
||||
if self.password and not re.match(r'\w+$', self.password):
|
||||
return False, _("Password should not contain special characters")
|
||||
if self.password and not no_special_chars(self.password):
|
||||
return False, _("Password should not contains special characters")
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
|
|
@ -22,7 +22,7 @@ class FavoriteAsset(CommonModelMixin):
|
|||
@classmethod
|
||||
def get_user_favorite_assets(cls, user):
|
||||
from assets.models import Asset
|
||||
from perms.utils.user_asset_permission import get_user_granted_all_assets
|
||||
from perms.utils.asset.user_permission import get_user_granted_all_assets
|
||||
asset_ids = get_user_granted_all_assets(user).values_list('id', flat=True)
|
||||
query_name = cls.asset.field.related_query_name()
|
||||
return Asset.org_objects.filter(**{f'{query_name}__user_id': user.id}, id__in=asset_ids).distinct()
|
||||
|
|
|
@ -13,7 +13,7 @@ from django.db.transaction import atomic
|
|||
from common.utils import get_logger
|
||||
from common.utils.common import lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from orgs.utils import get_current_org, tmp_to_org, current_org
|
||||
from orgs.utils import get_current_org, tmp_to_org
|
||||
from orgs.models import Organization
|
||||
|
||||
|
||||
|
@ -205,6 +205,28 @@ class FamilyMixin:
|
|||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
@classmethod
|
||||
def create_node_by_full_value(cls, full_value):
|
||||
if not full_value:
|
||||
return []
|
||||
nodes_family = full_value.split('/')
|
||||
org_root = cls.org_root()
|
||||
if nodes_family[0] == org_root.value:
|
||||
nodes_family = nodes_family[1:]
|
||||
return cls.create_nodes_recurse(nodes_family, org_root)
|
||||
|
||||
@classmethod
|
||||
def create_nodes_recurse(cls, values, parent=None):
|
||||
if not values:
|
||||
return None
|
||||
if parent is None:
|
||||
parent = cls.org_root()
|
||||
value = values[0]
|
||||
child, created = parent.get_or_create_child(value=value)
|
||||
if len(values) == 1:
|
||||
return child
|
||||
return cls.create_nodes_recurse(values[1:], child)
|
||||
|
||||
def get_family(self):
|
||||
ancestors = self.get_ancestors()
|
||||
children = self.get_all_children()
|
||||
|
@ -372,6 +394,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
|||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
full_value = models.CharField(max_length=4096, verbose_name=_('Full value'), default='')
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
parent_key = models.CharField(max_length=64, verbose_name=_("Parent key"),
|
||||
|
@ -387,7 +410,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
|||
ordering = ['key']
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
return self.full_value
|
||||
|
||||
# def __eq__(self, other):
|
||||
# if not other:
|
||||
|
@ -411,15 +434,14 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
|||
def name(self):
|
||||
return self.value
|
||||
|
||||
@lazyproperty
|
||||
def full_value(self):
|
||||
def computed_full_value(self):
|
||||
# 不要在列表中调用该属性
|
||||
values = self.__class__.objects.filter(
|
||||
key__in=self.get_ancestor_keys()
|
||||
).values_list('key', 'value')
|
||||
values = [v for k, v in sorted(values, key=lambda x: len(x[0]))]
|
||||
values.append(self.value)
|
||||
return ' / '.join(values)
|
||||
values.append(str(self.value))
|
||||
return '/' + '/'.join(values)
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
|
@ -458,3 +480,22 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
|||
if self.has_children_or_has_assets():
|
||||
return
|
||||
return super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
def update_child_full_value(self):
|
||||
nodes = self.get_all_children(with_self=True)
|
||||
sort_key_func = lambda n: [int(i) for i in n.key.split(':')]
|
||||
nodes_sorted = sorted(list(nodes), key=sort_key_func)
|
||||
nodes_mapper = {n.key: n for n in nodes_sorted}
|
||||
for node in nodes_sorted:
|
||||
parent = nodes_mapper.get(node.parent_key)
|
||||
if not parent:
|
||||
logger.error(f'Node parent node in mapper: {node.parent_key} {node.value}')
|
||||
continue
|
||||
node.full_value = parent.full_value + '/' + node.value
|
||||
self.__class__.objects.bulk_update(nodes, ['full_value'])
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_value = self.computed_full_value()
|
||||
instance = super().save(*args, **kwargs)
|
||||
self.update_child_full_value()
|
||||
return instance
|
||||
|
|
|
@ -72,6 +72,9 @@ class SystemUser(BaseUser):
|
|||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_MYSQL = 'mysql'
|
||||
PROTOCOL_ORACLE = 'oracle'
|
||||
PROTOCOL_MARIADB = 'mariadb'
|
||||
PROTOCOL_POSTGRESQL = 'postgresql'
|
||||
PROTOCOL_K8S = 'k8s'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
|
@ -79,6 +82,9 @@ class SystemUser(BaseUser):
|
|||
(PROTOCOL_TELNET, 'telnet'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
(PROTOCOL_MYSQL, 'mysql'),
|
||||
(PROTOCOL_ORACLE, 'oracle'),
|
||||
(PROTOCOL_MARIADB, 'mariadb'),
|
||||
(PROTOCOL_POSTGRESQL, 'postgresql'),
|
||||
(PROTOCOL_K8S, 'k8s'),
|
||||
)
|
||||
|
||||
|
@ -104,6 +110,7 @@ class SystemUser(BaseUser):
|
|||
token = models.TextField(default='', verbose_name=_('Token'))
|
||||
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
|
||||
system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
|
||||
ad_domain = models.CharField(default='', max_length=256)
|
||||
_prefer = 'system_user'
|
||||
|
||||
def __str__(self):
|
||||
|
@ -126,6 +133,24 @@ class SystemUser(BaseUser):
|
|||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
||||
@property
|
||||
def db_application_protocols(self):
|
||||
return [
|
||||
self.PROTOCOL_MYSQL, self.PROTOCOL_ORACLE, self.PROTOCOL_MARIADB,
|
||||
self.PROTOCOL_POSTGRESQL
|
||||
]
|
||||
|
||||
@property
|
||||
def cloud_application_protocols(self):
|
||||
return [self.PROTOCOL_K8S]
|
||||
|
||||
@property
|
||||
def application_category_protocols(self):
|
||||
protocols = []
|
||||
protocols.extend(self.db_application_protocols)
|
||||
protocols.extend(self.cloud_application_protocols)
|
||||
return protocols
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
|
||||
return True
|
||||
|
@ -138,11 +163,11 @@ class SystemUser(BaseUser):
|
|||
|
||||
@property
|
||||
def is_need_test_asset_connective(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
return self.protocol not in self.application_category_protocols
|
||||
|
||||
@property
|
||||
def can_perm_to_asset(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
return self.protocol not in self.application_category_protocols
|
||||
|
||||
def _merge_auth(self, other):
|
||||
super()._merge_auth(other)
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from django.db.models import Prefetch, F, Count
|
||||
from django.db.models import F
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Asset, Node, Label, Platform
|
||||
from ..models import Asset, Node, Platform
|
||||
from .base import ConnectivitySerializer
|
||||
|
||||
__all__ = [
|
||||
|
@ -67,8 +66,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
||||
)
|
||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||
domain_display = serializers.ReadOnlyField(source='domain.name')
|
||||
admin_user_display = serializers.ReadOnlyField(source='admin_user.name')
|
||||
domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name'))
|
||||
admin_user_display = serializers.ReadOnlyField(source='admin_user.name', label=_('Admin user name'))
|
||||
nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes name'), required=False)
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
|
@ -90,7 +90,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'platform': ['name']
|
||||
}
|
||||
fields_m2m = [
|
||||
'nodes', 'labels',
|
||||
'nodes', 'nodes_display', 'labels',
|
||||
]
|
||||
annotates_fields = {
|
||||
# 'admin_user_display': 'admin_user__name'
|
||||
|
@ -133,14 +133,32 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
if protocols_data:
|
||||
validated_data["protocols"] = ' '.join(protocols_data)
|
||||
|
||||
def perform_nodes_display_create(self, instance, nodes_display):
|
||||
if not nodes_display:
|
||||
return
|
||||
nodes_to_set = []
|
||||
for full_value in nodes_display:
|
||||
node = Node.objects.filter(full_value=full_value).first()
|
||||
if node:
|
||||
nodes_to_set.append(node)
|
||||
else:
|
||||
node = Node.create_node_by_full_value(full_value)
|
||||
nodes_to_set.append(node)
|
||||
instance.nodes.set(nodes_to_set)
|
||||
|
||||
def create(self, validated_data):
|
||||
self.compatible_with_old_protocol(validated_data)
|
||||
nodes_display = validated_data.pop('nodes_display', '')
|
||||
instance = super().create(validated_data)
|
||||
self.perform_nodes_display_create(instance, nodes_display)
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
nodes_display = validated_data.pop('nodes_display', '')
|
||||
self.compatible_with_old_protocol(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
instance = super().update(instance, validated_data)
|
||||
self.perform_nodes_display_create(instance, nodes_display)
|
||||
return instance
|
||||
|
||||
|
||||
class AssetDisplaySerializer(AssetSerializer):
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
from common.validators import NoSpecialChars
|
||||
from ..models import Domain, Gateway
|
||||
from .base import AuthSerializerMixin
|
||||
|
||||
|
||||
class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||
asset_count = serializers.SerializerMethodField()
|
||||
gateway_count = serializers.SerializerMethodField()
|
||||
asset_count = serializers.SerializerMethodField(label=_('Assets count'))
|
||||
application_count = serializers.SerializerMethodField(label=_('Applications count'))
|
||||
gateway_count = serializers.SerializerMethodField(label=_('Gateways count'))
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
|
@ -20,12 +22,12 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
|
|||
'comment', 'date_created'
|
||||
]
|
||||
fields_m2m = [
|
||||
'asset_count', 'assets', 'gateway_count',
|
||||
'asset_count', 'assets', 'application_count', 'gateway_count',
|
||||
]
|
||||
fields = fields_small + fields_m2m
|
||||
read_only_fields = ('asset_count', 'gateway_count', 'date_created')
|
||||
extra_kwargs = {
|
||||
'assets': {'required': False}
|
||||
'assets': {'required': False, 'label': _('Assets')},
|
||||
}
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
|
||||
|
@ -33,6 +35,10 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
|
|||
def get_asset_count(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
@staticmethod
|
||||
def get_application_count(obj):
|
||||
return obj.applications.count()
|
||||
|
||||
@staticmethod
|
||||
def get_gateway_count(obj):
|
||||
return obj.gateway_set.all().count()
|
||||
|
@ -47,6 +53,9 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
'private_key', 'public_key', 'domain', 'is_active', 'date_created',
|
||||
'date_updated', 'created_by', 'comment',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {'validators': [NoSpecialChars()]}
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
@ -25,6 +25,9 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
|
|||
read_only_fields = ['key', 'org_id']
|
||||
|
||||
def validate_value(self, data):
|
||||
if '/' in data:
|
||||
error = _("Can't contains: " + "/")
|
||||
raise serializers.ValidationError(error)
|
||||
if self.instance:
|
||||
instance = self.instance
|
||||
siblings = instance.get_siblings()
|
||||
|
|
|
@ -6,7 +6,6 @@ from common.serializers import AdaptedBulkListSerializer
|
|||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from common.utils import ssh_pubkey_gen
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from assets.models import Node
|
||||
from ..models import SystemUser, Asset
|
||||
from .base import AuthSerializerMixin
|
||||
|
||||
|
@ -35,17 +34,18 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
|
||||
'auto_generate_key', 'sftp_root', 'token',
|
||||
'assets_amount', 'date_created', 'created_by',
|
||||
'home', 'system_groups'
|
||||
'home', 'system_groups', 'ad_domain'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'token': {"write_only": True},
|
||||
'nodes_amount': {'label': _('Node')},
|
||||
'assets_amount': {'label': _('Asset')},
|
||||
'nodes_amount': {'label': _('Nodes amount')},
|
||||
'assets_amount': {'label': _('Assets amount')},
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True},
|
||||
'ad_domain': {'label': _('Ad domain')},
|
||||
}
|
||||
|
||||
def validate_auto_push(self, value):
|
||||
|
@ -154,14 +154,18 @@ class SystemUserListSerializer(SystemUserSerializer):
|
|||
'priority', "username_same_with_user",
|
||||
'auto_push', 'sudo', 'shell', 'comment',
|
||||
"assets_amount", 'home', 'system_groups',
|
||||
'auto_generate_key',
|
||||
'auto_generate_key', 'ad_domain',
|
||||
'sftp_root',
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'nodes_amount': {'label': _('Nodes amount')},
|
||||
'assets_amount': {'label': _('Assets amount')},
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True},
|
||||
'ad_domain': {'label': _('Ad domain')},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -179,7 +183,8 @@ class SystemUserWithAuthInfoSerializer(SystemUserSerializer):
|
|||
'login_mode', 'login_mode_display',
|
||||
'priority', 'username_same_with_user',
|
||||
'auto_push', 'sudo', 'shell', 'comment',
|
||||
'auto_generate_key', 'sftp_root', 'token'
|
||||
'auto_generate_key', 'sftp_root', 'token',
|
||||
'ad_domain',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'nodes_amount': {'label': _('Node')},
|
||||
|
|
|
@ -58,7 +58,8 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
|||
|
||||
|
||||
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
|
||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||
@on_transaction_commit
|
||||
def on_system_user_update(instance: SystemUser, created, **kwargs):
|
||||
"""
|
||||
当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
|
||||
其实应该当 用户名,密码,秘钥 sudo等更新时再推送,这里偷个懒,
|
||||
|
@ -68,26 +69,25 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
|||
if instance and not created:
|
||||
logger.info("System user update signal recv: {}".format(instance))
|
||||
assets = instance.assets.all().valid()
|
||||
push_system_user_to_assets.delay(instance, assets)
|
||||
push_system_user_to_assets.delay(instance.id, [_asset.id for _asset in assets])
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.assets.through)
|
||||
def on_system_user_assets_change(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
|
||||
def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
|
||||
"""
|
||||
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
|
||||
"""
|
||||
if action != POST_ADD:
|
||||
return
|
||||
logger.debug("System user assets change signal recv: {}".format(instance))
|
||||
queryset = model.objects.filter(pk__in=pk_set)
|
||||
if model == Asset:
|
||||
system_users = [instance]
|
||||
assets = queryset
|
||||
system_users_id = [instance.id]
|
||||
assets_id = pk_set
|
||||
else:
|
||||
system_users = queryset
|
||||
assets = [instance]
|
||||
for system_user in system_users:
|
||||
push_system_user_to_assets.delay(system_user, assets)
|
||||
system_users_id = pk_set
|
||||
assets_id = [instance.id]
|
||||
for system_user_id in system_users_id:
|
||||
push_system_user_to_assets.delay(system_user_id, assets_id)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.users.through)
|
||||
|
@ -140,7 +140,7 @@ def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs):
|
|||
logger.info("System user groups update signal recv: {}".format(instance))
|
||||
|
||||
users = User.objects.filter(groups__id__in=pk_set).distinct()
|
||||
instance.users.add(users)
|
||||
instance.users.add(*users)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from itertools import groupby
|
||||
from celery import shared_task
|
||||
from common.db.utils import get_object_if_need, get_objects_if_need
|
||||
from common.db.utils import get_object_if_need, get_objects_if_need, get_objects
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db.models import Empty
|
||||
|
||||
|
@ -36,6 +36,7 @@ def get_push_unixlike_system_user_tasks(system_user, username=None):
|
|||
username = system_user.username
|
||||
password = system_user.password
|
||||
public_key = system_user.public_key
|
||||
comment = system_user.name
|
||||
|
||||
groups = _split_by_comma(system_user.system_groups)
|
||||
|
||||
|
@ -47,7 +48,8 @@ def get_push_unixlike_system_user_tasks(system_user, username=None):
|
|||
'shell': system_user.shell or Empty,
|
||||
'state': 'present',
|
||||
'home': system_user.home or Empty,
|
||||
'groups': groups or Empty
|
||||
'groups': groups or Empty,
|
||||
'comment': comment
|
||||
}
|
||||
|
||||
tasks = [
|
||||
|
@ -64,24 +66,27 @@ def get_push_unixlike_system_user_tasks(system_user, username=None):
|
|||
'module': 'group',
|
||||
'args': 'name={} state=present'.format(username),
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
},
|
||||
{
|
||||
'name': "Set home dir permission",
|
||||
'action': {
|
||||
'module': 'file',
|
||||
'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
|
||||
},
|
||||
'when': 'home_existed.stat.exists == true'
|
||||
}
|
||||
]
|
||||
if not system_user.home:
|
||||
tasks.extend([
|
||||
{
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
},
|
||||
{
|
||||
'name': "Set home dir permission",
|
||||
'action': {
|
||||
'module': 'file',
|
||||
'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
|
||||
},
|
||||
'when': 'home_existed.stat.exists == true'
|
||||
}
|
||||
])
|
||||
if password:
|
||||
tasks.append({
|
||||
'name': 'Set {} password'.format(username),
|
||||
|
@ -240,10 +245,10 @@ def push_system_user_a_asset_manual(system_user, asset, username=None):
|
|||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def push_system_user_to_assets(system_user, assets, username=None):
|
||||
def push_system_user_to_assets(system_user_id, assets_id, username=None):
|
||||
system_user = SystemUser.objects.get(id=system_user_id)
|
||||
assets = get_objects(Asset, assets_id)
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
system_user = get_object_if_need(SystemUser, system_user)
|
||||
assets = get_objects_if_need(Asset, assets)
|
||||
return push_system_user_util(system_user, assets, task_name, username=username)
|
||||
|
||||
# @shared_task
|
||||
|
|
|
@ -45,6 +45,7 @@ urlpatterns = [
|
|||
path('admin-users/<uuid:pk>/assets/', api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
||||
|
||||
path('system-users/<uuid:pk>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||
path('system-users/<uuid:pk>/assets/<uuid:aid>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
||||
path('system-users/<uuid:pk>/tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'),
|
||||
path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||
|
|
|
@ -33,7 +33,7 @@ def is_asset_exists_in_node(asset_pk, node_key):
|
|||
|
||||
def is_query_node_all_assets(request):
|
||||
request = request
|
||||
query_all_arg = request.query_params.get('all')
|
||||
query_all_arg = request.query_params.get('all', 'true')
|
||||
show_current_asset_arg = request.query_params.get('show_current_asset')
|
||||
if show_current_asset_arg is not None:
|
||||
return not is_true(show_current_asset_arg)
|
||||
|
|
|
@ -12,7 +12,7 @@ from . import models
|
|||
|
||||
|
||||
class FTPLogSerializer(serializers.ModelSerializer):
|
||||
operate_display = serializers.ReadOnlyField(source='get_operate_display')
|
||||
operate_display = serializers.ReadOnlyField(source='get_operate_display', label=_('Operate for display'))
|
||||
|
||||
class Meta:
|
||||
model = models.FTPLog
|
||||
|
@ -23,9 +23,9 @@ class FTPLogSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class UserLoginLogSerializer(serializers.ModelSerializer):
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display')
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display')
|
||||
mfa_display = serializers.ReadOnlyField(source='get_mfa_display')
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type for display'))
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status for display'))
|
||||
mfa_display = serializers.ReadOnlyField(source='get_mfa_display', label=_('MFA for display'))
|
||||
|
||||
class Meta:
|
||||
model = models.UserLoginLog
|
||||
|
@ -33,6 +33,9 @@ class UserLoginLogSerializer(serializers.ModelSerializer):
|
|||
'id', 'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
|
||||
'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display'
|
||||
)
|
||||
extra_kwargs = {
|
||||
"user_agent": {'label': _('User agent')}
|
||||
}
|
||||
|
||||
|
||||
class OperateLogSerializer(serializers.ModelSerializer):
|
||||
|
@ -75,6 +78,8 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
|
|||
'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
|
||||
'run_as': {'label': _('Run as')},
|
||||
'user': {'label': _('User')},
|
||||
'run_as_display': {'label': _('Run as for display')},
|
||||
'user_display': {'label': _('User for display')},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -2,32 +2,44 @@
|
|||
#
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from celery import shared_task
|
||||
|
||||
from ops.celery.decorator import register_as_period_task
|
||||
from ops.celery.decorator import (
|
||||
register_as_period_task, after_app_shutdown_clean_periodic
|
||||
)
|
||||
from .models import UserLoginLog, OperateLog
|
||||
from common.utils import get_log_keep_day
|
||||
|
||||
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@shared_task
|
||||
@after_app_shutdown_clean_periodic
|
||||
def clean_login_log_period():
|
||||
now = timezone.now()
|
||||
try:
|
||||
days = int(settings.LOGIN_LOG_KEEP_DAYS)
|
||||
except ValueError:
|
||||
days = 9999
|
||||
days = get_log_keep_day('LOGIN_LOG_KEEP_DAYS')
|
||||
expired_day = now - datetime.timedelta(days=days)
|
||||
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
|
||||
|
||||
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@shared_task
|
||||
@after_app_shutdown_clean_periodic
|
||||
def clean_operation_log_period():
|
||||
now = timezone.now()
|
||||
try:
|
||||
days = int(settings.LOGIN_LOG_KEEP_DAYS)
|
||||
except ValueError:
|
||||
days = 9999
|
||||
days = get_log_keep_day('OPERATE_LOG_KEEP_DAYS')
|
||||
expired_day = now - datetime.timedelta(days=days)
|
||||
OperateLog.objects.filter(datetime__lt=expired_day).delete()
|
||||
|
||||
|
||||
@shared_task
|
||||
def clean_ftp_log_period():
|
||||
now = timezone.now()
|
||||
days = get_log_keep_day('FTP_LOG_KEEP_DAYS')
|
||||
expired_day = now - datetime.timedelta(days=days)
|
||||
OperateLog.objects.filter(datetime__lt=expired_day).delete()
|
||||
|
||||
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@shared_task
|
||||
def clean_audits_log_period():
|
||||
clean_audits_log_period()
|
||||
clean_operation_log_period()
|
||||
clean_ftp_log_period()
|
||||
|
|
|
@ -60,6 +60,7 @@ class SSOViewSet(AuthMixin, JmsGenericViewSet):
|
|||
此接口违反了 `Restful` 的规范
|
||||
`GET` 应该是安全的方法,但此接口是不安全的
|
||||
"""
|
||||
request.META['HTTP_X_JMS_LOGIN_TYPE'] = 'W'
|
||||
authkey = request.query_params.get(AUTH_KEY)
|
||||
next_url = request.query_params.get(NEXT_URL)
|
||||
if not next_url or not next_url.startswith('/'):
|
||||
|
|
|
@ -53,7 +53,7 @@ class AuthMixin:
|
|||
ip = ip or get_request_ip(self.request)
|
||||
return ip
|
||||
|
||||
def check_is_block(self):
|
||||
def check_is_block(self, raise_exception=True):
|
||||
if hasattr(self.request, 'data'):
|
||||
username = self.request.data.get("username")
|
||||
else:
|
||||
|
@ -61,7 +61,11 @@ class AuthMixin:
|
|||
ip = self.get_request_ip()
|
||||
if is_block_login(username, ip):
|
||||
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
||||
raise errors.BlockLoginError(username=username, ip=ip)
|
||||
exception = errors.BlockLoginError(username=username, ip=ip)
|
||||
if raise_exception:
|
||||
raise errors.BlockLoginError(username=username, ip=ip)
|
||||
else:
|
||||
return exception
|
||||
|
||||
def decrypt_passwd(self, raw_passwd):
|
||||
# 获取解密密钥,对密码进行解密
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="" required="" autofocus="autofocus">
|
||||
<span class="help-block">
|
||||
{% trans 'Open Google Authenticator and enter the 6-bit dynamic code' %}
|
||||
{% trans 'Open MFA Authenticator and enter the 6-bit dynamic code' %}
|
||||
</span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||
|
|
|
@ -87,6 +87,7 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
try:
|
||||
self.check_user_auth(decrypt_passwd=True)
|
||||
except errors.AuthFailedError as e:
|
||||
e = self.check_is_block(raise_exception=False) or e
|
||||
form.add_error(None, e.msg)
|
||||
ip = self.get_request_ip()
|
||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
||||
|
|
|
@ -25,3 +25,16 @@ def get_objects_if_need(model, pks):
|
|||
logger.error(f'DoesNotExist: <{model.__name__}: {not_found_pks}>')
|
||||
return objs
|
||||
return pks
|
||||
|
||||
|
||||
def get_objects(model, pks):
|
||||
if not pks:
|
||||
return pks
|
||||
|
||||
objs = list(model.objects.filter(id__in=pks))
|
||||
if len(objs) != len(pks):
|
||||
pks = set(pks)
|
||||
exists_pks = {o.id for o in objs}
|
||||
not_found_pks = ','.join(pks - exists_pks)
|
||||
logger.error(f'DoesNotExist: <{model.__name__}: {not_found_pks}>')
|
||||
return objs
|
||||
|
|
|
@ -6,9 +6,10 @@ from rest_framework.views import set_rollback
|
|||
from rest_framework.response import Response
|
||||
|
||||
from common.exceptions import JMSObjectDoesNotExist
|
||||
from common.utils import get_logger
|
||||
from logging import getLogger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
logger = getLogger('drf_exception')
|
||||
unexpected_exception_logger = getLogger('unexpected_exception')
|
||||
|
||||
|
||||
def extract_object_name(exc, index=0):
|
||||
|
@ -38,12 +39,14 @@ def common_exception_handler(exc, context):
|
|||
if getattr(exc, 'wait', None):
|
||||
headers['Retry-After'] = '%d' % exc.wait
|
||||
|
||||
if isinstance(exc.detail, (list, dict)):
|
||||
data = exc.detail
|
||||
if isinstance(exc.detail, str) and isinstance(exc.get_codes(), str):
|
||||
data = {'detail': exc.detail, 'code': exc.get_codes()}
|
||||
else:
|
||||
data = {'detail': exc.detail}
|
||||
data = exc.detail
|
||||
|
||||
set_rollback()
|
||||
return Response(data, status=exc.status_code, headers=headers)
|
||||
else:
|
||||
unexpected_exception_logger.exception('')
|
||||
|
||||
return None
|
||||
|
|
|
@ -7,6 +7,7 @@ from collections import OrderedDict
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from rest_framework.metadata import SimpleMetadata
|
||||
from rest_framework import exceptions, serializers
|
||||
|
@ -58,6 +59,10 @@ class SimpleMetadataWithFilters(SimpleMetadata):
|
|||
field_info['type'] = self.label_lookup[field]
|
||||
field_info['required'] = getattr(field, 'required', False)
|
||||
|
||||
default = getattr(field, 'default', False)
|
||||
if default and isinstance(default, (str, int)):
|
||||
field_info['default'] = default
|
||||
|
||||
for attr in self.attrs:
|
||||
value = getattr(field, attr, None)
|
||||
if value is not None and value != '':
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from ..utils import signer, aes_crypto, aes_ecb_crypto
|
||||
from ..utils import signer, crypto
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -116,27 +116,12 @@ class EncryptMixin:
|
|||
def decrypt_from_signer(self, value):
|
||||
return signer.unsign(value) or ''
|
||||
|
||||
def decrypt_from_aes(self, value):
|
||||
"""
|
||||
先尝试使用GCM模式解密,如果解不开,再尝试使用原来的ECB模式解密
|
||||
"""
|
||||
try:
|
||||
return aes_crypto.decrypt(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return aes_ecb_crypto.decrypt(value)
|
||||
except (TypeError, ValueError, UnicodeDecodeError):
|
||||
pass
|
||||
|
||||
def from_db_value(self, value, expression, connection, context):
|
||||
if value is None:
|
||||
return value
|
||||
value = force_text(value)
|
||||
|
||||
# 优先采用 aes 解密
|
||||
plain_value = self.decrypt_from_aes(value)
|
||||
plain_value = crypto.decrypt(value)
|
||||
|
||||
# 如果没有解开,使用原来的signer解密
|
||||
if not plain_value:
|
||||
|
@ -158,7 +143,7 @@ class EncryptMixin:
|
|||
value = sp.get_prep_value(value)
|
||||
value = force_text(value)
|
||||
# 替换新的加密方式
|
||||
return aes_crypto.encrypt(value)
|
||||
return crypto.encrypt(value)
|
||||
|
||||
|
||||
class EncryptTextField(EncryptMixin, models.TextField):
|
||||
|
|
|
@ -11,8 +11,6 @@ import time
|
|||
import ipaddress
|
||||
import psutil
|
||||
|
||||
from .timezone import dt_formater
|
||||
|
||||
|
||||
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
|
||||
ipip_db = None
|
||||
|
|
|
@ -2,8 +2,58 @@ import base64
|
|||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad
|
||||
from Crypto.Random import get_random_bytes
|
||||
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
def process_key(key):
|
||||
"""
|
||||
返回32 bytes 的key
|
||||
"""
|
||||
if not isinstance(key, bytes):
|
||||
key = bytes(key, encoding='utf-8')
|
||||
|
||||
if len(key) >= 32:
|
||||
return key[:32]
|
||||
|
||||
return pad(key, 32)
|
||||
|
||||
|
||||
class BaseCrypto:
|
||||
|
||||
def encrypt(self, text):
|
||||
return base64.urlsafe_b64encode(
|
||||
self._encrypt(bytes(text, encoding='utf8'))
|
||||
).decode('utf8')
|
||||
|
||||
def _encrypt(self, data: bytes) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
def decrypt(self, text):
|
||||
return self._decrypt(
|
||||
base64.urlsafe_b64decode(bytes(text, encoding='utf8'))
|
||||
).decode('utf8')
|
||||
|
||||
def _decrypt(self, data: bytes) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GMSM4EcbCrypto(BaseCrypto):
|
||||
def __init__(self, key):
|
||||
self.key = process_key(key)
|
||||
self.sm4_encryptor = CryptSM4()
|
||||
self.sm4_encryptor.set_key(self.key, SM4_ENCRYPT)
|
||||
|
||||
self.sm4_decryptor = CryptSM4()
|
||||
self.sm4_decryptor.set_key(self.key, SM4_DECRYPT)
|
||||
|
||||
def _encrypt(self, data: bytes) -> bytes:
|
||||
return self.sm4_encryptor.crypt_ecb(data)
|
||||
|
||||
def _decrypt(self, data: bytes) -> bytes:
|
||||
return self.sm4_decryptor.crypt_ecb(data)
|
||||
|
||||
|
||||
class AESCrypto:
|
||||
|
@ -52,20 +102,7 @@ class AESCryptoGCM:
|
|||
"""
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = self.process_key(key)
|
||||
|
||||
@staticmethod
|
||||
def process_key(key):
|
||||
"""
|
||||
返回32 bytes 的key
|
||||
"""
|
||||
if not isinstance(key, bytes):
|
||||
key = bytes(key, encoding='utf-8')
|
||||
|
||||
if len(key) >= 32:
|
||||
return key[:32]
|
||||
|
||||
return pad(key, 32)
|
||||
self.key = process_key(key)
|
||||
|
||||
def encrypt(self, text):
|
||||
"""
|
||||
|
@ -110,5 +147,50 @@ def get_aes_crypto(key=None, mode='GCM'):
|
|||
return a
|
||||
|
||||
|
||||
def get_gm_sm4_ecb_crypto(key=None):
|
||||
key = key or settings.SECRET_KEY
|
||||
return GMSM4EcbCrypto(key)
|
||||
|
||||
|
||||
aes_ecb_crypto = get_aes_crypto(mode='ECB')
|
||||
aes_crypto = get_aes_crypto(mode='GCM')
|
||||
gm_sm4_ecb_crypto = get_gm_sm4_ecb_crypto()
|
||||
|
||||
|
||||
class Crypto:
|
||||
cryptoes = {
|
||||
'aes_ecb': aes_ecb_crypto,
|
||||
'aes_gcm': aes_crypto,
|
||||
'aes': aes_crypto,
|
||||
'gm_sm4_ecb': gm_sm4_ecb_crypto,
|
||||
'gm': gm_sm4_ecb_crypto,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
cryptoes = self.__class__.cryptoes.copy()
|
||||
crypto = cryptoes.pop(settings.SECURITY_DATA_CRYPTO_ALGO, None)
|
||||
if crypto is None:
|
||||
raise ImproperlyConfigured(
|
||||
f'Crypto method not supported {settings.SECURITY_DATA_CRYPTO_ALGO}'
|
||||
)
|
||||
self.cryptoes = [crypto, *cryptoes.values()]
|
||||
|
||||
@property
|
||||
def encryptor(self):
|
||||
return self.cryptoes[0]
|
||||
|
||||
def encrypt(self, text):
|
||||
return self.encryptor.encrypt(text)
|
||||
|
||||
def decrypt(self, text):
|
||||
for decryptor in self.cryptoes:
|
||||
try:
|
||||
origin_text = decryptor.decrypt(text)
|
||||
if origin_text:
|
||||
# 有时不同算法解密不报错,但是返回空字符串
|
||||
return origin_text
|
||||
except (TypeError, ValueError, UnicodeDecodeError):
|
||||
continue
|
||||
|
||||
|
||||
crypto = Crypto()
|
||||
|
|
|
@ -10,13 +10,15 @@ UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
|||
|
||||
|
||||
def reverse(view_name, urlconf=None, args=None, kwargs=None,
|
||||
current_app=None, external=False):
|
||||
current_app=None, external=False, api_to_ui=False):
|
||||
url = dj_reverse(view_name, urlconf=urlconf, args=args,
|
||||
kwargs=kwargs, current_app=current_app)
|
||||
|
||||
if external:
|
||||
site_url = settings.SITE_URL
|
||||
url = site_url.strip('/') + url
|
||||
if api_to_ui:
|
||||
url = url.replace('api/v1', 'ui/#').rstrip('/')
|
||||
return url
|
||||
|
||||
|
||||
|
@ -48,3 +50,11 @@ def union_queryset(*args, base_queryset=None):
|
|||
base_queryset = args[0].model.objects
|
||||
queryset = base_queryset.filter(id__in=queryset_id)
|
||||
return queryset
|
||||
|
||||
|
||||
def get_log_keep_day(s, defaults=200):
|
||||
try:
|
||||
days = int(getattr(settings, s))
|
||||
except ValueError:
|
||||
days = defaults
|
||||
return days
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import re
|
||||
|
||||
|
||||
def no_special_chars(s):
|
||||
return bool(re.match(r'\w+$', s))
|
|
@ -2,10 +2,12 @@
|
|||
#
|
||||
from django.core.validators import RegexValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.validators import (
|
||||
UniqueTogetherValidator, ValidationError
|
||||
)
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.utils.strings import no_special_chars
|
||||
|
||||
|
||||
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
||||
|
@ -22,3 +24,11 @@ class ProjectUniqueValidator(UniqueTogetherValidator):
|
|||
continue
|
||||
errors[field] = _('This field must be unique.')
|
||||
raise ValidationError(errors)
|
||||
|
||||
|
||||
class NoSpecialChars:
|
||||
def __call__(self, value):
|
||||
if not no_special_chars(value):
|
||||
raise serializers.ValidationError(
|
||||
_("Should not contains special characters")
|
||||
)
|
||||
|
|
|
@ -224,7 +224,7 @@ class Config(dict):
|
|||
'TERMINAL_HEARTBEAT_INTERVAL': 20,
|
||||
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
|
||||
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
|
||||
'TERMINAL_SESSION_KEEP_DURATION': 9999,
|
||||
'TERMINAL_SESSION_KEEP_DURATION': 200,
|
||||
'TERMINAL_HOST_KEY': '',
|
||||
'TERMINAL_TELNET_REGEX': '',
|
||||
'TERMINAL_COMMAND_STORAGE': {},
|
||||
|
@ -244,12 +244,18 @@ class Config(dict):
|
|||
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
|
||||
'SECURITY_LOGIN_CHALLENGE_ENABLED': False,
|
||||
'SECURITY_LOGIN_CAPTCHA_ENABLED': True,
|
||||
'SECURITY_DATA_CRYPTO_ALGO': 'aes',
|
||||
'SECURITY_INSECURE_COMMAND': False,
|
||||
'SECURITY_INSECURE_COMMAND_LEVEL': 5,
|
||||
'SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER': '',
|
||||
|
||||
'HTTP_BIND_HOST': '0.0.0.0',
|
||||
'HTTP_LISTEN_PORT': 8080,
|
||||
'WS_LISTEN_PORT': 8070,
|
||||
'LOGIN_LOG_KEEP_DAYS': 9999,
|
||||
'TASK_LOG_KEEP_DAYS': 10,
|
||||
'LOGIN_LOG_KEEP_DAYS': 200,
|
||||
'TASK_LOG_KEEP_DAYS': 90,
|
||||
'OPERATE_LOG_KEEP_DAYS': 200,
|
||||
'FTP_LOG_KEEP_DAYS': 200,
|
||||
'ASSETS_PERM_CACHE_TIME': 3600 * 24,
|
||||
'SECURITY_MFA_VERIFY_TTL': 3600,
|
||||
'ASSETS_PERM_CACHE_ENABLE': HAS_XPACK,
|
||||
|
@ -366,7 +372,7 @@ class Config(dict):
|
|||
tp = type(default_value)
|
||||
# 对bool特殊处理
|
||||
if tp is bool and isinstance(v, str):
|
||||
if v in ("true", "True", "1"):
|
||||
if v.lower() in ("true", "1"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -242,6 +242,9 @@ CACHES = {
|
|||
'host': CONFIG.REDIS_HOST,
|
||||
'port': CONFIG.REDIS_PORT,
|
||||
'db': CONFIG.REDIS_DB_CACHE,
|
||||
},
|
||||
'OPTIONS': {
|
||||
"REDIS_CLIENT_KWARGS": {"health_check_interval": 30}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA
|
|||
SECURITY_SERVICE_ACCOUNT_REGISTRATION = DYNAMIC.SECURITY_SERVICE_ACCOUNT_REGISTRATION
|
||||
SECURITY_LOGIN_CAPTCHA_ENABLED = CONFIG.SECURITY_LOGIN_CAPTCHA_ENABLED
|
||||
SECURITY_LOGIN_CHALLENGE_ENABLED = CONFIG.SECURITY_LOGIN_CHALLENGE_ENABLED
|
||||
SECURITY_DATA_CRYPTO_ALGO = CONFIG.SECURITY_DATA_CRYPTO_ALGO
|
||||
SECURITY_INSECURE_COMMAND = DYNAMIC.SECURITY_INSECURE_COMMAND
|
||||
SECURITY_INSECURE_COMMAND_LEVEL = CONFIG.SECURITY_INSECURE_COMMAND_LEVEL
|
||||
SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = DYNAMIC.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER
|
||||
|
||||
# Terminal other setting
|
||||
TERMINAL_PASSWORD_AUTH = DYNAMIC.TERMINAL_PASSWORD_AUTH
|
||||
|
|
|
@ -5,6 +5,8 @@ from ..const import PROJECT_DIR, CONFIG
|
|||
|
||||
LOG_DIR = os.path.join(PROJECT_DIR, 'logs')
|
||||
JUMPSERVER_LOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
|
||||
DRF_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'drf_exception.log')
|
||||
UNEXPECTED_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'unexpected_exception.log')
|
||||
ANSIBLE_LOG_FILE = os.path.join(LOG_DIR, 'ansible.log')
|
||||
GUNICORN_LOG_FILE = os.path.join(LOG_DIR, 'gunicorn.log')
|
||||
LOG_LEVEL = CONFIG.LOG_LEVEL
|
||||
|
@ -20,6 +22,10 @@ LOGGING = {
|
|||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s',
|
||||
},
|
||||
'exception': {
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
'format': '\n%(asctime)s [%(levelname)s] %(message)s',
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
},
|
||||
|
@ -58,6 +64,24 @@ LOGGING = {
|
|||
'backupCount': 7,
|
||||
'filename': ANSIBLE_LOG_FILE,
|
||||
},
|
||||
'drf_exception': {
|
||||
'encoding': 'utf8',
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'formatter': 'exception',
|
||||
'maxBytes': 1024 * 1024 * 100,
|
||||
'backupCount': 7,
|
||||
'filename': DRF_EXCEPTION_LOG_FILE,
|
||||
},
|
||||
'unexpected_exception': {
|
||||
'encoding': 'utf8',
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'formatter': 'exception',
|
||||
'maxBytes': 1024 * 1024 * 100,
|
||||
'backupCount': 7,
|
||||
'filename': UNEXPECTED_EXCEPTION_LOG_FILE,
|
||||
},
|
||||
'syslog': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.NullHandler',
|
||||
|
@ -84,6 +108,14 @@ LOGGING = {
|
|||
'handlers': ['console', 'file'],
|
||||
'level': LOG_LEVEL,
|
||||
},
|
||||
'drf_exception': {
|
||||
'handlers': ['console', 'drf_exception'],
|
||||
'level': LOG_LEVEL,
|
||||
},
|
||||
'unexpected_exception': {
|
||||
'handlers': ['unexpected_exception'],
|
||||
'level': LOG_LEVEL,
|
||||
},
|
||||
'ops.ansible_api': {
|
||||
'handlers': ['console', 'ansible_logs'],
|
||||
'level': LOG_LEVEL,
|
||||
|
|
|
@ -4,6 +4,8 @@ from __future__ import unicode_literals
|
|||
from django.urls import path, include, re_path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.views.i18n import JavaScriptCatalog
|
||||
|
||||
from . import views, api
|
||||
|
||||
|
@ -66,7 +68,11 @@ urlpatterns = [
|
|||
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
# urlpatterns += js_i18n_patterns
|
||||
|
||||
js_i18n_patterns = i18n_patterns(
|
||||
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
||||
)
|
||||
urlpatterns += js_i18n_patterns
|
||||
|
||||
handler404 = 'jumpserver.views.handler404'
|
||||
handler500 = 'jumpserver.views.handler500'
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404
|
|||
from rest_framework import viewsets, generics
|
||||
from rest_framework.views import Response
|
||||
|
||||
from common.drf.api import JMSBulkModelViewSet
|
||||
from common.permissions import IsOrgAdmin
|
||||
from common.serializers import CeleryTaskSerializer
|
||||
from ..models import Task, AdHoc, AdHocExecution
|
||||
|
@ -22,7 +23,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class TaskViewSet(viewsets.ModelViewSet):
|
||||
class TaskViewSet(JMSBulkModelViewSet):
|
||||
queryset = Task.objects.all()
|
||||
filter_fields = ("name",)
|
||||
search_fields = filter_fields
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||
from rest_framework import serializers
|
||||
from django.shortcuts import reverse
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import Task, AdHoc, AdHocExecution, CommandExecution
|
||||
|
||||
|
||||
|
@ -45,7 +46,7 @@ class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer):
|
|||
]
|
||||
|
||||
|
||||
class TaskSerializer(serializers.ModelSerializer):
|
||||
class TaskSerializer(BulkOrgResourceModelSerializer):
|
||||
summary = serializers.ReadOnlyField(source='history_summary')
|
||||
latest_execution = AdHocExecutionExcludeResultSerializer(read_only=True)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from celery.exceptions import SoftTimeLimitExceeded
|
|||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import get_logger, get_object_or_none, get_disk_usage
|
||||
from common.utils import get_logger, get_object_or_none, get_disk_usage, get_log_keep_day
|
||||
from orgs.utils import tmp_to_root_org, tmp_to_org
|
||||
from .celery.decorator import (
|
||||
register_as_period_task, after_app_shutdown_clean_periodic,
|
||||
|
@ -83,10 +83,10 @@ def clean_tasks_adhoc_period():
|
|||
@after_app_shutdown_clean_periodic
|
||||
@register_as_period_task(interval=3600*24, description=_("Clean celery log period"))
|
||||
def clean_celery_tasks_period():
|
||||
expire_days = settings.TASK_LOG_KEEP_DAYS
|
||||
logger.debug("Start clean celery task history")
|
||||
one_month_ago = timezone.now() - timezone.timedelta(days=expire_days)
|
||||
tasks = CeleryTask.objects.filter(date_start__lt=one_month_ago)
|
||||
expire_days = get_log_keep_day('TASK_LOG_KEEP_DAYS')
|
||||
days_ago = timezone.now() - timezone.timedelta(days=expire_days)
|
||||
tasks = CeleryTask.objects.filter(date_start__lt=days_ago)
|
||||
tasks.delete()
|
||||
tasks = CeleryTask.objects.filter(date_start__isnull=True)
|
||||
tasks.delete()
|
||||
|
|
|
@ -3,13 +3,16 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
from .. import api
|
||||
|
||||
|
||||
app_name = "ops"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'tasks', api.TaskViewSet, 'task')
|
||||
bulk_router = BulkRouter()
|
||||
|
||||
bulk_router.register(r'tasks', api.TaskViewSet, 'task')
|
||||
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
|
||||
router.register(r'adhoc-executions', api.AdHocRunHistoryViewSet, 'execution')
|
||||
router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
|
||||
|
@ -22,3 +25,4 @@ urlpatterns = [
|
|||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
urlpatterns += bulk_router.urls
|
||||
|
|
|
@ -75,4 +75,3 @@ def send_server_performance_mail(path, usage, usages):
|
|||
send_mail_async(subject, message, recipient_list, html_message=message)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,11 +7,12 @@ from rest_framework.views import Response
|
|||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsSuperUserOrAppUser
|
||||
from common.drf.api import JMSBulkRelationModelViewSet, JMSModelViewSet
|
||||
from common.drf.api import JMSBulkRelationModelViewSet
|
||||
from .models import Organization, ROLE
|
||||
from .serializers import (
|
||||
OrgSerializer, OrgReadSerializer,
|
||||
OrgRetrieveSerializer, OrgMemberSerializer
|
||||
OrgRetrieveSerializer, OrgMemberSerializer,
|
||||
OrgMemberAdminSerializer, OrgMemberUserSerializer
|
||||
)
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||
|
@ -72,6 +73,55 @@ class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet):
|
|||
m2m_field = Organization.members.field
|
||||
serializer_class = OrgMemberSerializer
|
||||
filterset_class = OrgMemberRelationFilterSet
|
||||
search_fields = ('user__name', 'user__username', 'org__name')
|
||||
|
||||
def perform_bulk_create(self, serializer):
|
||||
data = serializer.validated_data
|
||||
relations = [OrganizationMember(**i) for i in data]
|
||||
OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True)
|
||||
|
||||
def perform_bulk_destroy(self, queryset):
|
||||
objs = list(queryset.all().prefetch_related('user', 'org'))
|
||||
queryset.delete()
|
||||
self.send_m2m_changed_signal(objs, action='post_remove')
|
||||
|
||||
|
||||
class OrgMemberAdminRelationBulkViewSet(JMSBulkRelationModelViewSet):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
m2m_field = Organization.members.field
|
||||
serializer_class = OrgMemberAdminSerializer
|
||||
filterset_class = OrgMemberRelationFilterSet
|
||||
search_fields = ('user__name', 'user__username', 'org__name')
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
org_id = self.kwargs.get('org_id')
|
||||
queryset = queryset.filter(org_id=org_id, role=ROLE.ADMIN)
|
||||
return queryset
|
||||
|
||||
def perform_bulk_create(self, serializer):
|
||||
data = serializer.validated_data
|
||||
relations = [OrganizationMember(**i) for i in data]
|
||||
OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True)
|
||||
|
||||
def perform_bulk_destroy(self, queryset):
|
||||
objs = list(queryset.all().prefetch_related('user', 'org'))
|
||||
queryset.delete()
|
||||
self.send_m2m_changed_signal(objs, action='post_remove')
|
||||
|
||||
|
||||
class OrgMemberUserRelationBulkViewSet(JMSBulkRelationModelViewSet):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
m2m_field = Organization.members.field
|
||||
serializer_class = OrgMemberUserSerializer
|
||||
filterset_class = OrgMemberRelationFilterSet
|
||||
search_fields = ('user__name', 'user__username', 'org__name')
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
org_id = self.kwargs.get('org_id')
|
||||
queryset = queryset.filter(org_id=org_id, role=ROLE.USER)
|
||||
return queryset
|
||||
|
||||
def perform_bulk_create(self, serializer):
|
||||
data = serializer.validated_data
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.13 on 2020-10-23 08:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orgs', '0008_auto_20200819_2041'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='comment',
|
||||
field=models.TextField(blank=True, default='', verbose_name='Comment'),
|
||||
),
|
||||
]
|
|
@ -23,7 +23,7 @@ class Organization(models.Model):
|
|||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
members = models.ManyToManyField('users.User', related_name='orgs', through='orgs.OrganizationMember',
|
||||
through_fields=('org', 'user'))
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from django.db.models import F
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models.user import User
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import BulkModelSerializer
|
||||
from common.db.models import concated_display as display
|
||||
from .models import Organization, OrganizationMember
|
||||
from .models import Organization, OrganizationMember, ROLE
|
||||
|
||||
|
||||
class OrgSerializer(ModelSerializer):
|
||||
|
@ -26,12 +27,12 @@ class OrgSerializer(ModelSerializer):
|
|||
read_only_fields = ['created_by', 'date_created']
|
||||
|
||||
def create(self, validated_data):
|
||||
members = self._pop_memebers(validated_data)
|
||||
members = self._pop_members(validated_data)
|
||||
instance = Organization.objects.create(**validated_data)
|
||||
OrganizationMember.objects.add_users_by_role(instance, *members)
|
||||
return instance
|
||||
|
||||
def _pop_memebers(self, validated_data):
|
||||
def _pop_members(self, validated_data):
|
||||
return (
|
||||
validated_data.pop('users', None),
|
||||
validated_data.pop('admins', None),
|
||||
|
@ -39,7 +40,7 @@ class OrgSerializer(ModelSerializer):
|
|||
)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
members = self._pop_memebers(validated_data)
|
||||
members = self._pop_members(validated_data)
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
|
@ -73,6 +74,28 @@ class OrgMemberSerializer(BulkModelSerializer):
|
|||
).distinct()
|
||||
|
||||
|
||||
class OrgMemberAdminSerializer(BulkModelSerializer):
|
||||
role = serializers.HiddenField(default=ROLE.ADMIN)
|
||||
organization = serializers.PrimaryKeyRelatedField(
|
||||
label=_('Organization'), queryset=Organization.objects.all(), required=True, source='org'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = OrganizationMember
|
||||
fields = ('id', 'organization', 'user', 'role')
|
||||
|
||||
|
||||
class OrgMemberUserSerializer(BulkModelSerializer):
|
||||
role = serializers.HiddenField(default=ROLE.USER)
|
||||
organization = serializers.PrimaryKeyRelatedField(
|
||||
label=_('Organization'), queryset=Organization.objects.all(), required=True, source='org'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = OrganizationMember
|
||||
fields = ('id', 'organization', 'user', 'role')
|
||||
|
||||
|
||||
class OrgRetrieveSerializer(OrgReadSerializer):
|
||||
admins = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
auditors = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
|
|
|
@ -14,7 +14,12 @@ router = DefaultRouter()
|
|||
bulk_router = BulkRouter()
|
||||
|
||||
router.register(r'orgs', api.OrgViewSet, 'org')
|
||||
bulk_router.register(r'org-memeber-relation', api.OrgMemberRelationBulkViewSet, 'org-memeber-relation')
|
||||
bulk_router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation')
|
||||
|
||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
||||
api.OrgMemberAdminRelationBulkViewSet, 'membership-admins')
|
||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
||||
api.OrgMemberUserRelationBulkViewSet, 'membership-users'),
|
||||
|
||||
old_version_urlpatterns = [
|
||||
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .asset_permission import *
|
||||
from .user_permission import *
|
||||
from .asset_permission_relation import *
|
||||
from .user_group_permission import *
|
||||
from .asset import *
|
||||
from .application import *
|
||||
|
||||
# TODO: 删除
|
||||
from .remote_app_permission import *
|
||||
from .remote_app_permission_relation import *
|
||||
from .user_remote_app_permission import *
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
from .user_permission import *
|
||||
from .application_permission import *
|
||||
from .application_permission_relation import *
|
||||
from .user_group_permission import *
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.permissions import IsOrgAdmin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from perms.models import ApplicationPermission
|
||||
from perms import serializers
|
||||
|
||||
|
||||
class ApplicationPermissionViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
应用授权列表的增删改查API
|
||||
"""
|
||||
model = ApplicationPermission
|
||||
serializer_class = serializers.ApplicationPermissionSerializer
|
||||
filter_fields = ['name', 'category', 'type']
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().prefetch_related(
|
||||
"applications", "users", "user_groups", "system_users"
|
||||
)
|
||||
return queryset
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import generics
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.functions import Concat
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from applications.models import Application
|
||||
from orgs.mixins.api import OrgRelationMixin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.utils import current_org
|
||||
from common.permissions import IsOrgAdmin
|
||||
from perms import serializers
|
||||
from perms import models
|
||||
|
||||
__all__ = [
|
||||
'ApplicationPermissionUserRelationViewSet',
|
||||
'ApplicationPermissionUserGroupRelationViewSet',
|
||||
'ApplicationPermissionApplicationRelationViewSet',
|
||||
'ApplicationPermissionSystemUserRelationViewSet',
|
||||
'ApplicationPermissionAllApplicationListApi',
|
||||
'ApplicationPermissionAllUserListApi',
|
||||
]
|
||||
|
||||
|
||||
class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
org_id = current_org.org_id()
|
||||
if org_id is not None:
|
||||
queryset = queryset.filter(applicationpermission__org_id=org_id)
|
||||
queryset = queryset.annotate(applicationpermission_display=F('applicationpermission__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class ApplicationPermissionUserRelationViewSet(RelationMixin):
|
||||
serializer_class = serializers.ApplicationPermissionUserRelationSerializer
|
||||
m2m_field = models.ApplicationPermission.users.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', "user", "applicationpermission",
|
||||
]
|
||||
search_fields = ("user__name", "user__username", "applicationpermission__name")
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(user_display=F('user__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class ApplicationPermissionUserGroupRelationViewSet(RelationMixin):
|
||||
serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer
|
||||
m2m_field = models.ApplicationPermission.user_groups.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', "usergroup", "applicationpermission"
|
||||
]
|
||||
search_fields = ["usergroup__name", "applicationpermission__name"]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(usergroup_display=F('usergroup__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class ApplicationPermissionApplicationRelationViewSet(RelationMixin):
|
||||
serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer
|
||||
m2m_field = models.ApplicationPermission.applications.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', 'application', 'applicationpermission',
|
||||
]
|
||||
search_fields = ["id", "application__name", "applicationpermission__name"]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(application_display=F('application__name'))
|
||||
return queryset
|
||||
|
||||
|
||||
class ApplicationPermissionSystemUserRelationViewSet(RelationMixin):
|
||||
serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer
|
||||
m2m_field = models.ApplicationPermission.system_users.field
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', 'systemuser', 'applicationpermission',
|
||||
]
|
||||
search_fields = [
|
||||
"applicactionpermission__name", "systemuser__name", "systemuser__username"
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset \
|
||||
.annotate(systemuser_display=Concat(
|
||||
F('systemuser__name'), Value('('), F('systemuser__username'),
|
||||
Value(')')
|
||||
))
|
||||
return queryset
|
||||
|
||||
|
||||
class ApplicationPermissionAllApplicationListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.ApplicationPermissionAllApplicationSerializer
|
||||
only_fields = serializers.ApplicationPermissionAllApplicationSerializer.Meta.only_fields
|
||||
filter_fields = ('name',)
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
perm = get_object_or_404(models.ApplicationPermission, pk=pk)
|
||||
applications = Application.objects.filter(granted_by_permissions=perm)\
|
||||
.only(*self.only_fields).distinct()
|
||||
return applications
|
||||
|
||||
|
||||
class ApplicationPermissionAllUserListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.ApplicationPermissionAllUserSerializer
|
||||
only_fields = serializers.ApplicationPermissionAllUserSerializer.Meta.only_fields
|
||||
filter_fields = ('username', 'name')
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
perm = get_object_or_404(models.ApplicationPermission, pk=pk)
|
||||
users = perm.get_all_users().only(*self.only_fields).distinct()
|
||||
return users
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db.models import Q
|
||||
from rest_framework.generics import ListAPIView
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from applications.models import Application
|
||||
from applications.api.mixin import ApplicationAttrsSerializerViewMixin
|
||||
from perms import serializers
|
||||
|
||||
__all__ = [
|
||||
'UserGroupGrantedApplicationsApi'
|
||||
]
|
||||
|
||||
|
||||
class UserGroupGrantedApplicationsApi(ApplicationAttrsSerializerViewMixin, ListAPIView):
|
||||
"""
|
||||
获取用户组直接授权的资产
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.ApplicationGrantedSerializer
|
||||
only_fields = serializers.ApplicationGrantedSerializer.Meta.only_fields
|
||||
filter_fields = ['id', 'name', 'category', 'type', 'comment']
|
||||
search_fields = ['name', 'comment']
|
||||
|
||||
def get_queryset(self):
|
||||
user_group_id = self.kwargs.get('pk', '')
|
||||
queryset = Application.objects\
|
||||
.filter(Q(granted_by_permissions__user_groups__id=user_group_id))\
|
||||
.distinct().only(*self.only_fields)
|
||||
return queryset
|
|
@ -0,0 +1,2 @@
|
|||
from .user_permission_applications import *
|
||||
from .common import *
|
|
@ -0,0 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404
|
||||
)
|
||||
|
||||
from applications.models import Application
|
||||
from perms.utils.application.permission import (
|
||||
get_application_system_users_id
|
||||
)
|
||||
from perms.api.asset.user_permission.mixin import ForAdminMixin, ForUserMixin
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from perms.hands import User, SystemUser
|
||||
from perms import serializers
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedApplicationSystemUsersApi',
|
||||
'MyGrantedApplicationSystemUsersApi',
|
||||
'ValidateUserApplicationPermissionApi'
|
||||
]
|
||||
|
||||
|
||||
class GrantedApplicationSystemUsersMixin(ListAPIView):
|
||||
serializer_class = serializers.ApplicationSystemUserSerializer
|
||||
only_fields = serializers.ApplicationSystemUserSerializer.Meta.only_fields
|
||||
user: None
|
||||
|
||||
def get_application_system_users_id(self, application):
|
||||
return get_application_system_users_id(self.user, application)
|
||||
|
||||
def get_queryset(self):
|
||||
application_id = self.kwargs.get('application_id')
|
||||
application = get_object_or_404(Application, id=application_id)
|
||||
system_users_id = self.get_application_system_users_id(application)
|
||||
system_users = SystemUser.objects.filter(id__in=system_users_id)\
|
||||
.only(*self.only_fields).order_by('priority')
|
||||
return system_users
|
||||
|
||||
|
||||
class UserGrantedApplicationSystemUsersApi(ForAdminMixin, GrantedApplicationSystemUsersMixin):
|
||||
pass
|
||||
|
||||
|
||||
class MyGrantedApplicationSystemUsersApi(ForUserMixin, GrantedApplicationSystemUsersMixin):
|
||||
pass
|
||||
|
||||
|
||||
class ValidateUserApplicationPermissionApi(APIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_id = request.query_params.get('user_id', '')
|
||||
application_id = request.query_params.get('application_id', '')
|
||||
system_user_id = request.query_params.get('system_user_id', '')
|
||||
|
||||
try:
|
||||
user_id = uuid.UUID(user_id)
|
||||
application_id = uuid.UUID(application_id)
|
||||
system_user_id = uuid.UUID(system_user_id)
|
||||
except ValueError:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
application = get_object_or_404(Application, id=application_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
|
||||
system_users_id = get_application_system_users_id(user, application)
|
||||
if system_user.id in system_users_id:
|
||||
return Response({'msg': True}, status=200)
|
||||
|
||||
return Response({'msg': False}, status=403)
|
|
@ -0,0 +1,60 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework.generics import ListAPIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
from applications.api.mixin import (
|
||||
SerializeApplicationToTreeNodeMixin, ApplicationAttrsSerializerViewMixin
|
||||
)
|
||||
from perms import serializers
|
||||
from perms.api.asset.user_permission.mixin import ForAdminMixin, ForUserMixin
|
||||
from perms.utils.application.user_permission import (
|
||||
get_user_granted_all_applications
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserAllGrantedApplicationsApi',
|
||||
'MyAllGrantedApplicationsApi',
|
||||
'UserAllGrantedApplicationsAsTreeApi',
|
||||
'MyAllGrantedApplicationsAsTreeApi',
|
||||
]
|
||||
|
||||
|
||||
class AllGrantedApplicationsMixin(ApplicationAttrsSerializerViewMixin, ListAPIView):
|
||||
only_fields = serializers.ApplicationGrantedSerializer.Meta.only_fields
|
||||
serializer_class = serializers.ApplicationGrantedSerializer
|
||||
filter_fields = ['id', 'name', 'category', 'type', 'comment']
|
||||
search_fields = ['name', 'comment']
|
||||
user: None
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = get_user_granted_all_applications(self.user)
|
||||
return queryset.only(*self.only_fields)
|
||||
|
||||
|
||||
class UserAllGrantedApplicationsApi(ForAdminMixin, AllGrantedApplicationsMixin):
|
||||
pass
|
||||
|
||||
|
||||
class MyAllGrantedApplicationsApi(ForUserMixin, AllGrantedApplicationsMixin):
|
||||
pass
|
||||
|
||||
|
||||
class ApplicationsAsTreeMixin(SerializeApplicationToTreeNodeMixin):
|
||||
"""
|
||||
将应用序列化成树的结构返回
|
||||
"""
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
data = self.serialize_applications(queryset)
|
||||
return Response(data=data)
|
||||
|
||||
|
||||
class UserAllGrantedApplicationsAsTreeApi(ApplicationsAsTreeMixin, UserAllGrantedApplicationsApi):
|
||||
pass
|
||||
|
||||
|
||||
class MyAllGrantedApplicationsAsTreeApi(ApplicationsAsTreeMixin, MyAllGrantedApplicationsApi):
|
||||
pass
|
|
@ -0,0 +1,4 @@
|
|||
from .user_permission import *
|
||||
from .asset_permission import *
|
||||
from .asset_permission_relation import *
|
||||
from .user_group_permission import *
|
|
@ -3,13 +3,13 @@
|
|||
from django.db.models import Q
|
||||
|
||||
from common.permissions import IsOrgAdmin
|
||||
from orgs.mixins.api import OrgModelViewSet
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import AssetPermission
|
||||
from ..hands import (
|
||||
from perms.models import AssetPermission
|
||||
from perms.hands import (
|
||||
User, UserGroup, Asset, Node, SystemUser,
|
||||
)
|
||||
from .. import serializers
|
||||
from perms import serializers
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -17,7 +17,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class AssetPermissionViewSet(OrgModelViewSet):
|
||||
class AssetPermissionViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
资产授权列表的增删改查api
|
||||
"""
|
|
@ -11,8 +11,8 @@ from orgs.mixins.api import OrgRelationMixin
|
|||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.utils import current_org
|
||||
from common.permissions import IsOrgAdmin
|
||||
from .. import serializers
|
||||
from .. import models
|
||||
from perms import serializers
|
||||
from perms import models
|
||||
|
||||
__all__ = [
|
||||
'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet',
|
|
@ -10,9 +10,9 @@ from common.permissions import IsOrgAdminOrAppUser
|
|||
from common.utils import lazyproperty
|
||||
from perms.models import AssetPermission
|
||||
from assets.models import Asset, Node
|
||||
from . import user_permission as uapi
|
||||
from perms.api.asset import user_permission as uapi
|
||||
from perms import serializers
|
||||
from perms.utils.asset_permission import get_asset_system_users_id_with_actions_by_group
|
||||
from perms.utils.asset.permission import get_asset_system_users_id_with_actions_by_group
|
||||
from assets.api.mixin import SerializeToTreeNodeMixin
|
||||
from users.models import UserGroup
|
||||
|
|
@ -10,13 +10,13 @@ from rest_framework.generics import (
|
|||
)
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from perms.utils.asset_permission import get_asset_system_users_id_with_actions_by_user
|
||||
from perms.utils.asset.permission import get_asset_system_users_id_with_actions_by_user
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin, IsValidUser
|
||||
from common.utils import get_logger, lazyproperty
|
||||
|
||||
from ...hands import User, Asset, SystemUser
|
||||
from ... import serializers
|
||||
from ...models import Action
|
||||
from perms.hands import User, Asset, SystemUser
|
||||
from perms import serializers
|
||||
from perms.models import Action
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -4,6 +4,7 @@ from rest_framework.request import Request
|
|||
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
|
||||
from common.utils import lazyproperty
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from users.models import User
|
||||
from perms.models import UserGrantedMappingNode
|
||||
|
||||
|
@ -47,6 +48,10 @@ class ForUserMixin:
|
|||
permission_classes = (IsValidUser,)
|
||||
request: Request
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
with tmp_to_root_org():
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@lazyproperty
|
||||
def user(self):
|
||||
return self.request.user
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.decorators import method_decorator
|
||||
from perms.api.user_permission.mixin import UserNodeGrantStatusDispatchMixin
|
||||
from perms.api.asset.user_permission.mixin import UserNodeGrantStatusDispatchMixin
|
||||
from rest_framework.generics import ListAPIView
|
||||
from rest_framework.response import Response
|
||||
from django.conf import settings
|
||||
|
@ -10,9 +9,8 @@ from assets.api.mixin import SerializeToTreeNodeMixin
|
|||
from common.utils import get_logger
|
||||
from perms.pagination import GrantedAssetLimitOffsetPagination
|
||||
from assets.models import Asset, Node, FavoriteAsset
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from ... import serializers
|
||||
from ...utils.user_asset_permission import (
|
||||
from perms import serializers
|
||||
from perms.utils.asset.user_permission import (
|
||||
get_node_all_granted_assets, get_user_direct_granted_assets,
|
||||
get_user_granted_all_assets
|
||||
)
|
||||
|
@ -22,7 +20,6 @@ from .mixin import ForAdminMixin, ForUserMixin
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class UserDirectGrantedAssetsApi(ListAPIView):
|
||||
"""
|
||||
用户直接授权的资产的列表,也就是授权规则上直接授权的资产,并非是来自节点的
|
||||
|
@ -40,7 +37,6 @@ class UserDirectGrantedAssetsApi(ListAPIView):
|
|||
return assets
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class UserFavoriteGrantedAssetsApi(ListAPIView):
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
|
||||
|
@ -55,7 +51,6 @@ class UserFavoriteGrantedAssetsApi(ListAPIView):
|
|||
return assets
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class AssetsAsTreeMixin(SerializeToTreeNodeMixin):
|
||||
"""
|
||||
将 资产 序列化成树的结构返回
|
||||
|
@ -82,12 +77,10 @@ class MyFavoriteGrantedAssetsApi(ForUserMixin, UserFavoriteGrantedAssetsApi):
|
|||
pass
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class UserDirectGrantedAssetsAsTreeForAdminApi(ForAdminMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi):
|
||||
pass
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class MyUngroupAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
|
@ -96,9 +89,11 @@ class MyUngroupAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserDirectGrante
|
|||
return queryset
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class UserAllGrantedAssetsApi(ListAPIView):
|
||||
class UserAllGrantedAssetsApi(ForAdminMixin, ListAPIView):
|
||||
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
filter_fields = ['hostname', 'ip', 'id', 'comment']
|
||||
search_fields = ['hostname', 'ip', 'comment']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = get_user_granted_all_assets(self.user)
|
||||
|
@ -106,11 +101,14 @@ class UserAllGrantedAssetsApi(ListAPIView):
|
|||
return queryset.only(*self.only_fields)
|
||||
|
||||
|
||||
class MyAllGrantedAssetsApi(ForUserMixin, UserAllGrantedAssetsApi):
|
||||
pass
|
||||
|
||||
|
||||
class MyAllAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserAllGrantedAssetsApi):
|
||||
search_fields = ['hostname', 'ip']
|
||||
|
||||
|
||||
@method_decorator(tmp_to_root_org(), name='list')
|
||||
class UserGrantedNodeAssetsApi(UserNodeGrantStatusDispatchMixin, ListAPIView):
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
|
|
@ -7,13 +7,12 @@ from rest_framework.generics import (
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.request import Request
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from assets.api.mixin import SerializeToTreeNodeMixin
|
||||
from common.utils import get_logger
|
||||
from .mixin import ForAdminMixin, ForUserMixin, UserNodeGrantStatusDispatchMixin
|
||||
from ...hands import Node, User
|
||||
from ... import serializers
|
||||
from ...utils.user_asset_permission import (
|
||||
from perms.hands import Node, User
|
||||
from perms import serializers
|
||||
from perms.utils.asset.user_permission import (
|
||||
get_indirect_granted_node_children,
|
||||
get_user_granted_nodes_list_via_mapping_node,
|
||||
get_top_level_granted_nodes,
|
||||
|
@ -59,7 +58,6 @@ class NodeChildrenMixin:
|
|||
class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta):
|
||||
serializer_class = serializers.NodeGrantedSerializer
|
||||
|
||||
@tmp_to_root_org()
|
||||
def list(self, request, *args, **kwargs):
|
||||
rebuild_user_tree_if_need(request, self.user)
|
||||
nodes = self.get_nodes()
|
||||
|
@ -72,7 +70,6 @@ class BaseNodeChildrenApi(NodeChildrenMixin, BaseGrantedNodeApi, metaclass=abc.A
|
|||
|
||||
|
||||
class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta):
|
||||
@tmp_to_root_org()
|
||||
def list(self, request: Request, *args, **kwargs):
|
||||
rebuild_user_tree_if_need(request, self.user)
|
||||
nodes = self.get_nodes()
|
|
@ -5,22 +5,21 @@ from rest_framework.request import Request
|
|||
from rest_framework.response import Response
|
||||
from django.db.models import F
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from common.permissions import IsValidUser
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from .mixin import UserNodeGrantStatusDispatchMixin, ForUserMixin, ForAdminMixin
|
||||
from ...utils.user_asset_permission import (
|
||||
get_user_resources_q_granted_by_permissions,
|
||||
from perms.utils.asset.user_permission import (
|
||||
get_indirect_granted_node_children, UNGROUPED_NODE_KEY, FAVORITE_NODE_KEY,
|
||||
get_user_direct_granted_assets, get_top_level_granted_nodes,
|
||||
get_user_granted_nodes_list_via_mapping_node,
|
||||
get_user_granted_all_assets, rebuild_user_tree_if_need,
|
||||
get_user_all_assetpermission_ids,
|
||||
get_user_all_assetpermissions_id,
|
||||
)
|
||||
|
||||
from assets.models import Asset, FavoriteAsset
|
||||
from assets.api import SerializeToTreeNodeMixin
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from ...hands import Node
|
||||
from perms.hands import Node
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
@ -66,14 +65,14 @@ class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(ForAdminMixin, UserNode
|
|||
|
||||
def get_data_on_node_indirect_granted(self, key):
|
||||
user = self.user
|
||||
asset_perm_ids = get_user_all_assetpermission_ids(user)
|
||||
asset_perms_id = get_user_all_assetpermissions_id(user)
|
||||
|
||||
nodes = get_indirect_granted_node_children(user, key)
|
||||
|
||||
assets = Asset.org_objects.filter(
|
||||
nodes__key=key,
|
||||
).filter(
|
||||
granted_by_permissions__id__in=asset_perm_ids
|
||||
granted_by_permissions__id__in=asset_perms_id
|
||||
).distinct()
|
||||
assets = assets.prefetch_related('platform')
|
||||
return nodes, assets
|
||||
|
@ -102,7 +101,6 @@ class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(ForAdminMixin, UserNode
|
|||
if node:
|
||||
return node.key
|
||||
|
||||
@tmp_to_root_org()
|
||||
def list(self, request: Request, *args, **kwargs):
|
||||
key = self.request.query_params.get('key')
|
||||
if key is None:
|
|
@ -60,7 +60,7 @@ class AssetPermissionForm(OrgModelForm):
|
|||
# 过滤系统用户
|
||||
system_users_field = self.fields.get('system_users')
|
||||
system_users_field.queryset = SystemUser.objects.exclude(
|
||||
protocol=SystemUser.PROTOCOL_MYSQL
|
||||
protocol__in=SystemUser.application_category_protocols
|
||||
)
|
||||
|
||||
def set_nodes_initial(self, nodes):
|
||||
|
|
|
@ -25,7 +25,7 @@ class DatabaseAppPermissionCreateUpdateForm(OrgModelForm):
|
|||
# 过滤系统用户
|
||||
system_users_field = self.fields.get('system_users')
|
||||
system_users_field.queryset = SystemUser.objects.filter(
|
||||
protocol=SystemUser.PROTOCOL_MYSQL
|
||||
protocol__in=SystemUser.application_category_protocols
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
# Generated by Django 2.2.13 on 2020-10-28 02:19
|
||||
|
||||
import common.utils.django
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
||||
def old_perm_to_application_permission_json(old_perm, category, _type):
|
||||
return {
|
||||
'id': old_perm.id,
|
||||
'name': old_perm.name,
|
||||
'category': category,
|
||||
'type': _type,
|
||||
'is_active': old_perm.is_active,
|
||||
'date_start': old_perm.date_start,
|
||||
'date_expired': old_perm.date_expired,
|
||||
'created_by': old_perm.created_by,
|
||||
'comment': old_perm.comment,
|
||||
'org_id': old_perm.org_id
|
||||
}
|
||||
|
||||
|
||||
def common_old_perm_relation_to_application_permission_json(old_perm_relation, data_json):
|
||||
return {
|
||||
'applicationpermission_id': getattr(old_perm_relation, data_json['relation_app_perm_id']),
|
||||
}
|
||||
|
||||
|
||||
def old_perm_relation_app_to_application_permission_json(old_perm_relation_app, data_json):
|
||||
data = common_old_perm_relation_to_application_permission_json(old_perm_relation_app, data_json)
|
||||
data.update({
|
||||
'application_id': getattr(old_perm_relation_app, data_json['relation_app_id'])
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
def old_perm_relation_system_user_to_application_permission_json(old_perm_relation_system_user, data_json):
|
||||
data = common_old_perm_relation_to_application_permission_json(old_perm_relation_system_user, data_json)
|
||||
data.update({
|
||||
'systemuser_id': old_perm_relation_system_user.systemuser_id,
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
def old_perm_relation_user_group_to_application_permission_json(old_perm_relation_user_group, data_json):
|
||||
data = common_old_perm_relation_to_application_permission_json(old_perm_relation_user_group, data_json)
|
||||
data.update({
|
||||
'usergroup_id': old_perm_relation_user_group.usergroup_id,
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
def old_perm_relation_user_to_application_permission_json(old_perm_relation_user, data_json):
|
||||
data = common_old_perm_relation_to_application_permission_json(old_perm_relation_user, data_json)
|
||||
data.update({
|
||||
'user_id': old_perm_relation_user.user_id,
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
CATEGORY_DB = 'db'
|
||||
CATEGORY_REMOTE = 'remote_app'
|
||||
CATEGORY_CLOUD = 'cloud'
|
||||
|
||||
TYPE_DB_MYSQL = 'mysql'
|
||||
TYPE_CLOUD_K8S = 'k8s'
|
||||
TYPE_REMOTE_CHROME = 'chrome'
|
||||
TYPE_REMOTE_MYSQL_WORKBENCH = 'mysql_workbench'
|
||||
TYPE_REMOTE_VMWARE_CLIENT = 'vmware_client'
|
||||
TYPE_REMOTE_CUSTOM = 'custom'
|
||||
|
||||
|
||||
OLD_PERM_MODELS_NAME_MAP_DATA_JSON = {
|
||||
'DatabaseAppPermission': {
|
||||
'app_m2m_fields': 'database_apps',
|
||||
'relation_app_perm_id': 'databaseapppermission_id',
|
||||
'relation_app_id': 'databaseapp_id',
|
||||
'category': CATEGORY_DB,
|
||||
'type': TYPE_DB_MYSQL,
|
||||
},
|
||||
'RemoteAppPermission': {
|
||||
'app_m2m_fields': 'remote_apps',
|
||||
'relation_app_perm_id': 'remoteapppermission_id',
|
||||
'relation_app_id': 'remoteapp_id',
|
||||
'category': CATEGORY_REMOTE,
|
||||
'type': None,
|
||||
},
|
||||
'K8sAppPermission': {
|
||||
'app_m2m_fields': 'k8s_apps',
|
||||
'relation_app_perm_id': 'k8sapppermission_id',
|
||||
'relation_app_id': 'k8sapp_id',
|
||||
'category': CATEGORY_CLOUD,
|
||||
'type': TYPE_CLOUD_K8S,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def migrate_and_integrate_application_permissions(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
new_app_perms_json = []
|
||||
new_app_perms_relation_apps_json = []
|
||||
new_app_perms_relation_system_users_json = []
|
||||
new_app_perms_relation_user_groups_json = []
|
||||
new_app_perms_relation_users_json = []
|
||||
|
||||
for old_perm_model_name, data_json in OLD_PERM_MODELS_NAME_MAP_DATA_JSON.items():
|
||||
|
||||
# model
|
||||
old_perm_model = apps.get_model("perms", old_perm_model_name)
|
||||
|
||||
# instances
|
||||
old_perms = old_perm_model.objects.using(db_alias).all()
|
||||
old_perms_relation_apps = getattr(old_perm_model, data_json['app_m2m_fields']).through.objects.using(db_alias).all()
|
||||
old_perms_relation_system_users = old_perm_model.system_users.through.objects.using(db_alias).all()
|
||||
old_perms_relation_user_groups = old_perm_model.user_groups.through.objects.using(db_alias).all()
|
||||
old_perms_relation_users = old_perm_model.users.through.objects.using(db_alias).all()
|
||||
|
||||
# json
|
||||
perms_json = []
|
||||
category = data_json['category']
|
||||
for old_perm in old_perms:
|
||||
if category == CATEGORY_REMOTE:
|
||||
type_list = list(old_perm.remote_apps.values_list('type', flat=True))
|
||||
if len(type_list) == 0:
|
||||
_type = TYPE_REMOTE_CHROME
|
||||
else:
|
||||
_type = max(type_list, key=type_list.count)
|
||||
else:
|
||||
_type = data_json['type']
|
||||
perm_json = old_perm_to_application_permission_json(old_perm, category, _type)
|
||||
perms_json.append(perm_json)
|
||||
|
||||
perms_relation_apps_json = [
|
||||
old_perm_relation_app_to_application_permission_json(old_perm_relation_app, data_json)
|
||||
for old_perm_relation_app in old_perms_relation_apps
|
||||
]
|
||||
perms_relation_system_users_json = [
|
||||
old_perm_relation_system_user_to_application_permission_json(old_perm_relation_system_user, data_json)
|
||||
for old_perm_relation_system_user in old_perms_relation_system_users
|
||||
]
|
||||
perms_relation_user_groups_json = [
|
||||
old_perm_relation_user_group_to_application_permission_json(old_perm_relation_user_group, data_json)
|
||||
for old_perm_relation_user_group in old_perms_relation_user_groups
|
||||
]
|
||||
perms_relation_users_json = [
|
||||
old_perm_relation_user_to_application_permission_json(old_perm_relation_user, data_json)
|
||||
for old_perm_relation_user in old_perms_relation_users
|
||||
]
|
||||
|
||||
new_app_perms_json.extend(perms_json)
|
||||
new_app_perms_relation_apps_json.extend(perms_relation_apps_json)
|
||||
new_app_perms_relation_system_users_json.extend(perms_relation_system_users_json)
|
||||
new_app_perms_relation_user_groups_json.extend(perms_relation_user_groups_json)
|
||||
new_app_perms_relation_users_json.extend(perms_relation_users_json)
|
||||
|
||||
# model
|
||||
new_app_perm_model = apps.get_model("perms", "ApplicationPermission")
|
||||
new_app_perm_relation_app_model = new_app_perm_model.applications.through
|
||||
new_app_perm_relation_system_user_model = new_app_perm_model.system_users.through
|
||||
new_app_perm_relation_user_group_model = new_app_perm_model.user_groups.through
|
||||
new_app_perm_relation_user_model = new_app_perm_model.users.through
|
||||
|
||||
# instances
|
||||
new_app_perm_objects = [
|
||||
new_app_perm_model(**data)
|
||||
for data in new_app_perms_json
|
||||
]
|
||||
new_app_perm_relation_app_objects = [
|
||||
new_app_perm_relation_app_model(**data)
|
||||
for data in new_app_perms_relation_apps_json
|
||||
]
|
||||
new_app_perm_relation_system_user_objects = [
|
||||
new_app_perm_relation_system_user_model(**data)
|
||||
for data in new_app_perms_relation_system_users_json
|
||||
]
|
||||
new_app_perm_relation_user_group_objects = [
|
||||
new_app_perm_relation_user_group_model(**data)
|
||||
for data in new_app_perms_relation_user_groups_json
|
||||
]
|
||||
new_app_perm_relation_user_objects = [
|
||||
new_app_perm_relation_user_model(**data)
|
||||
for data in new_app_perms_relation_users_json
|
||||
]
|
||||
|
||||
# create
|
||||
for new_app_perm_object in new_app_perm_objects:
|
||||
if new_app_perm_model.objects.using(db_alias).filter(name=new_app_perm_object.name).exists():
|
||||
new_app_perm_object.name = '{}-{}'.format(new_app_perm_object.name, str(new_app_perm_object.id)[:4])
|
||||
new_app_perm_object.save()
|
||||
new_app_perm_relation_app_model.objects.using(db_alias).bulk_create(new_app_perm_relation_app_objects)
|
||||
new_app_perm_relation_system_user_model.objects.using(db_alias).bulk_create(new_app_perm_relation_system_user_objects)
|
||||
new_app_perm_relation_user_group_model.objects.using(db_alias).bulk_create(new_app_perm_relation_user_group_objects)
|
||||
new_app_perm_relation_user_model.objects.using(db_alias).bulk_create(new_app_perm_relation_user_objects)
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0030_auto_20200819_2041'),
|
||||
('assets', '0059_auto_20201027_1905'),
|
||||
('applications', '0006_application'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('perms', '0015_auto_20200929_1728'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ApplicationPermission',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('date_start', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start')),
|
||||
('date_expired', models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired')),
|
||||
('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('category', models.CharField(choices=[('db', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Cloud')], max_length=16, verbose_name='Category')),
|
||||
('type', models.CharField(choices=[('mysql', 'MySQL'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('mariadb', 'MariaDB'), ('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom'), ('k8s', 'Kubernetes')], max_length=16, verbose_name='Type')),
|
||||
('applications', models.ManyToManyField(blank=True, related_name='granted_by_permissions', to='applications.Application', verbose_name='Application')),
|
||||
('system_users', models.ManyToManyField(related_name='granted_by_application_permissions', to='assets.SystemUser', verbose_name='System user')),
|
||||
('user_groups', models.ManyToManyField(blank=True, related_name='applicationpermissions', to='users.UserGroup', verbose_name='User group')),
|
||||
('users', models.ManyToManyField(blank=True, related_name='applicationpermissions', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Application permission',
|
||||
'ordering': ('name',),
|
||||
'unique_together': {('org_id', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_and_integrate_application_permissions),
|
||||
]
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
|
||||
from .asset_permission import *
|
||||
from .application_permission import *
|
||||
from .remote_app_permission import *
|
||||
from .database_app_permission import *
|
||||
from .k8s_app_permission import *
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from .base import BasePermission
|
||||
from users.models import User
|
||||
from applications.models import Category
|
||||
|
||||
__all__ = [
|
||||
'ApplicationPermission',
|
||||
]
|
||||
|
||||
|
||||
class ApplicationPermission(BasePermission):
|
||||
category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_('Category'))
|
||||
type = models.CharField(max_length=16, choices=Category.get_all_type_choices(), verbose_name=_('Type'))
|
||||
applications = models.ManyToManyField('applications.Application', related_name='granted_by_permissions', blank=True, verbose_name=_("Application"))
|
||||
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_application_permissions', verbose_name=_("System user"))
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'name')]
|
||||
verbose_name = _('Application permission')
|
||||
ordering = ('name',)
|
||||
|
||||
@lazyproperty
|
||||
def users_amount(self):
|
||||
return self.users.count()
|
||||
|
||||
@lazyproperty
|
||||
def user_groups_amount(self):
|
||||
return self.user_groups.count()
|
||||
|
||||
@lazyproperty
|
||||
def applications_amount(self):
|
||||
return self.applications.count()
|
||||
|
||||
@lazyproperty
|
||||
def system_users_amount(self):
|
||||
return self.system_users.count()
|
||||
|
||||
def get_all_users(self):
|
||||
users_id = self.users.all().values_list('id', flat=True)
|
||||
user_groups_id = self.user_groups.all().values_list('id', flat=True)
|
||||
users = User.objects.filter(
|
||||
Q(id__in=users_id) | Q(groups__id__in=user_groups_id)
|
||||
)
|
||||
return users
|
|
@ -1,11 +1,12 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
from .asset import *
|
||||
from .application import *
|
||||
from .system_user_permission import *
|
||||
from .asset_permission import *
|
||||
from .user_permission import *
|
||||
|
||||
# TODO: 删除
|
||||
from .remote_app_permission import *
|
||||
from .remote_app_permission_relation import *
|
||||
from .asset_permission_relation import *
|
||||
from .database_app_permission import *
|
||||
from .database_app_permission_relation import *
|
||||
from .base import *
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from .permission import *
|
||||
from .permission_relation import *
|
||||
from .user_permission import *
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from perms.models import ApplicationPermission
|
||||
|
||||
__all__ = [
|
||||
'ApplicationPermissionSerializer'
|
||||
]
|
||||
|
||||
|
||||
class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer):
|
||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
is_valid = serializers.BooleanField(read_only=True)
|
||||
is_expired = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ApplicationPermission
|
||||
mini_fields = ['id', 'name']
|
||||
small_fields = mini_fields + [
|
||||
'category', 'category_display', 'type', 'type_display', 'is_active', 'is_expired',
|
||||
'is_valid', 'created_by', 'date_created', 'date_expired', 'date_start', 'comment'
|
||||
]
|
||||
m2m_fields = [
|
||||
'users', 'user_groups', 'applications', 'system_users',
|
||||
'users_amount', 'user_groups_amount', 'applications_amount', 'system_users_amount',
|
||||
]
|
||||
fields = small_fields + m2m_fields
|
||||
read_only_fields = ['created_by', 'date_created']
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('users', 'user_groups', 'applications', 'system_users')
|
||||
return queryset
|
||||
|
||||
def validate_applications(self, applications):
|
||||
if self.instance:
|
||||
permission_type = self.instance.type
|
||||
else:
|
||||
permission_type = self.initial_data['type']
|
||||
|
||||
other_type_applications = [
|
||||
application for application in applications
|
||||
if application.type != permission_type
|
||||
]
|
||||
if len(other_type_applications) > 0:
|
||||
error = _(
|
||||
'The application list contains applications '
|
||||
'that are different from the permission type. ({})'
|
||||
).format(', '.join([application.name for application in other_type_applications]))
|
||||
raise serializers.ValidationError(error)
|
||||
return applications
|
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from perms.models import ApplicationPermission
|
||||
|
||||
__all__ = [
|
||||
'ApplicationPermissionUserRelationSerializer',
|
||||
'ApplicationPermissionUserGroupRelationSerializer',
|
||||
'ApplicationPermissionApplicationRelationSerializer',
|
||||
'ApplicationPermissionSystemUserRelationSerializer',
|
||||
'ApplicationPermissionAllApplicationSerializer',
|
||||
'ApplicationPermissionAllUserSerializer'
|
||||
]
|
||||
|
||||
|
||||
class RelationMixin(BulkSerializerMixin, serializers.Serializer):
|
||||
applicationpermission_display = serializers.ReadOnlyField()
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend(['applicationpermission', "applicationpermission_display"])
|
||||
return fields
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
|
||||
|
||||
class ApplicationPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||
user_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta(RelationMixin.Meta):
|
||||
model = ApplicationPermission.users.through
|
||||
fields = [
|
||||
'id', 'user', 'user_display',
|
||||
]
|
||||
|
||||
|
||||
class ApplicationPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||
usergroup_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta(RelationMixin.Meta):
|
||||
model = ApplicationPermission.user_groups.through
|
||||
fields = [
|
||||
'id', 'usergroup', "usergroup_display",
|
||||
]
|
||||
|
||||
|
||||
class ApplicationPermissionApplicationRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||
application_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta(RelationMixin.Meta):
|
||||
model = ApplicationPermission.applications.through
|
||||
fields = [
|
||||
'id', "application", "application_display",
|
||||
]
|
||||
|
||||
|
||||
class ApplicationPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||
systemuser_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta(RelationMixin.Meta):
|
||||
model = ApplicationPermission.system_users.through
|
||||
fields = [
|
||||
'id', 'systemuser', 'systemuser_display'
|
||||
]
|
||||
|
||||
|
||||
class ApplicationPermissionAllApplicationSerializer(serializers.Serializer):
|
||||
application = serializers.UUIDField(read_only=True, source='id')
|
||||
application_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
only_fields = ['id', 'name']
|
||||
|
||||
@staticmethod
|
||||
def get_application_display(obj):
|
||||
return str(obj)
|
||||
|
||||
|
||||
class ApplicationPermissionAllUserSerializer(serializers.Serializer):
|
||||
user = serializers.UUIDField(read_only=True, source='id')
|
||||
user_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
only_fields = ['id', 'username', 'name']
|
||||
|
||||
@staticmethod
|
||||
def get_user_display(obj):
|
||||
return str(obj)
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from assets.models import SystemUser
|
||||
from applications.models import Application
|
||||
|
||||
__all__ = [
|
||||
'ApplicationGrantedSerializer',
|
||||
'ApplicationSystemUserSerializer'
|
||||
]
|
||||
|
||||
|
||||
class ApplicationSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
查看授权的应用系统用户的数据结构,这个和SystemUserSerializer不同,字段少
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
only_fields = (
|
||||
'id', 'name', 'username', 'priority', 'protocol', 'login_mode'
|
||||
)
|
||||
fields = list(only_fields)
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class ApplicationGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权应用的数据结构
|
||||
"""
|
||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
|
||||
class Meta:
|
||||
model = Application
|
||||
only_fields = [
|
||||
'id', 'name', 'domain', 'category', 'type', 'attrs', 'comment', 'org_id'
|
||||
]
|
||||
fields = only_fields + ['category_display', 'type_display', 'org_name']
|
||||
read_only_fields = fields
|
|
@ -0,0 +1,3 @@
|
|||
from .permission import *
|
||||
from .permission_relation import *
|
||||
from .user_permission import *
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue