From 3e73dbdb11171d9c9bbe022fff9a64a1ae803268 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 21 Jun 2019 12:45:32 +0800 Subject: [PATCH 01/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=B0=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset.py | 21 ------------------- .../templates/assets/system_user_asset.html | 4 ++-- apps/assets/utils.py | 4 ---- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 4f7509d4f..45d4cfe3c 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -134,13 +134,6 @@ class Asset(OrgModelMixin): comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) objects = AssetManager.from_queryset(AssetQuerySet)() - CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}' - UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) - CONNECTIVITY_CHOICES = ( - (UNREACHABLE, _("Unreachable")), - (REACHABLE, _('Reachable')), - (UNKNOWN, _("Unknown")), - ) def __str__(self): return '{0.hostname}({0.ip})'.format(self) @@ -215,20 +208,6 @@ class Asset(OrgModelMixin): nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) return nodes - @classmethod - def get_queryset_by_fullname_list(cls, fullname_list): - org_fullname_map = defaultdict(list) - for fullname in fullname_list: - hostname, org = cls.split_fullname(fullname) - org_fullname_map[org].append(hostname) - filter_arg = Q() - for org, hosts in org_fullname_map.items(): - if org.is_real(): - filter_arg |= Q(hostname__in=hosts, org_id=org.id) - else: - filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts) - return Asset.objects.filter(filter_arg) - @property def cpu_info(self): info = "" diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index 1b72a9c9f..fb9dd9d4b 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -25,7 +25,7 @@
-
+
{% trans 'Assets of ' %} {{ system_user.name }} {{ paginator.count }} @@ -48,7 +48,7 @@
-
+
{% trans 'Quick update' %} diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 707d67a50..a4809e3a7 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -16,10 +16,6 @@ def get_system_users_by_id_list(id_list): return SystemUser.objects.filter(id__in=id_list) -def get_assets_by_fullname_list(hostname_list): - return Asset.get_queryset_by_fullname_list(hostname_list) - - def get_system_user_by_name(name): system_user = get_object_or_none(SystemUser, name=name) return system_user From dd4ef4c3830c2662bd489c0ccefb1d250ddcb02a Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 21 Jun 2019 16:00:59 +0800 Subject: [PATCH 02/53] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E4=BC=A0guacamole=E7=9A=84=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/reupload_guacamole_replays.py | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 utils/reupload_guacamole_replays.py diff --git a/utils/reupload_guacamole_replays.py b/utils/reupload_guacamole_replays.py new file mode 100644 index 000000000..979103f19 --- /dev/null +++ b/utils/reupload_guacamole_replays.py @@ -0,0 +1,62 @@ +#!/usr/bin/python + +import os +import datetime +import shutil +import sys +import django + +GUACAMOLE_REPLAYS_DIR = '/tmp/guacamole/record' +UPLOAD_TO = 'local' + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) +PROJECT_DIR = os.path.dirname(BASE_DIR) +LOCAL_REPLAY_DIR = os.path.join(PROJECT_DIR, 'data', 'media', 'replay') +APPS_DIR = os.path.join(PROJECT_DIR, "apps") + + +if os.path.exists(APPS_DIR): + sys.path.insert(0, APPS_DIR) + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") +django.setup() + +from terminal.models import Session + + +def find_replays(): + replays = [] + + for root, dirs, files in os.walk(GUACAMOLE_REPLAYS_DIR, topdown=True): + for name in files: + if name.startswith('20') and name.endswith('.gz'): + session_id = '-'.join(name.split('-')[3:]).replace(".gz", "") + file_path = os.path.join(root, name) + create_ts = os.stat(file_path).st_ctime + create_date = datetime.datetime.utcfromtimestamp(create_ts) + replays.append({ + "id": session_id, + "path": file_path, + "date": create_date, + }) + return replays + + +def upload_to_local(session): + source_path = session["path"] + session_id = session["id"] + target_filename = session_id + ".replay.gz" + date_created = session["date"].strftime("%Y-%m-%d") + target_dir = os.path.join(LOCAL_REPLAY_DIR, date_created ) + target_path = os.path.join(target_dir, target_filename) + if not os.path.isdir(target_dir): + os.makedirs(target_dir) + shutil.copy(source_path, target_path) + shutil.copystat(source_path, target_path) + os.unlink(source_path) + Session.objects.filter(id=session_id).update(is_finished=True) + + +if __name__ == '__main__': + for s in find_replays(): + upload_to_local(s) From 2e6ba2ffb2490079a54e5f82a5ebef3eb69a91e3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 21 Jun 2019 16:03:37 +0800 Subject: [PATCH 03/53] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/reupload_guacamole_replays.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/reupload_guacamole_replays.py b/utils/reupload_guacamole_replays.py index 979103f19..ee3bfbb89 100644 --- a/utils/reupload_guacamole_replays.py +++ b/utils/reupload_guacamole_replays.py @@ -51,6 +51,7 @@ def upload_to_local(session): target_path = os.path.join(target_dir, target_filename) if not os.path.isdir(target_dir): os.makedirs(target_dir) + print("Move {} => {}".format(source_path, target_path)) shutil.copy(source_path, target_path) shutil.copystat(source_path, target_path) os.unlink(source_path) From 9dd951dd0d114b889f9d84bbe9398a71f9c0844f Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 21 Jun 2019 20:57:51 +0800 Subject: [PATCH 04/53] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=E5=8F=AF?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 11 ++- apps/assets/models/asset.py | 10 +-- apps/assets/models/authbook.py | 20 ++--- apps/assets/models/base.py | 102 +++++++++++++++++--------- apps/assets/models/user.py | 93 +---------------------- apps/assets/serializers/admin_user.py | 28 +------ apps/assets/tasks.py | 25 +++---- apps/assets/utils.py | 65 +++++++++++++++- apps/common/utils/common.py | 8 ++ apps/terminal/api/session.py | 5 +- 10 files changed, 172 insertions(+), 195 deletions(-) diff --git a/apps/assets/const.py b/apps/assets/const.py index eebb5ecca..e5f65fce0 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # - +from django.utils.translation import ugettext_lazy as _ UPDATE_ASSETS_HARDWARE_TASKS = [ { @@ -11,7 +11,6 @@ UPDATE_ASSETS_HARDWARE_TASKS = [ } ] -ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}" TEST_ADMIN_USER_CONN_TASKS = [ { "name": "ping", @@ -49,7 +48,6 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [ } ] -ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}' TEST_ASSET_USER_CONN_TASKS = [ { "name": "ping", @@ -74,5 +72,10 @@ TASK_OPTIONS = { } CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}' - +CONN_UNREACHABLE, CONN_REACHABLE, CONN_UNKNOWN = range(0, 3) +CONNECTIVITY_CHOICES = ( + (CONN_UNREACHABLE, _("Unreachable")), + (CONN_REACHABLE, _('Reachable')), + (CONN_UNKNOWN, _("Unknown")), +) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 7042340fe..1e72d2614 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -6,16 +6,14 @@ import uuid import logging import random from functools import reduce -from collections import defaultdict from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from django.core.cache import cache from django.core.validators import MinValueValidator, MaxValueValidator from .user import AdminUser, SystemUser from orgs.mixins import OrgModelMixin, OrgManager +from ..utils import Connectivity __all__ = ['Asset', 'Protocol'] logger = logging.getLogger(__name__) @@ -230,12 +228,12 @@ class Asset(OrgModelMixin): @property def connectivity(self): if not self.admin_user: - return self.UNKNOWN - return self.admin_user.get_connectivity_of(self) + return Connectivity.unknown() + return self.admin_user.get_asset_connectivity(self) @connectivity.setter def connectivity(self, value): - self.admin_user.set_connectivity_of(self, value) + self.admin_user.set_asset_connectivity(self, value) def get_auth_info(self): if not self.admin_user: diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index e61c3423d..ba676c309 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -3,12 +3,10 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.core.cache import cache from orgs.mixins import OrgManager - +from ..utils import Connectivity from .base import AssetUser -from ..const import ASSET_USER_CONN_CACHE_KEY __all__ = ['AuthBook'] @@ -32,6 +30,7 @@ class AuthBook(AssetUser): backend = "db" # 用于system user和admin_user的动态设置 _connectivity = None + CONN_CACHE_KEY = "ASSET_USER_CONN_{}" class Meta: verbose_name = _('AuthBook') @@ -65,20 +64,17 @@ class AuthBook(AssetUser): self._set_version() self._set_latest() - @property - def _conn_cache_key(self): - return ASSET_USER_CONN_CACHE_KEY.format(self.id) + def get_related_assets(self): + return [self.asset] + + def generate_id_with_asset(self, asset): + return self.id @property def connectivity(self): if self._connectivity: return self._connectivity - value = cache.get(self._conn_cache_key, self.UNKNOWN) - return value - - @connectivity.setter - def connectivity(self, value): - cache.set(self._conn_cache_key, value, 3600) + return self.get_asset_connectivity(self.asset) @property def keyword(self): diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index d58b97a18..5b9aeddde 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -14,8 +14,11 @@ from common.utils import ( get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger ) from common.validators import alphanumeric +from common import fields from orgs.mixins import OrgModelMixin from .utils import private_key_validator +from ..utils import Connectivity +from .. import const signer = get_signer() @@ -26,7 +29,7 @@ class AssetUser(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric]) - _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + _password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key')) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -34,13 +37,8 @@ class AssetUser(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) - CONNECTIVITY_CHOICES = ( - (UNREACHABLE, _("Unreachable")), - (REACHABLE, _('Reachable')), - (UNKNOWN, _("Unknown")), - ) - CONNECTIVITY_CACHE_KEY = "CONNECTIVITY_{}" + CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_ASSET_CONNECTIVITY_{}" + _prefer = "system_user" @property @@ -109,6 +107,10 @@ class AssetUser(OrgModelMixin): pass return None + def get_related_assets(self): + assets = self.assets.all() + return assets + def set_auth(self, password=None, private_key=None, public_key=None): update_fields = [] if password: @@ -124,17 +126,52 @@ class AssetUser(OrgModelMixin): if update_fields: self.save(update_fields=update_fields) - def get_auth(self, asset=None): - pass + def set_connectivity(self, summary): + unreachable = summary.get('dark', {}).keys() + reachable = summary.get('contacted', {}).keys() - def get_connectivity_of(self, asset): - i = self.generate_id_with_asset(asset) - key = self.CONNECTIVITY_CACHE_KEY.format(i) - return cache.get(key) + for asset in self.get_related_assets(): + if asset.hostname in unreachable: + self.set_asset_connectivity(asset, Connectivity.unreachable()) + elif asset.hostname in reachable: + self.set_asset_connectivity(asset, Connectivity.reachable()) + else: + self.set_asset_connectivity(asset, Connectivity.unknown()) - def set_connectivity_of(self, asset, c): + @property + def connectivity(self): + assets = self.get_related_assets() + data = { + 'unreachable': [], + 'reachable': [], + 'unknown': [], + } + for asset in assets: + connectivity = self.get_asset_connectivity(asset) + if connectivity.is_reachable(): + data["reachable"].append(asset.hostname) + elif connectivity.is_unreachable(): + data["unreachable"].append(asset.hostname) + else: + data["unknown"].append(asset.hostname) + return data + + @property + def connectivity_amount(self): + return {k: len(v) for k, v in self.connectivity.items()} + + @property + def assets_amount(self): + return self.get_related_assets().count() + + def get_asset_connectivity(self, asset): i = self.generate_id_with_asset(asset) - key = self.CONNECTIVITY_CACHE_KEY.format(i) + key = self.CONNECTIVITY_ASSET_CACHE_KEY.format(i) + return cache.get(key, const.CONN_UNKNOWN) + + def set_asset_connectivity(self, asset, c): + i = self.generate_id_with_asset(asset) + key = self.CONNECTIVITY_ASSET_CACHE_KEY.format(i) cache.set(key, c, 3600) def load_specific_asset_auth(self, asset): @@ -168,9 +205,10 @@ class AssetUser(OrgModelMixin): private_key, public_key = ssh_key_gen( username=self.username ) - self.set_auth(password=password, - private_key=private_key, - public_key=public_key) + self.set_auth( + password=password, private_key=private_key, + public_key=public_key + ) def auto_gen_auth_password(self): password = str(uuid.uuid4()) @@ -187,24 +225,18 @@ class AssetUser(OrgModelMixin): } def generate_id_with_asset(self, asset): - id_ = '{}_{}'.format(asset.id, self.id) - id_ = uuid.UUID(md5(id_.encode()).hexdigest()) - return id_ + i = '{}_{}'.format(asset.id, self.id) + i = uuid.UUID(md5(i.encode()).hexdigest()) + return i def construct_to_authbook(self, asset): - from . import AuthBook - fields = [ - 'name', 'username', 'comment', 'org_id', - '_password', '_private_key', '_public_key', - 'date_created', 'date_updated', 'created_by' - ] - id_ = self.generate_id_with_asset(asset) - obj = AuthBook(id=id_, asset=asset, version=0, is_latest=True) - obj._connectivity = self.get_connectivity_of(asset) - for field in fields: - value = getattr(self, field) - setattr(obj, field, value) - return obj + i = self.generate_id_with_asset(asset) + self.id = i + self.asset = asset + self.version = 0 + self.is_latest = True + return self class Meta: abstract = True + diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 2cded41a1..32f569b80 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -4,13 +4,11 @@ import logging -from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator from common.utils import get_signer -from ..const import SYSTEM_USER_CONN_CACHE_KEY from .base import AssetUser @@ -31,7 +29,7 @@ class AdminUser(AssetUser): become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) become_user = models.CharField(default='root', max_length=64) _become_pass = models.CharField(default='', max_length=128) - CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}' + CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}' _prefer = "admin_user" def __str__(self): @@ -61,31 +59,6 @@ class AdminUser(AssetUser): info = None return info - def get_related_assets(self): - assets = self.assets.all() - return assets - - @property - def assets_amount(self): - return self.get_related_assets().count() - - @property - def connectivity(self): - from .asset import Asset - assets = self.get_related_assets().values_list('id', 'hostname', flat=True) - data = { - 'unreachable': [], - 'reachable': [], - } - for asset_id, hostname in assets: - key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id)) - value = cache.get(key, Asset.UNKNOWN) - if value == Asset.REACHABLE: - data['reachable'].append(hostname) - elif value == Asset.UNREACHABLE: - data['unreachable'].append(hostname) - return data - class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] @@ -141,9 +114,6 @@ class SystemUser(AssetUser): login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True) - SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}" - CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}' - def __str__(self): return '{0.name}({0.username})'.format(self) @@ -157,49 +127,6 @@ class SystemUser(AssetUser): 'auto_push': self.auto_push, } - def get_related_assets(self): - assets = set(self.assets.all()) - return assets - - @property - def connectivity(self): - cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id)) - value = cache.get(cache_key, None) - if not value or 'unreachable' not in value: - return {'unreachable': [], 'reachable': []} - else: - return value - - @connectivity.setter - def connectivity(self, value): - data = self.connectivity - unreachable = data['unreachable'] - reachable = data['reachable'] - assets = {asset.hostname: asset for asset in self.assets.all()} - - for host in value.get('dark', {}).keys(): - if host not in unreachable: - unreachable.append(host) - if host in reachable: - reachable.remove(host) - self.set_connectivity_of(assets.get(host), self.UNREACHABLE) - for host in value.get('contacted'): - if host not in reachable: - reachable.append(host) - if host in unreachable: - unreachable.remove(host) - self.set_connectivity_of(assets.get(host), self.REACHABLE) - cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id)) - cache.set(cache_key, data, 3600) - - @property - def assets_unreachable(self): - return self.connectivity.get('unreachable') - - @property - def assets_reachable(self): - return self.connectivity.get('reachable') - @property def login_mode_display(self): return self.get_login_mode_display() @@ -210,12 +137,6 @@ class SystemUser(AssetUser): else: return False - def set_cache(self): - cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600) - - def expire_cache(self): - cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id)) - @property def cmd_filter_rules(self): from .cmd_filter import CommandFilterRule @@ -233,18 +154,6 @@ class SystemUser(AssetUser): return False, matched_cmd return True, None - @classmethod - def get_system_user_by_id_or_cached(cls, sid): - cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid)) - if cached: - return cached - try: - system_user = cls.objects.get(id=sid) - system_user.set_cache() - return system_user - except cls.DoesNotExist: - return None - class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index 765559e70..142f5169a 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- # -from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.serializers import AdaptedBulkListSerializer from ..models import Node, AdminUser -from ..const import ADMIN_USER_CONN_CACHE_KEY from orgs.mixins import BulkOrgResourceModelSerializer from .base import AuthSerializer @@ -20,9 +18,6 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer): password = serializers.CharField( required=False, write_only=True, label=_('Password') ) - unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable')) - assets_amount = serializers.SerializerMethodField(label=_('Asset')) - reachable_amount = serializers.SerializerMethodField(label=_('Reachable')) class Meta: list_serializer_class = AdaptedBulkListSerializer @@ -38,33 +33,14 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer): 'date_created': {'label': _('Date created')}, 'date_updated': {'label': _('Date updated')}, 'become': {'read_only': True}, 'become_method': {'read_only': True}, - 'become_user': {'read_only': True}, 'created_by': {'read_only': True} + 'become_user': {'read_only': True}, 'created_by': {'read_only': True}, + 'assets_amount': {'label', _('Asset')} } def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) return [f for f in fields if not f.startswith('_')] - @staticmethod - def get_unreachable_amount(obj): - data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name)) - if data: - return len(data.get('dark')) - else: - return 0 - - @staticmethod - def get_reachable_amount(obj): - data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name)) - if data: - return len(data.get('contacted')) - else: - return 0 - - @staticmethod - def get_assets_amount(obj): - return obj.assets_amount - class AdminUserAuthSerializer(AuthSerializer): diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 149e9fa2c..01271be96 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -15,8 +15,9 @@ from ops.celery.decorator import ( register_as_period_task, after_app_shutdown_clean_periodic ) -from .models import SystemUser, AdminUser, Asset +from .models import SystemUser, AdminUser from . import const +from .utils import Connectivity FORKS = 10 @@ -208,7 +209,7 @@ def test_asset_connectivity_util(assets, task_name=None): created_by=created_by, ) result = task.run() - summary = result[1] + summary = result.get("summary", {}) success = summary.get('success', False) contacted = summary.get('contacted', {}) dark = summary.get('dark', {}) @@ -218,13 +219,12 @@ def test_asset_connectivity_util(assets, task_name=None): results_summary['dark'].update(dark) for asset in assets: - if asset.hostname in results_summary.get('dark', {}): - asset.connectivity = asset.UNREACHABLE - elif asset.hostname in results_summary.get('contacted', []): - asset.connectivity = asset.REACHABLE + if asset.hostname in results_summary.get('dark', {}).keys(): + asset.connectivity = Connectivity.unreachable() + elif asset.hostname in results_summary.get('contacted', {}).keys(): + asset.connectivity = Connectivity.reachable() else: - asset.connectivity = asset.UNKNOWN - + asset.connectivity = Connectivity.unknown() return results_summary @@ -286,10 +286,6 @@ def test_admin_user_connectivity_manual(admin_user): ## System user connective ## -@shared_task -def set_system_user_connectivity_info(system_user, summary): - system_user.connectivity = summary - @shared_task def test_system_user_connectivity_util(system_user, assets, task_name): @@ -346,7 +342,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name): results_summary['contacted'].update(contacted) results_summary['dark'].update(dark) - set_system_user_connectivity_info(system_user, results_summary) + system_user.set_connectivity(results_summary) return results_summary @@ -584,6 +580,7 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False) """ :param asset_user: 对象 :param task_name: + :param run_as_admin: :return: """ from ops.utils import update_or_create_ansible_task @@ -607,7 +604,7 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False) kwargs["run_as"] = asset_user.username task, created = update_or_create_ansible_task(*args, **kwargs) result = task.run() - set_asset_user_connectivity_info(asset_user, result) + asset_user.set_connectivity(result.get("summary", {})) @shared_task diff --git a/apps/assets/utils.py b/apps/assets/utils.py index a4809e3a7..cd022a9aa 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,8 +1,8 @@ # ~*~ coding: utf-8 ~*~ # -import os -import paramiko -from paramiko.ssh_exception import SSHException +from django.utils.translation import ugettext_lazy as _ +from django.core.cache import cache +from django.utils import timezone from common.utils import get_object_or_none from .models import Asset, SystemUser, Label @@ -45,3 +45,62 @@ class LabelFilter: for kwargs in conditions: queryset = queryset.filter(**kwargs) return queryset + + +class Connectivity: + UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) + CONNECTIVITY_CHOICES = ( + (UNREACHABLE, _("Unreachable")), + (REACHABLE, _('Reachable')), + (UNKNOWN, _("Unknown")), + ) + + value = UNKNOWN + datetime = timezone.now() + + def __init__(self, value, datetime): + self.value = value + self.datetime = datetime + + def display(self): + return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.value) + + def is_reachable(self): + return self.value == self.REACHABLE + + def is_unreachable(self): + return self.value == self.UNREACHABLE + + def is_unknown(self): + return self.value == self.UNKNOWN + + @classmethod + def unreachable(cls): + return cls(cls.UNREACHABLE, timezone.now()) + + @classmethod + def reachable(cls): + return cls(cls.REACHABLE, timezone.now()) + + @classmethod + def unknown(cls): + return cls(cls.UNKNOWN, timezone.now()) + + @classmethod + def set(cls, key, value, ttl=0): + cache.set(key, value, ttl) + + @classmethod + def get(cls, key): + return cache.get(key, cls.UNKNOWN) + + @classmethod + def set_unreachable(cls, key, ttl=0): + cls.set(key, cls.unreachable(), ttl) + + @classmethod + def set_reachable(cls, key, ttl=0): + cls.set(key, cls.reachable(), ttl) + + def __eq__(self, other): + return self.value == other.value diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 79146c039..6d79cfa6f 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -293,3 +293,11 @@ class LocalProxy(object): __rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o) __copy__ = lambda x: copy.copy(x._get_current_object()) __deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo) + + +def random_string(length): + import string + import random + charset = string.ascii_letters + string.digits + s = [random.choice(charset) for i in range(length)] + return ''.join(s) diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 2943641a1..5d37be0e0 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -47,9 +47,8 @@ class SessionViewSet(BulkModelViewSet): sid = serializer.validated_data["system_user"] # guacamole提交的是id if is_uuid(sid): - _system_user = SystemUser.get_system_user_by_id_or_cached(sid) - if _system_user: - serializer.validated_data["system_user"] = _system_user.name + _system_user = get_object_or_404(SystemUser, id=sid) + serializer.validated_data["system_user"] = _system_user.name return super().perform_create(serializer) From 63216addf6f3770685dd875886834bf1f98bd898 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 24 Jun 2019 14:23:29 +0800 Subject: [PATCH 05/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9connectivity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/__init__.py | 6 +- apps/assets/models/asset.py | 2 +- apps/assets/models/authbook.py | 3 - apps/assets/models/base.py | 32 +++++---- apps/assets/models/utils.py | 70 ++++++++++++++++++- apps/assets/serializers/admin_user.py | 21 +++--- apps/assets/tasks.py | 2 +- .../templates/assets/admin_user_list.html | 27 ++++--- apps/assets/utils.py | 58 +-------------- 9 files changed, 115 insertions(+), 106 deletions(-) diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index b87a18796..e1fbf6f8e 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,10 +1,10 @@ -from .user import * +from .asset import * from .label import Label +from .user import * from .cluster import * from .group import * from .domain import * from .node import * -from .asset import * from .cmd_filter import * -from .utils import * from .authbook import * +from .utils import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 1e72d2614..f0ea3f6f6 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -12,8 +12,8 @@ from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator from .user import AdminUser, SystemUser +from .utils import Connectivity from orgs.mixins import OrgModelMixin, OrgManager -from ..utils import Connectivity __all__ = ['Asset', 'Protocol'] logger = logging.getLogger(__name__) diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index ba676c309..85cb22606 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -5,7 +5,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from orgs.mixins import OrgManager -from ..utils import Connectivity from .base import AssetUser __all__ = ['AuthBook'] @@ -72,8 +71,6 @@ class AuthBook(AssetUser): @property def connectivity(self): - if self._connectivity: - return self._connectivity return self.get_asset_connectivity(self.asset) @property diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 5b9aeddde..d99bc982a 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -6,7 +6,6 @@ from hashlib import md5 import sshpubkeys from django.db import models -from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -16,9 +15,7 @@ from common.utils import ( from common.validators import alphanumeric from common import fields from orgs.mixins import OrgModelMixin -from .utils import private_key_validator -from ..utils import Connectivity -from .. import const +from .utils import private_key_validator, Connectivity signer = get_signer() @@ -167,12 +164,12 @@ class AssetUser(OrgModelMixin): def get_asset_connectivity(self, asset): i = self.generate_id_with_asset(asset) key = self.CONNECTIVITY_ASSET_CACHE_KEY.format(i) - return cache.get(key, const.CONN_UNKNOWN) + return Connectivity.get(key) def set_asset_connectivity(self, asset, c): i = self.generate_id_with_asset(asset) key = self.CONNECTIVITY_ASSET_CACHE_KEY.format(i) - cache.set(key, c, 3600) + Connectivity.set(key, c, 3600) def load_specific_asset_auth(self, asset): from ..backends import AssetUserManager @@ -225,17 +222,24 @@ class AssetUser(OrgModelMixin): } def generate_id_with_asset(self, asset): - i = '{}_{}'.format(asset.id, self.id) - i = uuid.UUID(md5(i.encode()).hexdigest()) - return i + user_id = str(self.id).split('-')[:3] + asset_id = str(asset.id).split('-')[3:] + ids = user_id + asset_id + return '-'.join(ids) def construct_to_authbook(self, asset): + from . import AuthBook + fields = [ + 'name', 'username', 'comment', 'org_id', + '_password', '_private_key', '_public_key', + 'date_created', 'date_updated', 'created_by' + ] i = self.generate_id_with_asset(asset) - self.id = i - self.asset = asset - self.version = 0 - self.is_latest = True - return self + obj = AuthBook(id=i, asset=asset, version=0, is_latest=True) + for field in fields: + value = getattr(self, field) + setattr(obj, field, value) + return obj class Meta: abstract = True diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index 11d347685..1f5b630bd 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -2,11 +2,17 @@ # -*- coding: utf-8 -*- # +from django.utils import timezone +from django.core.cache import cache from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ + from common.utils import validate_ssh_private_key -__all__ = ['init_model', 'generate_fake'] +__all__ = [ + 'init_model', 'generate_fake', 'private_key_validator', 'Connectivity', +] def init_model(): @@ -31,5 +37,63 @@ def private_key_validator(value): ) -if __name__ == '__main__': - pass +class Connectivity: + UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) + CONNECTIVITY_CHOICES = ( + (UNREACHABLE, _("Unreachable")), + (REACHABLE, _('Reachable')), + (UNKNOWN, _("Unknown")), + ) + + value = UNKNOWN + datetime = timezone.now() + + def __init__(self, value, datetime): + self.value = value + self.datetime = datetime + + def display(self): + return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.value) + + def is_reachable(self): + return self.value == self.REACHABLE + + def is_unreachable(self): + return self.value == self.UNREACHABLE + + def is_unknown(self): + return self.value == self.UNKNOWN + + @classmethod + def unreachable(cls): + return cls(cls.UNREACHABLE, timezone.now()) + + @classmethod + def reachable(cls): + return cls(cls.REACHABLE, timezone.now()) + + @classmethod + def unknown(cls): + return cls(cls.UNKNOWN, timezone.now()) + + @classmethod + def set(cls, key, value, ttl=0): + cache.set(key, value, ttl) + + @classmethod + def get(cls, key): + value = cache.get(key, cls.unknown()) + if not isinstance(value, cls): + value = cls.unknown() + return value + + @classmethod + def set_unreachable(cls, key, ttl=0): + cls.set(key, cls.unreachable(), ttl) + + @classmethod + def set_reachable(cls, key, ttl=0): + cls.set(key, cls.reachable(), ttl) + + def __eq__(self, other): + return self.value == other.value diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index 142f5169a..581e810b7 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -23,24 +23,19 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer): list_serializer_class = AdaptedBulkListSerializer model = AdminUser fields = [ - 'id', 'name', 'username', 'assets_amount', - 'reachable_amount', 'unreachable_amount', 'password', 'comment', - 'date_created', 'date_updated', 'become', 'become_method', - 'become_user', 'created_by', + 'id', 'name', 'username', 'password', 'comment', + 'connectivity_amount', 'assets_amount', + 'date_created', 'date_updated', 'created_by', ] extra_kwargs = { - 'date_created': {'label': _('Date created')}, - 'date_updated': {'label': _('Date updated')}, - 'become': {'read_only': True}, 'become_method': {'read_only': True}, - 'become_user': {'read_only': True}, 'created_by': {'read_only': True}, - 'assets_amount': {'label', _('Asset')} + 'date_created': {'read_only': True}, + 'date_updated': {'read_only': True}, + 'created_by': {'read_only': True}, + 'assets_amount': {'label': _('Asset')}, + 'connectivity_amount': {'label': _('Connectivity')}, } - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - return [f for f in fields if not f.startswith('_')] - class AdminUserAuthSerializer(AuthSerializer): diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 01271be96..8e7924e71 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -16,8 +16,8 @@ from ops.celery.decorator import ( ) from .models import SystemUser, AdminUser +from .models.utils import Connectivity from . import const -from .utils import Connectivity FORKS = 10 diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 4f4356539..d5383a0f6 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -75,27 +75,29 @@ function initTable() { }}, {targets: 4, createdCell: function (td, cellData) { var innerHtml = ""; - if (cellData !== 0) { - innerHtml = "" + cellData + ""; + var data = cellData['reachable']; + if (data !== 0) { + innerHtml = "" + data + ""; } else { - innerHtml = "" + cellData + ""; + innerHtml = "" + data + ""; } - $(td).html('' + innerHtml + ''); + $(td).html('' + innerHtml + ''); }}, {targets: 5, createdCell: function (td, cellData) { + var data = cellData['unreachable']; var innerHtml = ""; - if (cellData !== 0) { - innerHtml = "" + cellData + ""; + if (data !== 0) { + innerHtml = "" + data + ""; } else { - innerHtml = "" + cellData + ""; + innerHtml = "" + data + ""; } - $(td).html('' + innerHtml + ''); + $(td).html('' + innerHtml + ''); }}, {targets: 6, createdCell: function (td, cellData, rowData) { var val = 0; var innerHtml = ""; var total = rowData.assets_amount; - var reachable = rowData.reachable_amount; + var reachable = cellData.reachable; if (total !== 0) { val = reachable/total * 100; } @@ -114,8 +116,11 @@ function initTable() { $(td).html(update_btn + del_btn) }}], ajax_url: '{% url "api-assets:admin-user-list" %}', - columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, - {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}] + columns: [ + {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, + {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, + {data: "comment"}, {data: "id"} + ] }; admin_user_table = jumpserver.initServerSideDataTable(options); return admin_user_table diff --git a/apps/assets/utils.py b/apps/assets/utils.py index cd022a9aa..a796e210e 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -5,7 +5,7 @@ from django.core.cache import cache from django.utils import timezone from common.utils import get_object_or_none -from .models import Asset, SystemUser, Label +from .models import SystemUser, Label def get_assets_by_id_list(id_list): @@ -47,60 +47,4 @@ class LabelFilter: return queryset -class Connectivity: - UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) - CONNECTIVITY_CHOICES = ( - (UNREACHABLE, _("Unreachable")), - (REACHABLE, _('Reachable')), - (UNKNOWN, _("Unknown")), - ) - value = UNKNOWN - datetime = timezone.now() - - def __init__(self, value, datetime): - self.value = value - self.datetime = datetime - - def display(self): - return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.value) - - def is_reachable(self): - return self.value == self.REACHABLE - - def is_unreachable(self): - return self.value == self.UNREACHABLE - - def is_unknown(self): - return self.value == self.UNKNOWN - - @classmethod - def unreachable(cls): - return cls(cls.UNREACHABLE, timezone.now()) - - @classmethod - def reachable(cls): - return cls(cls.REACHABLE, timezone.now()) - - @classmethod - def unknown(cls): - return cls(cls.UNKNOWN, timezone.now()) - - @classmethod - def set(cls, key, value, ttl=0): - cache.set(key, value, ttl) - - @classmethod - def get(cls, key): - return cache.get(key, cls.UNKNOWN) - - @classmethod - def set_unreachable(cls, key, ttl=0): - cls.set(key, cls.unreachable(), ttl) - - @classmethod - def set_reachable(cls, key, ttl=0): - cls.set(key, cls.reachable(), ttl) - - def __eq__(self, other): - return self.value == other.value From 327febaf59a4d8b7b2314131993ee23a0296376b Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 24 Jun 2019 20:39:45 +0800 Subject: [PATCH 06/53] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=94=B9=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E8=AE=A4=E8=AF=81=E9=9C=80=E8=A6=81=E7=9A=84MFA?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E9=97=B4=E9=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset_user.py | 18 +- .../templates/assets/_asset_user_list.html | 5 +- apps/authentication/api/auth.py | 2 +- apps/common/permissions.py | 8 + apps/jumpserver/conf.py | 2 +- apps/jumpserver/context_processor.py | 1 + apps/jumpserver/settings.py | 1 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 77458 -> 77349 bytes apps/locale/zh/LC_MESSAGES/django.po | 376 +++++++++--------- 9 files changed, 216 insertions(+), 197 deletions(-) diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index ae8032cba..c3dc518fa 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -10,7 +10,7 @@ from rest_framework import filters from rest_framework_bulk import BulkModelViewSet from django.shortcuts import get_object_or_404 -from common.permissions import IsOrgAdminOrAppUser +from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify from common.utils import get_object_or_none, get_logger from common.mixins import IDInCacheFilterMixin from ..backends import AssetUserManager @@ -57,7 +57,7 @@ class AssetUserSearchBackend(filters.BaseFilterBackend): class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): pagination_class = LimitOffsetPagination serializer_class = serializers.AssetUserSerializer - permission_classes = (IsOrgAdminOrAppUser, ) + permission_classes = [IsOrgAdminOrAppUser] http_method_names = ['get', 'post'] filter_fields = [ "id", "ip", "hostname", "username", "asset_id", "node_id", @@ -111,22 +111,16 @@ class AssetUserExportViewSet(AssetUserViewSet): serializer_class = serializers.AssetUserExportSerializer http_method_names = ['get'] - def list(self, request, *args, **kwargs): - otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME") - if not otp_last_verify or time.time() - int(otp_last_verify) > 600: - return Response({"error": "Need MFA confirm mfa auth"}, status=403) - return super().list(request, *args, **kwargs) + def get_permissions(self): + self.permission_classes.append(NeedMFAVerify) + return super().get_permissions() class AssetUserAuthInfoApi(generics.RetrieveAPIView): serializer_class = serializers.AssetUserAuthInfoSerializer - permission_classes = (IsOrgAdminOrAppUser,) + permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] def retrieve(self, request, *args, **kwargs): - otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME") - if not otp_last_verify or time.time() - int(otp_last_verify) > 600: - return Response({"error": "Need MFA confirm mfa auth"}, status=403) - instance = self.get_object() serializer = self.get_serializer(instance) status_code = status.HTTP_200_OK diff --git a/apps/assets/templates/assets/_asset_user_list.html b/apps/assets/templates/assets/_asset_user_list.html index af90c017b..52cd94b52 100644 --- a/apps/assets/templates/assets/_asset_user_list.html +++ b/apps/assets/templates/assets/_asset_user_list.html @@ -32,8 +32,9 @@ var assetUserListUrl = "{% url "api-assets:asset-user-list" %}"; var assetUserTable; var needPush = false; var prefer = null; -var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}"; +var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}"; var testDatetime = "{% trans 'Test datetime: ' %}"; +var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}"; function initAssetUserTable() { var options = { @@ -109,7 +110,7 @@ $(document).ready(function(){ authUsername = $(this).data('user'); var now = new Date(); var nowTime = now.getTime() / 1000; - if ( !lastMFATime || nowTime - lastMFATime > 60*10 ) { + if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) { mfaFor = "viewAuth"; $("#mfa_auth_confirm").modal("show"); } else { diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py index 42c196c51..042150517 100644 --- a/apps/authentication/api/auth.py +++ b/apps/authentication/api/auth.py @@ -194,7 +194,7 @@ class UserOtpVerifyApi(CreateAPIView): code = serializer.validated_data["code"] if request.user.check_otp(code): - request.session["OTP_LAST_VERIFY_TIME"] = int(time.time()) + request.session["MFA_VERIFY_TIME"] = int(time.time()) return Response({"ok": "1"}) else: return Response({"error": "Code not valid"}, status=400) diff --git a/apps/common/permissions.py b/apps/common/permissions.py index ec004df0b..776c50e4d 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -132,3 +132,11 @@ class PermissionsMixin(UserPassesTestMixin): if not permission_class().has_permission(self.request, self): return False return True + + +class NeedMFAVerify(permissions.BasePermission): + def has_permission(self, request, view): + mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0) + if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL: + return True + return False diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 0362a68e3..8368cb993 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -374,7 +374,7 @@ defaults = { 'HTTP_LISTEN_PORT': 8080, 'LOGIN_LOG_KEEP_DAYS': 90, 'ASSETS_PERM_CACHE_TIME': 3600, - + 'SECURITY_MFA_VERIFY_TTL': 3600, } diff --git a/apps/jumpserver/context_processor.py b/apps/jumpserver/context_processor.py index 148611359..91a720fd7 100644 --- a/apps/jumpserver/context_processor.py +++ b/apps/jumpserver/context_processor.py @@ -17,6 +17,7 @@ def jumpserver_processor(request): 'VERSION': settings.VERSION, 'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019', 'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION, + 'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL, } return context diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index cc93909d3..63ebabfe0 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -565,6 +565,7 @@ SECURITY_PASSWORD_RULES = [ 'SECURITY_PASSWORD_NUMBER', 'SECURITY_PASSWORD_SPECIAL_CHAR' ] +SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c4e24380b3cf885b4f8afcd4e1406bd158425e35..68cbba8db1680026e55e1bc6f240d39c58ef3efd 100644 GIT binary patch delta 22799 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\n" "Language-Team: Jumpserver team\n" @@ -76,9 +76,9 @@ msgstr "运行参数" #: applications/templates/applications/remote_app_list.html:22 #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:315 assets/models/authbook.py:27 -#: assets/serializers/admin_user.py:24 assets/serializers/asset_user.py:105 -#: assets/serializers/system_user.py:28 +#: assets/models/asset.py:298 assets/models/authbook.py:24 +#: assets/serializers/admin_user.py:35 assets/serializers/asset_user.py:106 +#: assets/serializers/system_user.py:29 #: assets/templates/assets/admin_user_list.html:49 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 @@ -112,7 +112,7 @@ msgstr "资产" #: applications/templates/applications/remote_app_detail.html:61 #: applications/templates/applications/remote_app_list.html:23 #: applications/templates/applications/user_remote_app_list.html:19 -#: assets/models/user.py:251 assets/templates/assets/user_asset_list.html:172 +#: assets/models/user.py:160 assets/templates/assets/user_asset_list.html:172 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:72 #: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:39 @@ -135,7 +135,7 @@ msgstr "系统用户" #: applications/templates/applications/remote_app_list.html:20 #: applications/templates/applications/user_remote_app_list.html:16 #: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:148 -#: assets/models/asset.py:72 assets/models/base.py:27 +#: assets/models/asset.py:70 assets/models/base.py:27 #: assets/models/cluster.py:18 assets/models/cmd_filter.py:20 #: assets/models/domain.py:20 assets/models/group.py:20 #: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56 @@ -206,7 +206,7 @@ msgstr "参数" #: applications/models/remote_app.py:43 #: applications/templates/applications/remote_app_detail.html:77 -#: assets/models/asset.py:132 assets/models/base.py:35 +#: assets/models/asset.py:130 assets/models/base.py:35 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 #: assets/models/cmd_filter.py:58 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 @@ -230,10 +230,9 @@ msgstr "创建者" # msgstr "创建者" #: applications/models/remote_app.py:46 #: applications/templates/applications/remote_app_detail.html:73 -#: assets/models/asset.py:133 assets/models/base.py:33 +#: assets/models/asset.py:131 assets/models/base.py:33 #: assets/models/cluster.py:26 assets/models/domain.py:23 #: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/admin_user.py:38 #: assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/domain_detail.html:68 @@ -259,7 +258,7 @@ msgstr "创建日期" #: applications/templates/applications/remote_app_detail.html:81 #: applications/templates/applications/remote_app_list.html:24 #: applications/templates/applications/user_remote_app_list.html:20 -#: assets/models/asset.py:134 assets/models/base.py:32 +#: assets/models/asset.py:132 assets/models/base.py:32 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 #: assets/models/cmd_filter.py:55 assets/models/domain.py:21 #: assets/models/domain.py:53 assets/models/group.py:23 @@ -391,7 +390,7 @@ msgstr "提交" #: assets/templates/assets/cmd_filter_rule_list.html:19 #: assets/templates/assets/domain_detail.html:18 #: assets/templates/assets/domain_gateway_list.html:20 -#: assets/templates/assets/system_user_asset.html:18 +#: assets/templates/assets/system_user_assets.html:18 #: assets/templates/assets/system_user_detail.html:18 #: ops/templates/ops/adhoc_history.html:130 #: ops/templates/ops/task_adhoc.html:116 @@ -412,13 +411,13 @@ msgstr "详情" #: applications/templates/applications/remote_app_detail.html:21 #: applications/templates/applications/remote_app_list.html:56 -#: assets/templates/assets/_asset_user_list.html:62 +#: assets/templates/assets/_asset_user_list.html:69 #: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_list.html:29 -#: assets/templates/assets/admin_user_list.html:112 +#: assets/templates/assets/admin_user_list.html:114 #: assets/templates/assets/asset_detail.html:27 #: assets/templates/assets/asset_list.html:86 -#: assets/templates/assets/asset_list.html:190 +#: assets/templates/assets/asset_list.html:196 #: assets/templates/assets/cmd_filter_detail.html:29 #: assets/templates/assets/cmd_filter_list.html:58 #: assets/templates/assets/cmd_filter_rule_list.html:86 @@ -429,7 +428,7 @@ msgstr "详情" #: assets/templates/assets/label_list.html:39 #: assets/templates/assets/system_user_detail.html:26 #: assets/templates/assets/system_user_list.html:33 -#: assets/templates/assets/system_user_list.html:118 audits/models.py:33 +#: assets/templates/assets/system_user_list.html:119 audits/models.py:33 #: perms/templates/perms/asset_permission_detail.html:30 #: perms/templates/perms/asset_permission_list.html:181 #: perms/templates/perms/remote_app_permission_detail.html:30 @@ -458,9 +457,9 @@ msgstr "更新" #: applications/templates/applications/remote_app_detail.html:25 #: applications/templates/applications/remote_app_list.html:57 #: assets/templates/assets/admin_user_detail.html:28 -#: assets/templates/assets/admin_user_list.html:113 +#: assets/templates/assets/admin_user_list.html:115 #: assets/templates/assets/asset_detail.html:31 -#: assets/templates/assets/asset_list.html:191 +#: assets/templates/assets/asset_list.html:197 #: assets/templates/assets/cmd_filter_detail.html:33 #: assets/templates/assets/cmd_filter_list.html:59 #: assets/templates/assets/cmd_filter_rule_list.html:87 @@ -470,7 +469,7 @@ msgstr "更新" #: assets/templates/assets/domain_list.html:55 #: assets/templates/assets/label_list.html:40 #: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:119 audits/models.py:34 +#: assets/templates/assets/system_user_list.html:120 audits/models.py:34 #: ops/templates/ops/task_list.html:64 #: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_list.html:182 @@ -599,17 +598,36 @@ msgstr "更新节点资产硬件信息: {}" msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" -#: assets/forms/asset.py:45 assets/models/asset.py:103 -#: assets/models/user.py:134 assets/templates/assets/asset_detail.html:194 +#: assets/const.py:77 assets/models/utils.py:43 +#: assets/templates/assets/admin_user_list.html:51 +#: assets/templates/assets/system_user_list.html:57 +msgid "Unreachable" +msgstr "不可达" + +#: assets/const.py:78 assets/models/utils.py:44 +#: assets/templates/assets/admin_user_list.html:50 +#: assets/templates/assets/asset_list.html:107 +#: assets/templates/assets/system_user_list.html:56 +#: users/templates/users/user_group_granted_asset.html:47 +msgid "Reachable" +msgstr "可连接" + +#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:9 +#: xpack/plugins/license/models.py:78 +msgid "Unknown" +msgstr "未知" + +#: assets/forms/asset.py:45 assets/models/asset.py:101 +#: assets/models/user.py:107 assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/asset_detail.html:202 -#: assets/templates/assets/system_user_asset.html:83 +#: assets/templates/assets/system_user_assets.html:83 #: perms/models/asset_permission.py:38 #: xpack/plugins/change_auth_plan/models.py:72 msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:107 -#: assets/models/cluster.py:19 assets/models/user.py:92 +#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:105 +#: assets/models/cluster.py:19 assets/models/user.py:65 #: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 @@ -626,7 +644,7 @@ msgstr "管理用户" msgid "Label" msgstr "标签" -#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:102 +#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:100 #: assets/models/domain.py:26 assets/models/domain.py:52 #: assets/templates/assets/asset_detail.html:84 #: assets/templates/assets/user_asset_list.html:173 @@ -720,8 +738,8 @@ msgid "Password or private key passphrase" msgstr "密码或密钥密码" #: assets/forms/user.py:26 assets/models/base.py:29 -#: assets/serializers/admin_user.py:21 assets/serializers/asset_user.py:33 -#: assets/serializers/asset_user.py:86 assets/serializers/system_user.py:16 +#: assets/serializers/admin_user.py:19 assets/serializers/asset_user.py:34 +#: assets/serializers/asset_user.py:87 assets/serializers/system_user.py:16 #: assets/templates/assets/_asset_user_auth_update_modal.html:21 #: assets/templates/assets/_asset_user_auth_view_modal.html:27 #: authentication/forms.py:13 @@ -739,8 +757,8 @@ msgstr "密码或密钥密码" msgid "Password" msgstr "密码" -#: assets/forms/user.py:29 assets/serializers/asset_user.py:41 -#: assets/serializers/asset_user.py:94 +#: assets/forms/user.py:29 assets/serializers/asset_user.py:42 +#: assets/serializers/asset_user.py:95 #: assets/templates/assets/_asset_user_auth_update_modal.html:27 #: users/models/user.py:90 msgid "Private key" @@ -759,7 +777,7 @@ msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" #: assets/forms/user.py:151 assets/models/cmd_filter.py:31 -#: assets/models/user.py:142 assets/templates/assets/_system_user.html:66 +#: assets/models/user.py:115 assets/templates/assets/_system_user.html:66 #: assets/templates/assets/system_user_detail.html:165 msgid "Command filter" msgstr "命令过滤器" @@ -786,7 +804,7 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写" msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" -#: assets/models/asset.py:73 assets/models/asset.py:98 +#: assets/models/asset.py:71 assets/models/asset.py:96 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:69 #: assets/templates/assets/user_asset_list.html:168 @@ -794,8 +812,8 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" msgid "Port" msgstr "端口" -#: assets/models/asset.py:93 assets/models/domain.py:49 -#: assets/serializers/asset_user.py:28 +#: assets/models/asset.py:91 assets/models/domain.py:49 +#: assets/serializers/asset_user.py:29 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_user_list.html:15 #: assets/templates/assets/asset_detail.html:64 @@ -811,7 +829,7 @@ msgstr "端口" msgid "IP" msgstr "IP" -#: assets/models/asset.py:94 assets/serializers/asset_user.py:27 +#: assets/models/asset.py:92 assets/serializers/asset_user.py:28 #: assets/templates/assets/_asset_list_modal.html:45 #: assets/templates/assets/_asset_user_auth_update_modal.html:9 #: assets/templates/assets/_asset_user_auth_view_modal.html:15 @@ -828,8 +846,8 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:97 assets/models/asset.py:100 -#: assets/models/domain.py:51 assets/models/user.py:137 +#: assets/models/asset.py:95 assets/models/asset.py:98 +#: assets/models/domain.py:51 assets/models/user.py:110 #: assets/templates/assets/asset_detail.html:72 #: assets/templates/assets/domain_gateway_list.html:70 #: assets/templates/assets/system_user_detail.html:70 @@ -839,115 +857,94 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:108 +#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:108 #: assets/templates/assets/user_asset_list.html:170 msgid "Platform" msgstr "系统平台" -#: assets/models/asset.py:104 assets/models/cmd_filter.py:21 +#: assets/models/asset.py:102 assets/models/cmd_filter.py:21 #: assets/models/domain.py:54 assets/models/label.py:22 #: assets/templates/assets/asset_detail.html:116 #: assets/templates/assets/user_asset_list.html:174 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:110 assets/templates/assets/asset_detail.html:68 +#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:68 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:111 assets/templates/assets/asset_detail.html:124 +#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:124 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:114 assets/templates/assets/asset_detail.html:88 +#: assets/models/asset.py:112 assets/templates/assets/asset_detail.html:88 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:113 assets/templates/assets/asset_detail.html:92 msgid "Model" msgstr "型号" -#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:120 +#: assets/models/asset.py:114 assets/templates/assets/asset_detail.html:120 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:118 +#: assets/models/asset.py:116 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:119 +#: assets/models/asset.py:117 #: xpack/plugins/license/templates/license/license_detail.html:80 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:120 +#: assets/models/asset.py:118 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:121 +#: assets/models/asset.py:119 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:122 assets/templates/assets/asset_detail.html:100 +#: assets/models/asset.py:120 assets/templates/assets/asset_detail.html:100 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:123 +#: assets/models/asset.py:121 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:124 +#: assets/models/asset.py:122 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:126 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:124 assets/templates/assets/asset_detail.html:112 #: assets/templates/assets/user_asset_list.html:171 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:127 +#: assets/models/asset.py:125 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:128 +#: assets/models/asset.py:126 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:129 +#: assets/models/asset.py:127 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:131 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:129 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:231 templates/_nav.html:26 msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:140 assets/models/base.py:39 -#: assets/serializers/admin_user.py:23 assets/serializers/system_user.py:19 -#: assets/templates/assets/admin_user_list.html:51 -#: assets/templates/assets/system_user_list.html:57 -msgid "Unreachable" -msgstr "不可达" - -#: assets/models/asset.py:141 assets/models/base.py:40 -#: assets/serializers/admin_user.py:25 assets/serializers/system_user.py:27 -#: assets/templates/assets/admin_user_list.html:50 -#: assets/templates/assets/asset_list.html:107 -#: assets/templates/assets/system_user_list.html:56 -#: users/templates/users/user_group_granted_asset.html:47 -msgid "Reachable" -msgstr "可连接" - -#: assets/models/asset.py:142 assets/models/base.py:41 -#: authentication/utils.py:9 xpack/plugins/license/models.py:78 -msgid "Unknown" -msgstr "未知" - -#: assets/models/authbook.py:28 ops/templates/ops/task_detail.html:72 +#: assets/models/authbook.py:25 ops/templates/ops/task_detail.html:72 msgid "Latest version" msgstr "最新版本" -#: assets/models/authbook.py:29 +#: assets/models/authbook.py:26 #: assets/templates/assets/_asset_user_list.html:17 #: ops/templates/ops/adhoc_history.html:58 #: ops/templates/ops/adhoc_history_detail.html:57 @@ -955,7 +952,7 @@ msgstr "最新版本" msgid "Version" msgstr "版本" -#: assets/models/authbook.py:37 +#: assets/models/authbook.py:35 msgid "AuthBook" msgstr "" @@ -969,8 +966,7 @@ msgstr "ssh密钥" msgid "SSH public key" msgstr "ssh公钥" -#: assets/models/base.py:34 assets/serializers/admin_user.py:39 -#: assets/templates/assets/cmd_filter_detail.html:73 +#: assets/models/base.py:34 assets/templates/assets/cmd_filter_detail.html:73 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 msgid "Date updated" msgstr "更新日期" @@ -1005,7 +1001,7 @@ msgid "Operator" msgstr "运营商" #: assets/models/cluster.py:36 assets/models/group.py:34 -#: perms/utils/asset_permission.py:63 +#: perms/utils/asset_permission.py:64 msgid "Default" msgstr "默认" @@ -1070,7 +1066,7 @@ msgstr "过滤器" msgid "Type" msgstr "类型" -#: assets/models/cmd_filter.py:51 assets/models/user.py:136 +#: assets/models/cmd_filter.py:51 assets/models/user.py:109 #: assets/templates/assets/cmd_filter_rule_list.html:60 msgid "Priority" msgstr "优先级" @@ -1155,17 +1151,17 @@ msgstr "键" msgid "New node" msgstr "新节点" -#: assets/models/user.py:130 +#: assets/models/user.py:103 msgid "Automatic login" msgstr "自动登录" -#: assets/models/user.py:131 +#: assets/models/user.py:104 msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:135 +#: assets/models/user.py:108 #: assets/templates/assets/_asset_group_bulk_update_modal.html:11 -#: assets/templates/assets/system_user_asset.html:22 +#: assets/templates/assets/system_user_assets.html:22 #: assets/templates/assets/system_user_detail.html:22 #: assets/views/admin_user.py:30 assets/views/admin_user.py:49 #: assets/views/admin_user.py:66 assets/views/admin_user.py:82 @@ -1186,31 +1182,38 @@ msgstr "手动登录" msgid "Assets" msgstr "资产管理" -#: assets/models/user.py:138 assets/templates/assets/_system_user.html:59 +#: assets/models/user.py:111 assets/templates/assets/_system_user.html:59 #: assets/templates/assets/system_user_detail.html:122 #: assets/templates/assets/system_user_update.html:10 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:139 assets/templates/assets/system_user_detail.html:74 +#: assets/models/user.py:112 assets/templates/assets/system_user_detail.html:74 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:140 assets/templates/assets/system_user_detail.html:79 +#: assets/models/user.py:113 assets/templates/assets/system_user_detail.html:79 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:141 assets/templates/assets/system_user_detail.html:66 +#: assets/models/user.py:114 assets/templates/assets/system_user_detail.html:66 #: assets/templates/assets/system_user_list.html:54 msgid "Login mode" msgstr "登录模式" -#: assets/models/utils.py:29 +#: assets/models/utils.py:35 #, python-format msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/serializers/asset.py:46 assets/templates/assets/asset_create.html:24 +#: assets/serializers/admin_user.py:36 assets/serializers/asset.py:47 +#: assets/serializers/asset_user.py:30 assets/serializers/system_user.py:30 +#: assets/templates/assets/_asset_user_list.html:18 +msgid "Connectivity" +msgstr "连接" + +#: assets/serializers/asset.py:45 assets/serializers/asset.py:155 +#: assets/templates/assets/asset_create.html:24 msgid "Protocols" msgstr "协议组" @@ -1218,20 +1221,15 @@ msgstr "协议组" msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:74 assets/serializers/asset_user.py:29 -#: assets/templates/assets/_asset_user_list.html:18 -msgid "Connectivity" -msgstr "连接" - -#: assets/serializers/asset.py:75 orgs/mixins.py:223 +#: assets/serializers/asset.py:74 orgs/mixins.py:220 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:93 +#: assets/serializers/asset.py:92 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset_user.py:37 assets/serializers/asset_user.py:90 +#: assets/serializers/asset_user.py:38 assets/serializers/asset_user.py:91 #: users/forms.py:248 users/models/user.py:93 #: users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 @@ -1241,106 +1239,98 @@ msgstr "协议重复: {}" msgid "Public key" msgstr "ssh公钥" -#: assets/serializers/asset_user.py:43 +#: assets/serializers/asset_user.py:44 msgid "Backend" msgstr "后端" -#: assets/serializers/asset_user.py:65 +#: assets/serializers/asset_user.py:66 msgid "private key invalid" msgstr "密钥不合法" -#: assets/serializers/system_user.py:22 -msgid "Unreachable assets" -msgstr "不可达资产" - -#: assets/serializers/system_user.py:25 -msgid "Reachable assets" -msgstr "可连接资产" - -#: assets/serializers/system_user.py:41 +#: assets/serializers/system_user.py:31 msgid "Login mode display" msgstr "登录模式显示" -#: assets/tasks.py:32 +#: assets/tasks.py:33 msgid "Asset has been disabled, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:36 +#: assets/tasks.py:37 msgid "Asset may not be support ansible, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:49 +#: assets/tasks.py:50 msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" -#: assets/tasks.py:59 +#: assets/tasks.py:60 msgid "No assets matched related system user protocol, stop task" msgstr "没有匹配到与系统用户协议相关的资产,结束任务" -#: assets/tasks.py:85 +#: assets/tasks.py:86 msgid "Get asset info failed: {}" msgstr "获取资产信息失败:{}" -#: assets/tasks.py:135 +#: assets/tasks.py:136 msgid "Update some assets hardware info" msgstr "更新资产硬件信息" -#: assets/tasks.py:152 +#: assets/tasks.py:153 msgid "Update asset hardware info: {}" msgstr "更新资产硬件信息: {}" -#: assets/tasks.py:177 +#: assets/tasks.py:178 msgid "Test assets connectivity" msgstr "测试资产可连接性" -#: assets/tasks.py:229 +#: assets/tasks.py:232 msgid "Test assets connectivity: {}" msgstr "测试资产可连接性: {}" -#: assets/tasks.py:271 +#: assets/tasks.py:274 msgid "Test admin user connectivity period: {}" msgstr "定期测试管理账号可连接性: {}" -#: assets/tasks.py:278 +#: assets/tasks.py:281 msgid "Test admin user connectivity: {}" msgstr "测试管理行号可连接性: {}" -#: assets/tasks.py:348 +#: assets/tasks.py:349 msgid "Test system user connectivity: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks.py:355 +#: assets/tasks.py:356 msgid "Test system user connectivity: {} => {}" msgstr "测试系统用户可连接性: {} => {}" -#: assets/tasks.py:368 +#: assets/tasks.py:369 msgid "Test system user connectivity period: {}" msgstr "定期测试系统用户可连接性: {}" -#: assets/tasks.py:469 assets/tasks.py:555 +#: assets/tasks.py:470 assets/tasks.py:556 #: xpack/plugins/change_auth_plan/models.py:522 msgid "The asset {} system platform {} does not support run Ansible tasks" msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" -#: assets/tasks.py:481 +#: assets/tasks.py:482 msgid "" "Push system user task skip, auto push not enable or protocol is not ssh or " "rdp: {}" msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh或rdp: {}" -#: assets/tasks.py:488 +#: assets/tasks.py:489 msgid "For security, do not push user {}" msgstr "为了安全,禁止推送用户 {}" -#: assets/tasks.py:516 assets/tasks.py:530 +#: assets/tasks.py:517 assets/tasks.py:531 msgid "Push system users to assets: {}" msgstr "推送系统用户到入资产: {}" -#: assets/tasks.py:522 +#: assets/tasks.py:523 msgid "Push system users to asset: {} => {}" msgstr "推送系统用户到入资产: {} => {}" -#: assets/tasks.py:612 +#: assets/tasks.py:602 msgid "Test asset user connectivity: {}" msgstr "测试资产用户可连接性: {}" @@ -1440,21 +1430,26 @@ msgstr "关闭" msgid "Datetime" msgstr "日期" -#: assets/templates/assets/_asset_user_list.html:61 +#: assets/templates/assets/_asset_user_list.html:36 +#: assets/templates/assets/asset_list.html:166 +msgid "Test datetime: " +msgstr "测试日期: " + +#: assets/templates/assets/_asset_user_list.html:68 msgid "View" msgstr "查看" -#: assets/templates/assets/_asset_user_list.html:63 +#: assets/templates/assets/_asset_user_list.html:70 #: assets/templates/assets/admin_user_assets.html:61 #: assets/templates/assets/asset_asset_user_list.html:57 #: assets/templates/assets/asset_detail.html:182 -#: assets/templates/assets/system_user_asset.html:63 +#: assets/templates/assets/system_user_assets.html:63 #: assets/templates/assets/system_user_detail.html:151 msgid "Test" msgstr "测试" -#: assets/templates/assets/_asset_user_list.html:64 -#: assets/templates/assets/system_user_asset.html:72 +#: assets/templates/assets/_asset_user_list.html:71 +#: assets/templates/assets/system_user_assets.html:72 #: assets/templates/assets/system_user_detail.html:142 msgid "Push" msgstr "推送" @@ -1528,7 +1523,7 @@ msgid "Asset list of " msgstr "资产列表" #: assets/templates/assets/admin_user_assets.html:52 -#: assets/templates/assets/system_user_asset.html:54 +#: assets/templates/assets/system_user_assets.html:54 #: assets/templates/assets/system_user_detail.html:116 #: perms/templates/perms/asset_permission_detail.html:114 #: perms/templates/perms/remote_app_permission_detail.html:106 @@ -1554,11 +1549,11 @@ msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:211 -#: assets/templates/assets/asset_list.html:682 +#: assets/templates/assets/asset_list.html:688 #: assets/templates/assets/cmd_filter_detail.html:106 -#: assets/templates/assets/system_user_asset.html:100 +#: assets/templates/assets/system_user_assets.html:100 #: assets/templates/assets/system_user_detail.html:182 -#: assets/templates/assets/system_user_list.html:170 +#: assets/templates/assets/system_user_list.html:171 #: authentication/templates/authentication/_mfa_confirm_modal.html:20 #: settings/templates/settings/terminal_setting.html:168 #: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 @@ -1627,12 +1622,12 @@ msgstr "创建管理用户" msgid "Ratio" msgstr "比例" -#: assets/templates/assets/admin_user_list.html:160 -#: assets/templates/assets/admin_user_list.html:191 -#: assets/templates/assets/asset_list.html:492 -#: assets/templates/assets/asset_list.html:529 -#: assets/templates/assets/system_user_list.html:223 -#: assets/templates/assets/system_user_list.html:254 +#: assets/templates/assets/admin_user_list.html:165 +#: assets/templates/assets/admin_user_list.html:196 +#: assets/templates/assets/asset_list.html:498 +#: assets/templates/assets/asset_list.html:535 +#: assets/templates/assets/system_user_list.html:224 +#: assets/templates/assets/system_user_list.html:255 #: users/templates/users/user_group_list.html:163 #: users/templates/users/user_group_list.html:194 #: users/templates/users/user_list.html:158 @@ -1789,28 +1784,28 @@ msgstr "仅显示当前节点资产" msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:229 +#: assets/templates/assets/asset_list.html:235 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/asset_list.html:241 +#: assets/templates/assets/asset_list.html:247 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/asset_list.html:243 +#: assets/templates/assets/asset_list.html:249 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/asset_list.html:314 +#: assets/templates/assets/asset_list.html:320 msgid "Rename success" msgstr "重命名成功" -#: assets/templates/assets/asset_list.html:315 +#: assets/templates/assets/asset_list.html:321 msgid "Rename failed, do not change the root node name" msgstr "重命名失败,不能更改root节点的名称" -#: assets/templates/assets/asset_list.html:676 -#: assets/templates/assets/system_user_list.html:164 +#: assets/templates/assets/asset_list.html:682 +#: assets/templates/assets/system_user_list.html:165 #: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:408 #: users/templates/users/user_detail.html:476 @@ -1820,12 +1815,12 @@ msgstr "重命名失败,不能更改root节点的名称" msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:677 +#: assets/templates/assets/asset_list.html:683 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:680 -#: assets/templates/assets/system_user_list.html:168 +#: assets/templates/assets/asset_list.html:686 +#: assets/templates/assets/system_user_list.html:169 #: settings/templates/settings/terminal_setting.html:166 #: users/templates/users/user_detail.html:386 #: users/templates/users/user_detail.html:412 @@ -1838,16 +1833,16 @@ msgstr "删除选择资产" msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:693 +#: assets/templates/assets/asset_list.html:699 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:694 -#: assets/templates/assets/asset_list.html:698 +#: assets/templates/assets/asset_list.html:700 +#: assets/templates/assets/asset_list.html:704 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:697 +#: assets/templates/assets/asset_list.html:703 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1964,21 +1959,21 @@ msgstr "创建网域" msgid "Create label" msgstr "创建标签" -#: assets/templates/assets/system_user_asset.html:31 +#: assets/templates/assets/system_user_assets.html:31 msgid "Assets of " msgstr "资产" -#: assets/templates/assets/system_user_asset.html:60 +#: assets/templates/assets/system_user_assets.html:60 #: assets/templates/assets/system_user_detail.html:148 msgid "Test assets connective" msgstr "测试资产可连接性" -#: assets/templates/assets/system_user_asset.html:69 +#: assets/templates/assets/system_user_assets.html:69 #: assets/templates/assets/system_user_detail.html:139 msgid "Push system user now" msgstr "立刻推送系统" -#: assets/templates/assets/system_user_asset.html:91 +#: assets/templates/assets/system_user_assets.html:91 msgid "Add to node" msgstr "添加到节点" @@ -2027,20 +2022,20 @@ msgstr "" msgid "Create system user" msgstr "创建系统用户" -#: assets/templates/assets/system_user_list.html:165 +#: assets/templates/assets/system_user_list.html:166 msgid "This will delete the selected System Users !!!" msgstr "删除选择系统用户" -#: assets/templates/assets/system_user_list.html:174 +#: assets/templates/assets/system_user_list.html:175 msgid "System Users Deleted." msgstr "已被删除" -#: assets/templates/assets/system_user_list.html:175 -#: assets/templates/assets/system_user_list.html:180 +#: assets/templates/assets/system_user_list.html:176 +#: assets/templates/assets/system_user_list.html:181 msgid "System Users Delete" msgstr "删除系统用户" -#: assets/templates/assets/system_user_list.html:179 +#: assets/templates/assets/system_user_list.html:180 msgid "System Users Deleting failed." msgstr "系统用户删除失败" @@ -2321,8 +2316,8 @@ msgid "Date" msgstr "日期" #: audits/views.py:85 audits/views.py:129 audits/views.py:166 -#: audits/views.py:211 audits/views.py:243 ops/views/command.py:47 -#: templates/_nav.html:87 templates/_nav_audits.html:22 +#: audits/views.py:211 audits/views.py:243 templates/_nav.html:87 +#: templates/_nav_audits.html:22 msgid "Audits" msgstr "日志审计" @@ -2988,7 +2983,7 @@ msgstr "更新任务内容: {}" #: ops/views/adhoc.py:45 ops/views/adhoc.py:71 ops/views/adhoc.py:85 #: ops/views/adhoc.py:99 ops/views/adhoc.py:113 ops/views/adhoc.py:127 -#: ops/views/adhoc.py:141 ops/views/command.py:72 +#: ops/views/adhoc.py:141 ops/views/command.py:47 ops/views/command.py:72 msgid "Ops" msgstr "作业中心" @@ -3008,7 +3003,7 @@ msgstr "命令执行列表" msgid "Command execution" msgstr "命令执行" -#: orgs/mixins.py:85 orgs/mixins.py:222 orgs/models.py:24 +#: orgs/mixins.py:82 orgs/mixins.py:219 orgs/models.py:24 msgid "Organization" msgstr "组织" @@ -5768,7 +5763,7 @@ msgid "Interface settings" msgstr "界面设置" #: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25 +#: xpack/plugins/interface/views.py:25 msgid "Interface setting" msgstr "界面设置" @@ -5791,6 +5786,12 @@ msgstr "恢复默认成功!" msgid "Restore default failed." msgstr "恢复默认失败!" +#: xpack/plugins/interface/views.py:24 +#, fuzzy +#| msgid "Interval" +msgid "Interface" +msgstr "间隔" + #: xpack/plugins/interface/views.py:51 msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" @@ -5894,9 +5895,7 @@ msgstr "无效的许可证" msgid "Admin" msgstr "管理员" -#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 -#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60 -#: xpack/plugins/orgs/views.py:77 +#: xpack/plugins/orgs/meta.py:8 msgid "Organizations" msgstr "组织管理" @@ -5913,10 +5912,19 @@ msgstr "添加管理员" msgid "Create organization " msgstr "创建组织" +#: xpack/plugins/orgs/views.py:26 +msgid "Org" +msgstr "" + #: xpack/plugins/orgs/views.py:27 msgid "Org list" msgstr "组织列表" +#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60 +#: xpack/plugins/orgs/views.py:77 +msgid "Orgs" +msgstr "" + #: xpack/plugins/orgs/views.py:44 msgid "Create org" msgstr "创建组织" @@ -5925,8 +5933,8 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" -#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:22 -#: xpack/plugins/vault/views.py:37 +#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 +#: xpack/plugins/vault/views.py:38 msgid "Vault" msgstr "密码匣子" @@ -5934,14 +5942,20 @@ msgstr "密码匣子" msgid "Import vault" msgstr "导入密码" -#: xpack/plugins/vault/views.py:23 +#: xpack/plugins/vault/views.py:24 msgid "vault list" msgstr "密码匣子" -#: xpack/plugins/vault/views.py:38 +#: xpack/plugins/vault/views.py:39 msgid "vault create" msgstr "创建" +#~ msgid "Unreachable assets" +#~ msgstr "不可达资产" + +#~ msgid "Reachable assets" +#~ msgstr "可连接资产" + #~ msgid "User does not exist" #~ msgstr "用户不存在" From 824ba433f6cd5cb052d3f6bd9724976bdef835ec Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 24 Jun 2019 22:16:39 +0800 Subject: [PATCH 07/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=A1=A8?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset_user.py | 2 - .../migrations/0032_auto_20190624_2108.py | 75 +++++++++++++++++++ .../migrations/0033_auto_20190624_2108.py | 74 ++++++++++++++++++ apps/assets/models/base.py | 49 ++---------- apps/common/fields/model.py | 19 ++++- apps/common/tests.py | 13 ++++ apps/locale/zh/LC_MESSAGES/django.po | 12 +-- 7 files changed, 193 insertions(+), 51 deletions(-) create mode 100644 apps/assets/migrations/0032_auto_20190624_2108.py create mode 100644 apps/assets/migrations/0033_auto_20190624_2108.py diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index c3dc518fa..7bbca679d 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # -import time - from rest_framework.response import Response from rest_framework import viewsets, status, generics from rest_framework.pagination import LimitOffsetPagination diff --git a/apps/assets/migrations/0032_auto_20190624_2108.py b/apps/assets/migrations/0032_auto_20190624_2108.py new file mode 100644 index 000000000..441f13cdb --- /dev/null +++ b/apps/assets/migrations/0032_auto_20190624_2108.py @@ -0,0 +1,75 @@ +# Generated by Django 2.1.7 on 2019-06-24 13:08 + +import assets.models.utils +import common.fields.model +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0031_auto_20190621_1332'), + ] + + operations = [ + migrations.AlterField( + model_name='adminuser', + name='_password', + field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + ), + migrations.AlterField( + model_name='adminuser', + name='_private_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + ), + migrations.AlterField( + model_name='adminuser', + name='_public_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + ), + migrations.AlterField( + model_name='authbook', + name='_password', + field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + ), + migrations.AlterField( + model_name='authbook', + name='_private_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + ), + migrations.AlterField( + model_name='authbook', + name='_public_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + ), + migrations.AlterField( + model_name='gateway', + name='_password', + field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + ), + migrations.AlterField( + model_name='gateway', + name='_private_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + ), + migrations.AlterField( + model_name='gateway', + name='_public_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + ), + migrations.AlterField( + model_name='systemuser', + name='_password', + field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + ), + migrations.AlterField( + model_name='systemuser', + name='_private_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + ), + migrations.AlterField( + model_name='systemuser', + name='_public_key', + field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + ), + ] diff --git a/apps/assets/migrations/0033_auto_20190624_2108.py b/apps/assets/migrations/0033_auto_20190624_2108.py new file mode 100644 index 000000000..c9ac245d0 --- /dev/null +++ b/apps/assets/migrations/0033_auto_20190624_2108.py @@ -0,0 +1,74 @@ +# Generated by Django 2.1.7 on 2019-06-24 13:08 + +import common.fields.model +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0032_auto_20190624_2108'), + ] + + operations = [ + migrations.RenameField( + model_name='adminuser', + old_name='_private_key', + new_name='private_key', + ), + migrations.RenameField( + model_name='adminuser', + old_name='_public_key', + new_name='public_key', + ), + migrations.RenameField( + model_name='authbook', + old_name='_private_key', + new_name='private_key', + ), + migrations.RenameField( + model_name='authbook', + old_name='_public_key', + new_name='public_key', + ), + migrations.RenameField( + model_name='gateway', + old_name='_private_key', + new_name='private_key', + ), + migrations.RenameField( + model_name='gateway', + old_name='_public_key', + new_name='public_key', + ), + migrations.RenameField( + model_name='systemuser', + old_name='_private_key', + new_name='private_key', + ), + migrations.RenameField( + model_name='systemuser', + old_name='_public_key', + new_name='public_key', + ), + migrations.RenameField( + model_name='adminuser', + old_name='_password', + new_name='password', + ), + migrations.RenameField( + model_name='authbook', + old_name='_password', + new_name='password', + ), + migrations.RenameField( + model_name='gateway', + old_name='_password', + new_name='password', + ), + migrations.RenameField( + model_name='systemuser', + old_name='_password', + new_name='password', + ), + ] diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index b8ae16f1e..6c089e14b 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -26,9 +26,9 @@ class AssetUser(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric]) - _password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) - _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) - _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key')) + password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) + public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) @@ -38,28 +38,6 @@ class AssetUser(OrgModelMixin): _prefer = "system_user" - @property - def password(self): - if self._password: - return signer.unsign(self._password) - else: - return None - - @password.setter - def password(self, password_raw): - # raise AttributeError("Using set_auth do that") - self._password = signer.sign(password_raw) - - @property - def private_key(self): - if self._private_key: - return signer.unsign(self._private_key) - - @private_key.setter - def private_key(self, private_key_raw): - # raise AttributeError("Using set_auth do that") - self._private_key = signer.sign(private_key_raw) - @property def private_key_obj(self): if self._private_key: @@ -82,19 +60,6 @@ class AssetUser(OrgModelMixin): os.chmod(key_path, 0o400) return key_path - @property - def public_key(self): - key = signer.unsign(self._public_key) - if key: - return key - else: - return None - - @public_key.setter - def public_key(self, public_key_raw): - # raise AttributeError("Using set_auth do that") - self._public_key = signer.sign(public_key_raw) - @property def public_key_obj(self): if self.public_key: @@ -192,9 +157,9 @@ class AssetUser(OrgModelMixin): self.private_key = other.private_key def clear_auth(self): - self._password = '' - self._private_key = '' - self._public_key = '' + self.password = '' + self.private_key = '' + self.public_key = '' self.save() def auto_gen_auth(self): @@ -231,7 +196,7 @@ class AssetUser(OrgModelMixin): from . import AuthBook fields = [ 'name', 'username', 'comment', 'org_id', - '_password', '_private_key', '_public_key', + 'password', 'private_key', 'public_key', 'date_created', 'date_updated', 'created_by' ] i = self.generate_id_with_asset(asset) diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index c2bb1e0be..e1bd4e1e7 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -124,10 +124,27 @@ class EncryptTextField(EncryptMixin, models.TextField): class EncryptCharField(EncryptMixin, models.CharField): + @staticmethod + def change_max_length(kwargs): + kwargs.setdefault('max_length', 1024) + max_length = kwargs.get('max_length') + if max_length < 129: + max_length = 128 + max_length = max_length * 2 + kwargs['max_length'] = max_length + def __init__(self, *args, **kwargs): - kwargs['max_length'] = 2048 + self.change_max_length(kwargs) super().__init__(*args, **kwargs) + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + max_length = kwargs.pop('max_length') + if max_length > 255: + max_length = max_length // 2 + kwargs['max_length'] = max_length + return name, path, args, kwargs + class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField): pass diff --git a/apps/common/tests.py b/apps/common/tests.py index 7ce503c2d..a9edb8f69 100644 --- a/apps/common/tests.py +++ b/apps/common/tests.py @@ -1,3 +1,16 @@ from django.test import TestCase # Create your tests here. + +from .utils import random_string, get_signer + + +def test_signer_len(): + signer = get_signer() + results = {} + for i in range(1, 4096): + s = random_string(i) + encs = signer.sign(s) + results[i] = (len(encs)/len(s)) + results = sorted(results.items(), key=lambda x: x[1], reverse=True) + print(results) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index df9825fef..6183721f3 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-06-24 20:17+0800\n" +"POT-Creation-Date: 2019-06-24 21:38+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -411,7 +411,7 @@ msgstr "详情" #: applications/templates/applications/remote_app_detail.html:21 #: applications/templates/applications/remote_app_list.html:56 -#: assets/templates/assets/_asset_user_list.html:69 +#: assets/templates/assets/_asset_user_list.html:70 #: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_list.html:29 #: assets/templates/assets/admin_user_list.html:114 @@ -1435,11 +1435,11 @@ msgstr "日期" msgid "Test datetime: " msgstr "测试日期: " -#: assets/templates/assets/_asset_user_list.html:68 +#: assets/templates/assets/_asset_user_list.html:69 msgid "View" msgstr "查看" -#: assets/templates/assets/_asset_user_list.html:70 +#: assets/templates/assets/_asset_user_list.html:71 #: assets/templates/assets/admin_user_assets.html:61 #: assets/templates/assets/asset_asset_user_list.html:57 #: assets/templates/assets/asset_detail.html:182 @@ -1448,7 +1448,7 @@ msgstr "查看" msgid "Test" msgstr "测试" -#: assets/templates/assets/_asset_user_list.html:71 +#: assets/templates/assets/_asset_user_list.html:72 #: assets/templates/assets/system_user_assets.html:72 #: assets/templates/assets/system_user_detail.html:142 msgid "Push" @@ -1632,7 +1632,7 @@ msgstr "比例" #: users/templates/users/user_group_list.html:194 #: users/templates/users/user_list.html:158 #: users/templates/users/user_list.html:190 -#: xpack/plugins/vault/templates/vault/vault.html:223 +#: xpack/plugins/vault/templates/vault/vault.html:224 msgid "Please select file" msgstr "选择文件" From e08d542c87c288f8e396c5abf4c369ba61de9550 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Jun 2019 11:22:17 +0800 Subject: [PATCH 08/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9users=20publ?= =?UTF-8?q?ic=5Fkey=E7=AD=89=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/base.py | 12 +++--- apps/users/api/user.py | 2 +- .../migrations/0021_auto_20190625_1104.py | 29 +++++++++++++++ .../migrations/0022_auto_20190625_1105.py | 28 ++++++++++++++ apps/users/models/user.py | 37 ++++--------------- 5 files changed, 71 insertions(+), 37 deletions(-) create mode 100644 apps/users/migrations/0021_auto_20190625_1104.py create mode 100644 apps/users/migrations/0022_auto_20190625_1105.py diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 6c089e14b..1ad6d508d 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -76,14 +76,14 @@ class AssetUser(OrgModelMixin): def set_auth(self, password=None, private_key=None, public_key=None): update_fields = [] if password: - self._password = signer.sign(password) - update_fields.append('_password') + self.password = password + update_fields.append('password') if private_key: - self._private_key = signer.sign(private_key) - update_fields.append('_private_key') + self.private_key = private_key + update_fields.append('private_key') if public_key: - self._public_key = signer.sign(public_key) - update_fields.append('_public_key') + self.public_key = public_key + update_fields.append('public_key') if update_fields: self.save(update_fields=update_fields) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 116819770..88be7d934 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -54,7 +54,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): self.send_created_signal(users) def get_queryset(self): - queryset = current_org.get_org_users() + queryset = current_org.get_org_users().prefetch_related('groups') return queryset def get_permissions(self): diff --git a/apps/users/migrations/0021_auto_20190625_1104.py b/apps/users/migrations/0021_auto_20190625_1104.py new file mode 100644 index 000000000..651b9632c --- /dev/null +++ b/apps/users/migrations/0021_auto_20190625_1104.py @@ -0,0 +1,29 @@ +# Generated by Django 2.1.7 on 2019-06-25 03:04 + +import common.fields.model +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0020_auto_20190612_1825'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='_otp_secret_key', + field=common.fields.model.EncryptCharField(blank=True, max_length=128, null=True), + ), + migrations.AlterField( + model_name='user', + name='_private_key', + field=common.fields.model.EncryptTextField(blank=True, max_length=5000, verbose_name='Private key'), + ), + migrations.AlterField( + model_name='user', + name='_public_key', + field=common.fields.model.EncryptTextField(blank=True, max_length=5000, verbose_name='Public key'), + ), + ] diff --git a/apps/users/migrations/0022_auto_20190625_1105.py b/apps/users/migrations/0022_auto_20190625_1105.py new file mode 100644 index 000000000..1735e8376 --- /dev/null +++ b/apps/users/migrations/0022_auto_20190625_1105.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.7 on 2019-06-25 03:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0021_auto_20190625_1104'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='_otp_secret_key', + new_name='otp_secret_key', + ), + migrations.RenameField( + model_name='user', + old_name='_private_key', + new_name='private_key', + ), + migrations.RenameField( + model_name='user', + old_name='_public_key', + new_name='public_key', + ), + ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index a0ceb6c51..bd15514f9 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -17,6 +17,7 @@ from django.utils import timezone from django.shortcuts import reverse from common.utils import get_signer, date_expired_default, get_logger +from common import fields __all__ = ['User'] @@ -84,12 +85,12 @@ class User(AbstractUser): otp_level = models.SmallIntegerField( default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('MFA') ) - _otp_secret_key = models.CharField(max_length=128, blank=True, null=True) + otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True) # Todo: Auto generate key, let user download - _private_key = models.CharField( + private_key = fields.EncryptTextField( max_length=5000, blank=True, verbose_name=_('Private key') ) - _public_key = models.CharField( + public_key = fields.EncryptTextField( max_length=5000, blank=True, verbose_name=_('Public key') ) comment = models.TextField( @@ -141,14 +142,6 @@ class User(AbstractUser): def can_update_password(self): return self.is_local - @property - def otp_secret_key(self): - return signer.unsign(self._otp_secret_key) - - @otp_secret_key.setter - def otp_secret_key(self, item): - self._otp_secret_key = signer.sign(item) - def check_otp(self, code): from ..utils import check_otp_code return check_otp_code(self.otp_secret_key, code) @@ -161,13 +154,13 @@ class User(AbstractUser): Check if the user's ssh public key is valid. This function is used in base.html. """ - if self._public_key: + if self.public_key: return True return False @property def groups_display(self): - return ' '.join(self.groups.all().values_list('name', flat=True)) + return ' '.join([group.name for group in self.groups.all()]) @property def role_display(self): @@ -190,22 +183,6 @@ class User(AbstractUser): return True return False - @property - def private_key(self): - return signer.unsign(self._private_key) - - @private_key.setter - def private_key(self, private_key_raw): - self._private_key = signer.sign(private_key_raw) - - @property - def public_key(self): - return signer.unsign(self._public_key) - - @public_key.setter - def public_key(self, public_key_raw): - self._public_key = signer.sign(public_key_raw) - @property def public_key_obj(self): class PubKey(object): @@ -364,7 +341,7 @@ class User(AbstractUser): def generate_reset_token(self): letter = string.ascii_letters + string.digits - token =''.join([random.choice(letter) for _ in range(50)]) + token = ''.join([random.choice(letter) for _ in range(50)]) self.set_cache(token) return token From f10a7a75ae75e60e63175cbd4d16fc87fd9d6e38 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Jun 2019 11:51:25 +0800 Subject: [PATCH 09/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0021_auto_20190625_1104.py | 11 +++++--- apps/users/models/user.py | 25 +++---------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/apps/users/migrations/0021_auto_20190625_1104.py b/apps/users/migrations/0021_auto_20190625_1104.py index 651b9632c..f14e72d02 100644 --- a/apps/users/migrations/0021_auto_20190625_1104.py +++ b/apps/users/migrations/0021_auto_20190625_1104.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.7 on 2019-06-25 03:04 import common.fields.model -from django.db import migrations +from django.db import migrations, models class Migration(migrations.Migration): @@ -19,11 +19,16 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='_private_key', - field=common.fields.model.EncryptTextField(blank=True, max_length=5000, verbose_name='Private key'), + field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='Private key'), ), migrations.AlterField( model_name='user', name='_public_key', - field=common.fields.model.EncryptTextField(blank=True, max_length=5000, verbose_name='Public key'), + field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='Public key'), + ), + migrations.AlterField( + model_name='user', + name='comment', + field=models.TextField(blank=True, null=True, verbose_name='Comment'), ), ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index bd15514f9..2339c4cf6 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -88,13 +88,13 @@ class User(AbstractUser): otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True) # Todo: Auto generate key, let user download private_key = fields.EncryptTextField( - max_length=5000, blank=True, verbose_name=_('Private key') + blank=True, null=True, verbose_name=_('Private key') ) public_key = fields.EncryptTextField( - max_length=5000, blank=True, verbose_name=_('Public key') + blank=True, null=True, verbose_name=_('Public key') ) comment = models.TextField( - max_length=200, blank=True, verbose_name=_('Comment') + blank=True, null=True, verbose_name=_('Comment') ) is_first_login = models.BooleanField(default=True) date_expired = models.DateTimeField( @@ -276,7 +276,6 @@ class User(AbstractUser): self.role = 'Admin' self.is_active = True super().save(*args, **kwargs) - self.expire_user_cache() @property def private_token(self): @@ -425,26 +424,8 @@ class User(AbstractUser): def delete(self, using=None, keep_parents=False): if self.pk == 1 or self.username == 'admin': return - self.expire_user_cache() return super(User, self).delete() - def expire_user_cache(self): - key = self.user_cache_key_prefix.format(self.id) - cache.delete(key) - - @classmethod - def get_user_or_from_cache(cls, uid): - key = cls.user_cache_key_prefix.format(uid) - user = cache.get(key) - if user: - return user - try: - user = cls.objects.get(id=uid) - cache.set(key, user, 3600) - except cls.DoesNotExist: - user = None - return user - class Meta: ordering = ['username'] verbose_name = _("User") From e8ebc94191193602c1da2408463a6e180fad2899 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Jun 2019 14:32:25 +0800 Subject: [PATCH 10/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9assets=20use?= =?UTF-8?q?rs=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/base.py | 8 +- apps/assets/serializers/admin_user.py | 10 +- apps/assets/serializers/asset_user.py | 29 +---- apps/assets/serializers/system_user.py | 14 +-- apps/common/local.py | 9 ++ apps/common/signals_handlers.py | 8 +- apps/common/utils/common.py | 119 ------------------- apps/jumpserver/utils.py | 16 +-- apps/orgs/utils.py | 21 ++-- apps/users/api/user.py | 10 +- apps/users/serializers/v1.py | 32 ++++- apps/users/templates/users/user_profile.html | 30 +---- 12 files changed, 88 insertions(+), 218 deletions(-) create mode 100644 apps/common/local.py diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 1ad6d508d..aa6e639a6 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -40,9 +40,8 @@ class AssetUser(OrgModelMixin): @property def private_key_obj(self): - if self._private_key: - key_str = signer.unsign(self._private_key) - return ssh_key_string_to_obj(key_str, password=self.password) + if self.private_key: + return ssh_key_string_to_obj(self.private_key, password=self.password) else: return None @@ -52,8 +51,7 @@ class AssetUser(OrgModelMixin): return None project_dir = settings.PROJECT_DIR tmp_dir = os.path.join(project_dir, 'tmp') - key_str = signer.unsign(self._private_key) - key_name = '.' + md5(key_str.encode('utf-8')).hexdigest() + key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest() key_path = os.path.join(tmp_dir, key_name) if not os.path.exists(key_path): self.private_key_obj.write_private_key_file(key_path) diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index 581e810b7..dbbb16406 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -15,20 +15,20 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer): """ 管理用户 """ - password = serializers.CharField( - required=False, write_only=True, label=_('Password') - ) class Meta: list_serializer_class = AdaptedBulkListSerializer model = AdminUser fields = [ - 'id', 'name', 'username', 'password', 'comment', - 'connectivity_amount', 'assets_amount', + 'id', 'name', 'username', 'password', 'private_key', 'public_key', + 'comment', 'connectivity_amount', 'assets_amount', 'date_created', 'date_updated', 'created_by', ] extra_kwargs = { + 'password': {"write_only": True}, + 'private_key': {"write_only": True}, + 'public_key': {"write_only": True}, 'date_created': {'read_only': True}, 'date_updated': {'read_only': True}, 'created_by': {'read_only': True}, diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py index a64e71f45..a111f1993 100644 --- a/apps/assets/serializers/asset_user.py +++ b/apps/assets/serializers/asset_user.py @@ -29,18 +29,6 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer): ip = serializers.CharField(read_only=True, label=_("IP")) connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) - password = serializers.CharField( - max_length=256, allow_blank=True, allow_null=True, write_only=True, - required=False, label=_('Password') - ) - public_key = serializers.CharField( - max_length=4096, allow_blank=True, allow_null=True, write_only=True, - required=False, label=_('Public key') - ) - private_key = serializers.CharField( - max_length=4096, allow_blank=True, allow_null=True, write_only=True, - required=False, label=_('Private key') - ) backend = serializers.CharField(read_only=True, label=_("Backend")) class Meta: @@ -57,6 +45,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer): ] extra_kwargs = { 'username': {'required': True}, + 'password': {'write_only': True}, + 'private_key': {'write_only': True}, + 'public_key': {'write_only': True}, } def validate_private_key(self, key): @@ -67,17 +58,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer): return key def create(self, validated_data): - kwargs = { - 'name': validated_data.get('username'), - 'username': validated_data.get('username'), - 'asset': validated_data.get('asset'), - 'comment': validated_data.get('comment', ''), - 'org_id': validated_data.get('org_id', ''), - 'password': validated_data.get('password'), - 'public_key': validated_data.get('public_key'), - 'private_key': validated_data.get('private_key') - } - instance = AssetUserManager.create(**kwargs) + if not validated_data.get("name") and validated_data.get("username"): + validated_data["name"] = validated_data["username"] + instance = AssetUserManager.create(**validated_data) return instance diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 1931a5841..d09c3df9e 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -12,20 +12,20 @@ class SystemUserSerializer(BulkOrgResourceModelSerializer): """ 系统用户 """ - password = serializers.CharField( - required=False, write_only=True, label=_('Password') - ) class Meta: model = SystemUser list_serializer_class = AdaptedBulkListSerializer fields = [ - 'id', 'name', 'username', 'login_mode', 'login_mode_display', - 'priority', 'protocol', 'auto_push', 'password', - 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', 'assets', - 'assets_amount', 'connectivity_amount' + 'id', 'name', 'username', 'password', 'public_key', 'private_key', + 'login_mode', 'login_mode_display', 'priority', 'protocol', + 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', + 'assets', 'assets_amount', 'connectivity_amount' ] extra_kwargs = { + 'password': {"write_only": True}, + 'public_key': {"write_only": True}, + 'private_key': {"write_only": True}, 'assets_amount': {'label': _('Asset')}, 'connectivity_amount': {'label': _('Connectivity')}, 'login_mode_display': {'label': _('Login mode display')}, diff --git a/apps/common/local.py b/apps/common/local.py new file mode 100644 index 000000000..37a2ccb0b --- /dev/null +++ b/apps/common/local.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# +from werkzeug.local import Local + +thread_local = Local() + + +def _find(attr): + return getattr(thread_local, attr, None) diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py index 01adce9bb..cf8cc9a78 100644 --- a/apps/common/signals_handlers.py +++ b/apps/common/signals_handlers.py @@ -3,12 +3,13 @@ import re from collections import defaultdict from django.conf import settings - +from django.dispatch import receiver from django.core.signals import request_finished from django.db import connection from .utils import get_logger +from .local import thread_local logger = get_logger(__file__) pattern = re.compile(r'FROM `(\w+)`') @@ -50,6 +51,11 @@ def on_request_finished_logging_db_query(sender, **kwargs): ) +@receiver(request_finished) +def on_request_finished_release_local(sender, **kwargs): + thread_local.__release_local__() + + if settings.DEBUG: request_finished.connect(on_request_finished_logging_db_query) diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 6d79cfa6f..c241ff5ec 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -176,125 +176,6 @@ def with_cache(func): return wrapper -class LocalProxy(object): - - """ - Copy from werkzeug.local.LocalProxy - """ - __slots__ = ('__local', '__dict__', '__name__', '__wrapped__') - - def __init__(self, local, name=None): - object.__setattr__(self, '_LocalProxy__local', local) - object.__setattr__(self, '__name__', name) - if callable(local) and not hasattr(local, '__release_local__'): - # "local" is a callable that is not an instance of Local or - # LocalManager: mark it as a wrapped function. - object.__setattr__(self, '__wrapped__', local) - - def _get_current_object(self): - """Return the current object. This is useful if you want the real - object behind the proxy at a time for performance reasons or because - you want to pass the object into a different context. - """ - if not hasattr(self.__local, '__release_local__'): - return self.__local() - try: - return getattr(self.__local, self.__name__) - except AttributeError: - raise RuntimeError('no object bound to %s' % self.__name__) - - @property - def __dict__(self): - try: - return self._get_current_object().__dict__ - except RuntimeError: - raise AttributeError('__dict__') - - def __repr__(self): - try: - obj = self._get_current_object() - except RuntimeError: - return '<%s unbound>' % self.__class__.__name__ - return repr(obj) - - def __bool__(self): - try: - return bool(self._get_current_object()) - except RuntimeError: - return False - - def __dir__(self): - try: - return dir(self._get_current_object()) - except RuntimeError: - return [] - - def __getattr__(self, name): - if name == '__members__': - return dir(self._get_current_object()) - return getattr(self._get_current_object(), name) - - def __setitem__(self, key, value): - self._get_current_object()[key] = value - - def __delitem__(self, key): - del self._get_current_object()[key] - - __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v) - __delattr__ = lambda x, n: delattr(x._get_current_object(), n) - __str__ = lambda x: str(x._get_current_object()) - __lt__ = lambda x, o: x._get_current_object() < o - __le__ = lambda x, o: x._get_current_object() <= o - __eq__ = lambda x, o: x._get_current_object() == o - __ne__ = lambda x, o: x._get_current_object() != o - __gt__ = lambda x, o: x._get_current_object() > o - __ge__ = lambda x, o: x._get_current_object() >= o - __cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa - __hash__ = lambda x: hash(x._get_current_object()) - __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw) - __len__ = lambda x: len(x._get_current_object()) - __getitem__ = lambda x, i: x._get_current_object()[i] - __iter__ = lambda x: iter(x._get_current_object()) - __contains__ = lambda x, i: i in x._get_current_object() - __add__ = lambda x, o: x._get_current_object() + o - __sub__ = lambda x, o: x._get_current_object() - o - __mul__ = lambda x, o: x._get_current_object() * o - __floordiv__ = lambda x, o: x._get_current_object() // o - __mod__ = lambda x, o: x._get_current_object() % o - __divmod__ = lambda x, o: x._get_current_object().__divmod__(o) - __pow__ = lambda x, o: x._get_current_object() ** o - __lshift__ = lambda x, o: x._get_current_object() << o - __rshift__ = lambda x, o: x._get_current_object() >> o - __and__ = lambda x, o: x._get_current_object() & o - __xor__ = lambda x, o: x._get_current_object() ^ o - __or__ = lambda x, o: x._get_current_object() | o - __div__ = lambda x, o: x._get_current_object().__div__(o) - __truediv__ = lambda x, o: x._get_current_object().__truediv__(o) - __neg__ = lambda x: -(x._get_current_object()) - __pos__ = lambda x: +(x._get_current_object()) - __abs__ = lambda x: abs(x._get_current_object()) - __invert__ = lambda x: ~(x._get_current_object()) - __complex__ = lambda x: complex(x._get_current_object()) - __int__ = lambda x: int(x._get_current_object()) - __float__ = lambda x: float(x._get_current_object()) - __oct__ = lambda x: oct(x._get_current_object()) - __hex__ = lambda x: hex(x._get_current_object()) - __index__ = lambda x: x._get_current_object().__index__() - __coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o) - __enter__ = lambda x: x._get_current_object().__enter__() - __exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw) - __radd__ = lambda x, o: o + x._get_current_object() - __rsub__ = lambda x, o: o - x._get_current_object() - __rmul__ = lambda x, o: o * x._get_current_object() - __rdiv__ = lambda x, o: o / x._get_current_object() - __rtruediv__ = __rdiv__ - __rfloordiv__ = lambda x, o: o // x._get_current_object() - __rmod__ = lambda x, o: o % x._get_current_object() - __rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o) - __copy__ = lambda x: copy.copy(x._get_current_object()) - __deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo) - - def random_string(length): import string import random diff --git a/apps/jumpserver/utils.py b/apps/jumpserver/utils.py index 0f7f28eb9..aa808d2f2 100644 --- a/apps/jumpserver/utils.py +++ b/apps/jumpserver/utils.py @@ -1,24 +1,16 @@ # -*- coding: utf-8 -*- # - from functools import partial - -from common.utils import LocalProxy - -try: - from threading import local -except ImportError: - from django.utils._threading_local import local - -_thread_locals = local() +from werkzeug.local import LocalProxy +from common.local import thread_local def set_current_request(request): - setattr(_thread_locals, 'current_request', request) + setattr(thread_local, 'current_request', request) def _find(attr): - return getattr(_thread_locals, attr, None) + return getattr(thread_local, attr, None) def get_current_request(): diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 808536984..09bf081ae 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -1,15 +1,11 @@ # -*- coding: utf-8 -*- # -from functools import partial -from werkzeug.local import Local +from werkzeug.local import LocalProxy -from common.utils import LocalProxy +from common.local import thread_local from .models import Organization -_thread_locals = Local() - - def get_org_from_request(request): oid = request.session.get("oid") if not oid: @@ -19,7 +15,7 @@ def get_org_from_request(request): def set_current_org(org): - setattr(_thread_locals, 'current_org', org) + setattr(thread_local, 'current_org', org.id) def set_to_default_org(): @@ -31,17 +27,18 @@ def set_to_root_org(): def _find(attr): - return getattr(_thread_locals, attr, None) + return getattr(thread_local, attr, None) def get_current_org(): - return _find('current_org') + org_id = _find('current_org') + org = Organization.get_instance(org_id) + return org def get_current_org_id(): - org = get_current_org() - org_id = str(org.id) if org.is_real() else '' + org_id = _find('current_org') return org_id -current_org = LocalProxy(partial(_find, 'current_org')) +current_org = LocalProxy(get_current_org) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 88be7d934..1ccc9e3c3 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -48,9 +48,10 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): def perform_create(self, serializer): users = serializer.save() - for user in users: - if current_org and current_org.is_real(): - user.orgs.add(current_org.id) + if isinstance(users, User): + users = [users] + if current_org and current_org.is_real(): + current_org.users.add(*users) self.send_created_signal(users) def get_queryset(self): @@ -174,6 +175,7 @@ class UserResetPKApi(generics.UpdateAPIView): send_reset_ssh_key_mail(user) +# 废弃 class UserUpdatePKApi(generics.UpdateAPIView): queryset = User.objects.all() serializer_class = UserPKUpdateSerializer @@ -181,7 +183,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): def perform_update(self, serializer): user = self.get_object() - user.public_key = serializer.validated_data['_public_key'] + user.public_key = serializer.validated_data['public_key'] user.save() diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index 672d7b62a..b97f91efa 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -19,13 +19,16 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): model = User list_serializer_class = AdaptedBulkListSerializer fields = [ - 'id', 'name', 'username', 'email', 'groups', 'groups_display', + 'id', 'name', 'username', 'password', 'email', 'public_key', + 'groups', 'groups_display', 'role', 'role_display', 'wechat', 'phone', 'otp_level', 'comment', 'source', 'source_display', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', 'date_password_last_updated', 'date_expired', 'avatar_url', ] extra_kwargs = { + 'password': {'write_only': True}, + 'public_key': {'write_only': True}, 'groups_display': {'label': _('Groups name')}, 'source_display': {'label': _('Source name')}, 'is_first_login': {'label': _('Is first login'), 'read_only': True}, @@ -36,14 +39,37 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'created_by': {'read_only': True}, 'source': {'read_only': True} } + @staticmethod + def validate_password(value): + from ..utils import check_password_rules + if not check_password_rules(value): + msg = _('Password does not match security rules') + raise serializers.ValidationError(msg) + return value + + @staticmethod + def change_password_to_raw(validated_data): + password = validated_data.pop('password', None) + if password: + validated_data['password_raw'] = password + return validated_data + + def create(self, validated_data): + validated_data = self.change_password_to_raw(validated_data) + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data = self.change_password_to_raw(validated_data) + return super().update(instance, validated_data) + class UserPKUpdateSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', '_public_key'] + fields = ['id', 'public_key'] @staticmethod - def validate__public_key(value): + def validate_public_key(value): if not validate_ssh_public_key(value): raise serializers.ValidationError(_('Not a valid ssh public key')) return value diff --git a/apps/users/templates/users/user_profile.html b/apps/users/templates/users/user_profile.html index 49478de60..7a06df4c9 100644 --- a/apps/users/templates/users/user_profile.html +++ b/apps/users/templates/users/user_profile.html @@ -217,33 +217,9 @@ {% endblock %} {% block custom_foot_js %} {% endblock %} From b064be3ec0c368c87051f76dada875f9396056cf Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 1 Jul 2019 22:09:04 +0800 Subject: [PATCH 21/53] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=A1=B5=E9=9D=A2=E4=B8=8D=E8=B5=B0cache=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/user_asset_list.html | 2 +- apps/perms/api/user_permission.py | 11 +++++------ apps/users/templates/users/user_granted_asset.html | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index 7bd9331c5..23d0b34ed 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -151,7 +151,7 @@ function initTree() { } $(document).ready(function () { - {#initTree();#} + initTree(); initTable(); }).on('click', '.labels li', function () { var val = $(this).text(); diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index d692bd47c..5117c8098 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -58,7 +58,11 @@ class UserPermissionCacheMixin: return None def get_request_md5(self): - full_path = self.request.get_full_path() + path = self.request.path + query = {k: v for k, v in self.request.GET.items()} + query.pop("_", None) + query = "&".join(["{}={}".format(k, v) for k, v in query.items()]) + full_path = "{}?{}".format(path, query) return md5(full_path.encode()).hexdigest() def get_meta_cache_id(self): @@ -83,9 +87,7 @@ class UserPermissionCacheMixin: return None # 从响应缓冲里获取响应 key = self.get_response_key() - print("response key: {}".format(key)) data = cache.get(key) - print(data) if not data: logger.debug("Not get response from cache: {}".format(key)) return None @@ -94,7 +96,6 @@ class UserPermissionCacheMixin: return response def expire_response_cache(self): - print("Expire cache") obj_id = self.get_object_id() expire_cache_id = '{}_{}'.format(obj_id, '*') key = self.RESP_CACHE_KEY.format(expire_cache_id) @@ -109,7 +110,6 @@ class UserPermissionCacheMixin: key = self.get_response_key() cache.set(key, response.data, self.CACHE_TIME) logger.debug("Set response to cache: {}".format(key)) - print(self.CACHE_TIME) def get(self, request, *args, **kwargs): self.cache_policy = request.GET.get('cache_policy', '0') @@ -256,7 +256,6 @@ class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView) return user def get_queryset(self): - print("Call get queryset") queryset = [] self.show_assets = self.request.query_params.get('show_assets', '1') == '1' self.system_user_id = self.request.query_params.get('system_user') diff --git a/apps/users/templates/users/user_granted_asset.html b/apps/users/templates/users/user_granted_asset.html index ad030bba1..41aea998c 100644 --- a/apps/users/templates/users/user_granted_asset.html +++ b/apps/users/templates/users/user_granted_asset.html @@ -145,7 +145,7 @@ function initTree() { $(document).ready(function () { initTree(); - {#initTable();#} + initTable(); }); {% endblock %} From e43da3d6e18754786f3700fcce3fb1d708c41be1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 10:51:48 +0800 Subject: [PATCH 22/53] ipython --- apps/perms/api/user_permission.py | 25 ++++++++++------------- apps/perms/serializers/user_permission.py | 7 ++++++- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 5117c8098..73aba47f3 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -8,7 +8,7 @@ from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response from rest_framework.generics import ( - ListAPIView, get_object_or_404, + ListAPIView, get_object_or_404, GenericAPIView, RetrieveAPIView ) from rest_framework.pagination import LimitOffsetPagination @@ -433,13 +433,14 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): return Response({'msg': True}, status=200) -class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView): +class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView): permission_classes = (IsOrgAdminOrAppUser,) + serializers_class = serializers.ActionsSerializer - def get(self, request, *args, **kwargs): - user_id = request.query_params.get('user_id', '') - asset_id = request.query_params.get('asset_id', '') - system_id = request.query_params.get('system_user_id', '') + def get_object(self): + user_id = self.request.query_params.get('user_id', '') + asset_id = self.request.query_params.get('asset_id', '') + system_id = self.request.query_params.get('system_user_id', '') user = get_object_or_404(User, id=user_id) asset = get_object_or_404(Asset, id=asset_id) @@ -447,12 +448,8 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView): util = AssetPermissionUtil(user, cache_policy=self.cache_policy) granted_assets = util.get_assets() - granted_system_users = granted_assets.get(asset, []) - _su = next((s for s in granted_system_users if s.id == su.id), None) - if not _su: - return Response({'actions': []}, status=403) - - actions = [action.name for action in getattr(_su, 'actions', [])] - return Response({'actions': actions}, status=200) - + granted_system_users = granted_assets.get(asset, {}) + if su not in granted_system_users: + return {"actions": 0} + return granted_system_users[su] diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 1653667b6..43cf47136 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -11,6 +11,7 @@ from .asset_permission import ActionField __all__ = [ 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'NodeGrantedSerializer', 'AssetGrantedSerializer', + 'ActionsSerializer', ] @@ -110,4 +111,8 @@ class GrantedNodeSerializer(serializers.ModelSerializer): model = Node fields = [ 'id', 'name', 'key', 'value', - ] \ No newline at end of file + ] + + +class ActionsSerializer(serializers.Serializer): + actions = ActionField(read_only=True) \ No newline at end of file From e4880a247f09e1c18566024b2b1985fde8b6f6d8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 12:08:25 +0800 Subject: [PATCH 23/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission.py | 8 +- apps/perms/urls/api_urls.py | 115 ++++++++------------------- apps/perms/utils/asset_permission.py | 7 ++ apps/users/serializers/v1.py | 2 +- 4 files changed, 46 insertions(+), 86 deletions(-) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 73aba47f3..92a76615b 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -8,7 +8,7 @@ from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response from rest_framework.generics import ( - ListAPIView, get_object_or_404, GenericAPIView, RetrieveAPIView + ListAPIView, get_object_or_404, RetrieveAPIView ) from rest_framework.pagination import LimitOffsetPagination @@ -172,7 +172,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): """ - 查询用户授权的所有节点的API, 如果是超级用户或者是 app,切换到root org + 查询用户授权的所有节点的API """ permission_classes = (IsOrgAdminOrAppUser,) serializer_class = NodeSerializer @@ -188,8 +188,8 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): def get_queryset(self): user = self.get_object() util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - nodes = util.get_nodes_with_assets() - return nodes.keys() + nodes = util.get_nodes() + return nodes def get_permissions(self): if self.kwargs.get('pk') is None: diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 629e6d7a5..611661c5d 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -10,108 +10,61 @@ router = routers.DefaultRouter() router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission') + asset_permission_urlpatterns = [ # 查询某个用户授权的资产和资产组 - path('user//assets/', - api.UserGrantedAssetsApi.as_view()), - path('users//assets/', - api.UserGrantedAssetsApi.as_view(), name='user-assets'), - path('user/assets/', api.UserGrantedAssetsApi.as_view(), - name='my-assets'), - path('user//nodes/', - api.UserGrantedNodesApi.as_view(), name='user-nodes'), - path('user/nodes/', api.UserGrantedNodesApi.as_view(), - name='my-nodes'), - path('user/nodes/children/', api.UserGrantedNodeChildrenApi.as_view(), - name='my-node-children'), - path('user//nodes//assets/', - api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), - path('user/nodes//assets/', - api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'), - path('user//nodes-assets/', - api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), - path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), - name='my-nodes-assets'), - path('user//nodes-assets/tree/', - api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'), - path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), - name='my-nodes-assets-as-tree'), + path('user//assets/', api.UserGrantedAssetsApi.as_view()), + path('users//assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'), + path('user/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'), + path('user//nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), + path('user/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'), + path('user/nodes/children/', api.UserGrantedNodeChildrenApi.as_view(), name='my-node-children'), + path('user//nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), + path('user/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'), + path('user//nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), + path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), + path('user//nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'), + path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'), # 查询某个用户组授权的资产和资产组 - path('user-group//assets/', - api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), - path('user-group//nodes/', - api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), - path('user-group//nodes-assets/', - api.UserGroupGrantedNodesWithAssetsApi.as_view(), - name='user-group-nodes-assets'), - path('user-group//nodes-assets/tree/', - api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), - name='user-group-nodes-assets-as-tree'), - path('user-group//nodes//assets/', - api.UserGroupGrantedNodeAssetsApi.as_view(), - name='user-group-node-assets'), + path('user-group//assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), + path('user-group//nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), + path('user-group//nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), + path('user-group//nodes-assets/tree/', api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-group-nodes-assets-as-tree'), + path('user-group//nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), # 用户和资产授权变更 - path('asset-permissions//user/remove/', - api.AssetPermissionRemoveUserApi.as_view(), - name='asset-permission-remove-user'), - path('asset-permissions//user/add/', - api.AssetPermissionAddUserApi.as_view(), - name='asset-permission-add-user'), - path('asset-permissions//asset/remove/', - api.AssetPermissionRemoveAssetApi.as_view(), - name='asset-permission-remove-asset'), - path('asset-permissions//asset/add/', - api.AssetPermissionAddAssetApi.as_view(), - name='asset-permission-add-asset'), + path('asset-permissions//user/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'), + path('asset-permissions//user/add/', api.AssetPermissionAddUserApi.as_view(), name='asset-permission-add-user'), + path('asset-permissions//asset/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'), + path('asset-permissions//asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'), # 验证用户是否有某个资产和系统用户的权限 - path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), - name='validate-user-asset-permission'), - path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), - name='get-user-asset-permission-actions'), + path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), + path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), ] remote_app_permission_urlpatterns = [ # 查询用户授权的RemoteApp - path('user//remote-apps/', - api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), - path('user/remote-apps/', - api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), + path('user//remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), + path('user/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), # 获取用户授权的RemoteApp树 - path('user//remote-apps/tree/', - api.UserGrantedRemoteAppsAsTreeApi.as_view(), - name='user-remote-apps-as-tree'), - path('user/remote-apps/tree/', - api.UserGrantedRemoteAppsAsTreeApi.as_view(), - name='my-remote-apps-as-tree'), + path('user//remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'), + path('user/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'), # 查询用户组授权的RemoteApp - path('user-group//remote-apps/', - api.UserGroupGrantedRemoteAppsApi.as_view(), - name='user-group-remote-apps'), + path('user-group//remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'), # 校验用户对RemoteApp的权限 - path('remote-app-permission/user/validate/', - api.ValidateUserRemoteAppPermissionApi.as_view(), - name='validate-user-remote-app-permission'), + path('remote-app-permission/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'), # 用户和RemoteApp变更 - path('remote-app-permissions//user/add/', - api.RemoteAppPermissionAddUserApi.as_view(), - name='remote-app-permission-add-user'), - path('remote-app-permissions//user/remove/', - api.RemoteAppPermissionRemoveUserApi.as_view(), - name='remote-app-permission-remove-user'), - path('remote-app-permissions//remote-app/remove/', - api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), - name='remote-app-permission-remove-remote-app'), - path('remote-app-permissions//remote-app/add/', - api.RemoteAppPermissionAddRemoteAppApi.as_view(), - name='remote-app-permission-add-remote-app'), + path('remote-app-permissions//user/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'), + path('remote-app-permissions//user/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'), + path('remote-app-permissions//remote-app/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'), + path('remote-app-permissions//remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'), ] urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index e1dd45346..52b79be4d 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -180,6 +180,9 @@ class GenerateTree: self._nodes_with_assets = nodes return dict(nodes) + def get_nodes(self): + return self.nodes.keys() + def get_user_permissions(user, include_group=True): if include_group: @@ -436,6 +439,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): self.tree.add_nodes(nodes) return nodes + def get_nodes(self): + self.get_assets_direct() + return self.tree.get_nodes() + #@timeit def get_assets_direct(self): """ diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index b97f91efa..2060e45e8 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -27,7 +27,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'date_password_last_updated', 'date_expired', 'avatar_url', ] extra_kwargs = { - 'password': {'write_only': True}, + 'password': {'write_only': True, 'required': False}, 'public_key': {'write_only': True}, 'groups_display': {'label': _('Groups name')}, 'source_display': {'label': _('Source name')}, From 1983533e769398db9190b6eddc12472e96096c5b Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 2 Jul 2019 12:44:53 +0800 Subject: [PATCH 24/53] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E6=94=B9=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E6=9D=83=E9=99=90=E7=9A=84API=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission.py | 22 ++++++++++++---------- apps/perms/utils/asset_permission.py | 14 -------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 92a76615b..43ae30699 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -17,14 +17,13 @@ from common.tree import TreeNodeSerializer from common.utils import get_logger from ..utils import ( AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, - check_system_user_action, ) from ..hands import User, Asset, Node, SystemUser, NodeSerializer from .. import serializers, const from ..mixins import ( AssetsFilterMixin, ) -from ..models import Action +from ..models import ActionFlag logger = get_logger(__name__) @@ -407,7 +406,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView): class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): permission_classes = (IsOrgAdminOrAppUser,) - + def get(self, request, *args, **kwargs): user_id = request.query_params.get('user_id', '') asset_id = request.query_params.get('asset_id', '') @@ -417,17 +416,17 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): user = get_object_or_404(User, id=user_id) asset = get_object_or_404(Asset, id=asset_id) su = get_object_or_404(SystemUser, id=system_id) - action = get_object_or_404(Action, name=action_name) util = AssetPermissionUtil(user, cache_policy=self.cache_policy) granted_assets = util.get_assets() - granted_system_users = granted_assets.get(asset, []) + granted_system_users = granted_assets.get(asset, {}) if su not in granted_system_users: return Response({'msg': False}, status=403) - _su = next((s for s in granted_system_users if s.id == su.id), None) - if not check_system_user_action(_su, action): + action = granted_system_users[su] + choices = ActionFlag.value_to_choices(action) + if action_name not in choices: return Response({'msg': False}, status=403) return Response({'msg': True}, status=200) @@ -435,7 +434,7 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView): permission_classes = (IsOrgAdminOrAppUser,) - serializers_class = serializers.ActionsSerializer + serializer_class = serializers.ActionsSerializer def get_object(self): user_id = self.request.query_params.get('user_id', '') @@ -450,6 +449,9 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView granted_assets = util.get_assets() granted_system_users = granted_assets.get(asset, {}) + _object = {} if su not in granted_system_users: - return {"actions": 0} - return granted_system_users[su] + _object['actions'] = 0 + else: + _object['actions'] = granted_system_users[su] + return _object diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 52b79be4d..116a7714f 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -27,7 +27,6 @@ logger = get_logger(__file__) __all__ = [ 'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets', 'parse_asset_to_tree_node', 'parse_node_to_tree_node', - 'check_system_user_action', ] @@ -590,16 +589,3 @@ def parse_asset_to_tree_node(node, asset, system_users): } tree_node = TreeNode(**data) return tree_node - - -def check_system_user_action(system_user, action): - """ - :param system_user: SystemUser object (包含动态属性: actions) - :param action: Action object - :return: bool - """ - - check_actions = [Action.get_action_all(), action] - granted_actions = getattr(system_user, 'actions', []) - actions = list(set(granted_actions).intersection(set(check_actions))) - return bool(actions) From 31d2f2a7991d4eafcbe72beb78f4beef5900e859 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 14:17:56 +0800 Subject: [PATCH 25/53] =?UTF-8?q?[Update]=20=E5=8E=BB=E6=8E=89=E5=8E=9F?= =?UTF-8?q?=E6=9D=A5=E6=89=B9=E9=87=8F=E7=9A=84view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/urls/views_urls.py | 2 - apps/assets/views/asset.py | 149 +--------------------- apps/common/permissions.py | 11 ++ apps/jumpserver/conf.py | 2 +- apps/perms/api/user_permission.py | 12 +- apps/perms/utils/asset_permission.py | 19 ++- apps/users/api/user.py | 43 +------ apps/users/serializers/v1.py | 8 ++ apps/users/templates/users/user_list.html | 64 +++++----- apps/users/urls/views_urls.py | 2 - apps/users/views/user.py | 145 +-------------------- 11 files changed, 76 insertions(+), 381 deletions(-) diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index f9f67b849..71483a4df 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -9,8 +9,6 @@ urlpatterns = [ path('', views.AssetListView.as_view(), name='asset-index'), path('asset/', views.AssetListView.as_view(), name='asset-list'), path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'), - path('asset/export/', views.AssetExportView.as_view(), name='asset-export'), - path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'), path('asset//', views.AssetDetailView.as_view(), name='asset-detail'), path('asset//update/', views.AssetUpdateView.as_view(), name='asset-update'), path('asset//delete/', views.AssetDeleteView.as_view(), name='asset-delete'), diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index aef420171..984e2052e 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -37,7 +37,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain __all__ = [ 'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView', 'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView', - 'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView', + 'AssetDeleteView', ] logger = get_logger(__file__) @@ -229,150 +229,3 @@ class AssetDetailView(PermissionsMixin, DetailView): } kwargs.update(context) return super().get_context_data(**kwargs) - - -@method_decorator(csrf_exempt, name='dispatch') -class AssetExportView(PermissionsMixin, View): - permission_classes = [IsValidUser] - - def get(self, request): - spm = request.GET.get('spm', '') - assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [] - assets_id = cache.get(spm, assets_id_default) - fields = [ - field for field in Asset._meta.fields - if field.name not in [ - 'date_created', 'org_id' - ] - ] - filename = 'assets-{}.csv'.format( - timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S') - ) - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="%s"' % filename - response.write(codecs.BOM_UTF8) - assets = Asset.objects.filter(id__in=assets_id) - writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL) - - header = [field.verbose_name for field in fields] - writer.writerow(header) - - for asset in assets: - data = [getattr(asset, field.name) for field in fields] - writer.writerow(data) - return response - - def post(self, request, *args, **kwargs): - try: - assets_id = json.loads(request.body).get('assets_id', []) - node_id = json.loads(request.body).get('node_id', None) - except ValueError: - return HttpResponse('Json object not valid', status=400) - - if not assets_id: - node = get_object_or_none(Node, id=node_id) if node_id else Node.root() - assets = node.get_all_assets() - for asset in assets: - assets_id.append(asset.id) - - spm = uuid.uuid4().hex - cache.set(spm, assets_id, 300) - url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm - return JsonResponse({'redirect': url}) - - -class BulkImportAssetView(PermissionsMixin, JSONResponseMixin, FormView): - form_class = forms.FileForm - permission_classes = [IsOrgAdmin] - - def form_valid(self, form): - node_id = self.request.GET.get("node_id") - node = get_object_or_none(Node, id=node_id) if node_id else Node.root() - f = form.cleaned_data['file'] - det_result = chardet.detect(f.read()) - f.seek(0) # reset file seek index - - file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode()) - csv_file = StringIO(file_data) - reader = csv.reader(csv_file) - csv_data = [row for row in reader] - fields = [ - field for field in Asset._meta.fields - if field.name not in [ - 'date_created' - ] - ] - header_ = csv_data[0] - mapping_reverse = {field.verbose_name: field.name for field in fields} - attr = [mapping_reverse.get(n, None) for n in header_] - if None in attr: - data = {'valid': False, - 'msg': 'Must be same format as ' - 'template or export file'} - return self.render_json_response(data) - - created, updated, failed = [], [], [] - assets = [] - for row in csv_data[1:]: - if set(row) == {''}: - continue - - asset_dict_raw = dict(zip(attr, row)) - asset_dict = dict() - for k, v in asset_dict_raw.items(): - v = v.strip() - if k == 'is_active': - v = False if v in ['False', 0, 'false'] else True - elif k == 'admin_user': - v = get_object_or_none(AdminUser, name=v) - elif k in ['port', 'cpu_count', 'cpu_cores']: - try: - v = int(v) - except ValueError: - v = '' - elif k == 'domain': - v = get_object_or_none(Domain, name=v) - elif k == 'platform': - v = v.lower().capitalize() - if v != '': - asset_dict[k] = v - - asset = None - asset_id = asset_dict.pop('id', None) - if asset_id: - asset = get_object_or_none(Asset, id=asset_id) - if not asset: - try: - if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))): - raise Exception(_('already exists')) - with transaction.atomic(): - asset = Asset.objects.create(**asset_dict) - if node: - asset.nodes.set([node]) - created.append(asset_dict['hostname']) - assets.append(asset) - except Exception as e: - failed.append('%s: %s' % (asset_dict['hostname'], str(e))) - else: - for k, v in asset_dict.items(): - if v != '': - setattr(asset, k, v) - try: - asset.save() - updated.append(asset_dict['hostname']) - except Exception as e: - failed.append('%s: %s' % (asset_dict['hostname'], str(e))) - - data = { - 'created': created, - 'created_info': 'Created {}'.format(len(created)), - 'updated': updated, - 'updated_info': 'Updated {}'.format(len(updated)), - 'failed': failed, - 'failed_info': 'Failed {}'.format(len(failed)), - 'valid': True, - 'msg': 'Created: {}. Updated: {}, Error: {}'.format( - len(created), len(updated), len(failed)) - } - return self.render_json_response(data) - diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 776c50e4d..a2f4c9286 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -140,3 +140,14 @@ class NeedMFAVerify(permissions.BasePermission): if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL: return True return False + + +class CanUpdateSuperUser(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + if request.method in ['GET', 'OPTIONS']: + return True + if request.user.is_superuser: + return True + if hasattr(obj, 'is_superuser') and obj.is_superuser: + return False + return True diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 8368cb993..4730ada43 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -373,7 +373,7 @@ defaults = { 'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_LISTEN_PORT': 8080, 'LOGIN_LOG_KEEP_DAYS': 90, - 'ASSETS_PERM_CACHE_TIME': 3600, + 'ASSETS_PERM_CACHE_TIME': 3600*24, 'SECURITY_MFA_VERIFY_TTL': 3600, } diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 92a76615b..db4aed12c 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -21,9 +21,7 @@ from ..utils import ( ) from ..hands import User, Asset, Node, SystemUser, NodeSerializer from .. import serializers, const -from ..mixins import ( - AssetsFilterMixin, -) +from ..mixins import AssetsFilterMixin from ..models import Action logger = get_logger(__name__) @@ -155,13 +153,13 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV user = self.get_object() util = AssetPermissionUtil(user, cache_policy=self.cache_policy) assets = util.get_assets() - for k, v in assets.items(): + for asset, system_users in assets.items(): system_users_granted = [] - for system_user, actions in v.items(): + for system_user, actions in system_users.items(): system_user.actions = actions system_users_granted.append(system_user) - k.system_users_granted = system_users_granted - queryset.append(k) + asset.system_users_granted = system_users_granted + queryset.append(system_users_granted) return queryset def get_permissions(self): diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 52b79be4d..f3de9bcb7 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -224,6 +224,8 @@ class AssetPermissionCacheMixin: CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh')) cache_policy = '1' + obj_id = '' + _filter_id = None @classmethod def is_not_using_cache(cls, cache_policy): @@ -270,7 +272,6 @@ class AssetPermissionCacheMixin: def get_assets_from_cache(self): cached = cache.get(self.asset_key) if not cached: - print("Refresh cache") self.update_cache() cached = cache.get(self.asset_key) return cached @@ -320,7 +321,7 @@ class AssetPermissionCacheMixin: def get_meta_cache_key(self): cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}' key = cache_key.format( - obj_id=str(self.object.id), filter_id=self._filter_id + obj_id=self.obj_id, filter_id=self._filter_id ) return key @@ -345,7 +346,7 @@ class AssetPermissionCacheMixin: def expire_cache_meta(self): cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_*' - key = cache_key.format(obj_id=str(self.object.id)) + key = cache_key.format(obj_id=self.obj_id) cache.delete_pattern(key) def update_cache(self): @@ -378,6 +379,15 @@ class AssetPermissionCacheMixin: key = cls.CACHE_KEY_PREFIX + '*' cache.delete_pattern(key) + def get_assets_without_cache(self): + raise NotImplementedError() + + def get_nodes_with_assets_without_cache(self): + raise NotImplementedError() + + def get_system_user_without_cache(self): + raise NotImplementedError() + class AssetPermissionUtil(AssetPermissionCacheMixin): get_permissions_map = { @@ -472,8 +482,6 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): for node in nodes: pattern.add(r'^{0}$|^{0}:'.format(node.key)) pattern = '|'.join(list(pattern)) - now = time.time() - print("Get node assets start") if pattern: assets = Asset.objects.filter(nodes__key__regex=pattern)\ .prefetch_related('nodes', "protocols")\ @@ -481,7 +489,6 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): .distinct() else: assets = [] - print("Get node assets end, using: {}".format(time.time() - now)) self.tree.add_assets_without_system_users(assets) assets = self.tree.get_assets() self._assets = assets diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 1ccc9e3c3..8e6b59a3e 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -13,7 +13,8 @@ from rest_framework_bulk import BulkModelViewSet from rest_framework.pagination import LimitOffsetPagination from common.permissions import ( - IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser + IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser, + CanUpdateSuperUser, ) from common.mixins import IDInCacheFilterMixin from common.utils import get_logger @@ -37,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): search_fields = filter_fields queryset = User.objects.exclude(role=User.ROLE_APP) serializer_class = UserSerializer - permission_classes = (IsOrgAdmin,) + permission_classes = (IsOrgAdmin, CanUpdateSuperUser) pagination_class = LimitOffsetPagination def send_created_signal(self, users): @@ -70,28 +71,6 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): """ return not self.request.user.is_superuser and instance.is_superuser - def destroy(self, request, *args, **kwargs): - """ - rewrite because limit org_admin destroy superuser - """ - instance = self.get_object() - if self._deny_permission(instance): - data = {'msg': _("You do not have permission.")} - return Response(data=data, status=status.HTTP_403_FORBIDDEN) - - return super().destroy(request, *args, **kwargs) - - def update(self, request, *args, **kwargs): - """ - rewrite because limit org_admin update superuser - """ - instance = self.get_object() - if self._deny_permission(instance): - data = {'msg': _("You do not have permission.")} - return Response(data=data, status=status.HTTP_403_FORBIDDEN) - - return super().update(request, *args, **kwargs) - def _bulk_deny_permission(self, instances): deny_instances = [i for i in instances if self._deny_permission(i)] if len(deny_instances) > 0: @@ -108,26 +87,12 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): """ rewrite because limit org_admin update superuser """ - partial = kwargs.pop('partial', False) - # restrict the update to the filtered queryset queryset = self.filter_queryset(self.get_queryset()) if self._bulk_deny_permission(queryset): data = {'msg': _("You do not have permission.")} return Response(data=data, status=status.HTTP_403_FORBIDDEN) - - serializer = self.get_serializer( - queryset, data=request.data, many=True, partial=partial, - ) - - try: - serializer.is_valid(raise_exception=True) - except Exception as e: - data = {'error': str(e)} - return Response(data=data, status=status.HTTP_400_BAD_REQUEST) - - self.perform_bulk_update(serializer) - return Response(serializer.data, status=status.HTTP_200_OK) + return super().bulk_update(request, *args, **kwargs) class UserChangePasswordApi(generics.RetrieveUpdateAPIView): diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index 2060e45e8..126c40f5d 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -39,6 +39,14 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'created_by': {'read_only': True}, 'source': {'read_only': True} } + def validate_role(self, value): + request = self.context.get('request') + if not request.user.is_superuser and value != User.ROLE_USER: + role_display = dict(User.ROLE_CHOICES)[User.ROLE_USER] + msg = _("Role limit to {}".format(role_display)) + raise serializers.ValidationError(msg) + return value + @staticmethod def validate_password(value): from ..utils import check_password_rules diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index cf2a764b4..79203cec5 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -2,27 +2,27 @@ {% load i18n static %} {% block table_search %} + +
{% endblock %} {% block table_container %} @@ -92,23 +92,23 @@ function initTable() { }}, {targets: 7, createdCell: function (td, cellData, rowData) { var update_btn = ""; - if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) { - update_btn = '{% trans "Update" %}'; - } - else{ + {#if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) {#} + {# update_btn = '{% trans "Update" %}';#} + {#}#} + {#else{#} update_btn = '{% trans "Update" %}'.replace('00000000-0000-0000-0000-000000000000', cellData); - } + {#}#} var del_btn = ""; - if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) { - del_btn = '{% trans "Delete" %}' - .replace('{{ DEFAULT_PK }}', cellData) - .replace('99991938', rowData.name); - } else { + {#if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) {#} + {# del_btn = '{% trans "Delete" %}'#} + {# .replace('{{ DEFAULT_PK }}', cellData)#} + {# .replace('99991938', rowData.name);#} + {#} else {#} del_btn = '{% trans "Delete" %}' .replace('{{ DEFAULT_PK }}', cellData) .replace('99991938', rowData.name); - } + {#}#} $(td).html(update_btn + del_btn) }}], ajax_url: '{% url "api-users:user-list" %}', diff --git a/apps/users/urls/views_urls.py b/apps/users/urls/views_urls.py index f26300a04..dbed09888 100644 --- a/apps/users/urls/views_urls.py +++ b/apps/users/urls/views_urls.py @@ -29,9 +29,7 @@ urlpatterns = [ # User view path('user/', views.UserListView.as_view(), name='user-list'), - path('user/export/', views.UserExportView.as_view(), name='user-export'), path('first-login/', views.UserFirstLoginView.as_view(), name='user-first-login'), - path('user/import/', views.UserBulkImportView.as_view(), name='user-import'), path('user/create/', views.UserCreateView.as_view(), name='user-create'), path('user//update/', views.UserUpdateView.as_view(), name='user-update'), path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'), diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 74ee0bd02..ff9e03530 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -46,9 +46,7 @@ from ..signals import post_user_create __all__ = [ 'UserListView', 'UserCreateView', 'UserDetailView', - 'UserUpdateView', - 'UserGrantedAssetView', - 'UserExportView', 'UserBulkImportView', 'UserProfileView', + 'UserUpdateView', 'UserGrantedAssetView', 'UserProfileView', 'UserProfileUpdateView', 'UserPasswordUpdateView', 'UserPublicKeyUpdateView', 'UserBulkUpdateView', 'UserPublicKeyGenerateView', @@ -223,147 +221,6 @@ class UserDetailView(PermissionsMixin, DetailView): return queryset -@method_decorator(csrf_exempt, name='dispatch') -class UserExportView(View): - def get(self, request): - fields = [ - User._meta.get_field(name) - for name in [ - 'id', 'name', 'username', 'email', 'role', - 'wechat', 'phone', 'is_active', 'comment', - ] - ] - spm = request.GET.get('spm', '') - users_id = cache.get(spm, []) - filename = 'users-{}.csv'.format( - timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S') - ) - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="%s"' % filename - response.write(codecs.BOM_UTF8) - users = User.objects.filter(id__in=users_id) - writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL) - - header = [field.verbose_name for field in fields] - header.append(_('User groups')) - writer.writerow(header) - - for user in users: - groups = ','.join([group.name for group in user.groups.all()]) - data = [getattr(user, field.name) for field in fields] - data.append(groups) - writer.writerow(data) - - return response - - def post(self, request): - try: - users_id = json.loads(request.body).get('users_id', []) - except ValueError: - return HttpResponse('Json object not valid', status=400) - spm = uuid.uuid4().hex - cache.set(spm, users_id, 300) - url = reverse('users:user-export') + '?spm=%s' % spm - return JsonResponse({'redirect': url}) - - -class UserBulkImportView(PermissionsMixin, JSONResponseMixin, FormView): - form_class = forms.FileForm - permission_classes = [IsOrgAdmin] - - def form_invalid(self, form): - try: - error = form.errors.values()[-1][-1] - except Exception as e: - error = _('Invalid file.') - data = { - 'success': False, - 'msg': error - } - return self.render_json_response(data) - - # todo: need be patch, method to long - def form_valid(self, form): - f = form.cleaned_data['file'] - det_result = chardet.detect(f.read()) - f.seek(0) # reset file seek index - data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode()) - csv_file = StringIO(data) - reader = csv.reader(csv_file) - csv_data = [row for row in reader] - header_ = csv_data[0] - fields = [ - User._meta.get_field(name) - for name in [ - 'id', 'name', 'username', 'email', 'role', - 'wechat', 'phone', 'is_active', 'comment', - ] - ] - mapping_reverse = {field.verbose_name: field.name for field in fields} - mapping_reverse[_('User groups')] = 'groups' - attr = [mapping_reverse.get(n, None) for n in header_] - if None in attr: - data = {'valid': False, - 'msg': 'Must be same format as ' - 'template or export file'} - return self.render_json_response(data) - - created, updated, failed = [], [], [] - for row in csv_data[1:]: - if set(row) == {''}: - continue - user_dict = dict(zip(attr, row)) - id_ = user_dict.pop('id') - for k, v in user_dict.items(): - if k in ['is_active']: - if v.lower() == 'false': - v = False - else: - v = bool(v) - elif k == 'groups': - groups_name = v.split(',') - v = UserGroup.objects.filter(name__in=groups_name) - else: - continue - user_dict[k] = v - user = get_object_or_none(User, id=id_) if id_ and is_uuid(id_) else None - if not user: - try: - with transaction.atomic(): - groups = user_dict.pop('groups') - user = User.objects.create(**user_dict) - user.groups.set(groups) - created.append(user_dict['username']) - post_user_create.send(self.__class__, user=user) - except Exception as e: - failed.append('%s: %s' % (user_dict['username'], str(e))) - else: - for k, v in user_dict.items(): - if k == 'groups': - user.groups.set(v) - continue - if v: - setattr(user, k, v) - try: - user.save() - updated.append(user_dict['username']) - except Exception as e: - failed.append('%s: %s' % (user_dict['username'], str(e))) - - data = { - 'created': created, - 'created_info': 'Created {}'.format(len(created)), - 'updated': updated, - 'updated_info': 'Updated {}'.format(len(updated)), - 'failed': failed, - 'failed_info': 'Failed {}'.format(len(failed)), - 'valid': True, - 'msg': 'Created: {}. Updated: {}, Error: {}'.format( - len(created), len(updated), len(failed)) - } - return self.render_json_response(data) - - class UserGrantedAssetView(PermissionsMixin, DetailView): model = User template_name = 'users/user_granted_asset.html' From 9a3065ad4c8931162d32d9f3b0e64c6b9021de63 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 2 Jul 2019 16:01:45 +0800 Subject: [PATCH 26/53] =?UTF-8?q?[Bugfix]=20=E4=BC=9A=E8=AF=9D/=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=88=97=E8=A1=A8=E4=B8=AD=E8=8E=B7=E5=8F=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=88=97=E8=A1=A8=E6=8E=92=E9=99=A4app=E7=94=A8?= =?UTF-8?q?=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index 143625d44..d7dace0a9 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -13,7 +13,7 @@ def get_session_asset_list(): def get_session_user_list(): - return User.objects.values_list('username', flat=True) + return User.objects.exclude(role=User.ROLE_APP).values_list('username', flat=True) def get_session_system_user_list(): From 98260b5b526a574a32c5f9b309aee8e18717d5c8 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 2 Jul 2019 16:34:38 +0800 Subject: [PATCH 27/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7API=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E7=9A=84queryset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index ca583c388..2a2272536 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -158,7 +158,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV system_user.actions = actions system_users_granted.append(system_user) asset.system_users_granted = system_users_granted - queryset.append(system_users_granted) + queryset.append(asset) return queryset def get_permissions(self): From 930eb1d2e145589ea655b4048b08ce2e13711d17 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 16:45:26 +0800 Subject: [PATCH 28/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=AD=A3migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms/cmd_filter.py | 12 ++ apps/assets/serializers/cmd_filter.py | 9 + apps/locale/zh/LC_MESSAGES/django.po | 177 +++++++++--------- apps/perms/api/asset_permission.py | 2 +- apps/perms/api/user_permission.py | 4 +- apps/perms/const.py | 22 +-- apps/perms/forms/asset_permission.py | 10 +- apps/perms/migrations/0003_action.py | 9 - .../0004_assetpermission_actions.py | 13 -- .../migrations/0006_auto_20190628_1921.py | 13 +- .../0007_remove_assetpermission_actions.py | 1 + apps/perms/models/asset_permission.py | 27 +-- apps/perms/serializers/asset_permission.py | 10 +- apps/perms/signals_handler.py | 2 +- apps/perms/utils/asset_permission.py | 4 +- apps/perms/views/asset_permission.py | 5 +- 16 files changed, 141 insertions(+), 179 deletions(-) diff --git a/apps/assets/forms/cmd_filter.py b/apps/assets/forms/cmd_filter.py index 6cb35e631..308a008d0 100644 --- a/apps/assets/forms/cmd_filter.py +++ b/apps/assets/forms/cmd_filter.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # from django import forms +from django.core.exceptions import ValidationError +import re from orgs.mixins import OrgModelForm from ..models import CommandFilter, CommandFilterRule @@ -15,6 +17,8 @@ class CommandFilterForm(OrgModelForm): class CommandFilterRuleForm(OrgModelForm): + invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') + class Meta: model = CommandFilterRule fields = [ @@ -25,3 +29,11 @@ class CommandFilterRuleForm(OrgModelForm): 'placeholder': 'eg:\r\nreboot\r\nrm -rf' }), } + + def clean_content(self): + content = self.cleaned_data.get("content") + if self.invalid_pattern.search(content): + invalid_char = self.invalid_pattern.pattern.replace('\\', '') + msg = _("Content should not be contain: {}").format(invalid_char) + raise ValidationError(msg) + return content diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index b523a1779..e5ab62037 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import re from rest_framework import serializers from common.fields import ChoiceDisplayField @@ -20,8 +21,16 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): serializer_choice_field = ChoiceDisplayField + invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') class Meta: model = CommandFilterRule fields = '__all__' list_serializer_class = AdaptedBulkListSerializer + + def validate_content(self, content): + if self.invalid_pattern.search(content): + invalid_char = self.invalid_pattern.pattern.replace('\\', '') + msg = _("Content should not be contain: {}").format(invalid_char) + raise serializers.ValidationError(msg) + return content diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 3f803b786..056de6126 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-06-28 20:08+0800\n" +"POT-Creation-Date: 2019-07-02 14:47+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -86,7 +86,7 @@ msgstr "运行参数" #: assets/templates/assets/system_user_list.html:55 audits/models.py:19 #: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:71 -#: perms/forms/asset_permission.py:47 perms/models/asset_permission.py:53 +#: perms/forms/asset_permission.py:68 perms/models/asset_permission.py:85 #: perms/templates/perms/asset_permission_create_update.html:45 #: perms/templates/perms/asset_permission_list.html:48 #: perms/templates/perms/asset_permission_list.html:117 @@ -115,8 +115,8 @@ msgstr "资产" #: assets/models/user.py:160 assets/templates/assets/user_asset_list.html:172 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:72 -#: perms/forms/asset_permission.py:53 perms/models/asset_permission.py:55 -#: perms/models/asset_permission.py:76 +#: perms/forms/asset_permission.py:74 perms/models/asset_permission.py:87 +#: perms/models/asset_permission.py:104 #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:50 #: perms/templates/perms/asset_permission_list.html:71 @@ -149,7 +149,7 @@ msgstr "系统用户" #: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_list.html:51 ops/models/adhoc.py:37 #: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 -#: orgs/models.py:11 perms/models/asset_permission.py:22 +#: orgs/models.py:11 perms/models/asset_permission.py:23 #: perms/models/base.py:35 #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:45 @@ -215,10 +215,10 @@ msgstr "参数" #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:14 -#: perms/models/asset_permission.py:79 perms/models/base.py:41 +#: perms/models/asset_permission.py:107 perms/models/base.py:41 #: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/remote_app_permission_detail.html:90 -#: users/models/user.py:105 users/serializers/v1.py:99 +#: users/models/user.py:105 users/serializers/v1.py:107 #: users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:106 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 @@ -238,7 +238,7 @@ msgstr "创建者" #: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/system_user_detail.html:96 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orgs/models.py:15 perms/models/asset_permission.py:80 +#: orgs/models.py:15 perms/models/asset_permission.py:108 #: perms/models/base.py:42 #: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/remote_app_permission_detail.html:86 @@ -274,7 +274,7 @@ msgstr "创建日期" #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:59 #: assets/templates/assets/user_asset_list.html:175 ops/models/adhoc.py:43 -#: orgs/models.py:16 perms/models/asset_permission.py:81 +#: orgs/models.py:16 perms/models/asset_permission.py:109 #: perms/models/base.py:43 #: perms/templates/perms/asset_permission_detail.html:102 #: perms/templates/perms/remote_app_permission_detail.html:94 @@ -440,7 +440,6 @@ msgstr "详情" #: users/templates/users/user_group_list.html:20 #: users/templates/users/user_group_list.html:70 #: users/templates/users/user_list.html:20 -#: users/templates/users/user_list.html:96 #: users/templates/users/user_list.html:99 #: users/templates/users/user_profile.html:177 #: users/templates/users/user_profile.html:187 @@ -481,7 +480,6 @@ msgstr "更新" #: users/templates/users/user_detail.html:30 #: users/templates/users/user_group_detail.html:32 #: users/templates/users/user_group_list.html:72 -#: users/templates/users/user_list.html:104 #: users/templates/users/user_list.html:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 @@ -529,8 +527,7 @@ msgstr "创建远程应用" #: audits/templates/audits/operate_log_list.html:67 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34 -#: perms/forms/asset_permission.py:56 perms/models/asset_permission.py:26 -#: perms/models/asset_permission.py:57 +#: perms/forms/asset_permission.py:21 perms/models/asset_permission.py:27 #: perms/templates/perms/asset_permission_create_update.html:50 #: perms/templates/perms/asset_permission_list.html:52 #: perms/templates/perms/asset_permission_list.html:126 @@ -552,7 +549,7 @@ msgstr "动作" #: applications/templates/applications/user_remote_app_list.html:57 #: assets/templates/assets/user_asset_list.html:100 perms/const.py:19 -#: perms/models/asset_permission.py:45 +#: perms/models/asset_permission.py:46 msgid "Connect" msgstr "连接" @@ -591,11 +588,11 @@ msgstr "请选择需要更新的资产" msgid "You can't update the root node name" msgstr "不能修改根节点名称" -#: assets/api/node.py:281 +#: assets/api/node.py:283 msgid "Update node asset hardware information: {}" msgstr "更新节点资产硬件信息: {}" -#: assets/api/node.py:295 +#: assets/api/node.py:297 msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" @@ -622,7 +619,7 @@ msgstr "未知" #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/asset_detail.html:202 #: assets/templates/assets/system_user_assets.html:83 -#: perms/models/asset_permission.py:54 +#: perms/models/asset_permission.py:86 #: xpack/plugins/change_auth_plan/models.py:72 msgid "Nodes" msgstr "节点" @@ -656,8 +653,8 @@ msgstr "网域" #: assets/forms/asset.py:64 assets/forms/asset.py:86 assets/forms/asset.py:99 #: assets/forms/asset.py:134 assets/models/node.py:248 #: assets/templates/assets/asset_create.html:42 -#: perms/forms/asset_permission.py:50 perms/forms/asset_permission.py:60 -#: perms/models/asset_permission.py:74 +#: perms/forms/asset_permission.py:71 perms/forms/asset_permission.py:79 +#: perms/models/asset_permission.py:102 #: perms/templates/perms/asset_permission_list.html:49 #: perms/templates/perms/asset_permission_list.html:70 #: perms/templates/perms/asset_permission_list.html:120 @@ -695,6 +692,10 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, msgid "Select assets" msgstr "选择资产" +#: assets/forms/cmd_filter.py:37 assets/serializers/cmd_filter.py:34 +msgid "Content should not be contain: {}" +msgstr "内容不能包含: {}" + #: assets/forms/domain.py:51 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" @@ -1000,7 +1001,7 @@ msgid "Operator" msgstr "运营商" #: assets/models/cluster.py:36 assets/models/group.py:34 -#: perms/utils/asset_permission.py:67 +#: perms/utils/asset_permission.py:106 msgid "Default" msgstr "默认" @@ -1112,7 +1113,7 @@ msgstr "默认资产组" #: audits/templates/audits/password_change_log_list.html:50 #: ops/templates/ops/command_execution_list.html:35 #: ops/templates/ops/command_execution_list.html:60 -#: perms/forms/asset_permission.py:41 perms/forms/remote_app_permission.py:31 +#: perms/forms/asset_permission.py:62 perms/forms/remote_app_permission.py:31 #: perms/models/base.py:36 #: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:46 @@ -1124,9 +1125,9 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:301 -#: users/models/user.py:38 users/models/user.py:431 users/serializers/v1.py:88 +#: users/models/user.py:38 users/models/user.py:431 users/serializers/v1.py:96 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:36 users/views/user.py:407 +#: users/templates/users/user_group_list.html:36 users/views/user.py:264 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -1205,26 +1206,25 @@ msgstr "登录模式" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/serializers/admin_user.py:36 assets/serializers/asset.py:47 +#: assets/serializers/admin_user.py:36 assets/serializers/asset.py:46 #: assets/serializers/asset_user.py:30 assets/serializers/system_user.py:30 #: assets/templates/assets/_asset_user_list.html:18 msgid "Connectivity" msgstr "连接" -#: assets/serializers/asset.py:45 assets/serializers/asset.py:155 -#: assets/templates/assets/asset_create.html:24 +#: assets/serializers/asset.py:44 assets/templates/assets/asset_create.html:24 msgid "Protocols" msgstr "协议组" -#: assets/serializers/asset.py:73 +#: assets/serializers/asset.py:72 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:74 orgs/mixins.py:192 +#: assets/serializers/asset.py:73 orgs/mixins.py:192 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:92 +#: assets/serializers/asset.py:91 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" @@ -1710,7 +1710,7 @@ msgstr "创建日期" #: assets/templates/assets/asset_detail.html:154 #: assets/templates/assets/user_asset_list.html:46 -#: perms/models/asset_permission.py:77 perms/models/base.py:38 +#: perms/models/asset_permission.py:105 perms/models/base.py:38 #: perms/templates/perms/asset_permission_create_update.html:55 #: perms/templates/perms/asset_permission_detail.html:120 #: perms/templates/perms/remote_app_permission_create_update.html:54 @@ -2056,10 +2056,6 @@ msgstr "批量更新资产" msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:347 -msgid "already exists" -msgstr "已经存在" - #: assets/views/cmd_filter.py:32 msgid "Command filter list" msgstr "命令过滤器列表" @@ -2557,8 +2553,8 @@ msgstr "欢迎回来,请输入用户名和密码登录" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:172 users/views/user.py:555 -#: users/views/user.py:580 +#: authentication/views/login.py:172 users/views/user.py:412 +#: users/views/user.py:437 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -3000,11 +2996,11 @@ msgstr "命令执行" msgid "Organization" msgstr "组织" -#: perms/const.py:18 perms/models/asset_permission.py:44 settings/forms.py:143 +#: perms/const.py:18 perms/models/asset_permission.py:45 settings/forms.py:143 msgid "All" msgstr "全部" -#: perms/const.py:20 perms/models/asset_permission.py:46 +#: perms/const.py:20 perms/models/asset_permission.py:47 msgid "Upload file" msgstr "上传文件" @@ -3012,8 +3008,8 @@ msgstr "上传文件" msgid "Download file" msgstr "下载文件" -#: perms/forms/asset_permission.py:44 perms/forms/remote_app_permission.py:34 -#: perms/models/asset_permission.py:75 perms/models/base.py:37 +#: perms/forms/asset_permission.py:65 perms/forms/remote_app_permission.py:34 +#: perms/models/asset_permission.py:103 perms/models/base.py:37 #: perms/templates/perms/asset_permission_list.html:47 #: perms/templates/perms/asset_permission_list.html:67 #: perms/templates/perms/asset_permission_list.html:114 @@ -3026,30 +3022,34 @@ msgstr "下载文件" msgid "User group" msgstr "用户组" -#: perms/forms/asset_permission.py:63 +#: perms/forms/asset_permission.py:82 msgid "" "Tips: The RDP protocol does not support separate controls for uploading or " "downloading files" msgstr "提示:RDP 协议不支持单独控制上传或下载文件" -#: perms/forms/asset_permission.py:73 perms/forms/remote_app_permission.py:47 +#: perms/forms/asset_permission.py:92 perms/forms/remote_app_permission.py:47 msgid "User or group at least one required" msgstr "用户和用户组至少选一个" -#: perms/forms/asset_permission.py:82 +#: perms/forms/asset_permission.py:101 msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models/asset_permission.py:47 +#: perms/models/asset_permission.py:49 msgid "Upload download" msgstr "上传下载" -#: perms/models/asset_permission.py:61 perms/models/asset_permission.py:87 +#: perms/models/asset_permission.py:89 +msgid "Actions" +msgstr "动作" + +#: perms/models/asset_permission.py:93 perms/models/asset_permission.py:115 #: templates/_nav.html:44 msgid "Asset permission" msgstr "资产授权" -#: perms/models/asset_permission.py:78 perms/models/base.py:40 +#: perms/models/asset_permission.py:106 perms/models/base.py:40 #: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/remote_app_permission_detail.html:82 #: users/models/user.py:102 users/templates/users/user_detail.html:107 @@ -3199,8 +3199,8 @@ msgid "Add user group to this permission" msgstr "添加用户组" #: perms/views/asset_permission.py:34 perms/views/asset_permission.py:65 -#: perms/views/asset_permission.py:81 perms/views/asset_permission.py:97 -#: perms/views/asset_permission.py:134 perms/views/asset_permission.py:167 +#: perms/views/asset_permission.py:82 perms/views/asset_permission.py:99 +#: perms/views/asset_permission.py:136 perms/views/asset_permission.py:169 #: perms/views/remote_app_permission.py:33 #: perms/views/remote_app_permission.py:49 #: perms/views/remote_app_permission.py:65 @@ -3219,19 +3219,19 @@ msgstr "资产授权列表" msgid "Create asset permission" msgstr "创建权限规则" -#: perms/views/asset_permission.py:82 +#: perms/views/asset_permission.py:83 msgid "Update asset permission" msgstr "更新资产授权" -#: perms/views/asset_permission.py:98 +#: perms/views/asset_permission.py:100 msgid "Asset permission detail" msgstr "资产授权详情" -#: perms/views/asset_permission.py:135 +#: perms/views/asset_permission.py:137 msgid "Asset permission user list" msgstr "资产授权用户列表" -#: perms/views/asset_permission.py:168 +#: perms/views/asset_permission.py:170 msgid "Asset permission asset list" msgstr "资产授权资产列表" @@ -3828,7 +3828,7 @@ msgstr "商业支持" #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:388 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:245 msgid "Profile" msgstr "个人信息" @@ -3923,13 +3923,13 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:45 #: users/views/group.py:62 users/views/group.py:79 users/views/group.py:96 -#: users/views/login.py:154 users/views/user.py:70 users/views/user.py:87 -#: users/views/user.py:131 users/views/user.py:211 users/views/user.py:374 -#: users/views/user.py:426 users/views/user.py:467 +#: users/views/login.py:154 users/views/user.py:68 users/views/user.py:85 +#: users/views/user.py:129 users/views/user.py:209 users/views/user.py:231 +#: users/views/user.py:283 users/views/user.py:324 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/views/user.py:71 +#: templates/_nav.html:13 users/views/user.py:69 msgid "User list" msgstr "用户列表" @@ -4369,11 +4369,11 @@ msgid "" "You should use your ssh client tools connect terminal: {}

{}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api/user.py:79 users/api/user.py:90 users/api/user.py:116 +#: users/api/user.py:93 msgid "You do not have permission." msgstr "你没有权限" -#: users/api/user.py:221 +#: users/api/user.py:186 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" @@ -4405,7 +4405,7 @@ msgstr "添加到用户组" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:90 users/forms.py:237 users/serializers/v1.py:74 +#: users/forms.py:90 users/forms.py:237 users/serializers/v1.py:82 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -4532,7 +4532,7 @@ msgid "Date password last updated" msgstr "最后更新密码日期" #: users/models/user.py:139 users/templates/users/user_update.html:22 -#: users/views/login.py:46 users/views/login.py:107 users/views/user.py:439 +#: users/views/login.py:46 users/views/login.py:107 users/views/user.py:296 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" @@ -4569,10 +4569,12 @@ msgid "Avatar url" msgstr "头像路径" #: users/serializers/v1.py:46 -#, fuzzy -#| msgid "Password does not match" +msgid "Role limit to {}" +msgstr "角色只能为 {}" + +#: users/serializers/v1.py:54 msgid "Password does not match security rules" -msgstr "密码不一致" +msgstr "密码不满足安全规则" #: users/serializers_v2/user.py:36 msgid "name not unique" @@ -4624,7 +4626,7 @@ msgid "Import users" msgstr "导入用户" #: users/templates/users/_user_update_modal.html:4 -#: users/templates/users/user_update.html:4 users/views/user.py:132 +#: users/templates/users/user_update.html:4 users/views/user.py:130 msgid "Update user" msgstr "更新用户" @@ -4762,12 +4764,12 @@ msgid "Very strong" msgstr "很强" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:28 users/views/user.py:88 +#: users/templates/users/user_list.html:28 users/views/user.py:86 msgid "Create user" msgstr "创建用户" #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:212 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:210 msgid "User detail" msgstr "用户详情" @@ -4967,8 +4969,7 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:120 users/views/user.py:248 -#: users/views/user.py:303 +#: users/templates/users/user_profile.html:120 msgid "User groups" msgstr "用户组" @@ -5243,7 +5244,7 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:114 users/views/user.py:146 users/views/user.py:449 +#: users/views/login.py:114 users/views/user.py:144 users/views/user.py:306 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" @@ -5251,51 +5252,47 @@ msgstr "* 您的密码不符合要求" msgid "First login" msgstr "首次登录" -#: users/views/user.py:163 +#: users/views/user.py:161 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:191 +#: users/views/user.py:189 msgid "Bulk update user" msgstr "批量更新用户" -#: users/views/user.py:278 -msgid "Invalid file." -msgstr "文件不合法" - -#: users/views/user.py:375 +#: users/views/user.py:232 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:408 +#: users/views/user.py:265 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:427 +#: users/views/user.py:284 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:468 +#: users/views/user.py:325 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:510 +#: users/views/user.py:367 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:610 +#: users/views/user.py:467 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:611 +#: users/views/user.py:468 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:613 +#: users/views/user.py:470 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:614 +#: users/views/user.py:471 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -5783,10 +5780,8 @@ msgid "Restore default failed." msgstr "恢复默认失败!" #: xpack/plugins/interface/views.py:24 -#, fuzzy -#| msgid "Interval" msgid "Interface" -msgstr "间隔" +msgstr "界面" #: xpack/plugins/interface/views.py:51 msgid "It is already in the default setting state!" @@ -5946,6 +5941,12 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" +#~ msgid "already exists" +#~ msgstr "已经存在" + +#~ msgid "Invalid file." +#~ msgstr "文件不合法" + #~ msgid "Refresh all node assets amount" #~ msgstr "刷新所有节点资产数量" diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py index 267d83971..38e44abd0 100644 --- a/apps/perms/api/asset_permission.py +++ b/apps/perms/api/asset_permission.py @@ -10,7 +10,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.permissions import IsOrgAdmin from common.utils import get_object_or_none -from ..models import AssetPermission, Action +from ..models import AssetPermission from ..hands import ( User, UserGroup, Asset, Node, SystemUser, ) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index ca583c388..cf259fb4f 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -21,7 +21,7 @@ from ..utils import ( from ..hands import User, Asset, Node, SystemUser, NodeSerializer from .. import serializers, const from ..mixins import AssetsFilterMixin -from ..models import ActionFlag +from ..models import Action logger = get_logger(__name__) @@ -423,7 +423,7 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): return Response({'msg': False}, status=403) action = granted_system_users[su] - choices = ActionFlag.value_to_choices(action) + choices = Action.value_to_choices(action) if action_name not in choices: return Response({'msg': False}, status=403) diff --git a/apps/perms/const.py b/apps/perms/const.py index 457db580c..23d2ba044 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -1,24 +1,4 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ - -__all__ = [ - 'PERMS_ACTION_NAME_ALL', 'PERMS_ACTION_NAME_CONNECT', - 'PERMS_ACTION_NAME_DOWNLOAD_FILE', 'PERMS_ACTION_NAME_UPLOAD_FILE', - 'PERMS_ACTION_NAME_CHOICES' -] - -PERMS_ACTION_NAME_ALL = 'all' -PERMS_ACTION_NAME_CONNECT = 'connect' -PERMS_ACTION_NAME_UPLOAD_FILE = 'upload_file' -PERMS_ACTION_NAME_DOWNLOAD_FILE = 'download_file' - -PERMS_ACTION_NAME_CHOICES = ( - (PERMS_ACTION_NAME_ALL, _('All')), - (PERMS_ACTION_NAME_CONNECT, _('Connect')), - (PERMS_ACTION_NAME_UPLOAD_FILE, _('Upload file')), - (PERMS_ACTION_NAME_DOWNLOAD_FILE, _('Download file')), -) - -UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000000" +UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002" diff --git a/apps/perms/forms/asset_permission.py b/apps/perms/forms/asset_permission.py index fb63517ff..c061f8d2a 100644 --- a/apps/perms/forms/asset_permission.py +++ b/apps/perms/forms/asset_permission.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins import OrgModelForm from orgs.utils import current_org from assets.models import Asset, Node -from ..models import AssetPermission, ActionFlag +from ..models import AssetPermission, Action __all__ = [ 'AssetPermissionForm', @@ -16,20 +16,20 @@ __all__ = [ class ActionField(forms.MultipleChoiceField): def __init__(self, *args, **kwargs): - kwargs['choices'] = ActionFlag.CHOICES - kwargs['initial'] = ActionFlag.ALL + kwargs['choices'] = Action.CHOICES + kwargs['initial'] = Action.ALL kwargs['label'] = _("Action") kwargs['widget'] = forms.CheckboxSelectMultiple() super().__init__(*args, **kwargs) def to_python(self, value): value = super().to_python(value) - return ActionFlag.choices_to_value(value) + return Action.choices_to_value(value) def prepare_value(self, value): if value is None: return value - value = ActionFlag.value_to_choices(value) + value = Action.value_to_choices(value) return value diff --git a/apps/perms/migrations/0003_action.py b/apps/perms/migrations/0003_action.py index 41c0f9f39..c9dea7736 100644 --- a/apps/perms/migrations/0003_action.py +++ b/apps/perms/migrations/0003_action.py @@ -4,14 +4,6 @@ from django.db import migrations, models import uuid -def add_default_actions(apps, schema_editor): - from ..const import PERMS_ACTION_NAME_CHOICES - action_model = apps.get_model('perms', 'Action') - db_alias = schema_editor.connection.alias - for action, _ in PERMS_ACTION_NAME_CHOICES: - action_model.objects.using(db_alias).update_or_create(name=action) - - class Migration(migrations.Migration): dependencies = [ @@ -29,5 +21,4 @@ class Migration(migrations.Migration): 'verbose_name': 'Action', }, ), - migrations.RunPython(add_default_actions) ] diff --git a/apps/perms/migrations/0004_assetpermission_actions.py b/apps/perms/migrations/0004_assetpermission_actions.py index be468e7f0..dfe07f317 100644 --- a/apps/perms/migrations/0004_assetpermission_actions.py +++ b/apps/perms/migrations/0004_assetpermission_actions.py @@ -3,18 +3,6 @@ from django.db import migrations, models -def set_default_action_to_existing_perms(apps, schema_editor): - from orgs.utils import set_to_root_org - from ..models import Action - set_to_root_org() - perm_model = apps.get_model('perms', 'AssetPermission') - db_alias = schema_editor.connection.alias - perms = perm_model.objects.using(db_alias).all() - default_action = Action.get_action_all() - for perm in perms: - perm.actions.add(default_action.id) - - class Migration(migrations.Migration): dependencies = [ @@ -27,5 +15,4 @@ class Migration(migrations.Migration): name='actions', field=models.ManyToManyField(blank=True, related_name='permissions', to='perms.Action', verbose_name='Action'), ), - migrations.RunPython(set_default_action_to_existing_perms) ] diff --git a/apps/perms/migrations/0006_auto_20190628_1921.py b/apps/perms/migrations/0006_auto_20190628_1921.py index 0ea832751..f0faec017 100644 --- a/apps/perms/migrations/0006_auto_20190628_1921.py +++ b/apps/perms/migrations/0006_auto_20190628_1921.py @@ -6,21 +6,22 @@ from functools import reduce def migrate_old_actions(apps, schema_editor): from orgs.utils import set_to_root_org - from ..models import ActionFlag set_to_root_org() perm_model = apps.get_model('perms', 'AssetPermission') db_alias = schema_editor.connection.alias perms = perm_model.objects.using(db_alias).all() actions_map = { - "all": ActionFlag.ALL, - "connect": ActionFlag.CONNECT, - "upload_file": ActionFlag.UPLOAD, - "download_file": ActionFlag.DOWNLOAD, + "all": 0b11111111, + "connect": 0b00000001, + "upload_file": 0b00000010, + "download_file": 0b00000100, } for perm in perms: actions = perm.actions.all() - new_actions = [actions_map.get(action.name, ActionFlag.ALL) for action in actions] + if not actions: + continue + new_actions = [actions_map.get(action.name, 0b11111111) for action in actions] new_action = reduce(lambda x, y: x | y, new_actions) perm.action = new_action perm.save() diff --git a/apps/perms/migrations/0007_remove_assetpermission_actions.py b/apps/perms/migrations/0007_remove_assetpermission_actions.py index 63391ff85..1d8b3fbbb 100644 --- a/apps/perms/migrations/0007_remove_assetpermission_actions.py +++ b/apps/perms/migrations/0007_remove_assetpermission_actions.py @@ -19,4 +19,5 @@ class Migration(migrations.Migration): old_name='action', new_name='actions', ), + migrations.DeleteModel(name='Action'), ] diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index da8001227..bcb4fb0ee 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -4,37 +4,18 @@ from functools import reduce from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils import date_expired_default, set_or_append_attr_bulk +from common.utils import date_expired_default from orgs.mixins import OrgModelMixin -from ..const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL from .base import BasePermission __all__ = [ - 'Action', 'AssetPermission', 'NodePermission', 'ActionFlag' + 'AssetPermission', 'NodePermission', 'Action', ] -class Action(models.Model): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField( - max_length=128, unique=True, choices=PERMS_ACTION_NAME_CHOICES, - verbose_name=_('Name') - ) - - class Meta: - verbose_name = _('Action') - - def __str__(self): - return self.get_name_display() - - @classmethod - def get_action_all(cls): - return cls.objects.get(name=PERMS_ACTION_NAME_ALL) - - -class ActionFlag: +class Action: CONNECT = 0b00000001 UPLOAD = 0b00000010 DOWNLOAD = 0b00000100 @@ -86,7 +67,7 @@ class AssetPermission(BasePermission): nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user")) # actions = models.ManyToManyField(Action, related_name='permissions', blank=True, verbose_name=_('Action')) - actions = models.IntegerField(choices=ActionFlag.DB_CHOICES, default=ActionFlag.ALL, verbose_name=_("Actions")) + actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions")) class Meta: unique_together = [('org_id', 'name')] diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 5a770b527..13197dd24 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -5,7 +5,7 @@ from rest_framework import serializers from common.fields import StringManyToManyField from orgs.mixins import BulkOrgResourceModelSerializer -from perms.models import AssetPermission, ActionFlag +from perms.models import AssetPermission, Action __all__ = [ 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', @@ -16,20 +16,20 @@ __all__ = [ class ActionField(serializers.MultipleChoiceField): def __init__(self, *args, **kwargs): - kwargs['choices'] = ActionFlag.CHOICES + kwargs['choices'] = Action.CHOICES super().__init__(*args, **kwargs) def to_representation(self, value): - return ActionFlag.value_to_choices(value) + return Action.value_to_choices(value) def to_internal_value(self, data): - return ActionFlag.choices_to_value(data) + return Action.choices_to_value(data) class ActionDisplayField(ActionField): def to_representation(self, value): values = super().to_representation(value) - choices = dict(ActionFlag.CHOICES) + choices = dict(Action.CHOICES) return [choices.get(i) for i in values] diff --git a/apps/perms/signals_handler.py b/apps/perms/signals_handler.py index a1813916d..e8f2eecbf 100644 --- a/apps/perms/signals_handler.py +++ b/apps/perms/signals_handler.py @@ -6,7 +6,7 @@ from django.db import transaction from common.utils import get_logger from .utils import AssetPermissionUtil -from .models import AssetPermission, Action +from .models import AssetPermission logger = get_logger(__file__) diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index fbf0e0d6b..4f2b8561f 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -17,7 +17,7 @@ from orgs.utils import set_to_root_org from common.utils import get_logger from common.tree import TreeNode from .. import const -from ..models import AssetPermission, Action, ActionFlag +from ..models import AssetPermission, Action from ..hands import Node, Asset from assets.utils import NodeUtil @@ -569,7 +569,7 @@ def parse_asset_to_tree_node(node, asset, system_users): 'protocol': system_user.protocol, 'priority': system_user.priority, 'login_mode': system_user.login_mode, - 'actions': [ActionFlag.value_to_choices(action)], + 'actions': [Action.value_to_choices(action)], }) data = { 'id': str(asset.id), diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py index e354e7479..cf4b6a1ed 100644 --- a/apps/perms/views/asset_permission.py +++ b/apps/perms/views/asset_permission.py @@ -10,10 +10,9 @@ from django.conf import settings from common.permissions import PermissionsMixin, IsOrgAdmin from orgs.utils import current_org -from perms.hands import Node, Asset, SystemUser, User, UserGroup -from perms.models import AssetPermission, Action +from perms.hands import Node, Asset, SystemUser, UserGroup +from perms.models import AssetPermission from perms.forms import AssetPermissionForm -from perms.const import PERMS_ACTION_NAME_ALL __all__ = [ From 003601bbddf8db6b35a0f6b5905c870420444b05 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 2 Jul 2019 17:48:29 +0800 Subject: [PATCH 29/53] =?UTF-8?q?[Bugfix]=20=E8=A7=A3=E5=86=B3=E8=BF=9B?= =?UTF-8?q?=E5=85=A5=E6=8E=88=E6=9D=83=E8=AF=A6=E6=83=85=E9=A1=B5=E7=9A=84?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 8 ++++++++ apps/perms/views/asset_permission.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index da8001227..aba64ee0c 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -96,6 +96,14 @@ class AssetPermission(BasePermission): def get_queryset_with_prefetch(cls): return cls.objects.all().valid().prefetch_related('nodes', 'assets', 'system_users') + def get_all_assets(self): + assets = set(self.assets.all()) + for node in self.nodes.all(): + _assets = node.get_all_assets() + set_or_append_attr_bulk(_assets, 'inherit', node.value) + assets.update(set(_assets)) + return assets + class NodePermission(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py index e354e7479..ca2d27e42 100644 --- a/apps/perms/views/asset_permission.py +++ b/apps/perms/views/asset_permission.py @@ -156,7 +156,7 @@ class AssetPermissionAssetView(PermissionsMixin, permission_classes = [IsOrgAdmin] def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset = AssetPermission.objects.all()) + self.object = self.get_object(queryset=AssetPermission.objects.all()) return super().get(request, *args, **kwargs) def get_queryset(self): From 7bf1555c676ed291a516a639a8faa016e03568ec Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 17:51:50 +0800 Subject: [PATCH 30/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9Minxs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0002_auto_20180105_1807.py | 35 --- .../migrations/0003_auto_20180109_2331.py | 22 -- .../migrations/0004_auto_20180125_1218.py | 20 -- .../migrations/0005_auto_20180126_1637.py | 40 --- .../migrations/0006_auto_20180130_1502.py | 39 --- .../migrations/0007_auto_20180225_1815.py | 60 ----- .../migrations/0008_auto_20180306_1804.py | 40 --- .../migrations/0009_auto_20180307_1212.py | 20 -- .../migrations/0010_auto_20180307_1749.py | 20 -- .../migrations/0011_auto_20180326_0957.py | 55 ---- .../migrations/0012_auto_20180404_1302.py | 21 -- .../migrations/0013_auto_20180411_1135.py | 25 -- .../migrations/0014_auto_20180427_1245.py | 31 --- .../migrations/0015_auto_20180510_1235.py | 31 --- .../migrations/0016_auto_20180511_1203.py | 20 -- .../migrations/0017_auto_20180702_1415.py | 58 ----- .../migrations/0018_auto_20180807_1116.py | 84 ------- .../migrations/0019_auto_20180816_1320.py | 22 -- .../migrations/0033_auto_20190624_2108.py | 1 - apps/common/mixins.py | 234 ------------------ apps/common/mixins/__init__.py | 6 + apps/common/mixins/api.py | 84 +++++++ apps/common/mixins/models.py | 42 ++++ apps/common/mixins/serializers.py | 95 +++++++ apps/common/mixins/views.py | 40 +++ apps/jumpserver/settings.py | 8 +- apps/perms/models/asset_permission.py | 2 + apps/perms/serializers/asset_permission.py | 12 +- apps/perms/serializers/user_permission.py | 6 +- apps/settings/signals_handler.py | 1 + 30 files changed, 284 insertions(+), 890 deletions(-) delete mode 100644 apps/assets/migrations/0002_auto_20180105_1807.py delete mode 100644 apps/assets/migrations/0003_auto_20180109_2331.py delete mode 100644 apps/assets/migrations/0004_auto_20180125_1218.py delete mode 100644 apps/assets/migrations/0005_auto_20180126_1637.py delete mode 100644 apps/assets/migrations/0006_auto_20180130_1502.py delete mode 100644 apps/assets/migrations/0007_auto_20180225_1815.py delete mode 100644 apps/assets/migrations/0008_auto_20180306_1804.py delete mode 100644 apps/assets/migrations/0009_auto_20180307_1212.py delete mode 100644 apps/assets/migrations/0010_auto_20180307_1749.py delete mode 100644 apps/assets/migrations/0011_auto_20180326_0957.py delete mode 100644 apps/assets/migrations/0012_auto_20180404_1302.py delete mode 100644 apps/assets/migrations/0013_auto_20180411_1135.py delete mode 100644 apps/assets/migrations/0014_auto_20180427_1245.py delete mode 100644 apps/assets/migrations/0015_auto_20180510_1235.py delete mode 100644 apps/assets/migrations/0016_auto_20180511_1203.py delete mode 100644 apps/assets/migrations/0017_auto_20180702_1415.py delete mode 100644 apps/assets/migrations/0018_auto_20180807_1116.py delete mode 100644 apps/assets/migrations/0019_auto_20180816_1320.py delete mode 100644 apps/common/mixins.py create mode 100644 apps/common/mixins/__init__.py create mode 100644 apps/common/mixins/api.py create mode 100644 apps/common/mixins/models.py create mode 100644 apps/common/mixins/serializers.py create mode 100644 apps/common/mixins/views.py diff --git a/apps/assets/migrations/0002_auto_20180105_1807.py b/apps/assets/migrations/0002_auto_20180105_1807.py deleted file mode 100644 index bf1f022ac..000000000 --- a/apps/assets/migrations/0002_auto_20180105_1807.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-01-05 10:07 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='adminuser', - options={'ordering': ['name'], 'verbose_name': 'Admin user'}, - ), - migrations.AlterModelOptions( - name='asset', - options={'verbose_name': 'Asset'}, - ), - migrations.AlterModelOptions( - name='assetgroup', - options={'ordering': ['name'], 'verbose_name': 'Asset group'}, - ), - migrations.AlterModelOptions( - name='cluster', - options={'ordering': ['name'], 'verbose_name': 'Cluster'}, - ), - migrations.AlterModelOptions( - name='systemuser', - options={'ordering': ['name'], 'verbose_name': 'System user'}, - ), - ] diff --git a/apps/assets/migrations/0003_auto_20180109_2331.py b/apps/assets/migrations/0003_auto_20180109_2331.py deleted file mode 100644 index 254de6236..000000000 --- a/apps/assets/migrations/0003_auto_20180109_2331.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-01-09 15:31 -from __future__ import unicode_literals - -import assets.models.asset -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0002_auto_20180105_1807'), - ] - - operations = [ - migrations.AlterField( - model_name='asset', - name='cluster', - field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), - ), - ] diff --git a/apps/assets/migrations/0004_auto_20180125_1218.py b/apps/assets/migrations/0004_auto_20180125_1218.py deleted file mode 100644 index 1886fa499..000000000 --- a/apps/assets/migrations/0004_auto_20180125_1218.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-01-25 04:18 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0003_auto_20180109_2331'), - ] - - operations = [ - migrations.AlterField( - model_name='assetgroup', - name='created_by', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), - ), - ] diff --git a/apps/assets/migrations/0005_auto_20180126_1637.py b/apps/assets/migrations/0005_auto_20180126_1637.py deleted file mode 100644 index 8db19e482..000000000 --- a/apps/assets/migrations/0005_auto_20180126_1637.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-01-26 08:37 -from __future__ import unicode_literals - -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0004_auto_20180125_1218'), - ] - - operations = [ - migrations.CreateModel( - name='Label', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, verbose_name='Name')), - ('value', models.CharField(max_length=128, verbose_name='Value')), - ('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')), - ('is_active', models.BooleanField(default=True, verbose_name='Is active')), - ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ], - options={ - 'db_table': 'assets_label', - }, - ), - migrations.AlterUniqueTogether( - name='label', - unique_together=set([('name', 'value')]), - ), - migrations.AddField( - model_name='asset', - name='labels', - field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'), - ), - ] diff --git a/apps/assets/migrations/0006_auto_20180130_1502.py b/apps/assets/migrations/0006_auto_20180130_1502.py deleted file mode 100644 index b77470d27..000000000 --- a/apps/assets/migrations/0006_auto_20180130_1502.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-01-30 07:02 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0005_auto_20180126_1637'), - ] - - operations = [ - migrations.RemoveField( - model_name='asset', - name='cabinet_no', - ), - migrations.RemoveField( - model_name='asset', - name='cabinet_pos', - ), - migrations.RemoveField( - model_name='asset', - name='env', - ), - migrations.RemoveField( - model_name='asset', - name='remote_card_ip', - ), - migrations.RemoveField( - model_name='asset', - name='status', - ), - migrations.RemoveField( - model_name='asset', - name='type', - ), - ] diff --git a/apps/assets/migrations/0007_auto_20180225_1815.py b/apps/assets/migrations/0007_auto_20180225_1815.py deleted file mode 100644 index 009381bcb..000000000 --- a/apps/assets/migrations/0007_auto_20180225_1815.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-25 10:15 -from __future__ import unicode_literals - -import assets.models.asset -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0006_auto_20180130_1502'), - ] - - operations = [ - migrations.CreateModel( - name='Node', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('key', models.CharField(max_length=64, unique=True, verbose_name='Key')), - ('value', models.CharField(max_length=128, unique=True, verbose_name='Value')), - ('child_mark', models.IntegerField(default=0)), - ('date_create', models.DateTimeField(auto_now_add=True)), - ], - ), - migrations.RemoveField( - model_name='asset', - name='cluster', - ), - migrations.RemoveField( - model_name='asset', - name='groups', - ), - migrations.RemoveField( - model_name='systemuser', - name='cluster', - ), - migrations.AlterField( - model_name='asset', - name='admin_user', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'), - ), - migrations.AlterField( - model_name='systemuser', - name='protocol', - field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'), - ), - migrations.AddField( - model_name='asset', - name='nodes', - field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'), - ), - migrations.AddField( - model_name='systemuser', - name='nodes', - field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'), - ), - ] diff --git a/apps/assets/migrations/0008_auto_20180306_1804.py b/apps/assets/migrations/0008_auto_20180306_1804.py deleted file mode 100644 index 48d352619..000000000 --- a/apps/assets/migrations/0008_auto_20180306_1804.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-03-06 10:04 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0007_auto_20180225_1815'), - ] - - operations = [ - migrations.AlterField( - model_name='adminuser', - name='created_by', - field=models.CharField(max_length=128, null=True, verbose_name='Created by'), - ), - migrations.AlterField( - model_name='adminuser', - name='username', - field=models.CharField(max_length=128, verbose_name='Username'), - ), - migrations.AlterField( - model_name='asset', - name='platform', - field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'), - ), - migrations.AlterField( - model_name='systemuser', - name='created_by', - field=models.CharField(max_length=128, null=True, verbose_name='Created by'), - ), - migrations.AlterField( - model_name='systemuser', - name='username', - field=models.CharField(max_length=128, verbose_name='Username'), - ), - ] diff --git a/apps/assets/migrations/0009_auto_20180307_1212.py b/apps/assets/migrations/0009_auto_20180307_1212.py deleted file mode 100644 index 08d770642..000000000 --- a/apps/assets/migrations/0009_auto_20180307_1212.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-03-07 04:12 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0008_auto_20180306_1804'), - ] - - operations = [ - migrations.AlterField( - model_name='node', - name='value', - field=models.CharField(max_length=128, verbose_name='Value'), - ), - ] diff --git a/apps/assets/migrations/0010_auto_20180307_1749.py b/apps/assets/migrations/0010_auto_20180307_1749.py deleted file mode 100644 index 5e6be0943..000000000 --- a/apps/assets/migrations/0010_auto_20180307_1749.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-03-07 09:49 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0009_auto_20180307_1212'), - ] - - operations = [ - migrations.AlterField( - model_name='node', - name='value', - field=models.CharField(max_length=128, unique=True, verbose_name='Value'), - ), - ] diff --git a/apps/assets/migrations/0011_auto_20180326_0957.py b/apps/assets/migrations/0011_auto_20180326_0957.py deleted file mode 100644 index 07b9055dc..000000000 --- a/apps/assets/migrations/0011_auto_20180326_0957.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-03-26 01:57 -from __future__ import unicode_literals - -import assets.models.utils -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0010_auto_20180307_1749'), - ] - - operations = [ - migrations.CreateModel( - name='Domain', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ], - ), - migrations.CreateModel( - name='Gateway', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), - ('username', models.CharField(max_length=128, verbose_name='Username')), - ('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')), - ('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')), - ('date_created', models.DateTimeField(auto_now_add=True)), - ('date_updated', models.DateTimeField(auto_now=True)), - ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')), - ('port', models.IntegerField(default=22, verbose_name='Port')), - ('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')), - ('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')), - ('is_active', models.BooleanField(default=True, verbose_name='Is active')), - ('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='asset', - name='domain', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'), - ), - ] diff --git a/apps/assets/migrations/0012_auto_20180404_1302.py b/apps/assets/migrations/0012_auto_20180404_1302.py deleted file mode 100644 index 0ccb63e27..000000000 --- a/apps/assets/migrations/0012_auto_20180404_1302.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-04 05:02 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0011_auto_20180326_0957'), - ] - - operations = [ - migrations.AlterField( - model_name='asset', - name='domain', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'), - ), - ] diff --git a/apps/assets/migrations/0013_auto_20180411_1135.py b/apps/assets/migrations/0013_auto_20180411_1135.py deleted file mode 100644 index baaf789bd..000000000 --- a/apps/assets/migrations/0013_auto_20180411_1135.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-11 03:35 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0012_auto_20180404_1302'), - ] - - operations = [ - migrations.AddField( - model_name='systemuser', - name='assets', - field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'), - ), - migrations.AlterField( - model_name='systemuser', - name='sudo', - field=models.TextField(default='/bin/whoami', verbose_name='Sudo'), - ), - ] diff --git a/apps/assets/migrations/0014_auto_20180427_1245.py b/apps/assets/migrations/0014_auto_20180427_1245.py deleted file mode 100644 index 735a50879..000000000 --- a/apps/assets/migrations/0014_auto_20180427_1245.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-27 04:45 -from __future__ import unicode_literals - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0013_auto_20180411_1135'), - ] - - operations = [ - migrations.AlterField( - model_name='adminuser', - name='username', - field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.AlterField( - model_name='gateway', - name='username', - field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.AlterField( - model_name='systemuser', - name='username', - field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'), - ), - ] diff --git a/apps/assets/migrations/0015_auto_20180510_1235.py b/apps/assets/migrations/0015_auto_20180510_1235.py deleted file mode 100644 index 81d12d2e4..000000000 --- a/apps/assets/migrations/0015_auto_20180510_1235.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-10 04:35 -from __future__ import unicode_literals - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0014_auto_20180427_1245'), - ] - - operations = [ - migrations.AlterField( - model_name='adminuser', - name='username', - field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.AlterField( - model_name='gateway', - name='username', - field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.AlterField( - model_name='systemuser', - name='username', - field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), - ), - ] diff --git a/apps/assets/migrations/0016_auto_20180511_1203.py b/apps/assets/migrations/0016_auto_20180511_1203.py deleted file mode 100644 index 32f79a3c6..000000000 --- a/apps/assets/migrations/0016_auto_20180511_1203.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-05-11 04:03 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0015_auto_20180510_1235'), - ] - - operations = [ - migrations.AlterField( - model_name='node', - name='value', - field=models.CharField(max_length=128, verbose_name='Value'), - ), - ] diff --git a/apps/assets/migrations/0017_auto_20180702_1415.py b/apps/assets/migrations/0017_auto_20180702_1415.py deleted file mode 100644 index 9950424a6..000000000 --- a/apps/assets/migrations/0017_auto_20180702_1415.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-07-02 06:15 -from __future__ import unicode_literals - -import django.core.validators -from django.db import migrations, models - - -def migrate_win_to_ssh_protocol(apps, schema_editor): - asset_model = apps.get_model("assets", "Asset") - db_alias = schema_editor.connection.alias - asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp') - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0016_auto_20180511_1203'), - ] - - operations = [ - migrations.AddField( - model_name='asset', - name='protocol', - field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'), - ), - migrations.AddField( - model_name='systemuser', - name='login_mode', - field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'), - ), - migrations.AlterField( - model_name='adminuser', - name='username', - field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.AlterField( - model_name='asset', - name='platform', - field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'), - ), - migrations.AlterField( - model_name='gateway', - name='username', - field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.AlterField( - model_name='systemuser', - name='protocol', - field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'), - ), - migrations.AlterField( - model_name='systemuser', - name='username', - field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), - ), - migrations.RunPython(migrate_win_to_ssh_protocol), - ] diff --git a/apps/assets/migrations/0018_auto_20180807_1116.py b/apps/assets/migrations/0018_auto_20180807_1116.py deleted file mode 100644 index c4e848b43..000000000 --- a/apps/assets/migrations/0018_auto_20180807_1116.py +++ /dev/null @@ -1,84 +0,0 @@ -# Generated by Django 2.0.7 on 2018-08-07 03:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0017_auto_20180702_1415'), - ] - - operations = [ - migrations.AddField( - model_name='adminuser', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AddField( - model_name='asset', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AddField( - model_name='domain', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AddField( - model_name='gateway', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AddField( - model_name='label', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AddField( - model_name='node', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AddField( - model_name='systemuser', - name='org_id', - field=models.CharField(blank=True, default=None, max_length=36, null=True), - ), - migrations.AlterField( - model_name='adminuser', - name='name', - field=models.CharField(max_length=128, verbose_name='Name'), - ), - migrations.AlterField( - model_name='asset', - name='hostname', - field=models.CharField(max_length=128, verbose_name='Hostname'), - ), - migrations.AlterField( - model_name='gateway', - name='name', - field=models.CharField(max_length=128, verbose_name='Name'), - ), - migrations.AlterField( - model_name='systemuser', - name='name', - field=models.CharField(max_length=128, verbose_name='Name'), - ), - migrations.AlterUniqueTogether( - name='adminuser', - unique_together={('name', 'org_id')}, - ), - migrations.AlterUniqueTogether( - name='asset', - unique_together={('org_id', 'hostname')}, - ), - migrations.AlterUniqueTogether( - name='gateway', - unique_together={('name', 'org_id')}, - ), - migrations.AlterUniqueTogether( - name='systemuser', - unique_together={('name', 'org_id')}, - ), - ] diff --git a/apps/assets/migrations/0019_auto_20180816_1320.py b/apps/assets/migrations/0019_auto_20180816_1320.py deleted file mode 100644 index 0d468e511..000000000 --- a/apps/assets/migrations/0019_auto_20180816_1320.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.7 on 2018-08-16 05:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0018_auto_20180807_1116'), - ] - - operations = [ - migrations.AddField( - model_name='asset', - name='cpu_vcpus', - field=models.IntegerField(null=True, verbose_name='CPU vcpus'), - ), - migrations.AlterUniqueTogether( - name='label', - unique_together={('name', 'value', 'org_id')}, - ), - ] diff --git a/apps/assets/migrations/0033_auto_20190624_2108.py b/apps/assets/migrations/0033_auto_20190624_2108.py index c9ac245d0..dc89bc125 100644 --- a/apps/assets/migrations/0033_auto_20190624_2108.py +++ b/apps/assets/migrations/0033_auto_20190624_2108.py @@ -1,6 +1,5 @@ # Generated by Django 2.1.7 on 2019-06-24 13:08 -import common.fields.model from django.db import migrations diff --git a/apps/common/mixins.py b/apps/common/mixins.py deleted file mode 100644 index b46673cd6..000000000 --- a/apps/common/mixins.py +++ /dev/null @@ -1,234 +0,0 @@ -# coding: utf-8 - -from django.db import models -from django.http import JsonResponse -from django.utils import timezone -from django.core.cache import cache -from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages -from rest_framework.utils import html -from rest_framework.settings import api_settings -from rest_framework.exceptions import ValidationError -from rest_framework.fields import SkipField - -from .const import KEY_CACHE_RESOURCES_ID - - -class NoDeleteQuerySet(models.query.QuerySet): - - def delete(self): - return self.update(is_discard=True, discard_time=timezone.now()) - - -class NoDeleteManager(models.Manager): - - def get_all(self): - return NoDeleteQuerySet(self.model, using=self._db) - - def get_queryset(self): - return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False) - - def get_deleted(self): - return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True) - - -class NoDeleteModelMixin(models.Model): - is_discard = models.BooleanField(verbose_name=_("is discard"), default=False) - discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True) - - objects = NoDeleteManager() - - class Meta: - abstract = True - - def delete(self): - self.is_discard = True - self.discard_time = timezone.now() - return self.save() - - -class JSONResponseMixin(object): - """JSON mixin""" - @staticmethod - def render_json_response(context): - return JsonResponse(context) - - -class IDInFilterMixin(object): - def filter_queryset(self, queryset): - queryset = super(IDInFilterMixin, self).filter_queryset(queryset) - id_list = self.request.query_params.get('id__in') - if id_list: - import json - try: - ids = json.loads(id_list) - except Exception as e: - return queryset - if isinstance(ids, list): - queryset = queryset.filter(id__in=ids) - return queryset - - -class IDInCacheFilterMixin(object): - - def filter_queryset(self, queryset): - queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset) - spm = self.request.query_params.get('spm') - cache_key = KEY_CACHE_RESOURCES_ID.format(spm) - resources_id = cache.get(cache_key) - if resources_id and isinstance(resources_id, list): - queryset = queryset.filter(id__in=resources_id) - return queryset - - -class IDExportFilterMixin(object): - def filter_queryset(self, queryset): - # 下载导入模版 - if self.request.query_params.get('template') == 'import': - return [] - else: - return super(IDExportFilterMixin, self).filter_queryset(queryset) - - -class BulkSerializerMixin(object): - """ - Become rest_framework_bulk not support uuid as a primary key - so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66 - """ - def to_internal_value(self, data): - from rest_framework_bulk import BulkListSerializer - ret = super(BulkSerializerMixin, self).to_internal_value(data) - - id_attr = getattr(self.Meta, 'update_lookup_field', 'id') - if self.context.get('view'): - request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '') - # add update_lookup_field field back to validated data - # since super by default strips out read-only fields - # hence id will no longer be present in validated_data - if all((isinstance(self.root, BulkListSerializer), - id_attr, - request_method in ('PUT', 'PATCH'))): - id_field = self.fields[id_attr] - if data.get("id"): - id_value = id_field.to_internal_value(data.get("id")) - else: - id_value = id_field.to_internal_value(data.get("pk")) - ret[id_attr] = id_value - - return ret - - -class BulkListSerializerMixin(object): - """ - Become rest_framework_bulk doing bulk update raise Exception: - 'QuerySet' object has no attribute 'pk' when doing bulk update - so rewrite it . - https://github.com/miki725/django-rest-framework-bulk/issues/68 - """ - - def to_internal_value(self, data): - """ - List of dicts of native values <- List of dicts of primitive datatypes. - """ - if html.is_html_input(data): - data = html.parse_html_list(data) - - if not isinstance(data, list): - message = self.error_messages['not_a_list'].format( - input_type=type(data).__name__ - ) - raise ValidationError({ - api_settings.NON_FIELD_ERRORS_KEY: [message] - }, code='not_a_list') - - if not self.allow_empty and len(data) == 0: - if self.parent and self.partial: - raise SkipField() - - message = self.error_messages['empty'] - raise ValidationError({ - api_settings.NON_FIELD_ERRORS_KEY: [message] - }, code='empty') - - ret = [] - errors = [] - - for item in data: - try: - # prepare child serializer to only handle one instance - if 'id' in item.keys(): - self.child.instance = self.instance.get(id=item['id']) if self.instance else None - if 'pk' in item.keys(): - self.child.instance = self.instance.get(id=item['pk']) if self.instance else None - - self.child.initial_data = item - # raw - validated = self.child.run_validation(item) - except ValidationError as exc: - errors.append(exc.detail) - else: - ret.append(validated) - errors.append({}) - - if any(errors): - raise ValidationError(errors) - - return ret - - -class DatetimeSearchMixin: - date_format = '%Y-%m-%d' - date_from = date_to = None - - def get_date_range(self): - date_from_s = self.request.GET.get('date_from') - date_to_s = self.request.GET.get('date_to') - - if date_from_s: - date_from = timezone.datetime.strptime(date_from_s, self.date_format) - tz = timezone.get_current_timezone() - self.date_from = tz.localize(date_from) - else: - self.date_from = timezone.now() - timezone.timedelta(7) - - if date_to_s: - date_to = timezone.datetime.strptime( - date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S' - ) - self.date_to = date_to.replace( - tzinfo=timezone.get_current_timezone() - ) - else: - self.date_to = timezone.now() - - def get(self, request, *args, **kwargs): - self.get_date_range() - return super().get(request, *args, **kwargs) - - -class ApiMessageMixin: - success_message = _("%(name)s was %(action)s successfully") - _action_map = {"create": _("create"), "update": _("update")} - - def get_success_message(self, cleaned_data): - if not isinstance(cleaned_data, dict): - return '' - data = {k: v for k, v in cleaned_data.items()} - action = getattr(self, "action", "create") - data["action"] = self._action_map.get(action) - try: - message = self.success_message % data - except: - message = '' - return message - - def dispatch(self, request, *args, **kwargs): - resp = super().dispatch(request, *args, **kwargs) - if request.method.lower() in ("get", "delete", "patch"): - return resp - if resp.status_code >= 400: - return resp - message = self.get_success_message(resp.data) - if message: - messages.success(request, message) - return resp diff --git a/apps/common/mixins/__init__.py b/apps/common/mixins/__init__.py new file mode 100644 index 000000000..4249b3d64 --- /dev/null +++ b/apps/common/mixins/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# +from .models import * +from .serializers import * +from .api import * +from .views import * diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py new file mode 100644 index 000000000..410f077eb --- /dev/null +++ b/apps/common/mixins/api.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# +from django.http import JsonResponse +from django.core.cache import cache +from django.utils.translation import ugettext_lazy as _ +from django.contrib import messages + +from ..const import KEY_CACHE_RESOURCES_ID + +__all__ = [ + "JSONResponseMixin", "IDInCacheFilterMixin", "IDExportFilterMixin", + "IDInFilterMixin", "ApiMessageMixin" +] + + +class JSONResponseMixin(object): + """JSON mixin""" + @staticmethod + def render_json_response(context): + return JsonResponse(context) + + +class IDInFilterMixin(object): + def filter_queryset(self, queryset): + queryset = super(IDInFilterMixin, self).filter_queryset(queryset) + id_list = self.request.query_params.get('id__in') + if id_list: + import json + try: + ids = json.loads(id_list) + except Exception as e: + return queryset + if isinstance(ids, list): + queryset = queryset.filter(id__in=ids) + return queryset + + +class IDInCacheFilterMixin(object): + + def filter_queryset(self, queryset): + queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset) + spm = self.request.query_params.get('spm') + cache_key = KEY_CACHE_RESOURCES_ID.format(spm) + resources_id = cache.get(cache_key) + if resources_id and isinstance(resources_id, list): + queryset = queryset.filter(id__in=resources_id) + return queryset + + +class IDExportFilterMixin(object): + def filter_queryset(self, queryset): + # 下载导入模版 + if self.request.query_params.get('template') == 'import': + return [] + else: + return super(IDExportFilterMixin, self).filter_queryset(queryset) + + +class ApiMessageMixin: + success_message = _("%(name)s was %(action)s successfully") + _action_map = {"create": _("create"), "update": _("update")} + + def get_success_message(self, cleaned_data): + if not isinstance(cleaned_data, dict): + return '' + data = {k: v for k, v in cleaned_data.items()} + action = getattr(self, "action", "create") + data["action"] = self._action_map.get(action) + try: + message = self.success_message % data + except: + message = '' + return message + + def dispatch(self, request, *args, **kwargs): + resp = super().dispatch(request, *args, **kwargs) + if request.method.lower() in ("get", "delete", "patch"): + return resp + if resp.status_code >= 400: + return resp + message = self.get_success_message(resp.data) + if message: + messages.success(request, message) + return resp \ No newline at end of file diff --git a/apps/common/mixins/models.py b/apps/common/mixins/models.py new file mode 100644 index 000000000..d4af23896 --- /dev/null +++ b/apps/common/mixins/models.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# + +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + + +__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"] + + +class NoDeleteQuerySet(models.query.QuerySet): + + def delete(self): + return self.update(is_discard=True, discard_time=timezone.now()) + + +class NoDeleteManager(models.Manager): + + def get_all(self): + return NoDeleteQuerySet(self.model, using=self._db) + + def get_queryset(self): + return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False) + + def get_deleted(self): + return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True) + + +class NoDeleteModelMixin(models.Model): + is_discard = models.BooleanField(verbose_name=_("is discard"), default=False) + discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True) + + objects = NoDeleteManager() + + class Meta: + abstract = True + + def delete(self): + self.is_discard = True + self.discard_time = timezone.now() + return self.save() diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py new file mode 100644 index 000000000..009d0b994 --- /dev/null +++ b/apps/common/mixins/serializers.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework.utils import html +from rest_framework.settings import api_settings +from rest_framework.exceptions import ValidationError +from rest_framework.fields import SkipField + +__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin'] + + +class BulkSerializerMixin(object): + """ + Become rest_framework_bulk not support uuid as a primary key + so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66 + """ + def to_internal_value(self, data): + from rest_framework_bulk import BulkListSerializer + ret = super(BulkSerializerMixin, self).to_internal_value(data) + + id_attr = getattr(self.Meta, 'update_lookup_field', 'id') + if self.context.get('view'): + request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '') + # add update_lookup_field field back to validated data + # since super by default strips out read-only fields + # hence id will no longer be present in validated_data + if all((isinstance(self.root, BulkListSerializer), + id_attr, + request_method in ('PUT', 'PATCH'))): + id_field = self.fields[id_attr] + if data.get("id"): + id_value = id_field.to_internal_value(data.get("id")) + else: + id_value = id_field.to_internal_value(data.get("pk")) + ret[id_attr] = id_value + + return ret + + +class BulkListSerializerMixin(object): + """ + Become rest_framework_bulk doing bulk update raise Exception: + 'QuerySet' object has no attribute 'pk' when doing bulk update + so rewrite it . + https://github.com/miki725/django-rest-framework-bulk/issues/68 + """ + + def to_internal_value(self, data): + """ + List of dicts of native values <- List of dicts of primitive datatypes. + """ + if html.is_html_input(data): + data = html.parse_html_list(data) + + if not isinstance(data, list): + message = self.error_messages['not_a_list'].format( + input_type=type(data).__name__ + ) + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: [message] + }, code='not_a_list') + + if not self.allow_empty and len(data) == 0: + if self.parent and self.partial: + raise SkipField() + + message = self.error_messages['empty'] + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: [message] + }, code='empty') + + ret = [] + errors = [] + + for item in data: + try: + # prepare child serializer to only handle one instance + if 'id' in item.keys(): + self.child.instance = self.instance.get(id=item['id']) if self.instance else None + if 'pk' in item.keys(): + self.child.instance = self.instance.get(id=item['pk']) if self.instance else None + + self.child.initial_data = item + # raw + validated = self.child.run_validation(item) + except ValidationError as exc: + errors.append(exc.detail) + else: + ret.append(validated) + errors.append({}) + + if any(errors): + raise ValidationError(errors) + + return ret diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py new file mode 100644 index 000000000..a00535b5b --- /dev/null +++ b/apps/common/mixins/views.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# coding: utf-8 + +from django.utils import timezone + + +__all__ = ["DatetimeSearchMixin"] + + +class DatetimeSearchMixin: + date_format = '%Y-%m-%d' + date_from = date_to = None + + def get_date_range(self): + date_from_s = self.request.GET.get('date_from') + date_to_s = self.request.GET.get('date_to') + + if date_from_s: + date_from = timezone.datetime.strptime(date_from_s, self.date_format) + tz = timezone.get_current_timezone() + self.date_from = tz.localize(date_from) + else: + self.date_from = timezone.now() - timezone.timedelta(7) + + if date_to_s: + date_to = timezone.datetime.strptime( + date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S' + ) + self.date_to = date_to.replace( + tzinfo=timezone.get_current_timezone() + ) + else: + self.date_to = timezone.now() + + def get(self, request, *args, **kwargs): + self.get_date_range() + return super().get(request, *args, **kwargs) + + diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 0ca68d947..0f9f7df55 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -301,10 +301,10 @@ LOGGING = { 'handlers': ['gunicorn_console', 'gunicorn_file'], 'level': 'INFO', }, - # 'django.db': { - # 'handlers': ['console', 'file'], - # 'level': 'DEBUG' - # } + 'django.db': { + 'handlers': ['console', 'file'], + 'level': 'DEBUG' + } } } diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index bcb4fb0ee..d47b00bf9 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -55,6 +55,8 @@ class Action: x = cls.NAME_MAP_REVERSE.get(x, 0) y = cls.NAME_MAP_REVERSE.get(y, 0) return x | y + if not value: + return None return reduce(to_choices, value) @classmethod diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 13197dd24..ecbed669b 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -10,11 +10,11 @@ from perms.models import AssetPermission, Action __all__ = [ 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', - 'ActionField', + 'ActionsField', ] -class ActionField(serializers.MultipleChoiceField): +class ActionsField(serializers.MultipleChoiceField): def __init__(self, *args, **kwargs): kwargs['choices'] = Action.CHOICES super().__init__(*args, **kwargs) @@ -23,10 +23,12 @@ class ActionField(serializers.MultipleChoiceField): return Action.value_to_choices(value) def to_internal_value(self, data): + if data is None: + return data return Action.choices_to_value(data) -class ActionDisplayField(ActionField): +class ActionsDisplayField(ActionsField): def to_representation(self, value): values = super().to_representation(value) choices = dict(Action.CHOICES) @@ -34,7 +36,7 @@ class ActionDisplayField(ActionField): class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer): - actions = ActionField() + actions = ActionsField(required=False, allow_null=True) class Meta: model = AssetPermission @@ -47,7 +49,7 @@ class AssetPermissionListSerializer(BulkOrgResourceModelSerializer): assets = StringManyToManyField(many=True, read_only=True) nodes = StringManyToManyField(many=True, read_only=True) system_users = StringManyToManyField(many=True, read_only=True) - actions = ActionDisplayField() + actions = ActionsDisplayField() is_valid = serializers.BooleanField() is_expired = serializers.BooleanField() diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 43cf47136..639ba7140 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -6,7 +6,7 @@ from rest_framework import serializers from assets.models import Node, SystemUser from assets.serializers import AssetSerializer -from .asset_permission import ActionField +from .asset_permission import ActionsField __all__ = [ 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', @@ -19,7 +19,7 @@ class AssetSystemUserSerializer(serializers.ModelSerializer): """ 查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少 """ - actions = ActionField(read_only=True) + actions = ActionsField(read_only=True) class Meta: model = SystemUser @@ -115,4 +115,4 @@ class GrantedNodeSerializer(serializers.ModelSerializer): class ActionsSerializer(serializers.Serializer): - actions = ActionField(read_only=True) \ No newline at end of file + actions = ActionsField(read_only=True) diff --git a/apps/settings/signals_handler.py b/apps/settings/signals_handler.py index 7a1b782f5..9302b63e2 100644 --- a/apps/settings/signals_handler.py +++ b/apps/settings/signals_handler.py @@ -77,6 +77,7 @@ def monkey_patch_settings(sender, **kwargs): @receiver(django_ready) def auto_generate_terminal_host_key(sender, **kwargs): try: + print("Auto gen host key") if Setting.objects.filter(name='TERMINAL_HOST_KEY').exists(): return private_key, public_key = ssh_key_gen() From 1b44172bc51fe67e8686b0538851582891f1a1d0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 18:23:47 +0800 Subject: [PATCH 31/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0007_remove_assetpermission_actions.py | 2 +- ...5_1157_squashed_0019_auto_20190304_1459.py | 197 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py diff --git a/apps/perms/migrations/0007_remove_assetpermission_actions.py b/apps/perms/migrations/0007_remove_assetpermission_actions.py index 1d8b3fbbb..31e083c88 100644 --- a/apps/perms/migrations/0007_remove_assetpermission_actions.py +++ b/apps/perms/migrations/0007_remove_assetpermission_actions.py @@ -19,5 +19,5 @@ class Migration(migrations.Migration): old_name='action', new_name='actions', ), - migrations.DeleteModel(name='Action'), + migrations.DeleteModel('action'), ] diff --git a/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py b/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py new file mode 100644 index 000000000..399605915 --- /dev/null +++ b/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py @@ -0,0 +1,197 @@ +# Generated by Django 2.1.7 on 2019-07-02 09:54 + +import common.utils.django +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +# Functions from the following migrations need manual copying. +# Move them and any dependencies into this file, then update the +# RunPython operations to refer to the local versions: +# users.migrations.0010_auto_20180606_1505 + +def remove_deleted_group(apps, schema_editor): + db_alias = schema_editor.connection.alias + group_model = apps.get_model("users", "UserGroup") + group_model.objects.using(db_alias).filter(is_discard=True).delete() + + +class Migration(migrations.Migration): + + replaces = [('users', '0002_auto_20171225_1157'), ('users', '0003_auto_20180101_0046'), ('users', '0004_auto_20180125_1218'), ('users', '0005_auto_20180306_1804'), ('users', '0006_auto_20180411_1135'), ('users', '0007_auto_20180419_1036'), ('users', '0008_auto_20180425_1516'), ('users', '0009_auto_20180517_1537'), ('users', '0010_auto_20180606_1505'), ('users', '0011_user_source'), ('users', '0012_auto_20180710_1641'), ('users', '0013_auto_20180807_1116'), ('users', '0014_auto_20180816_1652'), ('users', '0015_auto_20181105_1112'), ('users', '0016_auto_20181109_1505'), ('users', '0017_auto_20181123_1113'), ('users', '0018_auto_20190107_1912'), ('users', '0019_auto_20190304_1459')] + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=128, unique=True, verbose_name='Email'), + ), + migrations.AlterField( + model_name='user', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterField( + model_name='user', + name='username', + field=models.CharField(max_length=128, unique=True, verbose_name='Username'), + ), + migrations.AlterField( + model_name='user', + name='wechat', + field=models.CharField(blank=True, max_length=128, verbose_name='Wechat'), + ), + migrations.AlterField( + model_name='user', + name='is_first_login', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='usergroup', + name='created_by', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterModelOptions( + name='user', + options={'ordering': ['username'], 'verbose_name': 'User'}, + ), + migrations.AlterModelOptions( + name='usergroup', + options={'ordering': ['name'], 'verbose_name': 'User group'}, + ), + migrations.RenameField( + model_name='user', + old_name='secret_key_otp', + new_name='otp_secret_key', + ), + migrations.RemoveField( + model_name='user', + name='enable_otp', + ), + migrations.AddField( + model_name='user', + name='otp_level', + field=models.SmallIntegerField(choices=[(0, 'Disable'), (1, 'Enable'), (2, 'Force enable')], default=0, verbose_name='MFA'), + ), + migrations.RemoveField( + model_name='user', + name='otp_secret_key', + ), + migrations.AddField( + model_name='user', + name='_otp_secret_key', + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AlterField( + model_name='usergroup', + name='name', + field=models.CharField(max_length=128, unique=True, verbose_name='Name'), + ), + migrations.RunPython( + code=remove_deleted_group, + ), + migrations.RemoveField( + model_name='usergroup', + name='discard_time', + ), + migrations.RemoveField( + model_name='usergroup', + name='is_discard', + ), + migrations.AlterField( + model_name='user', + name='date_expired', + field=models.DateTimeField(blank=True, db_index=True, default=common.utils.django.date_expired_default, null=True, verbose_name='Date expired'), + ), + migrations.AddField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius')], default='local', max_length=30, verbose_name='Source'), + ), + migrations.AddField( + model_name='loginlog', + name='mfa', + field=models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA'), + ), + migrations.AddField( + model_name='loginlog', + name='reason', + field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason'), + ), + migrations.AddField( + model_name='loginlog', + name='status', + field=models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status'), + ), + migrations.AddField( + model_name='usergroup', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AlterField( + model_name='user', + name='last_name', + field=models.CharField(blank=True, max_length=150, verbose_name='last name'), + ), + migrations.AlterField( + model_name='usergroup', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterUniqueTogether( + name='usergroup', + unique_together={('org_id', 'name')}, + ), + migrations.AlterField( + model_name='usergroup', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + migrations.AlterField( + model_name='loginlog', + name='username', + field=models.CharField(max_length=128, verbose_name='Username'), + ), + migrations.AddField( + model_name='user', + name='date_password_last_updated', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated'), + ), + migrations.AlterField( + model_name='accesskey', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_keys', to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + migrations.SeparateDatabaseAndState( + database_operations=[ + migrations.AlterModelTable( + name='accesskey', + table='authentication_accesskey', + ), + migrations.AlterModelTable( + name='privatetoken', + table='authentication_privatetoken', + ), + migrations.AlterModelTable( + name='loginlog', + table='audits_userloginlog', + ), + ], + state_operations=[ + migrations.DeleteModel( + name='accesskey', + ), + migrations.DeleteModel( + name='privatetoken', + ), + migrations.DeleteModel( + name='loginlog', + ), + ], + ), + ] From 186c22decd08ba91bf09043a28b247bc02b9f6da Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 2 Jul 2019 19:30:59 +0800 Subject: [PATCH 32/53] =?UTF-8?q?[Update]=20=E8=B5=84=E4=BA=A7=E6=8E=88?= =?UTF-8?q?=E6=9D=83Model=E6=A8=A1=E5=9D=97=E6=B7=BB=E5=8A=A0=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 29d8d10ec..296e0e5d4 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -4,7 +4,7 @@ from functools import reduce from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils import date_expired_default +from common.utils import date_expired_default, set_or_append_attr_bulk from orgs.mixins import OrgModelMixin from .base import BasePermission From b9f82fd0aca178c55c2fff597a6e87366ac7bc0f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jul 2019 22:08:50 +0800 Subject: [PATCH 33/53] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=AE=B0=E5=BD=95=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/assets/_asset_user_list.html | 4 +- .../templates/assets/system_user_list.html | 42 ++-- apps/common/apps.py | 3 +- apps/orgs/models.py | 8 + apps/orgs/signals_handler.py | 5 + apps/perms/api/user_permission.py | 1 + apps/perms/utils/asset_permission.py | 133 ++++++------ apps/settings/signals_handler.py | 2 +- apps/static/js/jumpserver.js | 7 +- apps/terminal/api/session.py | 14 +- apps/terminal/backends/command/multi.py | 6 +- .../templates/terminal/command_list.html | 189 +++++++++++------- apps/terminal/utils.py | 2 +- apps/terminal/views/command.py | 20 -- .../templates/users/user_group_list.html | 42 ++-- 15 files changed, 267 insertions(+), 211 deletions(-) diff --git a/apps/assets/templates/assets/_asset_user_list.html b/apps/assets/templates/assets/_asset_user_list.html index 52cd94b52..fda69e5d8 100644 --- a/apps/assets/templates/assets/_asset_user_list.html +++ b/apps/assets/templates/assets/_asset_user_list.html @@ -60,8 +60,8 @@ function initAssetUserTable() { }, { targets: 6, createdCell: function (td, cellData) { - var date = new Date(cellData); - $(td).html(date.toLocaleString()); + var data = formatDateAsCN(cellData); + $(td).html(data); }, }, { diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index 176744212..50d294026 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -15,27 +15,27 @@ {% block table_search %} + +
{% endblock %} {% block table_container %} diff --git a/apps/common/apps.py b/apps/common/apps.py index d5a78046e..9d4d80677 100644 --- a/apps/common/apps.py +++ b/apps/common/apps.py @@ -6,11 +6,12 @@ from django.dispatch import receiver from django.db.backends.signals import connection_created -@receiver(connection_created, dispatch_uid="my_unique_identifier") +@receiver(connection_created) def on_db_connection_ready(sender, **kwargs): from .signals import django_ready if 'migrate' not in sys.argv: django_ready.send(CommonConfig) + connection_created.disconnect(on_db_connection_ready) class CommonConfig(AppConfig): diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 2547e373c..7f44c861c 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -21,6 +21,7 @@ class Organization(models.Model): ROOT_NAME = 'ROOT' DEFAULT_ID = 'DEFAULT' DEFAULT_NAME = 'DEFAULT' + _user_admin_orgs = None class Meta: verbose_name = _("Organization") @@ -92,6 +93,8 @@ class Organization(models.Model): @classmethod def get_user_admin_orgs(cls, user): + if cls._user_admin_orgs and user.id in cls._user_admin_orgs: + return cls._user_admin_orgs[user.id] admin_orgs = [] if user.is_anonymous: return admin_orgs @@ -100,6 +103,11 @@ class Organization(models.Model): admin_orgs.append(cls.default()) elif user.is_org_admin: admin_orgs = user.admin_orgs.all() + + if cls._user_admin_orgs is None: + cls._user_admin_orgs = {user.id: admin_orgs} + else: + cls._user_admin_orgs[user.id] = admin_orgs return admin_orgs @classmethod diff --git a/apps/orgs/signals_handler.py b/apps/orgs/signals_handler.py index 42387ccde..9d3c8882f 100644 --- a/apps/orgs/signals_handler.py +++ b/apps/orgs/signals_handler.py @@ -41,3 +41,8 @@ def on_org_user_changed(sender, instance=None, **kwargs): for user_group in user_groups: user_group.users.remove(user) set_current_org(old_org) + + +@receiver(m2m_changed, sender=Organization.admins.through) +def on_org_admin_change(sender, **kwargs): + Organization._user_admin_orgs = None diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 666b7f55a..377b39722 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -263,6 +263,7 @@ class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView) system_users=self.system_user_id ) nodes = util.get_nodes_with_assets() + print(list(nodes.keys())) for node, assets in nodes.items(): data = parse_node_to_tree_node(node) queryset.append(data) diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 4f2b8561f..c26047ff4 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -180,7 +180,7 @@ class GenerateTree: return dict(nodes) def get_nodes(self): - return self.nodes.keys() + return list(self.nodes.keys()) def get_user_permissions(user, include_group=True): @@ -256,9 +256,13 @@ class AssetPermissionCacheMixin: ) @property - def node_key(self): + def node_asset_key(self): return self.get_cache_key('NODES_WITH_ASSETS') + @property + def node_key(self): + return self.get_cache_key('NODES') + @property def asset_key(self): key = self.get_cache_key('ASSETS') @@ -268,54 +272,47 @@ class AssetPermissionCacheMixin: def system_key(self): return self.get_cache_key('SYSTEM_USER') - def get_assets_from_cache(self): - cached = cache.get(self.asset_key) + def get_resource_from_cache(self, resource): + key_map = { + "assets": self.asset_key, + "nodes": self.node_key, + "nodes_with_assets": self.node_asset_key, + "system_users": self.system_key + } + key = key_map.get(resource) + if not key: + raise ValueError("Not a valid resource: {}".format(resource)) + cached = cache.get(key) if not cached: self.update_cache() - cached = cache.get(self.asset_key) + cached = cache.get(key) return cached - def get_nodes_with_assets_from_cache(self): - cached = cache.get(self.node_key) - if not cached: - self.update_cache() - cached = cache.get(self.node_key) - return cached + def get_resource(self, resource): + if self._is_using_cache(): + return self.get_resource_from_cache(resource) + elif self._is_refresh_cache(): + self.expire_cache() + data = self.get_resource_from_cache(resource) + return data + else: + return self.get_resource_without_cache(resource) + + def get_resource_without_cache(self, resource): + attr = 'get_{}_without_cache'.format(resource) + return getattr(self, attr)() def get_nodes_with_assets(self): - if self._is_using_cache(): - return self.get_nodes_with_assets_from_cache() - elif self._is_refresh_cache(): - self.expire_cache() - return self.get_nodes_with_assets_from_cache() - else: - return self.get_nodes_with_assets_without_cache() - - def get_system_user_from_cache(self): - cached = cache.get(self.system_key) - if not cached: - self.update_cache() - cached = cache.get(self.system_key) - return cached + return self.get_resource("nodes_with_assets") def get_assets(self): - if self._is_using_cache(): - return self.get_assets_from_cache() - elif self._is_refresh_cache(): - self.expire_cache() - return self.get_assets_from_cache() - else: - self.expire_cache() - return self.get_assets_without_cache() + return self.get_resource("assets") + + def get_nodes(self): + return self.get_resource("nodes") def get_system_users(self): - if self._is_using_cache(): - return self.get_system_user_from_cache() - elif self._is_refresh_cache(): - self.expire_cache() - return self.get_system_user_from_cache() - else: - return self.get_system_user_without_cache() + return self.get_resource("system_users") def get_meta_cache_key(self): cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}' @@ -332,6 +329,17 @@ class AssetPermissionCacheMixin: # print("Meta id: {}".format(meta["id"])) return meta + def update_cache(self): + assets = self.get_resource_without_cache("assets") + nodes_with_assets = self.get_resource_without_cache("nodes_with_assets") + system_users = self.get_resource_without_cache("system_users") + nodes = self.get_resource_without_cache("nodes") + cache.set(self.asset_key, assets, self.CACHE_TIME) + cache.set(self.node_asset_key, nodes_with_assets, self.CACHE_TIME) + cache.set(self.system_key, system_users, self.CACHE_TIME) + cache.set(self.node_key, nodes, self.CACHE_TIME) + self.set_meta_to_cache() + def set_meta_to_cache(self): key = self.get_meta_cache_key() meta = { @@ -348,15 +356,6 @@ class AssetPermissionCacheMixin: key = cache_key.format(obj_id=self.obj_id) cache.delete_pattern(key) - def update_cache(self): - assets = self.get_assets_without_cache() - nodes = self.get_nodes_with_assets_without_cache() - system_users = self.get_system_user_without_cache() - cache.set(self.asset_key, assets, self.CACHE_TIME) - cache.set(self.node_key, nodes, self.CACHE_TIME) - cache.set(self.system_key, system_users, self.CACHE_TIME) - self.set_meta_to_cache() - def expire_cache(self): """ 因为 获取用户的节点,资产,系统用户等都能会缓存,这里会清理所有与该对象有关的 @@ -378,15 +377,6 @@ class AssetPermissionCacheMixin: key = cls.CACHE_KEY_PREFIX + '*' cache.delete_pattern(key) - def get_assets_without_cache(self): - raise NotImplementedError() - - def get_nodes_with_assets_without_cache(self): - raise NotImplementedError() - - def get_system_user_without_cache(self): - raise NotImplementedError() - class AssetPermissionUtil(AssetPermissionCacheMixin): get_permissions_map = { @@ -396,8 +386,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): "Node": get_node_permissions, "SystemUser": get_system_user_permissions, } - assets_prefetch = ('id', 'hostname', 'ip', "platform", "domain_id", - "comment", "is_active", "os", "org_id") + assets_only = ( + 'id', 'hostname', 'ip', "platform", "domain_id", + 'comment', 'is_active', 'os', 'org_id' + ) def __init__(self, obj, cache_policy='0'): self.object = obj @@ -411,6 +403,8 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): self.change_org_if_need() self.nodes = None self._nodes = None + self._assets_direct = None + self._nodes_direct = None @staticmethod def change_org_if_need(): @@ -438,6 +432,8 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): 返回用户/组授权规则直接关联的节点 :return: {node1: {system_user1: {'actions': set()},}} """ + if self._nodes_direct: + return self._nodes_direct nodes = defaultdict(lambda: defaultdict(int)) for perm in self.permissions: actions = [perm.actions] @@ -446,9 +442,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): for node, system_user, action in itertools.product(_nodes, system_users, actions): nodes[node][system_user] |= action self.tree.add_nodes(nodes) + self._nodes_direct = nodes return nodes - def get_nodes(self): + def get_nodes_without_cache(self): self.get_assets_direct() return self.tree.get_nodes() @@ -458,15 +455,18 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): 返回用户授权规则直接关联的资产 :return: {asset1: {system_user1: 1,}} """ + if self._assets_direct: + return self._assets_direct assets = defaultdict(lambda: defaultdict(int)) for perm in self.permissions: actions = [perm.actions] - _assets = perm.assets.all().prefetch_related(*self.assets_prefetch) + _assets = perm.assets.all().only(*self.assets_only) system_users = perm.system_users.all() iterable = itertools.product(_assets, system_users, actions) for asset, system_user, action in iterable: assets[asset][system_user] |= action self.tree.add_assets(assets) + self._assets_direct = assets return assets #@timeit @@ -476,6 +476,7 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): """ if self._assets: return self._assets + self.get_assets_direct() nodes = self.get_nodes_direct() pattern = set() for node in nodes: @@ -484,7 +485,7 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): if pattern: assets = Asset.objects.filter(nodes__key__regex=pattern)\ .prefetch_related('nodes', "protocols")\ - .only(*self.assets_prefetch)\ + .only(*self.assets_only)\ .distinct() else: assets = [] @@ -501,9 +502,11 @@ class AssetPermissionUtil(AssetPermissionCacheMixin): :return: """ self.get_assets_without_cache() - return self.tree.get_nodes_with_assets() + nodes_assets = self.tree.get_nodes_with_assets() + print(nodes_assets.keys()) + return nodes_assets - def get_system_user_without_cache(self): + def get_system_users_without_cache(self): system_users = set() permissions = self.permissions.prefetch_related('system_users') for perm in permissions: diff --git a/apps/settings/signals_handler.py b/apps/settings/signals_handler.py index 9302b63e2..cb7603ec7 100644 --- a/apps/settings/signals_handler.py +++ b/apps/settings/signals_handler.py @@ -24,6 +24,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): @receiver(django_ready, dispatch_uid="my_unique_identifier") def monkey_patch_settings(sender, **kwargs): + logger.debug("Monkey patch settings") cache_key_prefix = '_SETTING_' custom_need_cache_settings = [ 'AUTHENTICATION_BACKENDS', 'TERMINAL_HOST_KEY', @@ -77,7 +78,6 @@ def monkey_patch_settings(sender, **kwargs): @receiver(django_ready) def auto_generate_terminal_host_key(sender, **kwargs): try: - print("Auto gen host key") if Setting.objects.filter(name='TERMINAL_HOST_KEY').exists(): return private_key, public_key = ssh_key_gen() diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 5f85d5e42..471cb00a4 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -648,8 +648,6 @@ jumpserver.initServerSideDataTable = function (options) { $.each(rows, function (id, row) { table.selected_rows.push(row); if (row.id && $.inArray(row.id, table.selected) === -1){ - console.log(table) - console.log(table.selected); table.selected.push(row.id) } }) @@ -1096,3 +1094,8 @@ function objectAttrsIsBool(obj, attrs) { } }) } + +function formatDateAsCN(d) { + var date = new Date(d); + return date.toISOString().replace("T", " ").replace(/\..*/, ""); +} diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 93628af22..04f97bfc3 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -55,7 +55,7 @@ class SessionViewSet(BulkModelViewSet): return super().perform_create(serializer) -class CommandViewSet(viewsets.ViewSet): +class CommandViewSet(viewsets.ModelViewSet): """接受app发送来的command log, 格式如下 { "user": "admin", @@ -70,10 +70,14 @@ class CommandViewSet(viewsets.ViewSet): """ command_store = get_command_storage() serializer_class = SessionCommandSerializer + pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser | IsAuditor,) + filter_fields = ("asset", "system_user", "user", "input") def get_queryset(self): - self.command_store.filter(**dict(self.request.query_params)) + multi_command_storage = get_multi_command_storage() + queryset = multi_command_storage.filter() + return queryset def create(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data, many=True) @@ -88,12 +92,6 @@ class CommandViewSet(viewsets.ViewSet): logger.error(msg) return Response({"msg": msg}, status=401) - def list(self, request, *args, **kwargs): - multi_command_storage = get_multi_command_storage() - queryset = multi_command_storage.filter() - serializer = self.serializer_class(queryset, many=True) - return Response(serializer.data) - class SessionReplayViewSet(viewsets.ViewSet): serializer_class = serializers.ReplaySerializer diff --git a/apps/terminal/backends/command/multi.py b/apps/terminal/backends/command/multi.py index 1ac822a81..0a4418c0d 100644 --- a/apps/terminal/backends/command/multi.py +++ b/apps/terminal/backends/command/multi.py @@ -9,8 +9,12 @@ class CommandStore(CommandBase): self.storage_list = storage_list def filter(self, **kwargs): - queryset = [] + if len(self.storage_list) == 1: + storage = list(self.storage_list)[0] + queryset = storage.filter(**kwargs) + return queryset + queryset = [] for storage in self.storage_list: queryset.extend(storage.filter(**kwargs)) return sorted(queryset, key=lambda command: command.timestamp, reverse=True) diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index 50daf682d..1ce2d80a5 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -8,109 +8,65 @@ {% endblock %} -{% block content_left_head %} +{% block table_pagination %} {% endblock %} {% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
-
{% endblock %} {% block table_container %} - +
- + - - {% for command in command_list %} - - - - - - - - - - - {% endfor %}
ID {% trans 'Command' %} {% trans 'User' %} {% trans 'Asset' %} {% trans 'System user'%} {% trans 'Session' %} {% trans 'Datetime' %}
{{ forloop.counter }}{{ command.input }}{{ command.user }}{{ command.asset }}{{ command.system_user }}{% trans "Goto" %}{{ command.timestamp|ts_to_date }}
{{ command.output }}
-
+
- +
-{% endblock %} + + +{% endblock %} +{% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index d7dace0a9..38a5d24d2 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -9,7 +9,7 @@ from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY def get_session_asset_list(): - return Asset.objects.values_list('hostname', flat=True) + return Asset.objects.values_list() def get_session_user_list(): diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index c1a5eb393..2810db9d8 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -23,26 +23,13 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView): template_name = "terminal/command_list.html" context_object_name = 'command_list' paginate_by = settings.DISPLAY_PER_PAGE - command = user = asset = system_user = "" date_from = date_to = None permission_classes = [IsOrgAdmin | IsAuditor] def get_queryset(self): - self.command = self.request.GET.get('command', '') - self.user = self.request.GET.get("user", '') - self.asset = self.request.GET.get('asset', '') - self.system_user = self.request.GET.get('system_user', '') filter_kwargs = dict() filter_kwargs['date_from'] = self.date_from filter_kwargs['date_to'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.asset: - filter_kwargs['asset'] = self.asset - if self.system_user: - filter_kwargs['system_user'] = self.system_user - if self.command: - filter_kwargs['input'] = self.command queryset = common_storage.filter(**filter_kwargs) return queryset @@ -50,15 +37,8 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView): context = { 'app': _('Sessions'), 'action': _('Command list'), - 'user_list': utils.get_session_user_list(), - 'asset_list': utils.get_session_asset_list(), - 'system_user_list': utils.get_session_system_user_list(), - 'command': self.command, 'date_from': self.date_from, 'date_to': self.date_to, - 'user': self.user, - 'asset': self.asset, - 'system_user': self.system_user, } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index c2839cad9..48394b30d 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -2,27 +2,27 @@ {% load i18n static %} {% block table_search %} + +
{% endblock %} {% block table_container %} From 5f6af8c07de37510a76371425eeca5652354bcc5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jul 2019 16:29:39 +0800 Subject: [PATCH 34/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9command?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/assets/system_user_list.html | 2 +- apps/common/permissions.py | 2 + apps/static/css/jumpserver.css | 10 ++- apps/static/js/jumpserver.js | 10 ++- .../bootstrap-datepicker.zh-CN.min.js | 1 + apps/terminal/api/session.py | 36 ++++++++-- apps/terminal/backends/command/db.py | 9 ++- .../templates/terminal/command_list.html | 70 +++++++++++++------ apps/terminal/views/command.py | 24 ++----- apps/users/templates/users/user_list.html | 24 +++---- 10 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 apps/static/js/plugins/datepicker/bootstrap-datepicker.zh-CN.min.js diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index 50d294026..a30d25a51 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -46,7 +46,7 @@ - + {% trans 'Name' %} {% trans 'Username' %} diff --git a/apps/common/permissions.py b/apps/common/permissions.py index a2f4c9286..3289d0af4 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -146,6 +146,8 @@ class CanUpdateSuperUser(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in ['GET', 'OPTIONS']: return True + if str(request.user.id) == str(obj.id): + return False if request.user.is_superuser: return True if hasattr(obj, 'is_superuser') and obj.is_superuser: diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index b1f2633d8..489279189 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -304,7 +304,7 @@ table.dataTable tbody td.selected td i.text-navy { div.dataTables_wrapper div.dataTables_filter, .dataTables_length { - float: right !important; + // float: right !important; } div.dataTables_wrapper div.dataTables_filter { @@ -466,3 +466,11 @@ div.dataTables_wrapper div.dataTables_filter { span.select2-selection__placeholder { line-height: 34px !important; } + +.p-l-5 { + padding-left: 5px; +} + +.p-r-5 { + padding-right: 5px; +} \ No newline at end of file diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 471cb00a4..3880b496e 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -567,7 +567,8 @@ jumpserver.initServerSideDataTable = function (options) { }; var table = ele.DataTable({ pageLength: options.pageLength || 15, - dom: options.dom || '<"#uc.pull-left">fltr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', + // dom: options.dom || '<"#uc.pull-left">fltr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', + dom: options.dom || '<"#uc.pull-left"><"pull-right"<"inline"l><"#fb.inline"><"inline"f><"#fa.inline">>tr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', order: options.order || [], buttons: [], columnDefs: columnDefs, @@ -668,8 +669,6 @@ jumpserver.initServerSideDataTable = function (options) { }) } }).on('draw', function(){ - $('#op').html(options.op_html || ''); - $('#uc').html(options.uc_html || ''); $('[data-toggle="popover"]').popover({ html: true, placement: 'bottom', @@ -691,6 +690,11 @@ jumpserver.initServerSideDataTable = function (options) { table.rows(index).select() } }); + }).on("init", function () { + $('#op').html(options.op_html || ''); + $('#uc').html(options.uc_html || ''); + $('#fb').html(options.fb_html || ''); + $('#fa').html(options.fa_html || ''); }); var table_id = table.settings()[0].sTableId; $('#' + table_id + ' .ipt_check_all').on('click', function() { diff --git a/apps/static/js/plugins/datepicker/bootstrap-datepicker.zh-CN.min.js b/apps/static/js/plugins/datepicker/bootstrap-datepicker.zh-CN.min.js new file mode 100644 index 000000000..8e6920b0c --- /dev/null +++ b/apps/static/js/plugins/datepicker/bootstrap-datepicker.zh-CN.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["zh-CN"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["周日","周一","周二","周三","周四","周五","周六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今天",monthsTitle:"选择月份",clear:"清除",format:"yyyy-mm-dd",titleFormat:"yyyy年mm月",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 04f97bfc3..f634f5628 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -7,6 +7,7 @@ from django.shortcuts import get_object_or_404 from django.core.files.storage import default_storage from django.http import HttpResponseNotFound from django.conf import settings +from django.utils import timezone from rest_framework.pagination import LimitOffsetPagination from rest_framework import viewsets from rest_framework.response import Response @@ -39,11 +40,15 @@ class SessionViewSet(BulkModelViewSet): terminal = get_object_or_404(Terminal, id=terminal_id) queryset = queryset.filter(terminal=terminal) return queryset - # 解决guacamole更新session时并发导致幽灵会话的问题 - if self.request.method in ('PATCH', ): - queryset = queryset.select_for_update() return queryset + def get_object(self): + # 解决guacamole更新session时并发导致幽灵会话的问题 + obj = super().get_object() + if self.request.method in ('PATCH', ): + obj = obj.select_for_update() + return obj + def perform_create(self, serializer): if hasattr(self.request.user, 'terminal'): serializer.validated_data["terminal"] = self.request.user.terminal @@ -71,14 +76,33 @@ class CommandViewSet(viewsets.ModelViewSet): command_store = get_command_storage() serializer_class = SessionCommandSerializer pagination_class = LimitOffsetPagination - permission_classes = (IsOrgAdminOrAppUser | IsAuditor,) - filter_fields = ("asset", "system_user", "user", "input") + permission_classes = [IsOrgAdminOrAppUser | IsAuditor] + filter_fields = [ + "asset", "system_user", "user", "input", "session", + ] + default_days_ago = 5 def get_queryset(self): + date_from, date_to = self.get_date_range() multi_command_storage = get_multi_command_storage() - queryset = multi_command_storage.filter() + queryset = multi_command_storage.filter(date_from=date_from, date_to=date_to) return queryset + def get_filter_fields(self): + fields = self.filter_fields + fields.extend(["date_from", "date_to"]) + return fields + + def get_date_range(self): + now = timezone.now() + days_ago = now - timezone.timedelta(days=self.default_days_ago) + default_start_st = days_ago.timestamp() + default_end_st = now.timestamp() + query_params = self.request.query_params + date_from_st = query_params.get("date_from") or default_start_st + date_to_st = query_params.get("date_to") or default_end_st + return int(date_from_st), int(date_to_st) + def create(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data, many=True) if serializer.is_valid(): diff --git a/apps/terminal/backends/command/db.py b/apps/terminal/backends/command/db.py index fa457b75d..4b9622191 100644 --- a/apps/terminal/backends/command/db.py +++ b/apps/terminal/backends/command/db.py @@ -71,9 +71,13 @@ class CommandStore(CommandBase): if not date_to and not session: date_to = date_to_default if date_from is not None: - filter_kwargs['timestamp__gte'] = int(date_from.timestamp()) + if isinstance(date_from, datetime.datetime): + date_from = date_from.timestamp() + filter_kwargs['timestamp__gte'] = int(date_from) if date_to is not None: - filter_kwargs['timestamp__lte'] = int(date_to.timestamp()) + if isinstance(date_to, datetime.datetime): + date_to = date_to.timestamp() + filter_kwargs['timestamp__lte'] = int(date_to) if user: filter_kwargs["user"] = user @@ -85,7 +89,6 @@ class CommandStore(CommandBase): filter_kwargs['input__icontains'] = input if session: filter_kwargs['session'] = session - return filter_kwargs def filter(self, date_from=None, date_to=None, diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index 1ce2d80a5..eba0d6a60 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -3,10 +3,7 @@ {% load static %} {% load common_tags %} {% block custom_head_css_js %} - - - {% endblock %} {% block content_left_head %} {% endblock %} +{% block table_pagination %} +{% endblock %} {% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-{#
#} -{# #} -{#
#} -
-
- -
-
-
{% endblock %} -{% block table_head %} - - {% trans 'ID' %} - {% trans 'User' %} - {% trans 'Asset' %} - {% trans 'System user' %} - {% trans 'Remote addr' %} - {% trans 'Protocol' %} - {% trans 'Login from' %} - {% trans 'Command' %} - {% trans 'Date start' %} -{# {% trans 'Date last active' %}#} - {% trans 'Duration' %} - {% trans 'Action' %} -{% endblock %} - -{% block table_body %} - {% for session in session_list %} - - - - {{ forloop.counter }} - - {{ session.user }} - {{ session.asset }} - {{ session.system_user }} - {{ session.remote_addr|default:"" }} - {{ session.protocol }} - {{ session.get_login_from_display }} - {{ session.command_amount }} - - {{ session.date_start }} -{# {{ session.date_last_active }}#} - {{ session.date_start|time_util_with_seconds:session.date_end }} - - {% if session.is_finished %} - {% trans "Replay" %} - {% else %} - {% if session.protocol == 'ssh' and request.user.is_org_admin%} - {% trans "Terminate" %} - {% else %} - {% trans "Terminate" %} - {% endif %} - {% endif %} - +{% block table_container %} + + + + + + + + + + + + + + + - {% endfor %} -{% endblock %} + + + +
{% trans 'ID' %}{% trans 'User' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Remote addr' %}{% trans 'Protocol' %}{% trans 'Login from' %}{% trans 'Command' %}{% trans 'Date start' %}{% trans 'Duration' %}{% trans 'Action' %}
-{% block content_bottom_left %} - {% if request.user.is_org_admin %} - + + {% endblock %} + {% block custom_foot_js %} - - + +function initTable() { + dateFrom = new Date(dateFrom * 1000).toISOString(); + dateTo = new Date(dateTo * 1000).toISOString(); + sessionListUrl = setUrlParam(sessionListUrl, "date_from", dateFrom); + sessionListUrl = setUrlParam(sessionListUrl, "date_to", dateTo); + var options = { + ele: $('#session_table'), + ordering: false, + columnDefs: [ + {targets: 1, createdCell: function (td, cellData, rowData, i) { + var index = i + 1; + var data = '' + + index + ""; + data = data.replace("{{ DEFAULT_PK }}", cellData); + + $(td).html(data); + }}, + {targets: 9, createdCell: function (td, cellData) { + var data = formatDateAsCN(cellData); + $(td).html(data); + }}, + {targets: 10, createdCell: function (td, cellData, rowData) { + var data = ""; + if (cellData && rowData.date_start) { + data = timeOffset(rowData.date_start, cellData) + } + $(td).html(data); + }}, + {targets: 11, createdCell: function (td, cellData, rowData) { + var btnGroup = ""; + var replayBtn = '{% trans "Replay" %}' + {#var replayBtn = '{% trans "Replay" %}'#} + replayBtn = replayBtn.replace("sessionID", rowData.id); + if (rowData.can_replay) { + replayBtn = replayBtn.replace("disabled", "") + } + var termBtn = '{% trans "Terminate" %}'; + if (rowData.protocol === "ssh" && "{{ request.user.is_org_admin }}" === "True") { + termBtn = termBtn.replace("disabled", "") + .replace("sessionID", cellData) + .replace("terminalID", rowData.terminal) + } + if (rowData.is_finished) { + btnGroup += replayBtn + } else { + btnGroup += termBtn; + } + $(td).html(btnGroup); + }}, + ], + ajax_url: sessionListUrl, + columns: [ + {data: "id"}, {data: "id"}, {data: "user", orderable: false}, + {data: "asset", orderable: false}, {data: "system_user", orderable: false}, + {data: "remote_addr"}, {data: "protocol"}, {data: "login_from_display"}, + {data: "command_amount"}, {data: "date_start"}, + {data: "date_end"}, {data: "id"}, + ], + op_html: $('#actions').html(), + fb_html: $("#daterange").html(), + }; + table = jumpserver.initServerSideDataTable(options); + return table +} + +function finishedSession(data) { + var the_url = "{% url 'api-terminal:session-list' %}"; + var success_message = '{% trans "Finish session success" %}'; + var success = function() { + location.reload(); + }; + APIUpdateAttr({ + url: the_url, + method: 'PATCH', + body: JSON.stringify(data), + success: success, + success_message: success_message + }); +} +var table; +$(document).ready(function() { + table = initTable("#session_table"); + $('.select2').select2({ + dropdownAutoWidth: true, + width: "auto" + }); + $('.input-daterange.input-group').datepicker({ + format: "yyyy-mm-dd", + todayBtn: "linked", + keyboardNavigation: false, + forceParse: false, + calendarWeeks: true, + autoclose: true + }); +}).on('click', '.btn-term', function () { + var $this = $(this); + var session_id = $this.attr('value'); + var data = [ + session_id + ]; + terminateSession(data) +}).on('click', '.btn-replay', function () { + var sessionID = $(this).data("session"); + var replayUrl = "/luna/replay/" + sessionID; + window.open(replayUrl, "height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no"); +}) +.on('click', '#btn_bulk_update', function () { + var action = $('#slct_bulk_update').val(); + var idList = table.selected; + + if (idList.length === 0) { + return false; + } + + function doTerminate() { + terminateSession(idList) + } + + function doFinishSession() { + var data = []; + $.each(idList, function (i, v) { + data.push({ + "id": v, + "is_finished": true + }) + }); + finishedSession(data) + } + switch(action) { + case 'terminate': + doTerminate(); + break; + case "finished": + doFinishSession(); + break; + default: + break; + } +}); + {% endblock %} diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 4167b3d47..b1b4b98a5 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- # -from django.views.generic import View, TemplateView +from django.views.generic import TemplateView from django.utils.translation import ugettext as _ -from django.http import HttpResponse -from django.template import loader from django.utils import timezone -import time -from common.mixins import DatetimeSearchMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor -from ..backends import get_multi_command_storage __all__ = ['CommandListView'] -common_storage = get_multi_command_storage() -class CommandListView(DatetimeSearchMixin, PermissionsMixin, TemplateView): +class CommandListView(PermissionsMixin, TemplateView): template_name = "terminal/command_list.html" permission_classes = [IsOrgAdmin | IsAuditor] default_days_ago = 5 diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index 6903c90fd..af401996a 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from django.views.generic import ListView +from django.views.generic import ListView, TemplateView from django.views.generic.edit import SingleObjectMixin from django.utils.translation import ugettext as _ from django.utils import timezone @@ -20,68 +20,40 @@ __all__ = [ ] -class SessionListView(PermissionsMixin, DatetimeSearchMixin, ListView): +class SessionListView(PermissionsMixin, TemplateView): model = Session template_name = 'terminal/session_list.html' - context_object_name = 'session_list' - paginate_by = settings.DISPLAY_PER_PAGE - user = asset = system_user = '' date_from = date_to = None permission_classes = [IsOrgAdmin | IsAuditor] - - def get_queryset(self): - self.queryset = super().get_queryset() - self.user = self.request.GET.get('user') - self.asset = self.request.GET.get('asset') - self.system_user = self.request.GET.get('system_user') - - filter_kwargs = dict() - filter_kwargs['date_start__gt'] = self.date_from - filter_kwargs['date_start__lt'] = self.date_to - return self.queryset + default_days_ago = 5 def get_context_data(self, **kwargs): + now = timezone.now() context = { - 'asset_list': utils.get_session_asset_list()[:10], - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'asset': self.asset, - 'system_user': self.system_user, + 'date_from': now - timezone.timedelta(days=self.default_days_ago), + 'date_to': now, } kwargs.update(context) return super().get_context_data(**kwargs) class SessionOnlineListView(SessionListView): - - def get_queryset(self): - queryset = super().get_queryset().filter(is_finished=False) - return queryset - def get_context_data(self, **kwargs): context = { 'app': _('Sessions'), 'action': _('Session online list'), 'type': 'online', - 'now': timezone.now(), } kwargs.update(context) return super().get_context_data(**kwargs) class SessionOfflineListView(SessionListView): - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.filter(is_finished=True) - return queryset - def get_context_data(self, **kwargs): context = { 'app': _('Sessions'), 'action': _('Session offline'), - 'now': timezone.now(), + 'type': 'offline', } kwargs.update(context) return super().get_context_data(**kwargs) From 2eb942a947848a2ddc863fe7d353602f3ca1f83f Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 4 Jul 2019 11:31:31 +0800 Subject: [PATCH 38/53] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9Permission?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=B1=82=E7=BC=93=E5=AD=98key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index ee624b242..e072fed95 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -73,7 +73,6 @@ class UserPermissionCacheMixin: request_md5 = self.get_request_md5() meta_cache_id = self.get_meta_cache_id() resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id) - resp_cache_id = md5(resp_cache_id.encode()).hexdigest() return resp_cache_id def get_response_from_cache(self): From 1448d23ca6bb732486d0cd0bab44294f057f2e52 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 4 Jul 2019 15:36:57 +0800 Subject: [PATCH 39/53] =?UTF-8?q?[Update]=20=E5=87=86=E5=A4=87=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20asset=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 8 +- apps/assets/api/asset_user.py | 11 ++- apps/assets/api/system_user.py | 3 +- apps/assets/backends/asset_user.py | 5 ++ apps/assets/backends/base.py | 4 +- apps/assets/backends/db.py | 2 +- apps/assets/backends/manager.py | 26 +++--- apps/assets/models/base.py | 51 +++++++++--- apps/assets/models/node.py | 3 +- apps/assets/serializers/admin_user.py | 2 +- apps/assets/serializers/asset.py | 4 +- apps/assets/serializers/system_user.py | 10 ++- apps/assets/signals_handler.py | 5 -- .../templates/assets/admin_user_list.html | 2 +- .../templates/assets/system_user_list.html | 9 ++- apps/assets/views/asset.py | 5 ++ apps/assets/views/system_user.py | 5 +- apps/common/signals_handlers.py | 5 +- apps/orgs/mixins/api.py | 6 +- apps/terminal/api/session.py | 3 +- apps/terminal/models.py | 20 ++--- .../templates/terminal/command_list.html | 13 +-- .../templates/terminal/session_list.html | 79 ++++++++++++++++--- 23 files changed, 185 insertions(+), 96 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index f8ed14cb9..3e1c10ede 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -20,6 +20,7 @@ from common.mixins import IDInCacheFilterMixin, ApiMessageMixin from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser +from orgs.mixins import OrgBulkModelViewSet from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from ..models import Asset, AdminUser, Node from .. import serializers @@ -36,7 +37,7 @@ __all__ = [ ] -class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModelViewSet): +class AssetViewSet(LabelFilter, ApiMessageMixin, OrgBulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ @@ -100,11 +101,6 @@ class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModel queryset = self.filter_admin_user_id(queryset) return queryset - def get_queryset(self): - queryset = super().get_queryset().distinct() - queryset = self.get_serializer_class().setup_eager_loading(queryset) - return queryset - class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView): """ diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index 237517790..cf092dab0 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -127,15 +127,14 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): return Response(serializer.data, status=status_code) def get_object(self): - username = self.request.GET.get('username') - asset_id = self.request.GET.get('asset_id') - prefer = self.request.GET.get("prefer") + query_params = self.request.query_params + username = query_params.get('username') + asset_id = query_params.get('asset_id') + prefer = query_params.get("prefer") asset = get_object_or_none(Asset, pk=asset_id) try: manger = AssetUserManager() - if prefer: - manger.prefer(prefer) - instance = manger.get(username, asset) + instance = manger.get(username, asset, prefer=prefer) except Exception as e: logger.error(e, exc_info=True) return None diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index f6398e974..8baacd4f8 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -22,6 +22,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.mixins import IDInCacheFilterMixin +from orgs.mixins import OrgBulkModelViewSet from ..models import SystemUser, Asset from .. import serializers from ..tasks import push_system_user_to_assets_manual, \ @@ -39,7 +40,7 @@ __all__ = [ ] -class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): +class SystemUserViewSet(OrgBulkModelViewSet): """ System user api set, for add,delete,update,list,retrieve resource """ diff --git a/apps/assets/backends/asset_user.py b/apps/assets/backends/asset_user.py index 62ab24b4d..774172e6c 100644 --- a/apps/assets/backends/asset_user.py +++ b/apps/assets/backends/asset_user.py @@ -14,6 +14,11 @@ class AssetUserBackend(BaseBackend): @classmethod def filter(cls, username=None, assets=None, **kwargs): queryset = cls.model.objects.all() + prefer_id = kwargs.get('prefer_id') + if prefer_id: + queryset = queryset.filter(id=prefer_id) + instances = cls.construct_authbook_objects(queryset, assets) + return instances if username: queryset = queryset.filter(username=username) if assets: diff --git a/apps/assets/backends/base.py b/apps/assets/backends/base.py index d6f30f940..f45b0b3b8 100644 --- a/apps/assets/backends/base.py +++ b/apps/assets/backends/base.py @@ -7,11 +7,13 @@ from abc import abstractmethod class BaseBackend: @classmethod @abstractmethod - def filter(cls, username=None, assets=None, latest=True): + def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None): """ :param username: 用户名 :param assets: 对象 :param latest: 是否是最新记录 + :param prefer: 优先使用 + :param prefer_id: 使用id :return: 元素为的可迭代对象( or ) """ pass diff --git a/apps/assets/backends/db.py b/apps/assets/backends/db.py index f37569e51..40fa41444 100644 --- a/apps/assets/backends/db.py +++ b/apps/assets/backends/db.py @@ -7,7 +7,7 @@ from .base import BaseBackend class AuthBookBackend(BaseBackend): @classmethod - def filter(cls, username=None, assets=None, latest=True): + def filter(cls, username=None, assets=None, latest=True, **kwargs): queryset = AuthBook.objects.all() if username is not None: queryset = queryset.filter(username=username) diff --git a/apps/assets/backends/manager.py b/apps/assets/backends/manager.py index 2cdd52b1e..6745ea44b 100644 --- a/apps/assets/backends/manager.py +++ b/apps/assets/backends/manager.py @@ -30,24 +30,22 @@ class AssetUserManager: ) _prefer = "system_user" - _using = None - def filter(self, username=None, assets=None, latest=True): + def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None): if assets is not None and not assets: return AssetUserQuerySet([]) - if self._using: - backend = dict(self.backends).get(self._using) - if not backend: - return self.none() - instances = backend.filter(username=username, assets=assets, latest=latest) - return AssetUserQuerySet(instances) + if prefer: + self._prefer = prefer instances_map = {} instances = [] for name, backend in self.backends: + if name != "db" and self._prefer != name: + continue _instances = backend.filter( - username=username, assets=assets, latest=latest + username=username, assets=assets, latest=latest, + prefer=self._prefer, prefer_id=prefer_id, ) instances_map[name] = _instances @@ -64,12 +62,12 @@ class AssetUserManager: else: ordering.extend(["admin_user", "system_user"]) # 根据prefer决定优先使用系统用户或管理用户谁的 - ordering_instances = [instances_map.get(i) for i in ordering] + ordering_instances = [instances_map.get(i, []) for i in ordering] instances = self._merge_instances(*ordering_instances) return AssetUserQuerySet(instances) - def get(self, username, asset): - instances = self.filter(username, assets=[asset]) + def get(self, username, asset, **kwargs): + instances = self.filter(username, assets=[asset], **kwargs) if len(instances) == 1: return instances[0] elif len(instances) == 0: @@ -95,10 +93,6 @@ class AssetUserManager: self._prefer = s return self - def using(self, s): - self._using = s - return self - @staticmethod def none(): return AssetUserQuerySet() diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index aa6e639a6..8c5c156a6 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -5,6 +5,7 @@ import uuid from hashlib import md5 import sshpubkeys +from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -34,7 +35,10 @@ class AssetUser(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_ASSET_CONNECTIVITY_{}" + CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_ASSET_CONNECTIVITY" + CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_CONNECTIVITY_AMOUNT" + ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT" + ASSET_USER_CACHE_TIME = 3600 * 24 _prefer = "system_user" @@ -67,6 +71,11 @@ class AssetUser(OrgModelMixin): pass return None + @property + def part_id(self): + i = '-'.join(str(self.id).split('-')[:3]) + return i + def get_related_assets(self): assets = self.assets.all() return assets @@ -97,10 +106,14 @@ class AssetUser(OrgModelMixin): self.set_asset_connectivity(asset, Connectivity.reachable()) else: self.set_asset_connectivity(asset, Connectivity.unknown()) + cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.part_id) + cache.delete(cache_key) @property def connectivity(self): - assets = self.get_related_assets() + assets = self.get_related_assets()\ + .select_related('admin_user')\ + .only('id', 'hostname', 'admin_user') data = { 'unreachable': [], 'reachable': [], @@ -118,11 +131,25 @@ class AssetUser(OrgModelMixin): @property def connectivity_amount(self): - return {k: len(v) for k, v in self.connectivity.items()} + cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.part_id) + amount = cache.get(cache_key) + if not amount: + connectivity = {k: len(v) for k, v in self.connectivity.items()} + cache.set(cache_key, connectivity, self.ASSET_USER_CACHE_TIME) + return amount @property def assets_amount(self): - return self.get_related_assets().count() + cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) + cached = cache.get(cache_key) + if not cached: + cached = self.get_related_assets().count() + cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME) + return cached + + def expire_assets_amount(self): + cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) + cache.delete(cache_key) def get_asset_connectivity(self, asset): i = self.generate_id_with_asset(asset) @@ -133,12 +160,15 @@ class AssetUser(OrgModelMixin): i = self.generate_id_with_asset(asset) key = self.CONNECTIVITY_ASSET_CACHE_KEY.format(i) Connectivity.set(key, c) + # 当为某个系统用户或管理用户设置的的时候,失效掉他们的连接数量 + amount_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.part_id) + cache.delete(amount_key) def get_asset_user(self, asset): from ..backends import AssetUserManager try: manager = AssetUserManager().prefer(self._prefer) - other = manager.get(username=self.username, asset=asset) + other = manager.get(username=self.username, asset=asset, prefer_id=self.id) return other except Exception as e: logger.error(e, exc_info=True) @@ -150,9 +180,12 @@ class AssetUser(OrgModelMixin): self._merge_auth(instance) def _merge_auth(self, other): - self.password = other.password - self.public_key = other.public_key - self.private_key = other.private_key + if other.password: + self.password = other.password + if other.public_key: + self.public_key = other.public_key + if other.private_key: + self.private_key = other.private_key def clear_auth(self): self.password = '' @@ -185,7 +218,7 @@ class AssetUser(OrgModelMixin): } def generate_id_with_asset(self, asset): - user_id = str(self.id).split('-')[:3] + user_id = [self.part_id] asset_id = str(asset.id).split('-')[3:] ids = user_id + asset_id return '-'.join(ids) diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index ace1237cc..6190357df 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -192,6 +192,7 @@ class AssetsAmountMixin: _assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}' _assets_amount = None key = '' + cache_time = 3600 * 24 * 7 @property def assets_amount(self): @@ -213,7 +214,7 @@ class AssetsAmountMixin: def assets_amount(self, value): self._assets_amount = value cache_key = self._assets_amount_cache_key.format(self.key) - cache.set(cache_key, value) + cache.set(cache_key, value, self.cache_time) def expire_assets_amount(self): ancestor_keys = self.get_ancestor_keys(with_self=True) diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index d62314858..dbbb16406 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -17,7 +17,7 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer): """ class Meta: - # list_serializer_class = AdaptedBulkListSerializer + list_serializer_class = AdaptedBulkListSerializer model = AdminUser fields = [ 'id', 'name', 'username', 'password', 'private_key', 'public_key', diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 6af18b84c..6cda758b8 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -76,8 +76,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('labels', 'nodes')\ - .select_related('admin_user') + queryset = queryset.prefetch_related('labels', 'nodes', 'protocols')\ + .select_related('admin_user', 'domain') return queryset @staticmethod diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 0caffd2fc..116dcdd36 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -20,7 +20,7 @@ class SystemUserSerializer(BulkOrgResourceModelSerializer): 'id', 'name', 'username', 'password', 'public_key', 'private_key', 'login_mode', 'login_mode_display', 'priority', 'protocol', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', - 'assets', 'assets_amount', 'connectivity_amount' + 'assets_amount', 'connectivity_amount' ] extra_kwargs = { 'password': {"write_only": True}, @@ -32,6 +32,12 @@ class SystemUserSerializer(BulkOrgResourceModelSerializer): 'created_by': {'read_only': True}, } + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related('cmd_filters', 'nodes') + return queryset + class SystemUserAuthSerializer(AuthSerializer): """ @@ -47,8 +53,6 @@ class SystemUserAuthSerializer(AuthSerializer): - - class SystemUserSimpleSerializer(serializers.ModelSerializer): """ 系统用户最基本信息的数据结构 diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index a145437d0..59d01c98a 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -27,11 +27,6 @@ def test_asset_conn_on_created(asset): test_asset_connectivity_util.delay([asset]) -def set_asset_root_node(asset): - logger.debug("Set asset default node: {}".format(Node.root())) - asset.nodes.add(Node.root()) - - @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") @on_transaction_commit def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 395e8d693..a930a6ce0 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -84,7 +84,7 @@ function initTable() { $(td).html(innerHtml) }}, {targets: 5, createdCell: function (td, cellData) { - var data = cellData['unreachable']; + var data = cellData.unreachable; var innerHtml = ""; if (data !== 0) { innerHtml = "" + data + ""; diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index a30d25a51..680bfa421 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -80,7 +80,7 @@ function initTable() { }}, {targets: 6, createdCell: function (td, cellData) { var innerHtml = ""; - var data = cellData['reachable']; + var data = cellData.reachable; if (data !== 0) { innerHtml = "" + data + ""; } else { @@ -89,7 +89,7 @@ function initTable() { $(td).html(innerHtml) }}, {targets: 7, createdCell: function (td, cellData) { - var data = cellData['unreachable']; + var data = cellData.unreachable; var innerHtml = ""; if (data !== 0) { innerHtml = "" + data + ""; @@ -103,7 +103,7 @@ function initTable() { var innerHtml = ""; var total = rowData.assets_amount; var reachable = cellData.reachable; - if (total !== 0) { + if (total && total !== 0) { val = reachable/total * 100; } @@ -119,7 +119,8 @@ function initTable() { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); $(td).html(update_btn + del_btn) - }}], + }}, + ], ajax_url: '{% url "api-assets:system-user-list" %}', columns: [ {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" }, diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 984e2052e..ed0fe7efc 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -220,6 +220,11 @@ class AssetDetailView(PermissionsMixin, DetailView): template_name = 'assets/asset_detail.html' permission_classes = [IsValidUser] + def get_queryset(self): + return super().get_queryset().prefetch_related( + "nodes", "labels", "protocols" + ).select_related('admin_user', 'domain') + def get_context_data(self, **kwargs): nodes_remain = Node.objects.exclude(assets=self.object) context = { diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index b7976ed2f..ca363ac03 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -97,7 +97,10 @@ class SystemUserAssetView(PermissionsMixin, DetailView): permission_classes = [IsOrgAdmin] def get_context_data(self, **kwargs): - nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True) + from ..utils import NodeUtil + nodes_remain = Node.objects.exclude(systemuser=self.object) + util = NodeUtil() + nodes_remain = util.get_nodes_by_queryset(nodes_remain) context = { 'app': _('assets'), 'action': _('System user asset'), diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py index cf8cc9a78..8a8463006 100644 --- a/apps/common/signals_handlers.py +++ b/apps/common/signals_handlers.py @@ -8,11 +8,12 @@ from django.core.signals import request_finished from django.db import connection -from .utils import get_logger +from common.utils import get_logger from .local import thread_local -logger = get_logger(__file__) pattern = re.compile(r'FROM `(\w+)`') +# logger = logging.getLogger('jmsdb') +logger = get_logger(__name__) class Counter: diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index 5e0cef5c3..180e9770b 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -27,7 +27,11 @@ class OrgModelViewSet(IDInCacheFilterMixin, ModelViewSet): class OrgBulkModelViewSet(IDInCacheFilterMixin, BulkModelViewSet): def get_queryset(self): - return super().get_queryset().all() + queryset = super().get_queryset().all() + if hasattr(self, 'serializer_class') and \ + hasattr(self.serializer_class, 'setup_eager_loading'): + queryset = self.serializer_class.setup_eager_loading(queryset) + return queryset class OrgMembershipModelViewSetMixin: diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index e81772fae..6dd48f591 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -31,7 +31,8 @@ class SessionViewSet(OrgBulkModelViewSet): pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser | IsAuditor, ) filter_fields = [ - "user", "asset", "system_user", "terminal", "is_finished", + "user", "asset", "system_user", "remote_addr", + "protocol", "terminal", "is_finished", ] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 2c5c7d666..43b8c19ce 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -191,26 +191,22 @@ class Session(OrgModelMixin): @property def _date_start_first_has_replay_rdp_session(self): - if self._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None: + if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None: instance = self.__class__.objects.filter( - protocol='rdp', has_replay=True).order_by('date_start').first() + protocol='rdp', has_replay=True + ).order_by('date_start').first() if not instance: - return None - self._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION = instance.date_start - - return self._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION + date_start = timezone.now() - timezone.timedelta(days=365) + else: + date_start = instance.date_start + self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION = date_start + return self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION def can_replay(self): if self.has_replay: return True - - # 判断对RDP Session添加上报has_replay状态机制之前的录像回放 - if self._date_start_first_has_replay_rdp_session is None: - return True - if self.date_start < self._date_start_first_has_replay_rdp_session: return True - return False def save_to_storage(self, f): diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index 751e63ac9..3c999f0f6 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -71,7 +71,7 @@ {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} - + +