Merge pull request #4982 from jumpserver/dev

Dev
pull/5041/head
老广 2020-11-12 14:12:35 +08:00 committed by GitHub
commit 841f707b6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 3346 additions and 775 deletions

View File

@ -2,8 +2,9 @@
name: 需求建议
about: 提出针对本项目的想法和建议
title: "[Feature] "
labels: 待处理, 需求
assignees: 'ibuler'
labels: 类型:需求
assignees: ibuler
---
**请描述您的需求或者改进建议.**

View File

@ -2,7 +2,7 @@
name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[Bug] "
labels: bug, 待处理
labels: 类型:bug
assignees: wojiushixiaobai
---

View File

@ -2,7 +2,7 @@
name: 问题咨询
about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[Question] "
labels: 提问, 待处理
labels: 类型:提问
assignees: wojiushixiaobai
---

View File

@ -1,3 +1,5 @@
from .application import *
from .mixin import *
from .remote_app import *
from .database_app import *
from .k8s_app import *

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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),
]

View File

@ -1,3 +1,4 @@
from .application import *
from .remote_app import *
from .database_app import *
from .k8s_app import *

View File

@ -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

View File

@ -1,3 +1,5 @@
from .application import *
from .remote_app import *
from .database_app import *
from .k8s_app import *
from .common import *

View File

@ -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

View File

@ -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

View File

@ -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')},
}

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -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")

View File

@ -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")

View File

@ -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()

View File

@ -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):

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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)
]

View File

@ -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']

View File

@ -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)

View File

@ -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())

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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')},

View File

@ -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)

View File

@ -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

View File

@ -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'),

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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('/'):

View File

@ -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):
# 获取解密密钥,对密码进行解密

View File

@ -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>

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 != '':

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -0,0 +1,5 @@
import re
def no_special_chars(s):
return bool(re.match(r'\w+$', s))

View File

@ -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")
)

View File

@ -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

View File

@ -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}
}
}
}

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -75,4 +75,3 @@ def send_server_performance_mail(path, usage, usages):
send_mail_async(subject, message, recipient_list, html_message=message)

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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'))

View File

@ -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)

View File

@ -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)

View File

@ -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 *

View File

@ -0,0 +1,4 @@
from .user_permission import *
from .application_permission import *
from .application_permission_relation import *
from .user_group_permission import *

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
from .user_permission_applications import *
from .common import *

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,4 @@
from .user_permission import *
from .asset_permission import *
from .asset_permission_relation import *
from .user_group_permission import *

View File

@ -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
"""

View File

@ -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',

View File

@ -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

View File

@ -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__)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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):

View File

@ -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:

View File

@ -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),
]

View File

@ -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 *

View File

@ -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

View File

@ -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 *

View File

@ -0,0 +1,3 @@
from .permission import *
from .permission_relation import *
from .user_permission import *

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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