mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-25 05:22:36 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8b049349c | ||
|
|
2ff92c5141 | ||
|
|
3260f0eba8 |
14
.github/workflows/release-drafter.yml
vendored
14
.github/workflows/release-drafter.yml
vendored
@@ -20,8 +20,6 @@ jobs:
|
||||
run: |
|
||||
TAG=$(basename ${GITHUB_REF})
|
||||
VERSION=${TAG/v/}
|
||||
wget https://raw.githubusercontent.com/jumpserver/installer/v${VERSION}/quick_start.sh
|
||||
sed -i "s@Version=.*@Version=v${VERSION}@g" quick_start.sh
|
||||
echo "::set-output name=TAG::$TAG"
|
||||
echo "::set-output name=VERSION::$VERSION"
|
||||
- name: Create Release
|
||||
@@ -33,16 +31,6 @@ jobs:
|
||||
config-name: release-config.yml
|
||||
version: ${{ steps.get_version.outputs.TAG }}
|
||||
tag: ${{ steps.get_version.outputs.TAG }}
|
||||
- name: Upload Quick Start Script
|
||||
id: upload-release-quick-start-shell
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ./quick_start.sh
|
||||
asset_name: quick_start.sh
|
||||
asset_content_type: application/text
|
||||
|
||||
build-and-release:
|
||||
needs: create-realese
|
||||
@@ -55,4 +43,4 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-realese.outputs.upload_url }}
|
||||
upload_url: ${{ needs.create-realese.outputs.upload_url }}
|
||||
23
.github/workflows/sync-gitee.yml
vendored
23
.github/workflows/sync-gitee.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: 🔀 Sync mirror to Gitee
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
create:
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'jumpserver/jumpserver'
|
||||
steps:
|
||||
- name: mirror
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'push' || (github.event_name == 'create' && github.event.ref_type == 'tag')
|
||||
uses: wearerequired/git-mirror-action@v1
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
|
||||
with:
|
||||
source-repo: 'git@github.com:jumpserver/jumpserver.git'
|
||||
destination-repo: 'git@gitee.com:jumpserver/jumpserver.git'
|
||||
@@ -1,5 +1,5 @@
|
||||
# 编译代码
|
||||
FROM python:3.8-slim as stage-build
|
||||
FROM python:3.8.6-slim as stage-build
|
||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
@@ -9,7 +9,7 @@ ADD . .
|
||||
RUN cd utils && bash -ixeu build.sh
|
||||
|
||||
# 构建运行时环境
|
||||
FROM python:3.8-slim
|
||||
FROM python:3.8.6-slim
|
||||
ARG PIP_MIRROR=https://pypi.douban.com/simple
|
||||
ENV PIP_MIRROR=$PIP_MIRROR
|
||||
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
|
||||
|
||||
@@ -124,7 +124,7 @@ JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.ju
|
||||
|
||||
### License & Copyright
|
||||
|
||||
Copyright (c) 2014-2022 飞致云 FIT2CLOUD, All rights reserved.
|
||||
Copyright (c) 2014-2021 飞致云 FIT2CLOUD, All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ If you find a security problem, please contact us directly:
|
||||
- 400-052-0755
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2022 FIT2CLOUD Tech, Inc., All rights reserved.
|
||||
Copyright (c) 2014-2021 FIT2CLOUD Tech, Inc., All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
|
||||
11
SECURITY.md
11
SECURITY.md
@@ -7,14 +7,3 @@ JumpServer 是一款正在成长的安全产品, 请参考 [基本安全建议
|
||||
- ibuler@fit2cloud.com
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
||||
|
||||
|
||||
# Security Policy
|
||||
JumpServer is a security product, The installation and development should follow our security tips.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
All security bugs should be reported to the contact as below:
|
||||
|
||||
- ibuler@fit2cloud.com
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
||||
|
||||
@@ -60,8 +60,7 @@ class LoginAssetCheckAPI(CreateAPIView):
|
||||
'check_confirm_status': {'method': 'GET', 'url': confirm_status_url},
|
||||
'close_confirm': {'method': 'DELETE', 'url': confirm_status_url},
|
||||
'ticket_detail_url': ticket_detail_url,
|
||||
'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees],
|
||||
'ticket_id': str(ticket.id)
|
||||
'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees]
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@@ -44,7 +44,11 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Account.get_queryset()
|
||||
queryset = Account.objects.all() \
|
||||
.annotate(type=F('app__type')) \
|
||||
.annotate(app_display=F('app__name')) \
|
||||
.annotate(systemuser_display=F('systemuser__name')) \
|
||||
.annotate(category=F('app__category'))
|
||||
return queryset
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
from urllib.parse import urlencode, parse_qsl
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework.generics import get_object_or_404
|
||||
|
||||
from common.tree import TreeNode
|
||||
from orgs.models import Organization
|
||||
from assets.models import SystemUser
|
||||
from applications.utils import KubernetesClient, KubernetesTree
|
||||
from perms.utils.application.permission import get_application_system_user_ids
|
||||
|
||||
from ..models import Application
|
||||
|
||||
__all__ = ['SerializeApplicationToTreeNodeMixin']
|
||||
|
||||
|
||||
class SerializeApplicationToTreeNodeMixin:
|
||||
|
||||
@staticmethod
|
||||
def filter_organizations(applications):
|
||||
organization_ids = set(applications.values_list('org_id', flat=True))
|
||||
@@ -39,47 +31,25 @@ class SerializeApplicationToTreeNodeMixin:
|
||||
})
|
||||
return node
|
||||
|
||||
def serialize_applications_with_org(self, applications, tree_id, parent_info, user):
|
||||
tree_nodes = []
|
||||
def serialize_applications_with_org(self, applications):
|
||||
if not applications:
|
||||
return tree_nodes
|
||||
return []
|
||||
root_node = self.create_root_node()
|
||||
tree_nodes = [root_node]
|
||||
organizations = self.filter_organizations(applications)
|
||||
|
||||
if not tree_id:
|
||||
root_node = self.create_root_node()
|
||||
tree_nodes.append(root_node)
|
||||
organizations = self.filter_organizations(applications)
|
||||
for i, org in enumerate(organizations):
|
||||
tree_id = urlencode({'org_id': str(org.id)})
|
||||
apps = applications.filter(org_id=org.id)
|
||||
# 组织节点
|
||||
org_node = org.as_tree_node(oid=tree_id, pid=root_node.id)
|
||||
org_node.name += '({})'.format(apps.count())
|
||||
tree_nodes.append(org_node)
|
||||
category_type_nodes = Application.create_category_type_tree_nodes(
|
||||
apps, tree_id, show_empty=False
|
||||
)
|
||||
tree_nodes += category_type_nodes
|
||||
for i, org in enumerate(organizations):
|
||||
# 组织节点
|
||||
org_node = org.as_tree_node(pid=root_node.id)
|
||||
tree_nodes.append(org_node)
|
||||
org_applications = applications.filter(org_id=org.id)
|
||||
count = org_applications.count()
|
||||
org_node.name += '({})'.format(count)
|
||||
|
||||
for app in apps:
|
||||
app_node = app.as_tree_node(tree_id, is_luna=True)
|
||||
tree_nodes.append(app_node)
|
||||
return tree_nodes
|
||||
|
||||
parent_info = dict(parse_qsl(parent_info))
|
||||
pod_name = parent_info.get('pod')
|
||||
app_id = parent_info.get('app_id')
|
||||
namespace = parent_info.get('namespace')
|
||||
system_user_id = parent_info.get('system_user_id')
|
||||
if app_id and not any([pod_name, namespace, system_user_id]):
|
||||
app = get_object_or_404(Application, id=app_id)
|
||||
system_user_ids = get_application_system_user_ids(user, app)
|
||||
system_users = SystemUser.objects.filter(id__in=system_user_ids).order_by('priority')
|
||||
for system_user in system_users:
|
||||
system_user_node = KubernetesTree(tree_id).as_system_user_tree_node(
|
||||
system_user, parent_info
|
||||
)
|
||||
tree_nodes.append(system_user_node)
|
||||
return tree_nodes
|
||||
|
||||
tree_nodes = KubernetesTree(tree_id).async_tree_node(parent_info)
|
||||
# 各应用节点
|
||||
apps_nodes = Application.create_tree_nodes(
|
||||
queryset=org_applications, root_node=org_node,
|
||||
show_empty=False
|
||||
)
|
||||
tree_nodes += apps_nodes
|
||||
return tree_nodes
|
||||
|
||||
@@ -17,7 +17,6 @@ class AppCategory(TextChoices):
|
||||
class AppType(TextChoices):
|
||||
# db category
|
||||
mysql = 'mysql', 'MySQL'
|
||||
redis = 'redis', 'Redis'
|
||||
oracle = 'oracle', 'Oracle'
|
||||
pgsql = 'postgresql', 'PostgreSQL'
|
||||
mariadb = 'mariadb', 'MariaDB'
|
||||
@@ -35,9 +34,7 @@ class AppType(TextChoices):
|
||||
@classmethod
|
||||
def category_types_mapper(cls):
|
||||
return {
|
||||
AppCategory.db: [
|
||||
cls.mysql, cls.oracle, cls.redis, cls.pgsql, cls.mariadb, cls.sqlserver
|
||||
],
|
||||
AppCategory.db: [cls.mysql, cls.oracle, cls.pgsql, cls.mariadb, cls.sqlserver],
|
||||
AppCategory.remote_app: [cls.chrome, cls.mysql_workbench, cls.vmware_client, cls.custom],
|
||||
AppCategory.cloud: [cls.k8s]
|
||||
}
|
||||
@@ -65,3 +62,7 @@ class AppType(TextChoices):
|
||||
@classmethod
|
||||
def cloud_types(cls):
|
||||
return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.1.13 on 2022-01-12 12:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('applications', '0014_auto_20211105_1605'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='application',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('mysql', 'MySQL'), ('redis', 'Redis'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('mariadb', 'MariaDB'), ('sqlserver', 'SQLServer'), ('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom'), ('k8s', 'Kubernetes')], max_length=16, verbose_name='Type'),
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 3.1.13 on 2022-01-18 06:55
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('applications', '0015_auto_20220112_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='app',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='applications.application', verbose_name='Application'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalaccount',
|
||||
name='app',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='applications.application', verbose_name='Application'),
|
||||
),
|
||||
]
|
||||
@@ -1,6 +1,5 @@
|
||||
from django.db import models
|
||||
from simple_history.models import HistoricalRecords
|
||||
from django.db.models import F
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
@@ -8,12 +7,8 @@ from assets.models.base import BaseUser
|
||||
|
||||
|
||||
class Account(BaseUser):
|
||||
app = models.ForeignKey(
|
||||
'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Application')
|
||||
)
|
||||
systemuser = models.ForeignKey(
|
||||
'assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")
|
||||
)
|
||||
app = models.ForeignKey('applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database'))
|
||||
systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user"))
|
||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
history = HistoricalRecords()
|
||||
|
||||
@@ -65,10 +60,6 @@ class Account(BaseUser):
|
||||
def type(self):
|
||||
return self.app.type
|
||||
|
||||
@lazyproperty
|
||||
def attrs(self):
|
||||
return self.app.attrs
|
||||
|
||||
@lazyproperty
|
||||
def app_display(self):
|
||||
return self.systemuser.name
|
||||
@@ -93,14 +84,5 @@ class Account(BaseUser):
|
||||
app = '*'
|
||||
return '{}@{}'.format(username, app)
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls):
|
||||
queryset = cls.objects.all() \
|
||||
.annotate(type=F('app__type')) \
|
||||
.annotate(app_display=F('app__name')) \
|
||||
.annotate(systemuser_display=F('systemuser__name')) \
|
||||
.annotate(category=F('app__category'))
|
||||
return queryset
|
||||
|
||||
def __str__(self):
|
||||
return self.smart_name
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from collections import defaultdict
|
||||
from urllib.parse import urlencode, parse_qsl
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -8,8 +7,6 @@ from orgs.mixins.models import OrgModelMixin
|
||||
from common.mixins import CommonModelMixin
|
||||
from common.tree import TreeNode
|
||||
from assets.models import Asset, SystemUser
|
||||
|
||||
from ..utils import KubernetesTree
|
||||
from .. import const
|
||||
|
||||
|
||||
@@ -19,13 +16,6 @@ class ApplicationTreeNodeMixin:
|
||||
type: str
|
||||
category: str
|
||||
|
||||
@staticmethod
|
||||
def create_tree_id(pid, type, v):
|
||||
i = dict(parse_qsl(pid))
|
||||
i[type] = v
|
||||
tree_id = urlencode(i)
|
||||
return tree_id
|
||||
|
||||
@classmethod
|
||||
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
|
||||
show_empty=True, show_count=True):
|
||||
@@ -75,13 +65,13 @@ class ApplicationTreeNodeMixin:
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
|
||||
def create_category_tree_nodes(cls, root_node, counts=None, show_empty=True, show_count=True):
|
||||
nodes = []
|
||||
categories = const.AppType.category_types_mapper().keys()
|
||||
for category in categories:
|
||||
i = cls.create_tree_id(pid, 'category', category.value)
|
||||
i = root_node.id + '_' + category.value
|
||||
node = cls.create_choice_node(
|
||||
category, i, pid=pid, tp='category',
|
||||
category, i, pid=root_node.id, tp='category',
|
||||
counts=counts, opened=False, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
@@ -91,20 +81,17 @@ class ApplicationTreeNodeMixin:
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
|
||||
def create_types_tree_nodes(cls, root_node, counts, show_empty=True, show_count=True):
|
||||
nodes = []
|
||||
temp_pid = pid
|
||||
type_category_mapper = const.AppType.type_category_mapper()
|
||||
types = const.AppType.type_category_mapper().keys()
|
||||
for tp in types:
|
||||
for tp in const.AppType.type_category_mapper().keys():
|
||||
category = type_category_mapper.get(tp)
|
||||
pid = cls.create_tree_id(pid, 'category', category.value)
|
||||
i = cls.create_tree_id(pid, 'type', tp.value)
|
||||
pid = root_node.id + '_' + category.value
|
||||
i = root_node.id + '_' + tp.value
|
||||
node = cls.create_choice_node(
|
||||
tp, i, pid, tp='type', counts=counts, opened=False,
|
||||
show_empty=show_empty, show_count=show_count
|
||||
)
|
||||
pid = temp_pid
|
||||
if not node:
|
||||
continue
|
||||
nodes.append(node)
|
||||
@@ -121,27 +108,9 @@ class ApplicationTreeNodeMixin:
|
||||
counts[category] += 1
|
||||
return counts
|
||||
|
||||
@classmethod
|
||||
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
|
||||
counts = cls.get_tree_node_counts(queryset)
|
||||
tree_nodes = []
|
||||
|
||||
# 类别的节点
|
||||
tree_nodes += cls.create_category_tree_nodes(
|
||||
pid, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
|
||||
# 类型的节点
|
||||
tree_nodes += cls.create_types_tree_nodes(
|
||||
pid, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
|
||||
return tree_nodes
|
||||
|
||||
@classmethod
|
||||
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
|
||||
counts = cls.get_tree_node_counts(queryset)
|
||||
tree_nodes = []
|
||||
|
||||
# 根节点有可能是组织名称
|
||||
@@ -149,36 +118,31 @@ class ApplicationTreeNodeMixin:
|
||||
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
|
||||
tree_nodes.append(root_node)
|
||||
|
||||
tree_nodes += cls.create_category_type_tree_nodes(
|
||||
queryset, root_node.id, show_empty=show_empty, show_count=show_count
|
||||
# 类别的节点
|
||||
tree_nodes += cls.create_category_tree_nodes(
|
||||
root_node, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
|
||||
# 类型的节点
|
||||
tree_nodes += cls.create_types_tree_nodes(
|
||||
root_node, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
|
||||
# 应用的节点
|
||||
for app in queryset:
|
||||
node = app.as_tree_node(root_node.id)
|
||||
tree_nodes.append(node)
|
||||
pid = root_node.id + '_' + app.type
|
||||
tree_nodes.append(app.as_tree_node(pid))
|
||||
return tree_nodes
|
||||
|
||||
def create_app_tree_pid(self, root_id):
|
||||
pid = self.create_tree_id(root_id, 'category', self.category)
|
||||
pid = self.create_tree_id(pid, 'type', self.type)
|
||||
return pid
|
||||
|
||||
def as_tree_node(self, pid, is_luna=False):
|
||||
if is_luna and self.type == const.AppType.k8s:
|
||||
node = KubernetesTree(pid).as_tree_node(self)
|
||||
else:
|
||||
node = self._as_tree_node(pid)
|
||||
return node
|
||||
|
||||
def _as_tree_node(self, pid):
|
||||
def as_tree_node(self, pid):
|
||||
icon_skin_category_mapper = {
|
||||
'remote_app': 'chrome',
|
||||
'db': 'database',
|
||||
'cloud': 'cloud'
|
||||
}
|
||||
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
|
||||
pid = self.create_app_tree_pid(pid)
|
||||
node = TreeNode(**{
|
||||
'id': str(self.id),
|
||||
'name': self.name,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -8,8 +9,7 @@ from assets.serializers.base import AuthSerializerMixin
|
||||
from common.drf.serializers import MethodSerializer
|
||||
from .attrs import (
|
||||
category_serializer_classes_mapping,
|
||||
type_serializer_classes_mapping,
|
||||
type_secret_serializer_classes_mapping
|
||||
type_serializer_classes_mapping
|
||||
)
|
||||
from .. import models
|
||||
from .. import const
|
||||
@@ -23,28 +23,17 @@ __all__ = [
|
||||
class AppSerializerMixin(serializers.Serializer):
|
||||
attrs = MethodSerializer()
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
if isinstance(self.instance, models.Application):
|
||||
instance = self.instance
|
||||
else:
|
||||
instance = None
|
||||
return instance
|
||||
|
||||
def get_attrs_serializer(self):
|
||||
default_serializer = serializers.Serializer(read_only=True)
|
||||
instance = self.app
|
||||
if instance:
|
||||
_type = instance.type
|
||||
_category = instance.category
|
||||
if isinstance(self.instance, models.Application):
|
||||
_type = self.instance.type
|
||||
_category = self.instance.category
|
||||
else:
|
||||
_type = self.context['request'].query_params.get('type')
|
||||
_category = self.context['request'].query_params.get('category')
|
||||
|
||||
if _type:
|
||||
if isinstance(self, AppAccountSecretSerializer):
|
||||
serializer_class = type_secret_serializer_classes_mapping.get(_type)
|
||||
else:
|
||||
serializer_class = type_serializer_classes_mapping.get(_type)
|
||||
serializer_class = type_serializer_classes_mapping.get(_type)
|
||||
elif _category:
|
||||
serializer_class = category_serializer_classes_mapping.get(_category)
|
||||
else:
|
||||
@@ -95,13 +84,11 @@ class MiniAppSerializer(serializers.ModelSerializer):
|
||||
fields = AppSerializer.Meta.fields_mini
|
||||
|
||||
|
||||
class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True)
|
||||
category_display = serializers.SerializerMethodField(label=_('Category display'))
|
||||
type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True)
|
||||
type_display = serializers.SerializerMethodField(label=_('Type display'))
|
||||
date_created = serializers.DateTimeField(label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True)
|
||||
date_updated = serializers.DateTimeField(label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True)
|
||||
|
||||
category_mapper = dict(const.AppCategory.choices)
|
||||
type_mapper = dict(const.AppType.choices)
|
||||
@@ -109,11 +96,10 @@ class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResou
|
||||
class Meta:
|
||||
model = models.Account
|
||||
fields_mini = ['id', 'username', 'version']
|
||||
fields_write_only = ['password', 'private_key', 'public_key', 'passphrase']
|
||||
fields_other = ['date_created', 'date_updated']
|
||||
fields_write_only = ['password', 'private_key']
|
||||
fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display']
|
||||
fields = fields_mini + fields_fk + fields_write_only + fields_other + [
|
||||
'type', 'type_display', 'category', 'category_display', 'attrs'
|
||||
fields = fields_mini + fields_fk + fields_write_only + [
|
||||
'type', 'type_display', 'category', 'category_display',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'default': '', 'required': False},
|
||||
@@ -126,14 +112,6 @@ class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResou
|
||||
'ignore_conflicts': True
|
||||
}
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
if isinstance(self.instance, models.Account):
|
||||
instance = self.instance.app
|
||||
else:
|
||||
instance = None
|
||||
return instance
|
||||
|
||||
def get_category_display(self, obj):
|
||||
return self.category_mapper.get(obj.category)
|
||||
|
||||
@@ -153,11 +131,6 @@ class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResou
|
||||
|
||||
class AppAccountSecretSerializer(AppAccountSerializer):
|
||||
class Meta(AppAccountSerializer.Meta):
|
||||
fields_backup = [
|
||||
'id', 'app_display', 'attrs', 'username', 'password', 'private_key',
|
||||
'public_key', 'date_created', 'date_updated', 'version'
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': False},
|
||||
'private_key': {'write_only': False},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = ['CloudSerializer']
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from assets.models import Asset
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
__all__ = ['RemoteAppSerializer']
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
from .mysql import *
|
||||
from .redis import *
|
||||
from .mariadb import *
|
||||
from .oracle import *
|
||||
from .pgsql import *
|
||||
|
||||
@@ -3,7 +3,8 @@ from rest_framework import serializers
|
||||
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
__all__ = ['ChromeSerializer', 'ChromeSecretSerializer']
|
||||
|
||||
__all__ = ['ChromeSerializer']
|
||||
|
||||
|
||||
class ChromeSerializer(RemoteAppSerializer):
|
||||
@@ -16,16 +17,10 @@ class ChromeSerializer(RemoteAppSerializer):
|
||||
max_length=128, allow_blank=True, required=False, label=_('Target URL'), allow_null=True,
|
||||
)
|
||||
chrome_username = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, label=_('Chrome username'), allow_null=True,
|
||||
max_length=128, allow_blank=True, required=False, label=_('Username'), allow_null=True,
|
||||
)
|
||||
chrome_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Chrome password'),
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
|
||||
class ChromeSecretSerializer(ChromeSerializer):
|
||||
chrome_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Chrome password'),
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
__all__ = ['CustomSerializer', 'CustomSecretSerializer']
|
||||
|
||||
__all__ = ['CustomSerializer']
|
||||
|
||||
|
||||
class CustomSerializer(RemoteAppSerializer):
|
||||
@@ -16,17 +18,10 @@ class CustomSerializer(RemoteAppSerializer):
|
||||
allow_null=True,
|
||||
)
|
||||
custom_username = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, label=_('Custom Username'),
|
||||
max_length=128, allow_blank=True, required=False, label=_('Username'),
|
||||
allow_null=True,
|
||||
)
|
||||
custom_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Custom password'),
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
|
||||
class CustomSecretSerializer(RemoteAppSerializer):
|
||||
custom_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Custom password'),
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from ..application_category import CloudSerializer
|
||||
|
||||
|
||||
__all__ = ['K8SSerializer']
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from .mysql import MySQLSerializer
|
||||
|
||||
|
||||
__all__ = ['MariaDBSerializer']
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,13 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['MySQLSerializer']
|
||||
|
||||
|
||||
class MySQLSerializer(DBSerializer):
|
||||
port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ from rest_framework import serializers
|
||||
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
__all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer']
|
||||
|
||||
__all__ = ['MySQLWorkbenchSerializer']
|
||||
|
||||
|
||||
class MySQLWorkbenchSerializer(RemoteAppSerializer):
|
||||
@@ -26,17 +27,10 @@ class MySQLWorkbenchSerializer(RemoteAppSerializer):
|
||||
allow_null=True,
|
||||
)
|
||||
mysql_workbench_username = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'),
|
||||
max_length=128, allow_blank=True, required=False, label=_('Username'),
|
||||
allow_null=True,
|
||||
)
|
||||
mysql_workbench_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Mysql workbench password'),
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
|
||||
class MySQLWorkbenchSecretSerializer(RemoteAppSerializer):
|
||||
mysql_workbench_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Mysql workbench password'),
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
@@ -3,8 +3,10 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['OracleSerializer']
|
||||
|
||||
|
||||
class OracleSerializer(DBSerializer):
|
||||
port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True)
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['PostgreSerializer']
|
||||
|
||||
|
||||
class PostgreSerializer(DBSerializer):
|
||||
port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
__all__ = ['RedisSerializer']
|
||||
|
||||
|
||||
class RedisSerializer(DBSerializer):
|
||||
port = serializers.IntegerField(default=6379, label=_('Port'), allow_null=True)
|
||||
|
||||
@@ -3,6 +3,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['SQLServerSerializer']
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ from rest_framework import serializers
|
||||
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
__all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer']
|
||||
|
||||
__all__ = ['VMwareClientSerializer']
|
||||
|
||||
|
||||
class VMwareClientSerializer(RemoteAppSerializer):
|
||||
@@ -22,17 +23,10 @@ class VMwareClientSerializer(RemoteAppSerializer):
|
||||
allow_null=True
|
||||
)
|
||||
vmware_username = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, label=_('Vmware username'),
|
||||
max_length=128, allow_blank=True, required=False, label=_('Username'),
|
||||
allow_null=True
|
||||
)
|
||||
vmware_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Vmware password'),
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
|
||||
class VMwareClientSecretSerializer(RemoteAppSerializer):
|
||||
vmware_password = serializers.CharField(
|
||||
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Vmware password'),
|
||||
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import copy
|
||||
|
||||
from rest_framework import serializers
|
||||
from applications import const
|
||||
from . import application_category, application_type
|
||||
|
||||
|
||||
__all__ = [
|
||||
'category_serializer_classes_mapping',
|
||||
'type_serializer_classes_mapping',
|
||||
'get_serializer_class_by_application_type',
|
||||
'type_secret_serializer_classes_mapping'
|
||||
]
|
||||
|
||||
|
||||
# define `attrs` field `category serializers mapping`
|
||||
# ---------------------------------------------------
|
||||
|
||||
@@ -25,37 +25,19 @@ category_serializer_classes_mapping = {
|
||||
type_serializer_classes_mapping = {
|
||||
# db
|
||||
const.AppType.mysql.value: application_type.MySQLSerializer,
|
||||
const.AppType.redis.value: application_type.RedisSerializer,
|
||||
const.AppType.mariadb.value: application_type.MariaDBSerializer,
|
||||
const.AppType.oracle.value: application_type.OracleSerializer,
|
||||
const.AppType.pgsql.value: application_type.PostgreSerializer,
|
||||
const.AppType.sqlserver.value: application_type.SQLServerSerializer,
|
||||
# cloud
|
||||
const.AppType.k8s.value: application_type.K8SSerializer
|
||||
}
|
||||
|
||||
remote_app_serializer_classes_mapping = {
|
||||
# remote-app
|
||||
const.AppType.chrome.value: application_type.ChromeSerializer,
|
||||
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
|
||||
const.AppType.vmware_client.value: application_type.VMwareClientSerializer,
|
||||
const.AppType.custom.value: application_type.CustomSerializer
|
||||
const.AppType.custom.value: application_type.CustomSerializer,
|
||||
# cloud
|
||||
const.AppType.k8s.value: application_type.K8SSerializer
|
||||
}
|
||||
|
||||
type_serializer_classes_mapping.update(remote_app_serializer_classes_mapping)
|
||||
|
||||
remote_app_secret_serializer_classes_mapping = {
|
||||
# remote-app
|
||||
const.AppType.chrome.value: application_type.ChromeSecretSerializer,
|
||||
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSecretSerializer,
|
||||
const.AppType.vmware_client.value: application_type.VMwareClientSecretSerializer,
|
||||
const.AppType.custom.value: application_type.CustomSecretSerializer
|
||||
}
|
||||
|
||||
type_secret_serializer_classes_mapping = copy.deepcopy(type_serializer_classes_mapping)
|
||||
|
||||
type_secret_serializer_classes_mapping.update(remote_app_secret_serializer_classes_mapping)
|
||||
|
||||
|
||||
def get_serializer_class_by_application_type(_application_type):
|
||||
return type_serializer_classes_mapping.get(_application_type)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .kubernetes_util import *
|
||||
@@ -1,186 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from urllib3.exceptions import MaxRetryError
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from kubernetes.client import api_client
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from kubernetes import client
|
||||
from kubernetes.client.exceptions import ApiException
|
||||
|
||||
from rest_framework.generics import get_object_or_404
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.tree import TreeNode
|
||||
from assets.models import SystemUser
|
||||
|
||||
from .. import const
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class KubernetesClient:
|
||||
def __init__(self, url, token):
|
||||
self.url = url
|
||||
self.token = token
|
||||
|
||||
def get_api(self):
|
||||
configuration = client.Configuration()
|
||||
configuration.host = self.url
|
||||
configuration.verify_ssl = False
|
||||
configuration.api_key = {"authorization": "Bearer " + self.token}
|
||||
c = api_client.ApiClient(configuration=configuration)
|
||||
api = core_v1_api.CoreV1Api(c)
|
||||
return api
|
||||
|
||||
def get_namespace_list(self):
|
||||
api = self.get_api()
|
||||
namespace_list = []
|
||||
for ns in api.list_namespace().items:
|
||||
namespace_list.append(ns.metadata.name)
|
||||
return namespace_list
|
||||
|
||||
def get_services(self):
|
||||
api = self.get_api()
|
||||
ret = api.list_service_for_all_namespaces(watch=False)
|
||||
for i in ret.items:
|
||||
print("%s \t%s \t%s \t%s \t%s \n" % (
|
||||
i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports))
|
||||
|
||||
def get_pod_info(self, namespace, pod):
|
||||
api = self.get_api()
|
||||
resp = api.read_namespaced_pod(namespace=namespace, name=pod)
|
||||
return resp
|
||||
|
||||
def get_pod_logs(self, namespace, pod):
|
||||
api = self.get_api()
|
||||
log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200)
|
||||
return log_content
|
||||
|
||||
def get_pods(self):
|
||||
api = self.get_api()
|
||||
try:
|
||||
ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3))
|
||||
except MaxRetryError:
|
||||
logger.warning('Kubernetes connection timed out')
|
||||
return
|
||||
except ApiException as e:
|
||||
if e.status == 401:
|
||||
logger.warning('Kubernetes User not authenticated')
|
||||
else:
|
||||
logger.warning(e)
|
||||
return
|
||||
data = {}
|
||||
for i in ret.items:
|
||||
namespace = i.metadata.namespace
|
||||
pod_info = {
|
||||
'pod_name': i.metadata.name,
|
||||
'containers': [j.name for j in i.spec.containers]
|
||||
}
|
||||
if namespace in data:
|
||||
data[namespace].append(pod_info)
|
||||
else:
|
||||
data[namespace] = [pod_info, ]
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_kubernetes_data(app_id, system_user_id):
|
||||
from ..models import Application
|
||||
app = get_object_or_404(Application, id=app_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
k8s = KubernetesClient(app.attrs['cluster'], system_user.token)
|
||||
return k8s.get_pods()
|
||||
|
||||
|
||||
class KubernetesTree:
|
||||
def __init__(self, tree_id):
|
||||
self.tree_id = tree_id
|
||||
|
||||
def as_tree_node(self, app):
|
||||
pid = app.create_app_tree_pid(self.tree_id)
|
||||
app_id = str(app.id)
|
||||
parent_info = {'app_id': app_id}
|
||||
node = self.create_tree_node(
|
||||
app_id, pid, app.name, 'k8s', parent_info
|
||||
)
|
||||
return node
|
||||
|
||||
def as_system_user_tree_node(self, system_user, parent_info):
|
||||
from ..models import ApplicationTreeNodeMixin
|
||||
system_user_id = str(system_user.id)
|
||||
username = system_user.username
|
||||
username = username if username else '*'
|
||||
name = f'{system_user.name}({username})'
|
||||
pid = urlencode({'app_id': self.tree_id})
|
||||
i = ApplicationTreeNodeMixin.create_tree_id(pid, 'system_user_id', system_user_id)
|
||||
parent_info.update({'system_user_id': system_user_id})
|
||||
node = self.create_tree_node(
|
||||
i, pid, name, 'system_user', parent_info, icon='user-tie'
|
||||
)
|
||||
return node
|
||||
|
||||
def as_namespace_pod_tree_node(self, name, meta, type, counts=0, is_container=False):
|
||||
from ..models import ApplicationTreeNodeMixin
|
||||
i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name)
|
||||
meta.update({type: name})
|
||||
name = name if is_container else f'{name}({counts})'
|
||||
node = self.create_tree_node(
|
||||
i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container
|
||||
)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
def create_tree_node(id_, pid, name, identity, parent_info, icon='', is_container=False):
|
||||
node = TreeNode(**{
|
||||
'id': id_,
|
||||
'name': name,
|
||||
'title': name,
|
||||
'pId': pid,
|
||||
'isParent': not is_container,
|
||||
'open': False,
|
||||
'iconSkin': icon,
|
||||
'parentInfo': urlencode(parent_info),
|
||||
'meta': {
|
||||
'type': 'application',
|
||||
'data': {
|
||||
'category': const.AppCategory.cloud,
|
||||
'type': const.AppType.k8s,
|
||||
'identity': identity
|
||||
}
|
||||
}
|
||||
})
|
||||
return node
|
||||
|
||||
def async_tree_node(self, parent_info):
|
||||
pod_name = parent_info.get('pod')
|
||||
app_id = parent_info.get('app_id')
|
||||
namespace = parent_info.get('namespace')
|
||||
system_user_id = parent_info.get('system_user_id')
|
||||
|
||||
tree_nodes = []
|
||||
data = KubernetesClient.get_kubernetes_data(app_id, system_user_id)
|
||||
if not data:
|
||||
return tree_nodes
|
||||
|
||||
if pod_name:
|
||||
for container in next(
|
||||
filter(
|
||||
lambda x: x['pod_name'] == pod_name, data[namespace]
|
||||
)
|
||||
)['containers']:
|
||||
container_node = self.as_namespace_pod_tree_node(
|
||||
container, parent_info, 'container', is_container=True
|
||||
)
|
||||
tree_nodes.append(container_node)
|
||||
elif namespace:
|
||||
for pod in data[namespace]:
|
||||
pod_nodes = self.as_namespace_pod_tree_node(
|
||||
pod['pod_name'], parent_info, 'pod', len(pod['containers'])
|
||||
)
|
||||
tree_nodes.append(pod_nodes)
|
||||
elif system_user_id:
|
||||
for namespace, pods in data.items():
|
||||
namespace_node = self.as_namespace_pod_tree_node(
|
||||
namespace, parent_info, 'namespace', len(pods)
|
||||
)
|
||||
tree_nodes.append(namespace_node)
|
||||
return tree_nodes
|
||||
@@ -10,4 +10,3 @@ from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .backup import *
|
||||
|
||||
@@ -64,7 +64,9 @@ class AccountViewSet(OrgBulkModelViewSet):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = AuthBook.get_queryset()
|
||||
queryset = super().get_queryset() \
|
||||
.annotate(ip=F('asset__ip')) \
|
||||
.annotate(hostname=F('asset__hostname'))
|
||||
return queryset
|
||||
|
||||
@action(methods=['post'], detail=True, url_path='verify')
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import status, mixins, viewsets
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.permissions import IsOrgAdmin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
from .. import serializers
|
||||
from ..tasks import execute_account_backup_plan
|
||||
from ..models import (
|
||||
AccountBackupPlan, AccountBackupPlanExecution
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AccountBackupPlanViewSet', 'AccountBackupPlanExecutionViewSet'
|
||||
]
|
||||
|
||||
|
||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||
model = AccountBackupPlan
|
||||
filter_fields = ('name',)
|
||||
search_fields = filter_fields
|
||||
ordering_fields = ('name',)
|
||||
ordering = ('name',)
|
||||
serializer_class = serializers.AccountBackupPlanSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AccountBackupPlanExecutionViewSet(
|
||||
mixins.CreateModelMixin, mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin, viewsets.GenericViewSet
|
||||
):
|
||||
serializer_class = serializers.AccountBackupPlanExecutionSerializer
|
||||
search_fields = ('trigger',)
|
||||
filterset_fields = ('trigger', 'plan_id')
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = AccountBackupPlanExecution.objects.all()
|
||||
return queryset
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
pid = serializer.data.get('plan')
|
||||
task = execute_account_backup_plan.delay(
|
||||
pid=pid, trigger=AccountBackupPlanExecution.Trigger.manual
|
||||
)
|
||||
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = queryset.order_by('-date_start')
|
||||
return queryset
|
||||
@@ -17,7 +17,7 @@ from common.mixins.api import SuggestionMixin
|
||||
from assets.models import Asset
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.tree import TreeNodeSerializer
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins.api import OrgModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from orgs.utils import current_org
|
||||
from ..hands import IsOrgAdmin
|
||||
@@ -42,13 +42,19 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet):
|
||||
class NodeViewSet(SuggestionMixin, OrgModelViewSet):
|
||||
model = Node
|
||||
filterset_fields = ('value', 'key', 'id')
|
||||
search_fields = ('value', )
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
# 仅支持根节点指直接创建,子节点下的节点需要通过children接口创建
|
||||
def perform_create(self, serializer):
|
||||
child_key = Node.org_root().get_next_child_key()
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
@action(methods=[POST], detail=False, url_path='check_assets_amount_task')
|
||||
def check_assets_amount_task(self, request):
|
||||
task = check_node_assets_amount_task.delay(current_org.id)
|
||||
|
||||
@@ -196,21 +196,41 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
||||
return CommandFilterRuleSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_groups = []
|
||||
user_id = self.request.query_params.get('user_id')
|
||||
user = get_object_or_none(User, pk=user_id)
|
||||
if user:
|
||||
user_groups.extend(list(user.groups.all()))
|
||||
user_group_id = self.request.query_params.get('user_group_id')
|
||||
user_group = get_object_or_none(UserGroup, pk=user_group_id)
|
||||
if user_group:
|
||||
user_groups.append(user_group)
|
||||
system_user_id = self.kwargs.get('pk', None)
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
if not system_user:
|
||||
system_user_id = self.request.query_params.get('system_user_id')
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
asset_id = self.request.query_params.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
application_id = self.request.query_params.get('application_id')
|
||||
rules = CommandFilterRule.get_queryset(
|
||||
user_id=user_id,
|
||||
user_group_id=user_group_id,
|
||||
system_user_id=system_user_id,
|
||||
asset_id=asset_id,
|
||||
application_id=application_id
|
||||
)
|
||||
application = get_object_or_none(Application, pk=application_id)
|
||||
q = Q()
|
||||
if user:
|
||||
q |= Q(users=user)
|
||||
if user_groups:
|
||||
q |= Q(user_groups__in=set(user_groups))
|
||||
if system_user:
|
||||
q |= Q(system_users=system_user)
|
||||
if asset:
|
||||
q |= Q(assets=asset)
|
||||
if application:
|
||||
q |= Q(applications=application)
|
||||
if q:
|
||||
cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True)
|
||||
rule_ids = cmd_filters.values_list('rules', flat=True)
|
||||
rules = CommandFilterRule.objects.filter(id__in=rule_ids)
|
||||
else:
|
||||
rules = CommandFilterRule.objects.none()
|
||||
return rules
|
||||
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# Generated by Django 3.1.13 on 2022-01-12 11:59
|
||||
|
||||
import common.db.encoder
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0083_auto_20211215_1436'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AccountBackupPlan',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('is_periodic', models.BooleanField(default=False)),
|
||||
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
|
||||
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
|
||||
('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')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('types', models.IntegerField(choices=[(255, 'All'), (1, 'Asset'), (2, 'Application')], default=255, verbose_name='Type')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('recipients', models.ManyToManyField(blank=True, related_name='recipient_escape_route_plans', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Account backup plan',
|
||||
'ordering': ['name'],
|
||||
'unique_together': {('name', 'org_id')},
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('redis', 'Redis'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AccountBackupPlanExecution',
|
||||
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)),
|
||||
('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')),
|
||||
('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')),
|
||||
('plan_snapshot', models.JSONField(blank=True, default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True, verbose_name='Account backup snapshot')),
|
||||
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')),
|
||||
('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')),
|
||||
('is_success', models.BooleanField(default=False, verbose_name='Is success')),
|
||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='execution', to='assets.accountbackupplan', verbose_name='Account backup plan')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Account backup execution',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -12,4 +12,3 @@ from .utils import *
|
||||
from .authbook import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .backup import *
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
@@ -117,15 +116,6 @@ class AuthBook(BaseUser, AbsConnectivity):
|
||||
self.asset.save()
|
||||
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls):
|
||||
queryset = cls.objects.all() \
|
||||
.annotate(ip=F('asset__ip')) \
|
||||
.annotate(hostname=F('asset__hostname')) \
|
||||
.annotate(platform=F('asset__platform__name')) \
|
||||
.annotate(protocols=F('asset__protocols'))
|
||||
return queryset
|
||||
|
||||
def __str__(self):
|
||||
return self.smart_name
|
||||
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
from celery import current_task
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from ops.mixin import PeriodTaskModelMixin
|
||||
from common.utils import get_logger
|
||||
from common.db.encoder import ModelJSONFieldEncoder
|
||||
from common.db.models import BitOperationChoice
|
||||
from common.mixins.models import CommonModelMixin
|
||||
|
||||
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type']
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class Type(BitOperationChoice):
|
||||
NONE = 0
|
||||
ALL = 0xff
|
||||
|
||||
Asset = 0b1
|
||||
App = 0b1 << 1
|
||||
|
||||
DB_CHOICES = (
|
||||
(ALL, _('All')),
|
||||
(Asset, _('Asset')),
|
||||
(App, _('Application'))
|
||||
)
|
||||
|
||||
NAME_MAP = {
|
||||
ALL: "all",
|
||||
Asset: "asset",
|
||||
App: "application"
|
||||
}
|
||||
|
||||
NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()}
|
||||
CHOICES = []
|
||||
for i, j in DB_CHOICES:
|
||||
CHOICES.append((NAME_MAP[i], j))
|
||||
|
||||
|
||||
class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
types = models.IntegerField(choices=Type.DB_CHOICES, default=Type.ALL, verbose_name=_('Type'))
|
||||
recipients = models.ManyToManyField(
|
||||
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
||||
verbose_name=_("Recipient")
|
||||
)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}({self.org_id})'
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _('Account backup plan')
|
||||
|
||||
def get_register_task(self):
|
||||
from ..tasks import execute_account_backup_plan
|
||||
name = "account_backup_plan_period_{}".format(str(self.id)[:8])
|
||||
task = execute_account_backup_plan.name
|
||||
args = (str(self.id), AccountBackupPlanExecution.Trigger.timing)
|
||||
kwargs = {}
|
||||
return name, task, args, kwargs
|
||||
|
||||
def to_attr_json(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'is_periodic': self.is_periodic,
|
||||
'interval': self.interval,
|
||||
'crontab': self.crontab,
|
||||
'org_id': self.org_id,
|
||||
'created_by': self.created_by,
|
||||
'types': Type.value_to_choices(self.types),
|
||||
'recipients': {
|
||||
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
||||
for recipient in self.recipients.all()
|
||||
}
|
||||
}
|
||||
|
||||
def execute(self, trigger):
|
||||
try:
|
||||
hid = current_task.request.id
|
||||
except AttributeError:
|
||||
hid = str(uuid.uuid4())
|
||||
execution = AccountBackupPlanExecution.objects.create(
|
||||
id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger
|
||||
)
|
||||
return execution.start()
|
||||
|
||||
|
||||
class AccountBackupPlanExecution(OrgModelMixin):
|
||||
class Trigger(models.TextChoices):
|
||||
manual = 'manual', _('Manual trigger')
|
||||
timing = 'timing', _('Timing trigger')
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
date_start = models.DateTimeField(
|
||||
auto_now_add=True, verbose_name=_('Date start')
|
||||
)
|
||||
timedelta = models.FloatField(
|
||||
default=0.0, verbose_name=_('Time'), null=True
|
||||
)
|
||||
plan_snapshot = models.JSONField(
|
||||
encoder=ModelJSONFieldEncoder, default=dict,
|
||||
blank=True, null=True, verbose_name=_('Account backup snapshot')
|
||||
)
|
||||
trigger = models.CharField(
|
||||
max_length=128, default=Trigger.manual, choices=Trigger.choices,
|
||||
verbose_name=_('Trigger mode')
|
||||
)
|
||||
reason = models.CharField(
|
||||
max_length=1024, blank=True, null=True, verbose_name=_('Reason')
|
||||
)
|
||||
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
|
||||
plan = models.ForeignKey(
|
||||
'AccountBackupPlan', related_name='execution', on_delete=models.CASCADE,
|
||||
verbose_name=_('Account backup plan')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Account backup execution')
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
types = self.plan_snapshot.get('types')
|
||||
return types
|
||||
|
||||
@property
|
||||
def recipients(self):
|
||||
recipients = self.plan_snapshot.get('recipients')
|
||||
if not recipients:
|
||||
return []
|
||||
return recipients.values()
|
||||
|
||||
def start(self):
|
||||
from ..task_handlers import ExecutionManager
|
||||
manager = ExecutionManager(execution=self)
|
||||
return manager.run()
|
||||
@@ -4,19 +4,15 @@ import uuid
|
||||
import re
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models import User, UserGroup
|
||||
from applications.models import Application
|
||||
from ..models import SystemUser, Asset
|
||||
|
||||
from common.utils import lazyproperty, get_logger, get_object_or_none
|
||||
from common.utils import lazyproperty, get_logger
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CommandFilter', 'CommandFilterRule'
|
||||
]
|
||||
@@ -76,14 +72,10 @@ class CommandFilterRule(OrgModelMixin):
|
||||
confirm = 2, _('Reconfirm')
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
filter = models.ForeignKey(
|
||||
'CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules'
|
||||
)
|
||||
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
|
||||
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 lower the value will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)]
|
||||
)
|
||||
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
action = models.IntegerField(default=ActionChoices.deny, choices=ActionChoices.choices, verbose_name=_("Action"))
|
||||
# 动作: 附加字段
|
||||
@@ -180,34 +172,3 @@ class CommandFilterRule(OrgModelMixin):
|
||||
ticket.create_process_map_and_node(self.reviewers.all())
|
||||
ticket.open(applicant=session.user_obj)
|
||||
return ticket
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, asset_id=None, application_id=None):
|
||||
user_groups = []
|
||||
user = get_object_or_none(User, pk=user_id)
|
||||
if user:
|
||||
user_groups.extend(list(user.groups.all()))
|
||||
user_group = get_object_or_none(UserGroup, pk=user_group_id)
|
||||
if user_group:
|
||||
user_groups.append(user_group)
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
application = get_object_or_none(Application, pk=application_id)
|
||||
q = Q()
|
||||
if user:
|
||||
q |= Q(users=user)
|
||||
if user_groups:
|
||||
q |= Q(user_groups__in=set(user_groups))
|
||||
if system_user:
|
||||
q |= Q(system_users=system_user)
|
||||
if asset:
|
||||
q |= Q(assets=asset)
|
||||
if application:
|
||||
q |= Q(applications=application)
|
||||
if q:
|
||||
cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True)
|
||||
rule_ids = cmd_filters.values_list('rules', flat=True)
|
||||
rules = cls.objects.filter(id__in=rule_ids)
|
||||
else:
|
||||
rules = cls.objects.none()
|
||||
return rules
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.core.cache import cache
|
||||
@@ -30,7 +29,6 @@ class ProtocolMixin:
|
||||
telnet = 'telnet', 'Telnet'
|
||||
vnc = 'vnc', 'VNC'
|
||||
mysql = 'mysql', 'MySQL'
|
||||
redis = 'redis', 'Redis'
|
||||
oracle = 'oracle', 'Oracle'
|
||||
mariadb = 'mariadb', 'MariaDB'
|
||||
postgresql = 'postgresql', 'PostgreSQL'
|
||||
@@ -46,8 +44,7 @@ class ProtocolMixin:
|
||||
Protocol.rdp
|
||||
]
|
||||
APPLICATION_CATEGORY_DB_PROTOCOLS = [
|
||||
Protocol.mysql, Protocol.redis, Protocol.oracle,
|
||||
Protocol.mariadb, Protocol.postgresql, Protocol.sqlserver
|
||||
Protocol.mysql, Protocol.oracle, Protocol.mariadb, Protocol.postgresql, Protocol.sqlserver
|
||||
]
|
||||
APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [
|
||||
Protocol.k8s
|
||||
@@ -150,37 +147,15 @@ class AuthMixin:
|
||||
|
||||
def load_asset_special_auth(self, asset, username=''):
|
||||
"""
|
||||
AuthBook 的数据状态
|
||||
| asset | systemuser | username |
|
||||
1 | * | * | x |
|
||||
2 | * | x | * |
|
||||
|
||||
当前 AuthBook 只有以上两种状态,systemuser 与 username 不会并存。
|
||||
正常的资产与系统用户关联产生的是第1种状态,改密则产生第2种状态。改密之后
|
||||
只有 username 而没有 systemuser 。
|
||||
|
||||
Freq: 关联同一资产的多个系统用户指定同一用户名时,修改用户密码会影响所有系统用户
|
||||
|
||||
这里有一个不对称的行为,同名系统用户密码覆盖
|
||||
当有相同 username 的多个系统用户时,有改密动作之后,所有的同名系统用户都使用最后
|
||||
一次改动,但如果没有发生过改密,同名系统用户使用的密码还是各自的。
|
||||
|
||||
"""
|
||||
if username == '':
|
||||
username = self.username
|
||||
|
||||
authbook = AuthBook.objects.filter(
|
||||
asset=asset, username=username, systemuser__isnull=True
|
||||
).order_by('-date_created').first()
|
||||
|
||||
if not authbook:
|
||||
authbook = AuthBook.objects.filter(
|
||||
asset=asset, systemuser=self
|
||||
).order_by('-date_created').first()
|
||||
|
||||
if not authbook:
|
||||
authbooks = list(AuthBook.objects.filter(asset=asset, systemuser=self))
|
||||
if len(authbooks) == 0:
|
||||
return None
|
||||
|
||||
elif len(authbooks) == 1:
|
||||
authbook = authbooks[0]
|
||||
else:
|
||||
authbooks.sort(key=lambda x: 1 if x.username == username else 0, reverse=True)
|
||||
authbook = authbooks[0]
|
||||
authbook.load_auth()
|
||||
self.password = authbook.password
|
||||
self.private_key = authbook.private_key
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models import User
|
||||
from common.tasks import send_mail_attachment_async
|
||||
|
||||
|
||||
class AccountBackupExecutionTaskMsg(object):
|
||||
subject = _('Notification of account backup route task results')
|
||||
|
||||
def __init__(self, name: str, user: User):
|
||||
self.name = name
|
||||
self.user = user
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
name = self.name
|
||||
if self.user.secret_key:
|
||||
return _('{} - The account backup passage task has been completed. See the attachment for details').format(name)
|
||||
return _("{} - The account backup passage task has been completed: the encryption password has not been set - "
|
||||
"please go to personal information -> file encryption password to set the encryption password").format(name)
|
||||
|
||||
def publish(self, attachment_list=None):
|
||||
send_mail_attachment_async.delay(
|
||||
self.subject, self.message, [self.user.email], attachment_list
|
||||
)
|
||||
@@ -11,4 +11,3 @@ from .cmd_filter import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
from .account import *
|
||||
from .backup import *
|
||||
|
||||
@@ -6,25 +6,16 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
from .base import AuthSerializerMixin
|
||||
from .utils import validate_password_contains_left_double_curly_bracket
|
||||
from common.utils.encode import ssh_pubkey_gen
|
||||
|
||||
|
||||
class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
ip = serializers.ReadOnlyField(label=_("IP"))
|
||||
hostname = serializers.ReadOnlyField(label=_("Hostname"))
|
||||
platform = serializers.ReadOnlyField(label=_("Platform"))
|
||||
protocols = serializers.SerializerMethodField(label=_("Protocols"))
|
||||
date_created = serializers.DateTimeField(
|
||||
label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True
|
||||
)
|
||||
date_updated = serializers.DateTimeField(
|
||||
label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
fields_mini = ['id', 'username', 'ip', 'hostname', 'platform', 'protocols', 'version']
|
||||
fields_write_only = ['password', 'private_key', "public_key", 'passphrase']
|
||||
fields_mini = ['id', 'username', 'ip', 'hostname', 'version']
|
||||
fields_write_only = ['password', 'private_key', "public_key"]
|
||||
fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment']
|
||||
fields_small = fields_mini + fields_write_only + fields_other
|
||||
fields_fk = ['asset', 'systemuser', 'systemuser_display']
|
||||
@@ -41,24 +32,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
}
|
||||
ref_name = 'AssetAccountSerializer'
|
||||
|
||||
def _validate_gen_key(self, attrs):
|
||||
private_key = attrs.get('private_key')
|
||||
if not private_key:
|
||||
return attrs
|
||||
|
||||
password = attrs.get('passphrase')
|
||||
username = attrs.get('username')
|
||||
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
|
||||
attrs['public_key'] = public_key
|
||||
return attrs
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = self._validate_gen_key(attrs)
|
||||
return attrs
|
||||
|
||||
def get_protocols(self, v):
|
||||
return v.protocols.replace(' ', ', ')
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
@@ -72,10 +45,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
|
||||
class AccountSecretSerializer(AccountSerializer):
|
||||
class Meta(AccountSerializer.Meta):
|
||||
fields_backup = [
|
||||
'hostname', 'ip', 'platform', 'protocols', 'username', 'password',
|
||||
'private_key', 'public_key', 'date_created', 'date_updated', 'version'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': False},
|
||||
'private_key': {'write_only': False},
|
||||
|
||||
@@ -5,6 +5,8 @@ from django.core.validators import RegexValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from users.models import User, UserGroup
|
||||
from perms.models import AssetPermission
|
||||
from ..models import Asset, Node, Platform, SystemUser
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ops.mixin import PeriodTaskSerializerMixin
|
||||
from common.utils import get_logger
|
||||
|
||||
from .base import TypesField
|
||||
|
||||
from ..models import AccountBackupPlan, AccountBackupPlanExecution
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = ['AccountBackupPlanSerializer', 'AccountBackupPlanExecutionSerializer']
|
||||
|
||||
|
||||
class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
types = TypesField(required=False, allow_null=True, label=_("Actions"))
|
||||
|
||||
class Meta:
|
||||
model = AccountBackupPlan
|
||||
fields = [
|
||||
'id', 'name', 'is_periodic', 'interval', 'crontab', 'date_created',
|
||||
'date_updated', 'created_by', 'periodic_display', 'comment',
|
||||
'recipients', 'types'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'required': True},
|
||||
'periodic_display': {'label': _('Periodic perform')},
|
||||
'recipients': {'label': _('Recipient'), 'help_text': _(
|
||||
'Currently only mail sending is supported'
|
||||
)}
|
||||
}
|
||||
|
||||
|
||||
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
|
||||
trigger_display = serializers.ReadOnlyField(
|
||||
source='get_trigger_display', label=_('Trigger mode')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AccountBackupPlanExecution
|
||||
fields = [
|
||||
'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason',
|
||||
'is_success', 'plan', 'org_id', 'recipients', 'trigger_display'
|
||||
]
|
||||
read_only_fields = (
|
||||
'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason',
|
||||
'is_success', 'org_id', 'recipients'
|
||||
)
|
||||
@@ -1,12 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from io import StringIO
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key
|
||||
from assets.models import Type
|
||||
from common.utils import ssh_pubkey_gen, validate_ssh_private_key
|
||||
|
||||
|
||||
class AuthSerializer(serializers.ModelSerializer):
|
||||
@@ -30,28 +28,17 @@ class AuthSerializer(serializers.ModelSerializer):
|
||||
return self.instance
|
||||
|
||||
|
||||
class AuthSerializerMixin(serializers.ModelSerializer):
|
||||
passphrase = serializers.CharField(
|
||||
allow_blank=True, allow_null=True, required=False, max_length=512,
|
||||
write_only=True, label=_('Key password')
|
||||
)
|
||||
|
||||
class AuthSerializerMixin:
|
||||
def validate_password(self, password):
|
||||
return password
|
||||
|
||||
def validate_private_key(self, private_key):
|
||||
if not private_key:
|
||||
return
|
||||
passphrase = self.initial_data.get('passphrase')
|
||||
passphrase = passphrase if passphrase else None
|
||||
valid = validate_ssh_private_key(private_key, password=passphrase)
|
||||
password = self.initial_data.get("password")
|
||||
valid = validate_ssh_private_key(private_key, password)
|
||||
if not valid:
|
||||
raise serializers.ValidationError(_("private key invalid or passphrase error"))
|
||||
|
||||
private_key = ssh_private_key_gen(private_key, password=passphrase)
|
||||
string_io = StringIO()
|
||||
private_key.write_private_key(string_io)
|
||||
private_key = string_io.getvalue()
|
||||
raise serializers.ValidationError(_("private key invalid"))
|
||||
return private_key
|
||||
|
||||
def validate_public_key(self, public_key):
|
||||
@@ -63,7 +50,6 @@ class AuthSerializerMixin(serializers.ModelSerializer):
|
||||
value = validated_data.get(field)
|
||||
if not value:
|
||||
validated_data.pop(field, None)
|
||||
validated_data.pop('passphrase', None)
|
||||
|
||||
def create(self, validated_data):
|
||||
self.clean_auth_fields(validated_data)
|
||||
@@ -72,24 +58,3 @@ class AuthSerializerMixin(serializers.ModelSerializer):
|
||||
def update(self, instance, validated_data):
|
||||
self.clean_auth_fields(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class TypesField(serializers.MultipleChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['choices'] = Type.CHOICES
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
return Type.value_to_choices(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if data is None:
|
||||
return data
|
||||
return Type.choices_to_value(data)
|
||||
|
||||
|
||||
class ActionsDisplayField(TypesField):
|
||||
def to_representation(self, value):
|
||||
values = super().to_representation(value)
|
||||
choices = dict(Type.CHOICES)
|
||||
return [choices.get(i) for i in values]
|
||||
|
||||
@@ -49,7 +49,7 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
model = Gateway
|
||||
fields_mini = ['id', 'name']
|
||||
fields_write_only = [
|
||||
'password', 'private_key', 'public_key', 'passphrase'
|
||||
'password', 'private_key', 'public_key',
|
||||
]
|
||||
fields_small = fields_mini + fields_write_only + [
|
||||
'username', 'ip', 'port', 'protocol',
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import Asset, Node
|
||||
|
||||
|
||||
__all__ = [
|
||||
'NodeSerializer', "NodeAddChildrenSerializer",
|
||||
"NodeAssetsSerializer", "NodeTaskSerializer",
|
||||
@@ -16,9 +17,6 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
|
||||
value = serializers.CharField(
|
||||
required=False, allow_blank=True, allow_null=True, label=_("value")
|
||||
)
|
||||
full_value = serializers.CharField(
|
||||
required=False, allow_blank=True, allow_null=True, label=_("Full value")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
@@ -42,19 +40,6 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
|
||||
)
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
full_value = validated_data.get('full_value')
|
||||
|
||||
# 直接多层级创建
|
||||
if full_value:
|
||||
node = Node.create_node_by_full_value(full_value)
|
||||
# 根据 value 在 root 下创建
|
||||
else:
|
||||
key = Node.org_root().get_next_child_key()
|
||||
validated_data['key'] = key
|
||||
node = Node.objects.create(**validated_data)
|
||||
return node
|
||||
|
||||
|
||||
class NodeAssetsSerializer(BulkOrgResourceModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.db.models import Count
|
||||
|
||||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from common.utils import ssh_pubkey_gen
|
||||
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
|
||||
from common.validators import alphanumeric_re, alphanumeric_cn_re
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import SystemUser, Asset
|
||||
from .utils import validate_password_contains_left_double_curly_bracket
|
||||
@@ -33,7 +33,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields_mini = ['id', 'name', 'username']
|
||||
fields_write_only = ['password', 'public_key', 'private_key', 'passphrase']
|
||||
fields_write_only = ['password', 'public_key', 'private_key']
|
||||
fields_small = fields_mini + fields_write_only + [
|
||||
'token', 'ssh_key_fingerprint',
|
||||
'type', 'type_display', 'protocol', 'is_asset_protocol',
|
||||
@@ -51,7 +51,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
'trim_whitespace': False,
|
||||
"validators": [validate_password_contains_left_double_curly_bracket]
|
||||
},
|
||||
'cmd_filters': {"required": False, 'label': _('Command filter')},
|
||||
'cmd_filters': {"required": False},
|
||||
'public_key': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'token': {"write_only": True},
|
||||
@@ -107,12 +107,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
def validate_username(self, username):
|
||||
protocol = self.get_initial_value("protocol")
|
||||
if username:
|
||||
regx = alphanumeric_re
|
||||
if protocol == SystemUser.Protocol.telnet:
|
||||
regx = alphanumeric_cn_re
|
||||
elif protocol == SystemUser.Protocol.rdp:
|
||||
regx = alphanumeric_win_re
|
||||
else:
|
||||
regx = alphanumeric_re
|
||||
if not regx.match(username):
|
||||
raise serializers.ValidationError(_('Special char not allowed'))
|
||||
return username
|
||||
@@ -122,8 +119,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
return ''
|
||||
|
||||
login_mode = self.get_initial_value("login_mode")
|
||||
if login_mode == SystemUser.LOGIN_AUTO and protocol != SystemUser.Protocol.vnc \
|
||||
and protocol != SystemUser.Protocol.redis:
|
||||
if login_mode == SystemUser.LOGIN_AUTO and protocol != SystemUser.Protocol.vnc:
|
||||
msg = _('* Automatic login mode must fill in the username.')
|
||||
raise serializers.ValidationError(msg)
|
||||
return username
|
||||
@@ -145,9 +141,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
|
||||
def validate_password(self, password):
|
||||
super().validate_password(password)
|
||||
auto_gen_key = self.get_initial_value('auto_generate_key', False)
|
||||
private_key = self.get_initial_value('private_key')
|
||||
login_mode = self.get_initial_value('login_mode')
|
||||
auto_gen_key = self.get_initial_value("auto_generate_key", False)
|
||||
private_key = self.get_initial_value("private_key")
|
||||
login_mode = self.get_initial_value("login_mode")
|
||||
|
||||
if not self.instance and not auto_gen_key and not password and \
|
||||
not private_key and login_mode == SystemUser.LOGIN_AUTO:
|
||||
@@ -191,9 +187,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
return attrs
|
||||
|
||||
def _validate_gen_key(self, attrs):
|
||||
username = attrs.get('username', 'manual')
|
||||
auto_gen_key = attrs.pop('auto_generate_key', False)
|
||||
protocol = attrs.get('protocol')
|
||||
username = attrs.get("username", "manual")
|
||||
auto_gen_key = attrs.pop("auto_generate_key", False)
|
||||
protocol = attrs.get("protocol")
|
||||
|
||||
if protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
|
||||
return attrs
|
||||
@@ -201,17 +197,17 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
# 自动生成
|
||||
if auto_gen_key and not self.instance:
|
||||
password = SystemUser.gen_password()
|
||||
attrs['password'] = password
|
||||
attrs["password"] = password
|
||||
if protocol == SystemUser.Protocol.ssh:
|
||||
private_key, public_key = SystemUser.gen_key(username)
|
||||
attrs['private_key'] = private_key
|
||||
attrs['public_key'] = public_key
|
||||
attrs["private_key"] = private_key
|
||||
attrs["public_key"] = public_key
|
||||
# 如果设置了private key,没有设置public key则生成
|
||||
elif attrs.get('private_key'):
|
||||
private_key = attrs['private_key']
|
||||
password = attrs.get('password')
|
||||
elif attrs.get("private_key", None):
|
||||
private_key = attrs["private_key"]
|
||||
password = attrs.get("password")
|
||||
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
|
||||
attrs['public_key'] = public_key
|
||||
attrs["public_key"] = public_key
|
||||
return attrs
|
||||
|
||||
def _validate_login_mode(self, attrs):
|
||||
@@ -236,7 +232,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset \
|
||||
queryset = queryset\
|
||||
.annotate(assets_amount=Count("assets")) \
|
||||
.prefetch_related('nodes', 'cmd_filters')
|
||||
return queryset
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .endpoint import *
|
||||
@@ -1,223 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import pandas as pd
|
||||
from collections import defaultdict, OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import AuthBook
|
||||
from assets.serializers import AccountSecretSerializer
|
||||
from assets.notifications import AccountBackupExecutionTaskMsg
|
||||
from applications.models import Account
|
||||
from applications.const import AppType
|
||||
from applications.serializers import AppAccountSecretSerializer
|
||||
from users.models import User
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now_display
|
||||
from common.utils.file import encrypt_and_compress_zip_file
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||
|
||||
|
||||
class BaseAccountHandler:
|
||||
@classmethod
|
||||
def unpack_data(cls, serializer_data, data=None):
|
||||
if data is None:
|
||||
data = {}
|
||||
for k, v in serializer_data.items():
|
||||
if isinstance(v, OrderedDict):
|
||||
cls.unpack_data(v, data)
|
||||
else:
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_header_fields(cls, serializer: serializers.Serializer):
|
||||
try:
|
||||
backup_fields = getattr(serializer, 'Meta').fields_backup
|
||||
except AttributeError:
|
||||
backup_fields = serializer.fields.keys()
|
||||
header_fields = {}
|
||||
for field in backup_fields:
|
||||
v = serializer.fields[field]
|
||||
if isinstance(v, serializers.Serializer):
|
||||
_fields = cls.get_header_fields(v)
|
||||
header_fields.update(_fields)
|
||||
else:
|
||||
header_fields[field] = v.label
|
||||
return header_fields
|
||||
|
||||
@classmethod
|
||||
def create_row(cls, account, serializer_cls):
|
||||
serializer = serializer_cls(account)
|
||||
data = cls.unpack_data(serializer.data)
|
||||
header_fields = cls.get_header_fields(serializer)
|
||||
row_dict = {}
|
||||
for field, header_name in header_fields.items():
|
||||
row_dict[header_name] = data[field]
|
||||
return row_dict
|
||||
|
||||
|
||||
class AssetAccountHandler(BaseAccountHandler):
|
||||
@staticmethod
|
||||
def get_filename(plan_name):
|
||||
filename = os.path.join(
|
||||
PATH, f'{plan_name}-{_("Asset")}-{local_now_display()}-{time.time()}.xlsx'
|
||||
)
|
||||
return filename
|
||||
|
||||
@classmethod
|
||||
def create_df(cls):
|
||||
df_dict = defaultdict(list)
|
||||
sheet_name = AuthBook._meta.verbose_name
|
||||
accounts = AuthBook.get_queryset()
|
||||
for account in accounts:
|
||||
account.load_auth()
|
||||
row = cls.create_row(account, AccountSecretSerializer)
|
||||
df_dict[sheet_name].append(row)
|
||||
|
||||
for k, v in df_dict.items():
|
||||
df_dict[k] = pd.DataFrame(v)
|
||||
|
||||
logger.info('\n\033[33m- 共收集{}条资产账号\033[0m'.format(accounts.count()))
|
||||
return df_dict
|
||||
|
||||
|
||||
class AppAccountHandler(BaseAccountHandler):
|
||||
@staticmethod
|
||||
def get_filename(plan_name):
|
||||
filename = os.path.join(
|
||||
PATH, f'{plan_name}-{_("Application")}-{local_now_display()}-{time.time()}.xlsx'
|
||||
)
|
||||
return filename
|
||||
|
||||
@classmethod
|
||||
def create_df(cls):
|
||||
df_dict = defaultdict(list)
|
||||
accounts = Account.get_queryset()
|
||||
for account in accounts:
|
||||
account.load_auth()
|
||||
app_type = account.type
|
||||
sheet_name = AppType.get_label(app_type)
|
||||
row = cls.create_row(account, AppAccountSecretSerializer)
|
||||
df_dict[sheet_name].append(row)
|
||||
for k, v in df_dict.items():
|
||||
df_dict[k] = pd.DataFrame(v)
|
||||
logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(accounts.count()))
|
||||
return df_dict
|
||||
|
||||
|
||||
handler_map = {
|
||||
'asset': AssetAccountHandler,
|
||||
'application': AppAccountHandler
|
||||
}
|
||||
|
||||
|
||||
class AccountBackupHandler:
|
||||
def __init__(self, execution):
|
||||
self.execution = execution
|
||||
self.plan_name = self.execution.plan.name
|
||||
self.is_frozen = False # 任务状态冻结标志
|
||||
|
||||
def create_excel(self):
|
||||
logger.info(
|
||||
'\n'
|
||||
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
|
||||
''
|
||||
)
|
||||
# Print task start date
|
||||
time_start = time.time()
|
||||
files = []
|
||||
for account_type in self.execution.types:
|
||||
handler = handler_map.get(account_type)
|
||||
if not handler:
|
||||
continue
|
||||
|
||||
df_dict = handler.create_df()
|
||||
if not df_dict:
|
||||
continue
|
||||
|
||||
filename = handler.get_filename(self.plan_name)
|
||||
with pd.ExcelWriter(filename) as w:
|
||||
for sheet, df in df_dict.items():
|
||||
sheet = sheet.replace(' ', '-')
|
||||
getattr(df, 'to_excel')(w, sheet_name=sheet, index=False)
|
||||
files.append(filename)
|
||||
timedelta = round((time.time() - time_start), 2)
|
||||
logger.info('步骤完成: 用时 {}s'.format(timedelta))
|
||||
return files
|
||||
|
||||
def send_backup_mail(self, files):
|
||||
recipients = self.execution.plan_snapshot.get('recipients')
|
||||
if not recipients:
|
||||
return
|
||||
if not files:
|
||||
return
|
||||
recipients = User.objects.filter(id__in=list(recipients))
|
||||
logger.info(
|
||||
'\n'
|
||||
'\033[32m>>> 发送备份邮件\033[0m'
|
||||
''
|
||||
)
|
||||
plan_name = self.plan_name
|
||||
for user in recipients:
|
||||
if not user.secret_key:
|
||||
attachment_list = []
|
||||
else:
|
||||
password = user.secret_key.encode('utf8')
|
||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
|
||||
encrypt_and_compress_zip_file(attachment, password, files)
|
||||
attachment_list = [attachment, ]
|
||||
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
||||
logger.info('邮件已发送至{}({})'.format(user, user.email))
|
||||
for file in files:
|
||||
os.remove(file)
|
||||
|
||||
def step_perform_task_update(self, is_success, reason):
|
||||
self.execution.reason = reason[:1024]
|
||||
self.execution.is_success = is_success
|
||||
self.execution.save()
|
||||
logger.info('已完成对任务状态的更新')
|
||||
|
||||
def step_finished(self, is_success):
|
||||
if is_success:
|
||||
logger.info('任务执行成功')
|
||||
else:
|
||||
logger.error('任务执行失败')
|
||||
|
||||
def _run(self):
|
||||
is_success = False
|
||||
error = '-'
|
||||
try:
|
||||
files = self.create_excel()
|
||||
self.send_backup_mail(files)
|
||||
except Exception as e:
|
||||
self.is_frozen = True
|
||||
logger.error('任务执行被异常中断')
|
||||
logger.info('下面打印发生异常的 Traceback 信息 : ')
|
||||
logger.error(e, exc_info=True)
|
||||
error = str(e)
|
||||
else:
|
||||
is_success = True
|
||||
finally:
|
||||
reason = error
|
||||
self.step_perform_task_update(is_success, reason)
|
||||
self.step_finished(is_success)
|
||||
|
||||
def run(self):
|
||||
logger.info('任务开始: {}'.format(local_now_display()))
|
||||
time_start = time.time()
|
||||
try:
|
||||
self._run()
|
||||
except Exception as e:
|
||||
logger.error('任务运行出现异常')
|
||||
logger.error('下面显示异常 Traceback 信息: ')
|
||||
logger.error(e, exc_info=True)
|
||||
finally:
|
||||
logger.info('\n任务结束: {}'.format(local_now_display()))
|
||||
timedelta = round((time.time() - time_start), 2)
|
||||
logger.info('用时: {}'.format(timedelta))
|
||||
@@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import time
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now_display
|
||||
|
||||
from .handlers import AccountBackupHandler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AccountBackupExecutionManager:
|
||||
def __init__(self, execution):
|
||||
self.execution = execution
|
||||
self.date_start = timezone.now()
|
||||
self.time_start = time.time()
|
||||
self.date_end = None
|
||||
self.time_end = None
|
||||
self.timedelta = 0
|
||||
|
||||
def do_run(self):
|
||||
execution = self.execution
|
||||
logger.info('\n\033[33m# 账号备份计划正在执行\033[0m')
|
||||
handler = AccountBackupHandler(execution)
|
||||
handler.run()
|
||||
|
||||
def pre_run(self):
|
||||
self.execution.date_start = self.date_start
|
||||
self.execution.save()
|
||||
|
||||
def post_run(self):
|
||||
self.time_end = time.time()
|
||||
self.date_end = timezone.now()
|
||||
|
||||
logger.info('\n\n' + '-' * 80)
|
||||
logger.info('计划执行结束 {}\n'.format(local_now_display()))
|
||||
self.timedelta = self.time_end - self.time_start
|
||||
logger.info('用时: {}s'.format(self.timedelta))
|
||||
self.execution.timedelta = self.timedelta
|
||||
self.execution.save()
|
||||
|
||||
def run(self):
|
||||
self.pre_run()
|
||||
self.do_run()
|
||||
self.post_run()
|
||||
@@ -1,10 +0,0 @@
|
||||
from .backup.manager import AccountBackupExecutionManager
|
||||
|
||||
|
||||
class ExecutionManager:
|
||||
manager_type = {
|
||||
'backup': AccountBackupExecutionManager
|
||||
}
|
||||
|
||||
def __new__(cls, execution):
|
||||
return AccountBackupExecutionManager(execution)
|
||||
@@ -9,4 +9,3 @@ from .gather_asset_hardware_info import *
|
||||
from .push_system_user import *
|
||||
from .system_user_connectivity import *
|
||||
from .nodes_amount import *
|
||||
from .backup import *
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _, gettext_noop
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import org_aware_func
|
||||
@@ -104,6 +104,6 @@ def test_accounts_connectivity_manual(accounts):
|
||||
:param accounts: <AuthBook>对象
|
||||
"""
|
||||
for account in accounts:
|
||||
task_name = gettext_noop("Test account connectivity: ") + str(account)
|
||||
task_name = _("Test account connectivity: {}").format(account)
|
||||
test_account_connectivity_util(account, task_name)
|
||||
print(".\n")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from itertools import groupby
|
||||
from collections import defaultdict
|
||||
from celery import shared_task
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import org_aware_func
|
||||
@@ -46,7 +46,7 @@ def test_asset_connectivity_util(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
if task_name is None:
|
||||
task_name = gettext_noop("Test assets connectivity. ")
|
||||
task_name = _("Test assets connectivity")
|
||||
|
||||
hosts = clean_ansible_task_hosts(assets)
|
||||
if not hosts:
|
||||
@@ -88,7 +88,7 @@ def test_asset_connectivity_util(assets, task_name=None):
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def test_asset_connectivity_manual(asset):
|
||||
task_name = gettext_noop("Test assets connectivity: ") + str(asset)
|
||||
task_name = _("Test assets connectivity: {}").format(asset)
|
||||
summary = test_asset_connectivity_util([asset], task_name=task_name)
|
||||
|
||||
if summary.get('dark'):
|
||||
@@ -99,7 +99,7 @@ def test_asset_connectivity_manual(asset):
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def test_assets_connectivity_manual(assets):
|
||||
task_name = gettext_noop("Test assets connectivity: ") + str([asset.hostname for asset in assets])
|
||||
task_name = _("Test assets connectivity: {}").format([asset.hostname for asset in assets])
|
||||
summary = test_asset_connectivity_util(assets, task_name=task_name)
|
||||
|
||||
if summary.get('dark'):
|
||||
@@ -110,7 +110,8 @@ def test_assets_connectivity_manual(assets):
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def test_node_assets_connectivity_manual(node):
|
||||
task_name = gettext_noop("Test if the assets under the node are connectable: ") + node.name
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
assets = node.get_all_assets()
|
||||
result = test_asset_connectivity_util(assets, task_name=task_name)
|
||||
return result
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from celery import shared_task
|
||||
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from assets.models import AccountBackupPlan
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def execute_account_backup_plan(pid, trigger):
|
||||
with tmp_to_root_org():
|
||||
plan = get_object_or_none(AccountBackupPlan, pk=pid)
|
||||
if not plan:
|
||||
logger.error("No account backup route plan found: {}".format(pid))
|
||||
return
|
||||
with tmp_to_org(plan.org):
|
||||
plan.execute(trigger)
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import re
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _, gettext_noop
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import (
|
||||
capacity_convert, sum_capacity, get_logger
|
||||
@@ -94,7 +94,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
if task_name is None:
|
||||
task_name = gettext_noop("Update some assets hardware info. ")
|
||||
task_name = _("Update some assets hardware info")
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
hosts = clean_ansible_task_hosts(assets)
|
||||
if not hosts:
|
||||
@@ -111,13 +111,13 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def update_asset_hardware_info_manual(asset):
|
||||
task_name = gettext_noop("Update asset hardware info: ") + str(asset.hostname)
|
||||
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||
update_assets_hardware_info_util([asset], task_name=task_name)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def update_assets_hardware_info_manual(assets):
|
||||
task_name = gettext_noop("Update assets hardware info: ") + str([asset.hostname for asset in assets])
|
||||
task_name = _("Update assets hardware info: {}").format([asset.hostname for asset in assets])
|
||||
update_assets_hardware_info_util(assets, task_name=task_name)
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ def update_assets_hardware_info_period():
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def update_node_assets_hardware_info_manual(node):
|
||||
task_name = gettext_noop("Update node asset hardware information: ") + str(node.name)
|
||||
task_name = _("Update node asset hardware information: {}").format(node.name)
|
||||
assets = node.get_all_assets()
|
||||
result = update_assets_hardware_info_util(assets, task_name=task_name)
|
||||
return result
|
||||
|
||||
@@ -4,7 +4,7 @@ import re
|
||||
from collections import defaultdict
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import timezone
|
||||
|
||||
from orgs.utils import tmp_to_org, org_aware_func
|
||||
@@ -108,7 +108,7 @@ def add_asset_users(assets, results):
|
||||
def gather_asset_users(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
if task_name is None:
|
||||
task_name = gettext_noop("Gather assets users")
|
||||
task_name = _("Gather assets users")
|
||||
assets = clean_ansible_task_hosts(assets)
|
||||
if not assets:
|
||||
return
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
from itertools import groupby
|
||||
from celery import shared_task
|
||||
from common.db.utils import get_object_if_need, get_objects
|
||||
from django.utils.translation import ugettext as _, gettext_noop
|
||||
from django.db.models import Empty
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db.models import Empty, Q
|
||||
|
||||
from common.utils import encrypt_password, get_logger
|
||||
from assets.models import SystemUser, Asset
|
||||
from assets.models import SystemUser, Asset, AuthBook
|
||||
from orgs.utils import org_aware_func, tmp_to_root_org
|
||||
from . import const
|
||||
from .utils import clean_ansible_task_hosts, group_asset_by_platform
|
||||
@@ -178,7 +178,6 @@ def get_push_windows_system_user_tasks(system_user: SystemUser, username=None):
|
||||
|
||||
def get_push_system_user_tasks(system_user, platform="unixlike", username=None):
|
||||
"""
|
||||
获取推送系统用户的 ansible 命令,跟资产无关
|
||||
:param system_user:
|
||||
:param platform:
|
||||
:param username: 当动态时,近推送某个
|
||||
@@ -210,10 +209,18 @@ def push_system_user_util(system_user, assets, task_name, username=None):
|
||||
if not assets:
|
||||
return {}
|
||||
|
||||
# 资产按平台分类
|
||||
assets_sorted = sorted(assets, key=group_asset_by_platform)
|
||||
platform_hosts = groupby(assets_sorted, key=group_asset_by_platform)
|
||||
|
||||
def run_task(_tasks, _hosts):
|
||||
if not _tasks:
|
||||
return
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
)
|
||||
task.run()
|
||||
|
||||
if system_user.username_same_with_user:
|
||||
if username is None:
|
||||
# 动态系统用户,但是没有指定 username
|
||||
@@ -225,15 +232,6 @@ def push_system_user_util(system_user, assets, task_name, username=None):
|
||||
assert username is None, 'Only Dynamic user can assign `username`'
|
||||
usernames = [system_user.username]
|
||||
|
||||
def run_task(_tasks, _hosts):
|
||||
if not _tasks:
|
||||
return
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
)
|
||||
task.run()
|
||||
|
||||
for platform, _assets in platform_hosts:
|
||||
_assets = list(_assets)
|
||||
if not _assets:
|
||||
@@ -241,11 +239,36 @@ def push_system_user_util(system_user, assets, task_name, username=None):
|
||||
print(_("Start push system user for platform: [{}]").format(platform))
|
||||
print(_("Hosts count: {}").format(len(_assets)))
|
||||
|
||||
for u in usernames:
|
||||
for a in _assets:
|
||||
system_user.load_asset_special_auth(a, u)
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=u)
|
||||
run_task(tasks, [a])
|
||||
id_asset_map = {_asset.id: _asset for _asset in _assets}
|
||||
asset_ids = id_asset_map.keys()
|
||||
no_special_auth = []
|
||||
special_auth_set = set()
|
||||
|
||||
auth_books = AuthBook.objects.filter(asset_id__in=asset_ids).filter(
|
||||
Q(username__in=usernames) | Q(systemuser__username__in=usernames)
|
||||
).prefetch_related('systemuser')
|
||||
|
||||
for auth_book in auth_books:
|
||||
auth_book.load_auth()
|
||||
special_auth_set.add((auth_book.username, auth_book.asset_id))
|
||||
|
||||
for _username in usernames:
|
||||
no_special_assets = []
|
||||
for asset_id in asset_ids:
|
||||
if (_username, asset_id) not in special_auth_set:
|
||||
no_special_assets.append(id_asset_map[asset_id])
|
||||
if no_special_assets:
|
||||
no_special_auth.append((_username, no_special_assets))
|
||||
|
||||
for _username, no_special_assets in no_special_auth:
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=_username)
|
||||
run_task(tasks, no_special_assets)
|
||||
|
||||
for auth_book in auth_books:
|
||||
system_user._merge_auth(auth_book)
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=auth_book.username)
|
||||
asset = id_asset_map[auth_book.asset_id]
|
||||
run_task(tasks, [asset])
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@@ -256,7 +279,7 @@ def push_system_user_to_assets_manual(system_user, username=None):
|
||||
"""
|
||||
system_user = get_object_if_need(SystemUser, system_user)
|
||||
assets = system_user.get_related_assets()
|
||||
task_name = gettext_noop("Push system users to assets: ") + system_user.name
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util(system_user, assets, task_name=task_name, username=username)
|
||||
|
||||
|
||||
@@ -268,7 +291,7 @@ def push_system_user_a_asset_manual(system_user, asset, username=None):
|
||||
"""
|
||||
# if username is None:
|
||||
# username = system_user.username
|
||||
task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format(
|
||||
task_name = _("Push system users to asset: {}({}) => {}").format(
|
||||
system_user.name, username, asset
|
||||
)
|
||||
return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
|
||||
@@ -289,7 +312,7 @@ def push_system_user_to_assets(system_user_id, asset_ids, username=None):
|
||||
"""
|
||||
system_user = SystemUser.objects.get(id=system_user_id)
|
||||
assets = get_objects(Asset, asset_ids)
|
||||
task_name = gettext_noop("Push system users to assets: ") + system_user.name
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
|
||||
return push_system_user_util(system_user, assets, task_name, username=username)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from itertools import groupby
|
||||
from collections import defaultdict
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _, gettext_noop
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from assets.models import Asset
|
||||
from common.utils import get_logger
|
||||
@@ -115,7 +115,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("system_user")
|
||||
def test_system_user_connectivity_manual(system_user, asset_ids=None):
|
||||
task_name = gettext_noop("Test system user connectivity: ") + str(system_user)
|
||||
task_name = _("Test system user connectivity: {}").format(system_user)
|
||||
if asset_ids:
|
||||
assets = Asset.objects.filter(id__in=asset_ids)
|
||||
else:
|
||||
@@ -126,7 +126,7 @@ def test_system_user_connectivity_manual(system_user, asset_ids=None):
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("system_user")
|
||||
def test_system_user_connectivity_a_asset(system_user, asset):
|
||||
task_name = gettext_noop("Test system user connectivity: ") + "{} => {}".format(
|
||||
task_name = _("Test system user connectivity: {} => {}").format(
|
||||
system_user, asset
|
||||
)
|
||||
test_system_user_connectivity_util(system_user, [asset], task_name)
|
||||
@@ -145,7 +145,7 @@ def test_system_user_connectivity_period():
|
||||
return
|
||||
queryset_map = SystemUser.objects.all_group_by_org()
|
||||
for org, system_user in queryset_map.items():
|
||||
task_name = gettext_noop("Test system user connectivity period: ") + str(system_user)
|
||||
task_name = _("Test system user connectivity period: {}").format(system_user)
|
||||
with tmp_to_org(org):
|
||||
assets = system_user.get_related_assets()
|
||||
test_system_user_connectivity_util(system_user, assets, task_name)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||
router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation')
|
||||
router.register(r'system-users-nodes-relations', api.SystemUserNodeRelationViewSet, 'system-users-nodes-relation')
|
||||
router.register(r'system-users-users-relations', api.SystemUserUserRelationViewSet, 'system-users-users-relation')
|
||||
router.register(r'backup', api.AccountBackupPlanViewSet, 'backup')
|
||||
router.register(r'backup-execution', api.AccountBackupPlanExecutionViewSet, 'backup-execution')
|
||||
|
||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
|
||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||
|
||||
@@ -2,9 +2,8 @@ import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext, ugettext_lazy as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from common.utils import lazyproperty
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin, Organization
|
||||
from orgs.utils import current_org
|
||||
@@ -64,10 +63,6 @@ class OperateLog(OrgModelMixin):
|
||||
def __str__(self):
|
||||
return "<{}> {} <{}>".format(self.user, self.action, self.resource)
|
||||
|
||||
@lazyproperty
|
||||
def resource_type_display(self):
|
||||
return gettext(self.resource_type)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if current_org.is_root() and not self.org_id:
|
||||
self.org_id = Organization.ROOT_ID
|
||||
@@ -118,10 +113,6 @@ class UserLoginLog(models.Model):
|
||||
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
|
||||
backend = models.CharField(max_length=32, default='', verbose_name=_('Authentication backend'))
|
||||
|
||||
@property
|
||||
def backend_display(self):
|
||||
return gettext(self.backend)
|
||||
|
||||
@classmethod
|
||||
def get_login_logs(cls, date_from=None, date_to=None, user=None, keyword=None):
|
||||
login_logs = cls.objects.all()
|
||||
|
||||
@@ -35,33 +35,27 @@ class UserLoginLogSerializer(serializers.ModelSerializer):
|
||||
fields_mini = ['id']
|
||||
fields_small = fields_mini + [
|
||||
'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
|
||||
'mfa', 'mfa_display', 'reason', 'reason_display', 'backend', 'backend_display',
|
||||
'mfa', 'mfa_display', 'reason', 'reason_display', 'backend',
|
||||
'status', 'status_display',
|
||||
'datetime',
|
||||
]
|
||||
fields = fields_small
|
||||
extra_kwargs = {
|
||||
"user_agent": {'label': _('User agent')},
|
||||
"reason_display": {'label': _('Reason display')},
|
||||
'backend_display': {'label': _('Authentication backend')}
|
||||
"reason_display": {'label': _('Reason display')}
|
||||
}
|
||||
|
||||
|
||||
class OperateLogSerializer(serializers.ModelSerializer):
|
||||
action_display = serializers.CharField(source='get_action_display', label=_('Action'))
|
||||
|
||||
class Meta:
|
||||
model = models.OperateLog
|
||||
fields_mini = ['id']
|
||||
fields_small = fields_mini + [
|
||||
'user', 'action', 'action_display',
|
||||
'resource_type', 'resource_type_display', 'resource',
|
||||
'remote_addr', 'datetime', 'org_id'
|
||||
'user', 'action', 'resource_type', 'resource', 'remote_addr',
|
||||
'datetime',
|
||||
'org_id'
|
||||
]
|
||||
fields = fields_small
|
||||
extra_kwargs = {
|
||||
'resource_type_display': {'label': _('Resource Type')}
|
||||
}
|
||||
|
||||
|
||||
class PasswordChangeLogSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -10,7 +10,6 @@ from django.utils import timezone
|
||||
from django.utils.functional import LazyObject
|
||||
from django.contrib.auth import BACKEND_SESSION_KEY
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import translation
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.request import Request
|
||||
|
||||
@@ -84,8 +83,7 @@ def create_operate_log(action, sender, resource):
|
||||
model_name = sender._meta.object_name
|
||||
if model_name not in MODELS_NEED_RECORD:
|
||||
return
|
||||
with translation.override('en'):
|
||||
resource_type = sender._meta.verbose_name
|
||||
resource_type = sender._meta.verbose_name
|
||||
remote_addr = get_request_ip(current_request)
|
||||
|
||||
data = {
|
||||
@@ -292,16 +290,13 @@ def generate_data(username, request, login_type=None):
|
||||
if login_type is None:
|
||||
login_type = 'W'
|
||||
|
||||
with translation.override('en'):
|
||||
backend = str(get_login_backend(request))
|
||||
|
||||
data = {
|
||||
'username': username,
|
||||
'ip': login_ip,
|
||||
'type': login_type,
|
||||
'user_agent': user_agent[0:254],
|
||||
'datetime': timezone.now(),
|
||||
'backend': backend,
|
||||
'backend': get_login_backend(request)
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from common.utils.common import get_file_by_arch
|
||||
from orgs.mixins.api import RootOrgViewMixin
|
||||
from common.http import is_true
|
||||
from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_user
|
||||
from perms.models.base import Action
|
||||
from perms.models.asset_permission import Action
|
||||
|
||||
from ..serializers import (
|
||||
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
|
||||
@@ -231,7 +231,7 @@ class SecretDetailMixin:
|
||||
|
||||
@staticmethod
|
||||
def _get_application_secret_detail(application):
|
||||
from perms.models.base import Action
|
||||
from perms.models import Action
|
||||
gateway = None
|
||||
|
||||
if not application.category_remote_app:
|
||||
@@ -298,6 +298,9 @@ class SecretDetailMixin:
|
||||
data['type'] = 'application'
|
||||
data.update(app_detail)
|
||||
|
||||
self.request.session['auth_backend'] = settings.AUTH_BACKEND_AUTH_TOKEN
|
||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request, login_type='T')
|
||||
|
||||
serializer = self.get_serializer(data)
|
||||
return Response(data=serializer.data, status=200)
|
||||
|
||||
@@ -388,10 +391,10 @@ class UserConnectionTokenViewSet(
|
||||
asset = get_object_or_404(Asset, id=value.get('asset'))
|
||||
if not asset.is_active:
|
||||
raise serializers.ValidationError("Asset disabled")
|
||||
has_perm, actions, expired_at = asset_validate_permission(user, asset, system_user)
|
||||
has_perm, expired_at = asset_validate_permission(user, asset, system_user, 'connect')
|
||||
else:
|
||||
app = get_object_or_404(Application, id=value.get('application'))
|
||||
has_perm, actions, expired_at = app_validate_permission(user, app, system_user)
|
||||
has_perm, expired_at = app_validate_permission(user, app, system_user)
|
||||
|
||||
if not has_perm:
|
||||
raise serializers.ValidationError('Permission expired or invalid')
|
||||
|
||||
@@ -12,7 +12,6 @@ from rest_framework.response import Response
|
||||
|
||||
from common.permissions import IsValidUser, NeedMFAVerify
|
||||
from common.utils import get_logger
|
||||
from common.exceptions import UnexpectError
|
||||
from users.models.user import User
|
||||
from ..serializers import OtpVerifySerializer
|
||||
from .. import serializers
|
||||
@@ -36,45 +35,30 @@ class MFASendCodeApi(AuthMixin, CreateAPIView):
|
||||
"""
|
||||
permission_classes = (AllowAny,)
|
||||
serializer_class = serializers.MFASelectTypeSerializer
|
||||
username = ''
|
||||
ip = ''
|
||||
|
||||
def get_user_from_db(self, username):
|
||||
try:
|
||||
user = get_object_or_404(User, username=username)
|
||||
return user
|
||||
except Exception as e:
|
||||
self.incr_mfa_failed_time(username, self.ip)
|
||||
raise e
|
||||
|
||||
def get_user_from_db(self, username):
|
||||
"""避免暴力测试用户名"""
|
||||
ip = self.get_request_ip()
|
||||
self.check_mfa_is_block(username, ip)
|
||||
try:
|
||||
user = get_object_or_404(User, username=username)
|
||||
return user
|
||||
except Exception as e:
|
||||
self.incr_mfa_failed_time(username, ip)
|
||||
raise e
|
||||
|
||||
def perform_create(self, serializer):
|
||||
username = serializer.validated_data.get('username', '')
|
||||
mfa_type = serializer.validated_data['type']
|
||||
|
||||
if not username:
|
||||
user = self.get_user_from_session()
|
||||
else:
|
||||
user = self.get_user_from_db(username)
|
||||
user = get_object_or_404(User, username=username)
|
||||
|
||||
mfa_backend = user.get_active_mfa_backend_by_type(mfa_type)
|
||||
if not mfa_backend or not mfa_backend.challenge_required:
|
||||
error = _('Current user not support mfa type: {}').format(mfa_type)
|
||||
raise ValidationError({'error': error})
|
||||
raise ValidationError('MFA type not support: {} {}'.format(mfa_type, mfa_backend))
|
||||
mfa_backend.send_challenge()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
try:
|
||||
mfa_backend.send_challenge()
|
||||
self.perform_create(serializer)
|
||||
return Response(serializer.data, status=201)
|
||||
except Exception as e:
|
||||
raise UnexpectError(str(e))
|
||||
logger.exception(e)
|
||||
return Response({'error': str(e)}, status=400)
|
||||
|
||||
|
||||
class MFAChallengeVerifyApi(AuthMixin, CreateAPIView):
|
||||
|
||||
@@ -76,10 +76,11 @@ class PrepareRequestMixin:
|
||||
@staticmethod
|
||||
def get_attribute_consuming_service():
|
||||
attr_mapping = settings.SAML2_RENAME_ATTRIBUTES
|
||||
name_prefix = settings.SITE_URL
|
||||
if attr_mapping and isinstance(attr_mapping, dict):
|
||||
attr_list = [
|
||||
{
|
||||
"name": sp_key,
|
||||
"name": '{}/{}'.format(name_prefix, sp_key),
|
||||
"friendlyName": idp_key, "isRequired": True
|
||||
}
|
||||
for idp_key, sp_key in attr_mapping.items()
|
||||
@@ -167,10 +168,12 @@ class PrepareRequestMixin:
|
||||
|
||||
def get_attributes(self, saml_instance):
|
||||
user_attrs = {}
|
||||
real_key_index = len(settings.SITE_URL) + 1
|
||||
attrs = saml_instance.get_attributes()
|
||||
valid_attrs = ['username', 'name', 'email', 'comment', 'phone']
|
||||
|
||||
for attr, value in attrs.items():
|
||||
attr = attr[real_key_index:]
|
||||
if attr not in valid_attrs:
|
||||
continue
|
||||
user_attrs[attr] = self.value_to_str(value)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 3.1.13 on 2021-12-27 02:59
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('authentication', '0005_delete_loginconfirmsetting'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ssotoken',
|
||||
name='user',
|
||||
field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||
),
|
||||
]
|
||||
@@ -335,11 +335,6 @@ class MFAMixin:
|
||||
mfa_backends = User.get_user_mfa_backends(user)
|
||||
return {'mfa_backends': mfa_backends}
|
||||
|
||||
@staticmethod
|
||||
def incr_mfa_failed_time(username, ip):
|
||||
util = MFABlockUtils(username, ip)
|
||||
util.incr_failed_count()
|
||||
|
||||
|
||||
class AuthPostCheckMixin:
|
||||
@classmethod
|
||||
@@ -455,10 +450,7 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
|
||||
)
|
||||
if not user:
|
||||
self.raise_credential_error(errors.reason_password_failed)
|
||||
|
||||
self.request.session['auth_backend'] = getattr(user, 'backend', settings.AUTH_BACKEND_MODEL)
|
||||
|
||||
if user.is_expired:
|
||||
elif user.is_expired:
|
||||
self.raise_credential_error(errors.reason_user_expired)
|
||||
elif not user.is_active:
|
||||
self.raise_credential_error(errors.reason_user_inactive)
|
||||
|
||||
@@ -44,4 +44,4 @@ class SSOToken(models.JMSBaseModel):
|
||||
"""
|
||||
authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token'))
|
||||
expired = models.BooleanField(default=False, verbose_name=_('Expired'))
|
||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False)
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT, verbose_name=_('User'), db_constraint=False)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from notifications.notifications import UserMessage
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now_display
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@@ -15,7 +15,8 @@ class DifferentCityLoginMessage(UserMessage):
|
||||
super().__init__(user)
|
||||
|
||||
def get_html_msg(self) -> dict:
|
||||
now = local_now_display()
|
||||
now_local = timezone.localtime(timezone.now())
|
||||
now = now_local.strftime("%Y-%m-%d %H:%M:%S")
|
||||
subject = _('Different city login reminder')
|
||||
context = dict(
|
||||
subject=subject,
|
||||
@@ -38,36 +39,3 @@ class DifferentCityLoginMessage(UserMessage):
|
||||
ip = '8.8.8.8'
|
||||
city = '洛杉矶'
|
||||
return cls(user, ip, city)
|
||||
|
||||
|
||||
class OAuthBindMessage(UserMessage):
|
||||
def __init__(self, user, ip, oauth_name, oauth_id):
|
||||
super().__init__(user)
|
||||
self.ip = ip
|
||||
self.oauth_name = oauth_name
|
||||
self.oauth_id = oauth_id
|
||||
|
||||
def get_html_msg(self) -> dict:
|
||||
now = local_now_display()
|
||||
subject = self.oauth_name + ' ' + _('binding reminder')
|
||||
context = dict(
|
||||
subject=subject,
|
||||
name=self.user.name,
|
||||
username=self.user.username,
|
||||
ip=self.ip,
|
||||
time=now,
|
||||
oauth_name=self.oauth_name,
|
||||
oauth_id=self.oauth_id
|
||||
)
|
||||
message = render_to_string('authentication/_msg_oauth_bind.html', context)
|
||||
return {
|
||||
'subject': subject,
|
||||
'message': message
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def gen_test_msg(cls):
|
||||
from users.models import User
|
||||
user = User.objects.first()
|
||||
ip = '8.8.8.8'
|
||||
return cls(user, ip, _('WeCom'), '000000')
|
||||
|
||||
@@ -9,7 +9,7 @@ from assets.models import Asset, SystemUser, Gateway
|
||||
from applications.models import Application
|
||||
from users.serializers import UserProfileSerializer
|
||||
from assets.serializers import ProtocolsField
|
||||
from perms.serializers.base import ActionsField
|
||||
from perms.serializers.asset.permission import ActionsField
|
||||
from .models import AccessKey
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{% load i18n %}
|
||||
<p>
|
||||
{% trans 'Hello' %} {{ name }},
|
||||
</p>
|
||||
<p>
|
||||
{% trans 'Your account has just been bound to' %} {{ oauth_name }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{% trans 'Username' %}:</b> {{ username }}<br>
|
||||
<b>{{ oauth_name }}:</b> {{ oauth_id }}<br>
|
||||
<b>{% trans 'Time' %}:</b> {{ time }}<br>
|
||||
<b>{% trans 'IP' %}:</b> {{ ip }}
|
||||
</p>
|
||||
|
||||
-
|
||||
<p>
|
||||
{% trans 'If the operation is not your own, unbind and change the password.' %}
|
||||
</p>
|
||||
@@ -109,7 +109,7 @@
|
||||
}
|
||||
|
||||
.select-con {
|
||||
width: 35%;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.mfa-div {
|
||||
|
||||
@@ -19,8 +19,6 @@ from common.mixins.views import PermissionsMixin
|
||||
from authentication import errors
|
||||
from authentication.mixins import AuthMixin
|
||||
from common.sdk.im.dingtalk import DingTalk
|
||||
from common.utils.common import get_request_ip
|
||||
from authentication.notifications import OAuthBindMessage
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@@ -156,8 +154,6 @@ class DingTalkQRBindCallbackView(DingTalkQRMixin, View):
|
||||
return response
|
||||
raise e
|
||||
|
||||
ip = get_request_ip(request)
|
||||
OAuthBindMessage(user, ip, _('DingTalk'), user_id).publish_async()
|
||||
msg = _('Binding DingTalk successfully')
|
||||
response = self.get_success_response(redirect_url, msg, msg)
|
||||
return response
|
||||
|
||||
@@ -16,10 +16,8 @@ from common.utils.random import random_string
|
||||
from common.utils.django import reverse, get_object_or_none
|
||||
from common.mixins.views import PermissionsMixin
|
||||
from common.sdk.im.feishu import FeiShu, URL
|
||||
from common.utils.common import get_request_ip
|
||||
from authentication import errors
|
||||
from authentication.mixins import AuthMixin
|
||||
from authentication.notifications import OAuthBindMessage
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@@ -144,8 +142,6 @@ class FeiShuQRBindCallbackView(FeiShuQRMixin, View):
|
||||
return response
|
||||
raise e
|
||||
|
||||
ip = get_request_ip(request)
|
||||
OAuthBindMessage(user, ip, _('FeiShu'), user_id).publish_async()
|
||||
msg = _('Binding FeiShu successfully')
|
||||
response = self.get_success_response(redirect_url, msg, msg)
|
||||
return response
|
||||
|
||||
@@ -17,10 +17,8 @@ from common.utils.django import reverse, get_object_or_none
|
||||
from common.sdk.im.wecom import URL
|
||||
from common.sdk.im.wecom import WeCom
|
||||
from common.mixins.views import PermissionsMixin
|
||||
from common.utils.common import get_request_ip
|
||||
from authentication import errors
|
||||
from authentication.mixins import AuthMixin
|
||||
from authentication.notifications import OAuthBindMessage
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@@ -154,8 +152,6 @@ class WeComQRBindCallbackView(WeComQRMixin, View):
|
||||
return response
|
||||
raise e
|
||||
|
||||
ip = get_request_ip(request)
|
||||
OAuthBindMessage(user, ip, _('WeCom'), wecom_userid).publish_async()
|
||||
msg = _('Binding WeCom successfully')
|
||||
response = self.get_success_response(redirect_url, msg, msg)
|
||||
return response
|
||||
|
||||
@@ -66,47 +66,6 @@ class ChoiceSet(metaclass=ChoiceSetType):
|
||||
choices = None # 用于 Django Model 中的 choices 配置, 为了代码提示在此声明
|
||||
|
||||
|
||||
class BitOperationChoice:
|
||||
NONE = 0
|
||||
NAME_MAP: dict
|
||||
DB_CHOICES: tuple
|
||||
NAME_MAP_REVERSE: dict
|
||||
|
||||
@classmethod
|
||||
def value_to_choices(cls, value):
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
value = int(value)
|
||||
choices = [cls.NAME_MAP[i] for i, j in cls.DB_CHOICES if value & i == i]
|
||||
return choices
|
||||
|
||||
@classmethod
|
||||
def value_to_choices_display(cls, value):
|
||||
choices = cls.value_to_choices(value)
|
||||
return [str(dict(cls.choices())[i]) for i in choices]
|
||||
|
||||
@classmethod
|
||||
def choices_to_value(cls, value):
|
||||
if not isinstance(value, list):
|
||||
return cls.NONE
|
||||
db_value = [
|
||||
cls.NAME_MAP_REVERSE[v] for v in value
|
||||
if v in cls.NAME_MAP_REVERSE.keys()
|
||||
]
|
||||
if not db_value:
|
||||
return cls.NONE
|
||||
|
||||
def to_choices(x, y):
|
||||
return x | y
|
||||
|
||||
result = reduce(to_choices, db_value)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
return [(cls.NAME_MAP[i], j) for i, j in cls.DB_CHOICES]
|
||||
|
||||
|
||||
class JMSBaseModel(Model):
|
||||
created_by = CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
updated_by = CharField(max_length=32, null=True, blank=True, verbose_name=_('Updated by'))
|
||||
|
||||
@@ -44,7 +44,6 @@ def get_objects(model, pks):
|
||||
return objs
|
||||
|
||||
|
||||
# 复制 django.db.close_old_connections, 因为它没有导出,ide 提示有问题
|
||||
def close_old_connections():
|
||||
for conn in connections.all():
|
||||
conn.close_if_unusable_or_obsolete()
|
||||
|
||||
@@ -45,9 +45,3 @@ class MFAVerifyRequired(JMSException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_code = 'mfa_verify_required'
|
||||
default_detail = _('This action require verify your MFA')
|
||||
|
||||
|
||||
class UnexpectError(JMSException):
|
||||
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
default_code = 'unexpect_error'
|
||||
default_detail = _('Unexpect error occur')
|
||||
|
||||
@@ -13,7 +13,6 @@ class TreeNode:
|
||||
pId = ""
|
||||
open = False
|
||||
iconSkin = ""
|
||||
parentInfo = ''
|
||||
meta = {}
|
||||
|
||||
_tree = None
|
||||
@@ -96,7 +95,6 @@ class TreeNodeSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=128)
|
||||
title = serializers.CharField(max_length=128)
|
||||
pId = serializers.CharField(max_length=128)
|
||||
parentInfo = serializers.CharField(max_length=4096, allow_blank=True)
|
||||
isParent = serializers.BooleanField(default=False)
|
||||
open = serializers.BooleanField(default=False)
|
||||
iconSkin = serializers.CharField(max_length=128, allow_blank=True)
|
||||
|
||||
@@ -23,7 +23,6 @@ class RedisPubSub:
|
||||
def __init__(self, ch, db=10):
|
||||
self.ch = ch
|
||||
self.redis = get_redis_client(db)
|
||||
self.subscriber = None
|
||||
|
||||
def subscribe(self):
|
||||
ps = self.redis.pubsub()
|
||||
@@ -42,9 +41,7 @@ class RedisPubSub:
|
||||
:param handle: lambda item: do_something
|
||||
:return:
|
||||
"""
|
||||
self.close_handle_msg()
|
||||
sub = self.subscribe()
|
||||
self.subscriber = sub
|
||||
msgs = sub.listen()
|
||||
|
||||
try:
|
||||
@@ -68,7 +65,3 @@ class RedisPubSub:
|
||||
except Exception as e:
|
||||
logger.error("Redis observer close error: ", e)
|
||||
|
||||
def close_handle_msg(self):
|
||||
if self.subscriber:
|
||||
self.subscriber.close()
|
||||
self.subscriber = None
|
||||
|
||||
@@ -22,6 +22,7 @@ from django.db.models.fields.files import FileField
|
||||
|
||||
from .http import http_date
|
||||
|
||||
|
||||
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||
|
||||
|
||||
@@ -40,7 +41,6 @@ class Singleton(type):
|
||||
|
||||
class Signer(metaclass=Singleton):
|
||||
"""用来加密,解密,和基于时间戳的方式验证token"""
|
||||
|
||||
def __init__(self, secret_key=None):
|
||||
self.secret_key = secret_key
|
||||
|
||||
@@ -88,16 +88,11 @@ def ssh_key_string_to_obj(text, password=None):
|
||||
return key
|
||||
|
||||
|
||||
def ssh_private_key_gen(private_key, password=None):
|
||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
||||
if isinstance(private_key, bytes):
|
||||
private_key = private_key.decode("utf-8")
|
||||
if isinstance(private_key, string_types):
|
||||
private_key = ssh_key_string_to_obj(private_key, password=password)
|
||||
return private_key
|
||||
|
||||
|
||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
||||
private_key = ssh_private_key_gen(private_key, password=password)
|
||||
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
||||
raise IOError('Invalid private key')
|
||||
|
||||
@@ -235,3 +230,4 @@ def model_to_json(instance, sort_keys=True, indent=2, cls=None):
|
||||
if cls is None:
|
||||
cls = DjangoJSONEncoder
|
||||
return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls)
|
||||
|
||||
|
||||
@@ -10,11 +10,10 @@ def create_csv_file(filename, headers, rows, ):
|
||||
w.writerows(rows)
|
||||
|
||||
|
||||
def encrypt_and_compress_zip_file(filename, secret_password, encrypted_filenames):
|
||||
def encrypt_and_compress_zip_file(filename, secret_password, encrypted_filename):
|
||||
with pyzipper.AESZipFile(
|
||||
filename, 'w', compression=pyzipper.ZIP_LZMA, encryption=pyzipper.WZ_AES
|
||||
) as zf:
|
||||
zf.setpassword(secret_password)
|
||||
for encrypted_filename in encrypted_filenames:
|
||||
with open(encrypted_filename, 'rb') as f:
|
||||
zf.writestr(os.path.basename(encrypted_filename), f.read())
|
||||
with open(encrypted_filename, 'rb') as f:
|
||||
zf.writestr(os.path.basename(encrypted_filename), f.read())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.shortcuts import reverse
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from .random import random_string
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from django.utils.translation import gettext
|
||||
|
||||
|
||||
def translate_value(value):
|
||||
if not value:
|
||||
return value
|
||||
sps = ['. ', ': ']
|
||||
spb = {str(sp in value): sp for sp in sps}
|
||||
sp = spb.get('True')
|
||||
if not sp:
|
||||
return value
|
||||
|
||||
tpl, data = value.split(sp, 1)
|
||||
return gettext(tpl + sp) + data
|
||||
@@ -17,8 +17,6 @@ alphanumeric_re = re.compile(r'^[0-9a-zA-Z_@\-\.]*$')
|
||||
|
||||
alphanumeric_cn_re = re.compile(r'^[0-9a-zA-Z_@\-\.\u4E00-\u9FA5]*$')
|
||||
|
||||
alphanumeric_win_re = re.compile(r'^[0-9a-zA-Z_@#%&~\^\$\-\.\u4E00-\u9FA5]*$')
|
||||
|
||||
|
||||
class ProjectUniqueValidator(UniqueTogetherValidator):
|
||||
def __call__(self, attrs, serializer):
|
||||
|
||||
@@ -31,7 +31,7 @@ def jumpserver_processor(request):
|
||||
context = default_context
|
||||
context.update({
|
||||
'VERSION': settings.VERSION,
|
||||
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2022',
|
||||
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2021',
|
||||
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
|
||||
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
|
||||
'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
from functools import partial
|
||||
from werkzeug.local import LocalProxy
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from common.local import thread_local
|
||||
@@ -27,18 +26,4 @@ def has_valid_xpack_license():
|
||||
return License.has_valid_license()
|
||||
|
||||
|
||||
def get_xpack_license_info() -> dict:
|
||||
if has_valid_xpack_license():
|
||||
from xpack.plugins.license.models import License
|
||||
info = License.get_license_detail()
|
||||
corporation = info.get('corporation', '')
|
||||
else:
|
||||
current_year = datetime.now().year
|
||||
corporation = f'Copyright - FIT2CLOUD 飞致云 © 2014-{current_year}'
|
||||
info = {
|
||||
'corporation': corporation
|
||||
}
|
||||
return info
|
||||
|
||||
|
||||
current_request = LocalProxy(partial(_find, 'current_request'))
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:07244b630278a5574b97c46218ae453de71d01a1ea6682b88baa741a99cf8c22
|
||||
size 97436
|
||||
oid sha256:151597996418474bb0ec965084ef04c8e1f93b6546b6829a1e0174e9e1f2e43a
|
||||
size 95238
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ from channels.generic.websocket import JsonWebsocketConsumer
|
||||
from common.utils import get_logger
|
||||
from common.db.utils import safe_db_connection
|
||||
from .site_msg import SiteMessageUtil
|
||||
from .signals_handler import NewSiteMsgSubPub
|
||||
from .signals_handler import new_site_msg_chan
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -13,10 +13,6 @@ logger = get_logger(__name__)
|
||||
class SiteMsgWebsocket(JsonWebsocketConsumer):
|
||||
refresh_every_seconds = 10
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SiteMsgWebsocket, self).__init__(*args, **kwargs)
|
||||
self.subscriber = None
|
||||
|
||||
def connect(self):
|
||||
user = self.scope["user"]
|
||||
if user.is_authenticated:
|
||||
@@ -27,10 +23,6 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
|
||||
else:
|
||||
self.close()
|
||||
|
||||
def disconnect(self, code):
|
||||
if self.subscriber:
|
||||
self.subscriber.close_handle_msg()
|
||||
|
||||
def receive(self, text_data=None, bytes_data=None, **kwargs):
|
||||
data = json.loads(text_data)
|
||||
refresh_every_seconds = data.get('refresh_every_seconds')
|
||||
@@ -64,6 +56,4 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
|
||||
if user_id in users:
|
||||
ws.send_unread_msg_count()
|
||||
|
||||
subscriber = NewSiteMsgSubPub()
|
||||
self.subscriber = subscriber
|
||||
subscriber.keep_handle_msg(handle_new_site_msg_recv)
|
||||
new_site_msg_chan.keep_handle_msg(handle_new_site_msg_recv)
|
||||
|
||||
@@ -13,6 +13,5 @@ class OpsConfig(AppConfig):
|
||||
from orgs.utils import set_current_org
|
||||
set_current_org(Organization.root())
|
||||
from .celery import signal_handler
|
||||
from . import signals_handler
|
||||
from . import notifications
|
||||
super().ready()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user