#}
{# {% trans 'Change auth period' %}: | #}
{# #}
@@ -239,6 +248,7 @@ $(document).ready(function () {
if($('#id_protocol_type').text() === 'rdp'){
$('.only-ssh').addClass('hidden')
}
+ $(".panel-body .table tr:visible:first").addClass('no-borders-tr');
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
@@ -321,6 +331,13 @@ $(document).ready(function () {
success: success,
flash_message: false
});
+}).on('click', '.btn-clear-auth', function () {
+ var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}';
+ APIUpdateAttr({
+ url: the_url,
+ method: 'DELETE',
+ success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
+ });
})
{% endblock %}
diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo
index 36220e321..6b02da8fa 100644
Binary files a/apps/i18n/zh/LC_MESSAGES/django.mo and b/apps/i18n/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po
index 8f95f9291..196c3fb36 100644
--- a/apps/i18n/zh/LC_MESSAGES/django.po
+++ b/apps/i18n/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: 2018-05-08 17:24+0800\n"
+"POT-Creation-Date: 2018-05-17 11:32+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler \n"
"Language-Team: Jumpserver team\n"
@@ -17,22 +17,22 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: assets/api/node.py:96
+#: assets/api/node.py:106
msgid "New node {}"
msgstr "新节点 {}"
-#: assets/api/node.py:225
+#: assets/api/node.py:242
msgid "更新节点资产硬件信息: {}"
msgstr ""
-#: assets/api/node.py:238
+#: assets/api/node.py:255
msgid "测试节点下资产是否可连接: {}"
msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:66 assets/models/user.py:103
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191
-#: assets/templates/assets/system_user_detail.html:166 perms/models.py:33
+#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33
msgid "Nodes"
msgstr "节点管理"
@@ -438,7 +438,7 @@ msgstr "默认资产组"
msgid "User"
msgstr "用户"
-#: assets/models/label.py:18 assets/models/node.py:15
+#: assets/models/label.py:18 assets/models/node.py:18
#: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value"
msgstr "值"
@@ -535,7 +535,7 @@ msgstr "测试系统用户可连接性: {}"
msgid "定期测试系统用户可连接性: {}"
msgstr ""
-#: assets/tasks.py:401
+#: assets/tasks.py:402
msgid "推送系统用户到入资产: {}"
msgstr ""
@@ -660,7 +660,7 @@ msgstr "重置"
#: common/templates/common/ldap_setting.html:60
#: common/templates/common/terminal_setting.html:103
#: perms/templates/perms/asset_permission_create_update.html:70
-#: terminal/templates/terminal/session_list.html:120
+#: terminal/templates/terminal/session_list.html:124
#: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47
#: users/templates/users/forgot_password.html:44
@@ -782,8 +782,8 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200
-#: assets/templates/assets/asset_list.html:634
-#: assets/templates/assets/system_user_detail.html:183
+#: assets/templates/assets/asset_list.html:636
+#: assets/templates/assets/system_user_detail.html:192
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:362
@@ -963,19 +963,19 @@ msgstr "仅显示当前节点资产"
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
-#: assets/templates/assets/asset_list.html:215
+#: assets/templates/assets/asset_list.html:217
msgid "Create node failed"
msgstr "创建节点失败"
-#: assets/templates/assets/asset_list.html:227
+#: assets/templates/assets/asset_list.html:229
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
-#: assets/templates/assets/asset_list.html:229
+#: assets/templates/assets/asset_list.html:231
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
-#: assets/templates/assets/asset_list.html:629
+#: assets/templates/assets/asset_list.html:631
#: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:357
#: users/templates/users/user_detail.html:382
@@ -984,20 +984,20 @@ msgstr "存在资产,不能删除"
msgid "Are you sure?"
msgstr "你确认吗?"
-#: assets/templates/assets/asset_list.html:630
+#: assets/templates/assets/asset_list.html:632
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
-#: assets/templates/assets/asset_list.html:638
+#: assets/templates/assets/asset_list.html:640
msgid "Asset Deleted."
msgstr "已被删除"
-#: assets/templates/assets/asset_list.html:639
-#: assets/templates/assets/asset_list.html:644
+#: assets/templates/assets/asset_list.html:641
+#: assets/templates/assets/asset_list.html:646
msgid "Asset Delete"
msgstr "删除"
-#: assets/templates/assets/asset_list.html:643
+#: assets/templates/assets/asset_list.html:645
msgid "Asset Deleting failed."
msgstr "删除失败"
@@ -1032,6 +1032,7 @@ msgid "Create gateway"
msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:87
+#: assets/templates/assets/domain_gateway_list.html:89
#: common/templates/common/email_setting.html:58
#: common/templates/common/ldap_setting.html:58
msgid "Test connection"
@@ -1080,10 +1081,23 @@ msgstr "家目录"
msgid "Uid"
msgstr "Uid"
-#: assets/templates/assets/system_user_detail.html:174
+#: assets/templates/assets/system_user_detail.html:153
+#: assets/templates/assets/system_user_detail.html:339
+msgid "Clear auth"
+msgstr "清除认证信息"
+
+#: assets/templates/assets/system_user_detail.html:156
+msgid "Clear"
+msgstr "清除"
+
+#: assets/templates/assets/system_user_detail.html:183
msgid "Add to node"
msgstr "添加到节点"
+#: assets/templates/assets/system_user_detail.html:339
+msgid "success"
+msgstr "成功"
+
#: assets/templates/assets/system_user_list.html:18
#: assets/views/system_user.py:45
msgid "Create system user"
@@ -2113,15 +2127,16 @@ msgstr "时长"
msgid "Monitor"
msgstr "监控"
-#: terminal/templates/terminal/session_list.html:105
+#: terminal/templates/terminal/session_list.html:106
+#: terminal/templates/terminal/session_list.html:108
msgid "Terminate"
msgstr "终断"
-#: terminal/templates/terminal/session_list.html:116
+#: terminal/templates/terminal/session_list.html:120
msgid "Terminate selected"
msgstr "终断所选"
-#: terminal/templates/terminal/session_list.html:136
+#: terminal/templates/terminal/session_list.html:140
msgid "Terminate task send, waiting ..."
msgstr "终断任务已发送,请等待"
diff --git a/apps/perms/api.py b/apps/perms/api.py
index be69d6158..8f663a0f4 100644
--- a/apps/perms/api.py
+++ b/apps/perms/api.py
@@ -6,7 +6,7 @@ from rest_framework.views import APIView, Response
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
from rest_framework import viewsets
-from common.utils import set_or_append_attr_bulk
+from common.utils import set_or_append_attr_bulk, get_object_or_none
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
from .utils import AssetPermissionUtil
from .models import AssetPermission
@@ -147,8 +147,13 @@ class UserGrantedNodeAssetsApi(ListAPIView):
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
- node = get_object_or_404(Node, id=node_id)
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
+ node = get_object_or_none(Node, id=node_id)
+
+ if not node:
+ unnode = [node for node in nodes if node.name == 'Unnode']
+ node = unnode[0] if unnode else None
+
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
diff --git a/apps/perms/utils.py b/apps/perms/utils.py
index b23b1cb7c..7899cc5cb 100644
--- a/apps/perms/utils.py
+++ b/apps/perms/utils.py
@@ -13,7 +13,6 @@ logger = get_logger(__file__)
class AssetPermissionUtil:
-
@staticmethod
def get_user_permissions(user):
return AssetPermission.objects.all().valid().filter(users=user)
@@ -122,6 +121,24 @@ class AssetPermissionUtil:
nodes[node].update(set(_system_users))
return nodes
+ @classmethod
+ def get_user_nodes_inherit_group(cls, user):
+ nodes = defaultdict(set)
+ groups = user.groups.all()
+ for group in groups:
+ _nodes = cls.get_user_group_nodes(group)
+ for node, system_users in _nodes.items():
+ nodes[node].update(set(system_users))
+ return nodes
+
+ @classmethod
+ def get_user_nodes(cls, user):
+ nodes = cls.get_user_nodes_direct(user)
+ nodes_inherit = cls.get_user_nodes_inherit_group(user)
+ for node, system_users in nodes_inherit.items():
+ nodes[node].update(set(system_users))
+ return nodes
+
@classmethod
def get_user_nodes_assets_direct(cls, user):
assets = defaultdict(set)
@@ -164,15 +181,24 @@ class AssetPermissionUtil:
:param user:
:return: {node: {asset: set(su1, su2)}}
"""
+ from assets.models import Node
+ unnode = Node(value='Unnode')
nodes = defaultdict(dict)
+ for _node in cls.get_user_nodes(user):
+ children = _node.get_family()
+ for node in children:
+ nodes[node] = defaultdict(set)
+ nodes[unnode] = defaultdict(set)
_assets = cls.get_user_assets(user)
for asset, _system_users in _assets.items():
_nodes = asset.get_nodes()
+ in_node = False
for node in _nodes:
- if asset in nodes[node]:
+ if node in nodes:
+ in_node = True
nodes[node][asset].update(_system_users)
- else:
- nodes[node][asset] = _system_users
+ if not in_node:
+ nodes[unnode][asset].update(_system_users)
return nodes
@classmethod
diff --git a/apps/terminal/api.py b/apps/terminal/api.py
index c9bb68ed6..924a30dfd 100644
--- a/apps/terminal/api.py
+++ b/apps/terminal/api.py
@@ -9,6 +9,7 @@ from django.core.cache import cache
from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone
from django.core.files.storage import default_storage
+from django.http.response import HttpResponseRedirectBase
from django.http import HttpResponseNotFound
from django.conf import settings
@@ -25,7 +26,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \
SessionSerializer, TaskSerializer, ReplaySerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly
-from .backends import get_command_store, get_multi_command_store, \
+from .backends import get_command_storage, get_multi_command_storage, \
SessionCommandSerializer
logger = logging.getLogger(__file__)
@@ -227,8 +228,8 @@ class CommandViewSet(viewsets.ViewSet):
}
"""
- command_store = get_command_store()
- multi_command_storage = get_multi_command_store()
+ command_store = get_command_storage()
+ multi_command_storage = get_multi_command_storage()
serializer_class = SessionCommandSerializer
permission_classes = (IsSuperUserOrAppUser,)
@@ -291,19 +292,20 @@ class SessionReplayViewSet(viewsets.ViewSet):
url = default_storage.url(path)
return redirect(url)
else:
- configs = settings.TERMINAL_REPLAY_STORAGE.items()
+ configs = settings.TERMINAL_REPLAY_STORAGE
+ configs = [cfg for cfg in configs if cfg['TYPE'] != 'server']
if not configs:
return HttpResponseNotFound()
- for name, config in configs:
- client = jms_storage.init(config)
- date = self.session.date_start.strftime('%Y-%m-%d')
- file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
- target_path = default_storage.base_location + '/' + path
-
- if client and client.has_file(file_path) and \
- client.download_file(file_path, target_path):
- return redirect(default_storage.url(path))
+ date = self.session.date_start.strftime('%Y-%m-%d')
+ file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
+ target_path = default_storage.base_location + '/' + path
+ storage = jms_storage.get_multi_object_storage(configs)
+ ok, err = storage.download(file_path, target_path)
+ if ok:
+ return redirect(default_storage.url(path))
+ else:
+ logger.error("Failed download replay file: {}".format(err))
return HttpResponseNotFound()
@@ -313,34 +315,14 @@ class SessionReplayV2ViewSet(SessionReplayViewSet):
session = None
def retrieve(self, request, *args, **kwargs):
- session_id = kwargs.get('pk')
- self.session = get_object_or_404(Session, id=session_id)
- path = self.gen_session_path()
+ response = super().retrieve(request, *args, **kwargs)
data = {
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
'src': '',
}
-
- if default_storage.exists(path):
- url = default_storage.url(path)
- data['src'] = url
+ if isinstance(response, HttpResponseRedirectBase):
+ data['src'] = response.url
return Response(data)
- else:
- configs = settings.TERMINAL_REPLAY_STORAGE.items()
- if not configs:
- return HttpResponseNotFound()
-
- for name, config in configs:
- client = jms_storage.init(config)
- date = self.session.date_start.strftime('%Y-%m-%d')
- file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
- target_path = default_storage.base_location + '/' + path
-
- if client and client.has_file(file_path) and \
- client.download_file(file_path, target_path):
- url = default_storage.url(path)
- data['src'] = url
- return Response(data)
return HttpResponseNotFound()
diff --git a/apps/terminal/backends/__init__.py b/apps/terminal/backends/__init__.py
index ef1ba56a9..9a1c338f5 100644
--- a/apps/terminal/backends/__init__.py
+++ b/apps/terminal/backends/__init__.py
@@ -7,19 +7,19 @@ TYPE_ENGINE_MAPPING = {
}
-def get_command_store():
- params = settings.COMMAND_STORAGE
- engine_class = import_module(params['ENGINE'])
- storage = engine_class.CommandStore(params)
+def get_command_storage():
+ config = settings.COMMAND_STORAGE
+ engine_class = import_module(config['ENGINE'])
+ storage = engine_class.CommandStore(config)
return storage
-def get_terminal_command_store():
+def get_terminal_command_storages():
storage_list = {}
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
tp = params['TYPE']
if tp == 'server':
- storage = get_command_store()
+ storage = get_command_storage()
else:
if not TYPE_ENGINE_MAPPING.get(tp):
continue
@@ -29,9 +29,9 @@ def get_terminal_command_store():
return storage_list
-def get_multi_command_store():
+def get_multi_command_storage():
from .command.multi import CommandStore
- storage_list = get_terminal_command_store().values()
+ storage_list = get_terminal_command_storages().values()
storage = CommandStore(storage_list)
return storage
diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py
index 9c75fc978..dde8e1e95 100644
--- a/apps/terminal/backends/command/es.py
+++ b/apps/terminal/backends/command/es.py
@@ -1,41 +1,22 @@
# -*- coding: utf-8 -*-
#
-from jms_es_sdk import ESStore
+from jms_storage.es import ESStorage
from .base import CommandBase
from .models import AbstractSessionCommand
-class CommandStore(CommandBase, ESStore):
+class CommandStore(ESStorage, CommandBase):
def __init__(self, params):
- hosts = params.get('HOSTS', ['http://localhost'])
- ESStore.__init__(self, hosts=hosts)
-
- def save(self, command):
- return ESStore.save(self, command)
-
- def bulk_save(self, commands):
- return ESStore.bulk_save(self, commands)
+ super().__init__(params)
def filter(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
- data = ESStore.filter(
- self, date_from=date_from, date_to=date_to,
- user=user, asset=asset, system_user=system_user,
- input=input, session=session
- )
+ data = super().filter(date_from=date_from, date_to=date_to,
+ user=user, asset=asset, system_user=system_user,
+ input=input, session=session)
return AbstractSessionCommand.from_multi_dict(
[item["_source"] for item in data["hits"] if item]
)
-
- def count(self, date_from=None, date_to=None,
- user=None, asset=None, system_user=None,
- input=None, session=None):
- amount = ESStore.count(
- self, date_from=date_from, date_to=date_to,
- user=user, asset=asset, system_user=system_user,
- input=input, session=session
- )
- return amount
diff --git a/apps/terminal/serializers.py b/apps/terminal/serializers.py
index 8c40315a7..c18c5023d 100644
--- a/apps/terminal/serializers.py
+++ b/apps/terminal/serializers.py
@@ -9,7 +9,7 @@ from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from common.utils import get_object_or_none
from .models import Terminal, Status, Session, Task
-from .backends import get_multi_command_store
+from .backends import get_multi_command_storage
class TerminalSerializer(serializers.ModelSerializer):
@@ -47,7 +47,7 @@ class TerminalSerializer(serializers.ModelSerializer):
class SessionSerializer(serializers.ModelSerializer):
command_amount = serializers.SerializerMethodField()
- command_store = get_multi_command_store()
+ command_store = get_multi_command_storage()
class Meta:
model = Session
diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py
index cd7120fec..c5643c67b 100644
--- a/apps/terminal/templatetags/terminal_tags.py
+++ b/apps/terminal/templatetags/terminal_tags.py
@@ -1,10 +1,10 @@
# ~*~ coding: utf-8 ~*~
from django import template
-from ..backends import get_multi_command_store
+from ..backends import get_multi_command_storage
register = template.Library()
-command_store = get_multi_command_store()
+command_store = get_multi_command_storage()
@register.filter
diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py
index 0af0b5bfd..748261414 100644
--- a/apps/terminal/views/command.py
+++ b/apps/terminal/views/command.py
@@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from ..models import Command
from .. import utils
-from ..backends import get_multi_command_store
+from ..backends import get_multi_command_storage
__all__ = ['CommandListView']
-common_storage = get_multi_command_store()
+common_storage = get_multi_command_storage()
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py
index 3b66baff7..71caeae48 100644
--- a/apps/terminal/views/session.py
+++ b/apps/terminal/views/session.py
@@ -10,7 +10,7 @@ from django.conf import settings
from users.utils import AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal
-from ..backends import get_multi_command_store
+from ..backends import get_multi_command_storage
from .. import utils
@@ -19,7 +19,7 @@ __all__ = [
'SessionDetailView',
]
-command_store = get_multi_command_store()
+command_store = get_multi_command_storage()
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
diff --git a/apps/users/models/group.py b/apps/users/models/group.py
index 128fbdbcb..48ed2e949 100644
--- a/apps/users/models/group.py
+++ b/apps/users/models/group.py
@@ -11,7 +11,7 @@ __all__ = ['UserGroup']
class UserGroup(NoDeleteModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
- name = models.CharField(max_length=128, verbose_name=_('Name'))
+ 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'))
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index e0ecc634e..af682f5ca 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -40,7 +40,6 @@ itsdangerous==0.24
itypes==1.1.0
Jinja2==2.10
jmespath==0.9.3
-jms-es-sdk
kombu==4.0.2
ldap3==2.4
MarkupSafe==1.0
@@ -62,7 +61,7 @@ pytz==2017.3
PyYAML==3.12
redis==2.10.6
requests==2.18.4
-jms-storage==0.0.13
+jms-storage==0.0.17
s3transfer==0.1.13
simplejson==3.13.2
six==1.11.0
diff --git a/utils/clean_duplicate_user_groups.py b/utils/clean_duplicate_user_groups.py
new file mode 100644
index 000000000..be8b2d9ec
--- /dev/null
+++ b/utils/clean_duplicate_user_groups.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+#
+
+import os
+import sys
+from collections import Counter
+import django
+from django.db.models import Count
+
+
+if os.path.exists('../apps'):
+ sys.path.insert(0, '../apps')
+elif os.path.exists('./apps'):
+ sys.path.insert(0, './apps')
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
+django.setup()
+
+from users.models import UserGroup
+
+
+def clean_group(interactive=True):
+ groups = UserGroup.objects.all()
+ groups_name_list = groups.values_list('name', flat=True)
+ groups_with_info = groups.annotate(Count('users'))\
+ .annotate(Count('asset_permissions'))
+
+ counter = Counter(groups_name_list)
+ for name, count in counter.items():
+ if count == 0:
+ continue
+ groups_duplicate = groups_with_info.filter(name=name)
+ need_clean_count = groups_duplicate.count()
+
+ for group in groups_duplicate:
+ need_clean = True
+ if group.users__count > 0:
+ need_clean = False
+ elif group.asset_permissions__count > 0:
+ need_clean = False
+ elif need_clean_count == 1:
+ need_clean = False
+
+ if need_clean:
+ confirm = True
+ if interactive:
+ confirm = False
+ while True:
+ confirm = input(
+ "Delete user group <{}>, create at {}? ([y]/n)".format(
+ name, group.date_created)
+ )
+ if confirm.lower() == "y":
+ confirm = True
+ break
+ elif confirm.lower() == "n":
+ confirm = False
+ break
+ else:
+ print("No valid input")
+ continue
+ if confirm:
+ group.delete()
+ print("Delete success: {}".format(name))
+ need_clean_count -= 1
+ else:
+ continue
+
+if __name__ == '__main__':
+ clean_group()
|