mirror of https://github.com/jumpserver/jumpserver
[Update] Auth Info (#2806)
* [Update] 修改支持auth info导出 * [Update] 统一认证查看 * [Update] 修改auth book manager * [Update] 修改auth info * [Update] 完成修改auth info * [Update] 优化apipull/2812/head
parent
8adaf629b4
commit
9cd75390bf
|
@ -6,11 +6,10 @@ from django.views.generic import TemplateView
|
|||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
|
||||
from ..models import RemoteApp
|
||||
|
@ -92,8 +91,9 @@ class RemoteAppDetailView(PermissionsMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserRemoteAppListView(LoginRequiredMixin, TemplateView):
|
||||
class UserRemoteAppListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'applications/user_remote_app_list.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
|
|
@ -1,58 +1,121 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets, status, generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
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.utils import get_object_or_none, get_logger
|
||||
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..models import Asset
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from ..backends import AssetUserManager
|
||||
from ..models import Asset, Node, SystemUser, AdminUser
|
||||
from .. import serializers
|
||||
from ..tasks import test_asset_users_connectivity_manual
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
|
||||
'AssetUserExportViewSet',
|
||||
]
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AssetUserViewSet(viewsets.GenericViewSet):
|
||||
class AssetUserFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
kwargs = {}
|
||||
for field in view.filter_fields:
|
||||
value = request.GET.get(field)
|
||||
if not value:
|
||||
continue
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
continue
|
||||
kwargs[field] = value
|
||||
return queryset.filter(**kwargs)
|
||||
|
||||
|
||||
class AssetUserSearchBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
value = request.GET.get('search')
|
||||
if not value:
|
||||
return queryset
|
||||
_queryset = AssetUserManager.none()
|
||||
for field in view.search_fields:
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
continue
|
||||
_queryset |= queryset.filter(**{field: value})
|
||||
return _queryset
|
||||
|
||||
|
||||
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
pagination_class = LimitOffsetPagination
|
||||
serializer_class = serializers.AssetUserSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser, )
|
||||
http_method_names = ['get', 'post']
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
filter_fields = [
|
||||
"id", "ip", "hostname", "username", "asset_id", "node_id",
|
||||
"system_user_id", "admin_user_id"
|
||||
]
|
||||
search_fields = filter_fields
|
||||
filter_backends = (
|
||||
filters.OrderingFilter,
|
||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
# 尽可能先返回更少的数据
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
queryset = AssetUserManager.filter(username=username, asset=asset)
|
||||
node_id = self.request.GET.get('node_id')
|
||||
admin_user_id = self.request.GET.get("admin_user_id")
|
||||
system_user_id = self.request.GET.get("system_user_id")
|
||||
|
||||
kwargs = {}
|
||||
assets = []
|
||||
|
||||
manager = AssetUserManager()
|
||||
if system_user_id:
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
assets = system_user.assets.all()
|
||||
username = system_user.username
|
||||
elif admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
assets = admin_user.assets.all()
|
||||
username = admin_user.username
|
||||
manager.prefer('admin_user')
|
||||
|
||||
if asset_id:
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
assets = [asset]
|
||||
elif node_id:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
assets = node.assets.all()
|
||||
|
||||
if username:
|
||||
kwargs['username'] = username
|
||||
if assets:
|
||||
kwargs['assets'] = assets
|
||||
|
||||
queryset = manager.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = sorted(
|
||||
queryset,
|
||||
key=lambda q: (q.asset.hostname, q.connectivity, q.username)
|
||||
)
|
||||
return queryset
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
|
@ -60,6 +123,10 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
|||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
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
|
||||
|
@ -70,9 +137,13 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
|||
def get_object(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
prefer = self.request.GET.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
try:
|
||||
instance = AssetUserManager.get(username, asset)
|
||||
manger = AssetUserManager()
|
||||
if prefer:
|
||||
manger.prefer(prefer)
|
||||
instance = manger.get(username, asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
@ -84,18 +155,36 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_asset_users(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
asset_users = AssetUserManager.filter(username=username, asset=asset)
|
||||
manager = AssetUserManager()
|
||||
asset_users = manager.filter(username=username, assets=[asset])
|
||||
return asset_users
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_users = self.get_asset_users()
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users)
|
||||
prefer = self.request.GET.get("prefer")
|
||||
kwargs = {}
|
||||
if prefer == "admin_user":
|
||||
kwargs["run_as_admin"] = True
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users, **kwargs)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetUserPushApi(generics.CreateAPIView):
|
||||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
serializer_class = serializers.AssetUserPushSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
asset = serializer.validated_data["asset"]
|
||||
username = serializer.validated_data["username"]
|
||||
pass
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .manager import AssetUserManager
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..models import AdminUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AdminUserBackend(AssetUserBackend):
|
||||
model = AdminUser
|
||||
backend = 'AdminUser'
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
model = None
|
||||
backend = "AssetUser"
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, **kwargs):
|
||||
queryset = cls.model.objects.all()
|
||||
if username:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
queryset = queryset.filter(assets__in=assets).distinct()
|
||||
queryset = cls.filter_queryset_more(queryset)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, asset_users, assets):
|
||||
instances = []
|
||||
for asset_user in asset_users:
|
||||
if not assets:
|
||||
assets = asset_user.assets.all()
|
||||
for asset in assets:
|
||||
instance = asset_user.construct_to_authbook(asset)
|
||||
instance.backend = cls.backend
|
||||
instances.append(instance)
|
||||
return instances
|
|
@ -1,60 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
NotSupportError = NotSupportError
|
||||
MSG_NOT_EXIST = '{} Object matching query does not exist'
|
||||
MSG_MULTIPLE = '{} get() returned more than one object ' \
|
||||
'-- it returned {}!'
|
||||
|
||||
@classmethod
|
||||
def get(cls, username, asset):
|
||||
instances = cls.filter(username, asset)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
cls.raise_does_not_exist(cls.__name__)
|
||||
else:
|
||||
cls.raise_multiple_return(cls.__name__, len(instances))
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
def filter(cls, username=None, assets=None, latest=True):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param asset: <Asset>对象
|
||||
:param assets: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def create(cls, **kwargs):
|
||||
"""
|
||||
:param kwargs:
|
||||
{
|
||||
name, username, asset, comment, password, public_key, private_key,
|
||||
(org_id)
|
||||
}
|
||||
:return: <AuthBook>对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def raise_does_not_exist(cls, name):
|
||||
raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name))
|
||||
class AssetUserQuerySet(list):
|
||||
def order_by(self, *ordering):
|
||||
_ordering = []
|
||||
reverse = False
|
||||
for i in ordering:
|
||||
if i[0] == '-':
|
||||
reverse = True
|
||||
i = i[1:]
|
||||
_ordering.append(i)
|
||||
self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def raise_multiple_return(cls, name, length):
|
||||
raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length))
|
||||
def filter_in(self, kwargs):
|
||||
in_kwargs = {}
|
||||
queryset = []
|
||||
for k, v in kwargs.items():
|
||||
if len(v) == 0:
|
||||
return self
|
||||
if k.find("__in") >= 0:
|
||||
in_kwargs[k] = v
|
||||
for k in in_kwargs:
|
||||
kwargs.pop(k)
|
||||
|
||||
if len(in_kwargs) == 0:
|
||||
return self
|
||||
for i in self:
|
||||
matched = True
|
||||
for k, v in in_kwargs.items():
|
||||
key = k.split('__')[0]
|
||||
attr = getattr(i, key, None)
|
||||
# 如果属性或者value中是uuid,则转换成string
|
||||
if isinstance(v[0], uuid.UUID):
|
||||
v = [str(i) for i in v]
|
||||
if isinstance(attr, uuid.UUID):
|
||||
attr = str(attr)
|
||||
if attr not in v:
|
||||
matched = False
|
||||
if matched:
|
||||
queryset.append(i)
|
||||
return AssetUserQuerySet(queryset)
|
||||
|
||||
def filter_equal(self, kwargs):
|
||||
def filter_it(obj):
|
||||
wanted = []
|
||||
real = []
|
||||
for k, v in kwargs.items():
|
||||
wanted.append(v)
|
||||
value = getattr(obj, k)
|
||||
if isinstance(value, uuid.UUID):
|
||||
value = str(value)
|
||||
real.append(value)
|
||||
return wanted == real
|
||||
if len(kwargs) > 0:
|
||||
queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
|
||||
else:
|
||||
queryset = self
|
||||
return queryset
|
||||
|
||||
def filter(self, **kwargs):
|
||||
queryset = self.filter_in(kwargs).filter_equal(kwargs)
|
||||
return queryset
|
||||
|
||||
def __or__(self, other):
|
||||
self.extend(other)
|
||||
return self
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import AuthBook
|
||||
|
||||
from ..base import BaseBackend
|
||||
from ..models import AuthBook
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AuthBookBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
def filter(cls, username=None, assets=None, latest=True):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
if assets:
|
||||
queryset = queryset.filter(asset__in=assets)
|
||||
if latest:
|
||||
queryset = queryset.latest_version()
|
||||
return queryset
|
|
@ -1,2 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -1,4 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .utils import construct_authbook_object
|
||||
|
||||
|
||||
class AdminUserBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
instances = cls.construct_authbook_objects(username, asset)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _get_assets(cls, asset):
|
||||
if not asset:
|
||||
assets = Asset.objects.all().prefetch_related('admin_user')
|
||||
else:
|
||||
assets = [asset]
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, username, asset):
|
||||
instances = []
|
||||
assets = cls._get_assets(asset)
|
||||
for asset in assets:
|
||||
if username is not None and asset.admin_user.username != username:
|
||||
continue
|
||||
instance = construct_authbook_object(asset.admin_user, asset)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise cls.NotSupportError("Not support create")
|
|
@ -1,32 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .admin_user import AdminUserBackend
|
||||
from .system_user import SystemUserBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
admin_user_instances = AdminUserBackend.filter(username, asset)
|
||||
system_user_instances = SystemUserBackend.filter(username, asset)
|
||||
instances = cls._merge_instances(admin_user_instances, system_user_instances)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _merge_instances(cls, admin_user_instances, system_user_instances):
|
||||
admin_user_instances_keyword_list = [
|
||||
{'username': instance.username, 'asset': instance.asset}
|
||||
for instance in admin_user_instances
|
||||
]
|
||||
instances = [
|
||||
instance for instance in system_user_instances
|
||||
if instance.keyword not in admin_user_instances_keyword_list
|
||||
]
|
||||
admin_user_instances.extend(instances)
|
||||
return admin_user_instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise cls.NotSupportError("Not support create")
|
|
@ -1,75 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .utils import construct_authbook_object
|
||||
|
||||
|
||||
class SystemUserBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
instances = cls.construct_authbook_objects(username, asset)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _distinct_system_users_by_username(cls, system_users):
|
||||
system_users = sorted(
|
||||
system_users,
|
||||
key=lambda su: (su.username, su.priority, su.date_updated),
|
||||
reverse=True,
|
||||
)
|
||||
results = itertools.groupby(system_users, key=lambda su: su.username)
|
||||
system_users = [next(result[1]) for result in results]
|
||||
return system_users
|
||||
|
||||
@classmethod
|
||||
def _filter_system_users_by_username(cls, system_users, username):
|
||||
_system_users = cls._distinct_system_users_by_username(system_users)
|
||||
if username is not None:
|
||||
_system_users = [su for su in _system_users if username == su.username]
|
||||
return _system_users
|
||||
|
||||
@classmethod
|
||||
def _construct_authbook_objects(cls, system_users, asset):
|
||||
instances = []
|
||||
for system_user in system_users:
|
||||
instance = construct_authbook_object(system_user, asset)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _get_assets_with_system_users(cls, asset=None):
|
||||
"""
|
||||
{ 'asset': set(<SystemUser>, <SystemUser>, ...) }
|
||||
"""
|
||||
if not asset:
|
||||
_assets = Asset.objects.all().prefetch_related('systemuser_set')
|
||||
else:
|
||||
_assets = [asset]
|
||||
|
||||
assets = {asset: set(asset.systemuser_set.all()) for asset in _assets}
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, username, asset):
|
||||
"""
|
||||
:return: [<AuthBook>, <AuthBook>, ...]
|
||||
"""
|
||||
instances = []
|
||||
assets = cls._get_assets_with_system_users(asset)
|
||||
for _asset, _system_users in assets.items():
|
||||
_system_users = cls._filter_system_users_by_username(_system_users, username)
|
||||
_instances = cls._construct_authbook_objects(_system_users, _asset)
|
||||
instances.extend(_instances)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise Exception("Not support create")
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import AuthBook
|
||||
|
||||
|
||||
def construct_authbook_object(asset_user, asset):
|
||||
"""
|
||||
作用: 将<AssetUser>对象构造成为<AuthBook>对象并返回
|
||||
|
||||
:param asset_user: <AdminUser>或<SystemUser>对象
|
||||
:param asset: <Asset>对象
|
||||
:return: <AuthBook>对象
|
||||
"""
|
||||
fields = [
|
||||
'id', 'name', 'username', 'comment', 'org_id',
|
||||
'_password', '_private_key', '_public_key',
|
||||
'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
|
||||
obj = AuthBook(asset=asset, version=0, is_latest=True)
|
||||
for field in fields:
|
||||
value = getattr(asset_user, field)
|
||||
setattr(obj, field, value)
|
||||
return obj
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
|
||||
from .base import AssetUserQuerySet
|
||||
from .db import AuthBookBackend
|
||||
from .system_user import SystemUserBackend
|
||||
from .admin_user import AdminUserBackend
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
NotSupportError = NotSupportError
|
||||
MSG_NOT_EXIST = '{} Object matching query does not exist'
|
||||
MSG_MULTIPLE = '{} get() returned more than one object ' \
|
||||
'-- it returned {}!'
|
||||
|
||||
backends = (
|
||||
('db', AuthBookBackend),
|
||||
('system_user', SystemUserBackend),
|
||||
('admin_user', AdminUserBackend),
|
||||
)
|
||||
|
||||
_prefer = "system_user"
|
||||
_using = None
|
||||
|
||||
def filter(self, username=None, assets=None, latest=True):
|
||||
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)
|
||||
|
||||
instances_map = {}
|
||||
instances = []
|
||||
for name, backend in self.backends:
|
||||
_instances = backend.filter(
|
||||
username=username, assets=assets, latest=latest
|
||||
)
|
||||
instances_map[name] = _instances
|
||||
|
||||
# 如果不是获取最新版本,就不再merge
|
||||
if not latest:
|
||||
for _instances in instances_map.values():
|
||||
instances.extend(_instances)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
# merge的顺序
|
||||
ordering = ["db"]
|
||||
if self._prefer == "system_user":
|
||||
ordering.extend(["system_user", "admin_user"])
|
||||
else:
|
||||
ordering.extend(["admin_user", "system_user"])
|
||||
# 根据prefer决定优先使用系统用户或管理用户谁的
|
||||
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])
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
self.raise_does_not_exist(self.__name__)
|
||||
else:
|
||||
self.raise_multiple_return(self.__name__, len(instances))
|
||||
|
||||
def raise_does_not_exist(self, name):
|
||||
raise self.ObjectDoesNotExist(self.MSG_NOT_EXIST.format(name))
|
||||
|
||||
def raise_multiple_return(self, name, length):
|
||||
raise self.MultipleObjectsReturned(self.MSG_MULTIPLE.format(name, length))
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
instance = AuthBookBackend.create(**kwargs)
|
||||
return instance
|
||||
|
||||
def all(self):
|
||||
return self.filter()
|
||||
|
||||
def prefer(self, s):
|
||||
self._prefer = s
|
||||
return self
|
||||
|
||||
def using(self, s):
|
||||
self._using = s
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def none():
|
||||
return AssetUserQuerySet()
|
||||
|
||||
@staticmethod
|
||||
def _merge_instances(*args):
|
||||
instances = list(args[0])
|
||||
keywords = [obj.keyword for obj in instances]
|
||||
|
||||
for _instances in args[1:]:
|
||||
need_merge_instances = [obj for obj in _instances if obj.keyword not in keywords]
|
||||
need_merge_keywords = [obj.keyword for obj in need_merge_instances]
|
||||
instances.extend(need_merge_instances)
|
||||
keywords.extend(need_merge_keywords)
|
||||
return instances
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .base import BaseBackend
|
||||
|
||||
from .external.utils import get_backend
|
||||
from .internal.asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AssetUserManager(BaseBackend):
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
external_backend = get_backend()
|
||||
internal_backend = AssetUserBackend
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
external_instance = list(cls.external_backend.filter(username, asset))
|
||||
internal_instance = list(cls.internal_backend.filter(username, asset))
|
||||
instances = cls._merge_instances(external_instance, internal_instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
instance = cls.external_backend.create(**kwargs)
|
||||
return instance
|
||||
|
||||
@classmethod
|
||||
def _merge_instances(cls, external_instances, internal_instances):
|
||||
external_instances_keyword_list = [
|
||||
{'username': instance.username, 'asset': instance.asset}
|
||||
for instance in external_instances
|
||||
]
|
||||
instances = [
|
||||
instance for instance in internal_instances
|
||||
if instance.keyword not in external_instances_keyword_list
|
||||
]
|
||||
external_instances.extend(instances)
|
||||
return external_instances
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import SystemUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class SystemUserBackend(AssetUserBackend):
|
||||
model = SystemUser
|
||||
backend = 'SystemUser'
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
queryset = cls._distinct_system_users_by_username(queryset)
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def _distinct_system_users_by_username(cls, system_users):
|
||||
system_users = sorted(
|
||||
system_users,
|
||||
key=lambda su: (su.username, su.priority, su.date_updated),
|
||||
reverse=True,
|
||||
)
|
||||
results = itertools.groupby(system_users, key=lambda su: su.username)
|
||||
system_users = [next(result[1]) for result in results]
|
||||
return system_users
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
|
|||
}
|
||||
]
|
||||
|
||||
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
|
||||
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}'
|
||||
TEST_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
|
|
|
@ -104,7 +104,7 @@ class Asset(OrgModelMixin):
|
|||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
|
||||
|
||||
# Some information
|
||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
|
@ -250,16 +250,11 @@ class Asset(OrgModelMixin):
|
|||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if not self.is_unixlike():
|
||||
return self.REACHABLE
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cached = cache.get(key, None)
|
||||
return cached if cached is not None else self.UNKNOWN
|
||||
return self.admin_user.get_connectivity_of(self)
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cache.set(key, value, 3600*2)
|
||||
self.admin_user.set_connectivity_of(self, value)
|
||||
|
||||
def get_auth_info(self):
|
||||
if not self.admin_user:
|
||||
|
|
|
@ -29,6 +29,9 @@ class AuthBook(AssetUser):
|
|||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
|
||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
||||
backend = "db"
|
||||
# 用于system user和admin_user的动态设置
|
||||
_connectivity = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('AuthBook')
|
||||
|
@ -40,7 +43,8 @@ class AuthBook(AssetUser):
|
|||
|
||||
def _get_pre_obj(self):
|
||||
pre_obj = self.__class__.objects.filter(
|
||||
username=self.username, asset=self.asset).latest_version().first()
|
||||
username=self.username, asset=self.asset
|
||||
).latest_version().first()
|
||||
return pre_obj
|
||||
|
||||
def _remove_pre_obj_latest(self):
|
||||
|
@ -63,30 +67,30 @@ class AuthBook(AssetUser):
|
|||
|
||||
@property
|
||||
def _conn_cache_key(self):
|
||||
return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id)
|
||||
return ASSET_USER_CONN_CACHE_KEY.format(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):
|
||||
_connectivity = self.UNKNOWN
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.UNREACHABLE
|
||||
|
||||
for host in value.get('contacted', []):
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.REACHABLE
|
||||
|
||||
cache.set(self._conn_cache_key, _connectivity, 3600)
|
||||
cache.set(self._conn_cache_key, value, 3600)
|
||||
|
||||
@property
|
||||
def keyword(self):
|
||||
return {'username': self.username, 'asset': self.asset}
|
||||
return '{}_#_{}'.format(self.username, str(self.asset.id))
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
return self.asset.hostname
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.asset.ip
|
||||
|
||||
def __str__(self):
|
||||
return '{}@{}'.format(self.username, self.asset)
|
||||
|
|
|
@ -6,6 +6,7 @@ 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
|
||||
|
||||
|
@ -39,6 +40,8 @@ class AssetUser(OrgModelMixin):
|
|||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
CONNECTIVITY_CACHE_KEY = "CONNECTIVITY_{}"
|
||||
_prefer = "system_user"
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
|
@ -124,10 +127,21 @@ class AssetUser(OrgModelMixin):
|
|||
def get_auth(self, asset=None):
|
||||
pass
|
||||
|
||||
def get_connectivity_of(self, asset):
|
||||
i = self.generate_id_with_asset(asset)
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(i)
|
||||
return cache.get(key)
|
||||
|
||||
def set_connectivity_of(self, asset, c):
|
||||
i = self.generate_id_with_asset(asset)
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(i)
|
||||
cache.set(key, c, 3600)
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..backends import AssetUserManager
|
||||
try:
|
||||
other = AssetUserManager.get(username=self.username, asset=asset)
|
||||
manager = AssetUserManager().prefer(self._prefer)
|
||||
other = manager.get(username=self.username, asset=asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
else:
|
||||
|
@ -172,5 +186,25 @@ class AssetUser(OrgModelMixin):
|
|||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
id_ = '{}_{}'.format(asset.id, self.id)
|
||||
id_ = uuid.UUID(md5(id_.encode()).hexdigest())
|
||||
return id_
|
||||
|
||||
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
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -32,6 +32,7 @@ class AdminUser(AssetUser):
|
|||
become_user = models.CharField(default='root', max_length=64)
|
||||
_become_pass = models.CharField(default='', max_length=128)
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
|
||||
_prefer = "admin_user"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -61,7 +62,7 @@ class AdminUser(AssetUser):
|
|||
return info
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.asset_set.all()
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
|
||||
@property
|
||||
|
@ -174,17 +175,20 @@ class SystemUser(AssetUser):
|
|||
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)
|
||||
|
||||
|
|
|
@ -4,49 +4,70 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import AuthBook
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
from common.utils import validate_ssh_private_key
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
|
||||
'AssetUserExportSerializer', 'AssetUserPushSerializer',
|
||||
]
|
||||
|
||||
|
||||
class AssetUserSerializer(serializers.ModelSerializer):
|
||||
class BasicAssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['hostname', 'ip']
|
||||
|
||||
|
||||
class AssetUserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
||||
connectivity = serializers.CharField(read_only=True, label=_("Connectivity"))
|
||||
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, help_text=_('Password')
|
||||
required=False, label=_('Password')
|
||||
)
|
||||
public_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, help_text=_('Public key')
|
||||
required=False, label=_('Public key')
|
||||
)
|
||||
private_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, help_text=_('Private key')
|
||||
required=False, label=_('Private key')
|
||||
)
|
||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
||||
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
read_only_fields = (
|
||||
'date_created', 'date_updated', 'created_by',
|
||||
'is_latest', 'version', 'connectivity',
|
||||
)
|
||||
fields = '__all__'
|
||||
fields = [
|
||||
"id", "hostname", "ip", "username", "password", "asset", "version",
|
||||
"is_latest", "connectivity", "backend", "org_id",
|
||||
"date_created", "date_updated", "private_key", "public_key",
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'required': True}
|
||||
'username': {'required': True},
|
||||
}
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields = [f for f in fields if not f.startswith('_') and f != 'id']
|
||||
fields.extend(['connectivity'])
|
||||
return fields
|
||||
def validate_private_key(self, key):
|
||||
password = self.initial_data.get("password")
|
||||
valid = validate_ssh_private_key(key, password)
|
||||
if not valid:
|
||||
raise serializers.ValidationError(_("private key invalid"))
|
||||
return key
|
||||
|
||||
def create(self, validated_data):
|
||||
kwargs = {
|
||||
'name': validated_data.get('name'),
|
||||
'name': validated_data.get('username'),
|
||||
'username': validated_data.get('username'),
|
||||
'asset': validated_data.get('asset'),
|
||||
'comment': validated_data.get('comment', ''),
|
||||
|
@ -59,7 +80,33 @@ class AssetUserSerializer(serializers.ModelSerializer):
|
|||
return instance
|
||||
|
||||
|
||||
class AssetUserExportSerializer(AssetUserSerializer):
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Password')
|
||||
)
|
||||
public_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Public key')
|
||||
)
|
||||
private_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Private key')
|
||||
)
|
||||
|
||||
|
||||
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
fields = ['password', 'private_key', 'public_key']
|
||||
|
||||
|
||||
class AssetUserPushSerializer(serializers.Serializer):
|
||||
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
|
||||
username = serializers.CharField(max_length=1024)
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
|
|
@ -563,11 +563,17 @@ def get_test_asset_user_connectivity_tasks(asset):
|
|||
@shared_task
|
||||
def set_asset_user_connectivity_info(asset_user, result):
|
||||
summary = result[1]
|
||||
asset_user.connectivity = summary
|
||||
if summary.get('contacted'):
|
||||
connectivity = 1
|
||||
elif summary.get("dark"):
|
||||
connectivity = 0
|
||||
else:
|
||||
connectivity = 3
|
||||
asset_user.connectivity = connectivity
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_user_connectivity_util(asset_user, task_name):
|
||||
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
|
||||
"""
|
||||
:param asset_user: <AuthBook>对象
|
||||
:param task_name:
|
||||
|
@ -582,23 +588,29 @@ def test_asset_user_connectivity_util(asset_user, task_name):
|
|||
if not tasks:
|
||||
return
|
||||
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS,
|
||||
run_as=asset_user.username, created_by=asset_user.org_id
|
||||
)
|
||||
args = (task_name,)
|
||||
kwargs = {
|
||||
'hosts': [asset_user.asset], 'tasks': tasks,
|
||||
'pattern': 'all', 'options': const.TASK_OPTIONS,
|
||||
'created_by': asset_user.org_id,
|
||||
}
|
||||
if run_as_admin:
|
||||
kwargs["run_as_admin"] = True
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_users_connectivity_manual(asset_users):
|
||||
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
|
||||
"""
|
||||
:param asset_users: <AuthBook>对象
|
||||
"""
|
||||
for asset_user in asset_users:
|
||||
task_name = _("Test asset user connectivity: {}").format(asset_user)
|
||||
test_asset_user_connectivity_util(asset_user, task_name)
|
||||
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
|
||||
|
||||
|
||||
# @shared_task
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}asset_user_auth_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_modal_confirm').trigger('click'); return false;}">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Hostname" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static" id="id_hostname_p"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static" id="id_username_p"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Password" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" id="id_password" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_user_auth_modal_confirm{% endblock %}
|
|
@ -0,0 +1,87 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}asset_user_auth_update_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_update_modal_confirm').trigger('click'); return false;}">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Hostname" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static" id="id_hostname_p"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static" id="id_username_p"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Password" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" id="id_password_auth" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Private key" %}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row bootstrap3-multi-input">
|
||||
<div class="col-xs-12">
|
||||
<input id="id_private_key" type="file" name="private_key"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
var authHostname, authUsername, authAssetId = null;
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
}).on("show.bs.modal", "#asset_user_auth_update_modal", function () {
|
||||
$('#id_hostname_p').html(authHostname);
|
||||
$('#id_username_p').html(authUsername);
|
||||
$('#id_password_auth').parent().removeClass('has-error');
|
||||
$('#id_password_auth').val('');
|
||||
}).on('click', '#btn_asset_user_auth_update_modal_confirm', function(){
|
||||
var password = $('#id_password_auth').val();
|
||||
var privateKey = $('#id_private_key').prop('files');
|
||||
var hasPrivateKey = privateKey.length > 0;
|
||||
if (!password && !hasPrivateKey) {
|
||||
$('#id_password_auth').parent().addClass('has-error');
|
||||
return
|
||||
}
|
||||
var data = {
|
||||
'asset': authAssetId,
|
||||
'username': authUsername
|
||||
};
|
||||
if (password) {
|
||||
data["password"] = password
|
||||
}
|
||||
var props = {
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
form: $("form"),
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
$("#asset_user_auth_update_modal").modal('hide');
|
||||
}
|
||||
};
|
||||
if (hasPrivateKey) {
|
||||
var reader = new FileReader();//新建一个FileReader
|
||||
reader.readAsText(privateKey[0], "UTF-8");//读取文件
|
||||
reader.onload = function(evt){ //读取完文件之后会回来这里
|
||||
data["private_key"] = evt.target.result;
|
||||
formSubmit(props);
|
||||
}
|
||||
}
|
||||
if (!hasPrivateKey) {
|
||||
formSubmit(props);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_user_auth_update_modal_confirm{% endblock %}
|
|
@ -10,17 +10,7 @@
|
|||
}
|
||||
</style>
|
||||
<form class="form-horizontal" action="" style="padding-top: 20px">
|
||||
<div class="form-group mfa-field">
|
||||
<label for="mfa" class="col-sm-2 control-label">{% trans 'MFA' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="mfa" class="form-control input-sm" name="mfa">
|
||||
<span id="mfa_error" class="help-block">{% trans "Need otp auth for view auth" %}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div hidden class="auth-field">
|
||||
<div class="auth-field">
|
||||
<div class="form-group">
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label>
|
||||
<div class="col-sm-8">
|
||||
|
@ -45,13 +35,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% include 'authentication/_mfa_confirm_modal.html' %}
|
||||
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
|
||||
<script>
|
||||
var showPassword = false;
|
||||
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
|
||||
var asset_id = "";
|
||||
var host = "";
|
||||
var username = "";
|
||||
var authAssetId = "";
|
||||
var authHostname = "";
|
||||
var authUsername = "";
|
||||
var mfaFor = "";
|
||||
|
||||
function initClipboard() {
|
||||
var clipboard = new Clipboard('.btn-copy-password', {
|
||||
|
@ -65,12 +57,12 @@ function initClipboard() {
|
|||
}
|
||||
|
||||
function showAuth() {
|
||||
$(".mfa-field").hide();
|
||||
$(".auth-field").show();
|
||||
|
||||
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + asset_id + "&username=" + username;
|
||||
$("#id_username_view").html(username);
|
||||
$("#id_hostname_view").html(host);
|
||||
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + authAssetId + "&username=" + authUsername;
|
||||
if (prefer) {
|
||||
url = setUrlParam(url, 'prefer', prefer)
|
||||
}
|
||||
$("#id_username_view").html(authUsername);
|
||||
$("#id_hostname_view").html(authHostname);
|
||||
var success = function (data) {
|
||||
var password = data.password;
|
||||
$("#id_password_view").val(password);
|
||||
|
@ -88,11 +80,6 @@ function showAuth() {
|
|||
})
|
||||
}
|
||||
|
||||
function showMFA() {
|
||||
$(".mfa-field").show();
|
||||
$(".auth-field").hide();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initClipboard();
|
||||
}).on("click", ".btn-show-password", function () {
|
||||
|
@ -108,30 +95,21 @@ $(document).ready(function () {
|
|||
lastMFATime = 0
|
||||
}
|
||||
var nowTime = now.getTime() / 1000;
|
||||
if (nowTime - lastMFATime < 60*10 ) {
|
||||
if (nowTime - lastMFATime > 60*10 ) {
|
||||
setTimeout(function () {
|
||||
$("#asset_user_auth_view").modal("hide");
|
||||
}, 100);
|
||||
mfaFor = "viewAuth";
|
||||
$("#mfa_auth_confirm").modal("show");
|
||||
} else {
|
||||
showAuth();
|
||||
}
|
||||
}).on("click", ".btn-mfa", function () {
|
||||
var url = "{% url 'api-auth:user-otp-verify' %}";
|
||||
var data = {
|
||||
code: $("#mfa").val()
|
||||
};
|
||||
var success = function () {
|
||||
var now = new Date();
|
||||
lastMFATime = now.getTime() / 1000;
|
||||
showAuth()
|
||||
};
|
||||
var error = function () {
|
||||
$("#mfa_error").addClass("text-danger").html("Code error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success: success,
|
||||
flash_message: false,
|
||||
error: error
|
||||
})
|
||||
}).on("success", '#mfa_auth_confirm', function () {
|
||||
if (mfaFor !== "viewAuth") {
|
||||
return
|
||||
}
|
||||
$("#asset_user_auth_view").modal("show");
|
||||
showAuth();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,129 @@
|
|||
{% load i18n %}
|
||||
<style>
|
||||
.btn-group>.btn+.dropdown-toggle {
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
<table class="table table-striped table-bordered table-hover" id="asset_user_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Version' %}</th>
|
||||
<th class="text-center">{% trans 'Connectivity'%}</th>
|
||||
<th class="text-center">{% trans 'Datetime' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_auth_update_modal.html' %}
|
||||
{% include 'assets/_asset_user_auth_view_modal.html' %}
|
||||
|
||||
<script>
|
||||
var assetUserListUrl = "{% url "api-assets:asset-user-list" %}";
|
||||
var assetUserTable;
|
||||
var needPush = false;
|
||||
var prefer = null;
|
||||
|
||||
function initAssetUserTable() {
|
||||
var options = {
|
||||
ele: $('#asset_user_list_table'),
|
||||
toggle: true,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData == 1) {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
} else if (cellData == 0) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: 6, createdCell: function (td, cellData) {
|
||||
var date = new Date(cellData);
|
||||
$(td).html(date.toLocaleString());
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
var view_btn = '<button class="btn btn-xs btn-primary m-l-xs btn-view-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans "View" %}</button>'
|
||||
var update_btn = '<li><a class="btn-update-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Update' %}</a></li>';
|
||||
var test_btn = '<li><a class="btn-test-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Test' %}</a></li>';
|
||||
var push_btn = '<li><a class="btn-push-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Push' %}</a></li>';
|
||||
if (needPush) {
|
||||
test_btn += push_btn;
|
||||
}
|
||||
var actions = '<div class="btn-group">' + view_btn +
|
||||
' <button data-toggle="dropdown" class="btn btn-primary btn-xs dropdown-toggle">' +
|
||||
' <span class="caret"></span>' +
|
||||
' </button>' +
|
||||
' <ul class="dropdown-menu">' +
|
||||
update_btn + test_btn +
|
||||
' </ul>' +
|
||||
' </div>';
|
||||
actions = actions.replaceAll("username123", rowData.username)
|
||||
.replaceAll("hostname123", rowData.hostname)
|
||||
.replaceAll("asset123", rowData.asset);
|
||||
$(td).html(actions);
|
||||
},
|
||||
width: '70px'
|
||||
}
|
||||
],
|
||||
ajax_url: assetUserListUrl,
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname"}, {data: "ip"},
|
||||
{data: "username", orderable: false}, {data: "version", orderable: false},
|
||||
{data: "connectivity", orderable: false},
|
||||
{data: "date_created", orderable: false},
|
||||
{data: "asset", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
table = jumpserver.initServerSideDataTable(options);
|
||||
return table
|
||||
}
|
||||
$(document).ready(function(){
|
||||
})
|
||||
.on('click', '.btn-view-auth', function () {
|
||||
authAssetId = $(this).data("asset") ;
|
||||
authHostname = $(this).data("hostname");
|
||||
authUsername = $(this).data('user');
|
||||
$("#asset_user_auth_view").modal('show');
|
||||
})
|
||||
.on('click', '.btn-update-auth', function() {
|
||||
authUsername = $(this).data("user") ;
|
||||
authHostname = $(this).data("hostname");
|
||||
authAssetId = $(this).data("asset");
|
||||
$("#asset_user_auth_update_modal").modal('show');
|
||||
})
|
||||
.on("click", '.btn-test-auth', function () {
|
||||
authUsername = $(this).data("user") ;
|
||||
authAssetId = $(this).data("asset");
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id=" + authAssetId + "&username=" + authUsername;
|
||||
if (prefer) {
|
||||
the_url = setUrlParam(the_url, "prefer", prefer)
|
||||
}
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
|
@ -23,7 +23,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="col-sm-9" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
|
||||
|
@ -42,23 +42,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-striped table-bordered table-hover" id="asset_list_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="col-sm-3" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
|
@ -84,64 +72,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
||||
function initTable() {
|
||||
var reachable = {{ admin_user.REACHABLE }};
|
||||
var unreachable = {{ admin_user.UNREACHABLE }};
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (cellData === unreachable) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (cellData === reachable) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "View auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
btn += view_btn;
|
||||
btn += test_btn;
|
||||
$(td).html(btn);
|
||||
}}
|
||||
],
|
||||
|
||||
ajax_url: '{% url "api-assets:admin-user-assets" pk=admin_user.id %}',
|
||||
columns: [
|
||||
{data: "hostname" }, {data: "ip" },
|
||||
{data: "port" }, {data: "connectivity" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
function initAssetUserAuthModalForm(hostname, username){
|
||||
$('#id_hostname_p').html(hostname);
|
||||
$('#id_username_p').html(username);
|
||||
$('#id_password').parent().removeClass('has-error');
|
||||
$('#id_password').val('');
|
||||
}
|
||||
|
||||
var assetId ;
|
||||
|
||||
$(document).ready(function () {
|
||||
initTable();
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "admin_user_id", "{{ admin_user.id }}");
|
||||
prefer = "admin_user";
|
||||
initAssetUserTable();
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var asset_id = $(this).data('uid');
|
||||
|
@ -173,37 +111,10 @@ $(document).ready(function () {
|
|||
});
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
assetId = $(this).data('aid');
|
||||
var hostname = $(this).data('hostname');
|
||||
var username = '{{ admin_user.username }}';
|
||||
initAssetUserAuthModalForm(hostname, username);
|
||||
$("#asset_user_auth_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
|
||||
var password = $('#id_password').val();
|
||||
if (password){
|
||||
var data = {
|
||||
'name': "{{ admin_user.username }}",
|
||||
'asset': assetId,
|
||||
'username': "{{ admin_user.username }}",
|
||||
'password': password
|
||||
};
|
||||
formSubmit({
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
},
|
||||
error: function () {
|
||||
toastr.error("{% trans 'Update failed!' %}");
|
||||
}
|
||||
});
|
||||
$("#asset_user_auth_modal").modal('hide');
|
||||
}
|
||||
else{
|
||||
$('#id_password').parent().addClass('has-error');
|
||||
}
|
||||
asset_id = $(this).data('aid');
|
||||
hostname = $(this).data('hostname');
|
||||
username = '{{ admin_user.username }}';
|
||||
$("#asset_user_auth_update_modal").modal();
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = $(this).data("aid") ;
|
||||
|
|
|
@ -37,20 +37,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover" id="asset_user_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Password version' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Date updated' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -82,110 +69,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetUserAuthModalForm(hostname){
|
||||
$('#id_hostname_p').html(hostname);
|
||||
$('#id_username_p').html(username);
|
||||
$('#id_password').parent().removeClass('has-error');
|
||||
$('#id_password').val('');
|
||||
}
|
||||
function initAssetUserTable() {
|
||||
var reachable = {{ asset.admin_user.REACHABLE }};
|
||||
var unreachable = {{ asset.admin_user.UNREACHABLE }};
|
||||
var options = {
|
||||
ele: $('#asset_user_list'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (cellData === unreachable) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (cellData === reachable) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
$(td).html(cellData.slice(0, -6));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
var btn = '<a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Update auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-username="DEFAULT_USERNAME">{% trans "View auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
btn += view_btn;
|
||||
{% if asset.is_support_ansible %}
|
||||
btn += test_btn;
|
||||
{% endif %}
|
||||
$(td).html(btn);
|
||||
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-user-list" %}' + '?asset_id={{ asset.id }}',
|
||||
columns: [
|
||||
{data: function (){return ''}}, {data: "username" },
|
||||
{data: "version"}, {data: "connectivity"}, {data: "date_updated"},
|
||||
{data: "username", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}
|
||||
var username;
|
||||
$(document).ready(function () {
|
||||
initAssetUserTable();
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
username = $(this).data('username');
|
||||
var hostname = "{{ asset.hostname }}";
|
||||
initAssetUserAuthModalForm(hostname, username);
|
||||
$("#asset_user_auth_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
|
||||
var password = $('#id_password').val();
|
||||
if (password){
|
||||
var data = {
|
||||
'name': username,
|
||||
'asset': "{{ asset.id }}",
|
||||
'username': username,
|
||||
'password': password
|
||||
};
|
||||
formSubmit({
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
},
|
||||
error: function () {
|
||||
toastr.error("{% trans 'Update failed!' %}");
|
||||
}
|
||||
});
|
||||
$("#asset_user_auth_modal").modal('hide');
|
||||
}
|
||||
else{
|
||||
$('#id_password').parent().addClass('has-error');
|
||||
}
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var username = $(this).data('username');
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}" + "&username=" + username;
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "asset_id", "{{ asset.id }}");
|
||||
initAssetUserTable()
|
||||
})
|
||||
|
||||
.on('click', '#btn-bulk-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}";
|
||||
var success = function (data) {
|
||||
|
@ -200,11 +91,5 @@ $(document).ready(function () {
|
|||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = "{{ asset.id }}" ;
|
||||
host = "{{ asset.hostname }}";
|
||||
username = $(this).data("username");
|
||||
$("#asset_user_auth_view").modal();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -202,7 +202,7 @@ $(document).ready(function () {
|
|||
$(this).parent().parent().find(".protocol-port").val(port);
|
||||
})
|
||||
</script>
|
||||
{% block form_submit %}
|
||||
{% block form_submit %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
|
@ -245,5 +245,5 @@ $(document).ready(function () {
|
|||
formSubmit(props);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
{% block form_submit %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("submit", "form", function (evt) {
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("submit", "form", function (evt) {
|
||||
evt.preventDefault();
|
||||
var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}';
|
||||
var redirect_to = '{% url "assets:asset-list" %}';
|
||||
|
@ -35,6 +35,9 @@
|
|||
return v
|
||||
});
|
||||
data["protocols"] = protocols;
|
||||
if (typeof data.labels === "string") {
|
||||
data["labels"] = [data["labels"]];
|
||||
}
|
||||
if (typeof data["nodes"] == "string") {
|
||||
data["nodes"] = [data["nodes"]]
|
||||
}
|
||||
|
@ -46,6 +49,6 @@
|
|||
redirect_to: redirect_to
|
||||
};
|
||||
formSubmit(props);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -44,19 +44,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover" id="system_user_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -132,50 +120,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetsTable() {
|
||||
var connectivity = {{ system_user.connectivity | safe }};
|
||||
var options = {
|
||||
ele: $('#system_user_list'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (connectivity.unreachable.indexOf(cellData) >= 0) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (connectivity.reachable.indexOf(cellData) >= 0 ) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var push_btn = '';
|
||||
{% if system_user.auto_push %}
|
||||
push_btn = ' <a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
{% endif %}
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "View auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
|
||||
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
$(td).html(update_auth_btn + view_btn + push_btn + test_btn);
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
function updateSystemUserNode(nodes) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
|
@ -207,15 +154,6 @@ function updateSystemUserNode(nodes) {
|
|||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
|
||||
function initAssetUserAuthModalForm(hostname, username){
|
||||
$('#id_hostname_p').html(hostname);
|
||||
$('#id_username_p').html(username);
|
||||
$('#id_password').parent().removeClass('has-error');
|
||||
$('#id_password').val('');
|
||||
}
|
||||
|
||||
var assetId;
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
|
@ -226,7 +164,10 @@ $(document).ready(function () {
|
|||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
initAssetsTable();
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}");
|
||||
needPush = true;
|
||||
initAssetUserTable();
|
||||
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
|
@ -289,9 +230,9 @@ $(document).ready(function () {
|
|||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-push-asset', function () {
|
||||
.on('click', '.btn-push-auth', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var asset_id = $this.data('asset');
|
||||
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
|
@ -309,64 +250,7 @@ $(document).ready(function () {
|
|||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var the_url = "{% url 'api-assets:system-user-test-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
assetId = $(this).data('aid');
|
||||
var hostname = $(this).data('hostname');
|
||||
var username = '{{ system_user.username }}';
|
||||
initAssetUserAuthModalForm(hostname, username);
|
||||
$("#asset_user_auth_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
|
||||
var password = $('#id_password').val();
|
||||
if (password){
|
||||
var data = {
|
||||
'name': "{{ system_user.username }}",
|
||||
'asset': assetId,
|
||||
'username': "{{ system_user.username }}",
|
||||
'password': password
|
||||
};
|
||||
formSubmit({
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
},
|
||||
error: function () {
|
||||
toastr.error("{% trans 'Update failed!' %}");
|
||||
}
|
||||
});
|
||||
$("#asset_user_auth_modal").modal('hide');
|
||||
}
|
||||
else{
|
||||
$('#id_password').parent().addClass('has-error');
|
||||
}
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = $(this).data("aid") ;
|
||||
host = $(this).data("hostname");
|
||||
username = "{{ system_user.username }}";
|
||||
$("#asset_user_auth_view").modal();
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -18,6 +18,7 @@ router.register(r'domain', api.DomainViewSet, 'domain')
|
|||
router.register(r'gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
|
||||
router.register(r'asset-user', api.AssetUserViewSet, 'asset-user')
|
||||
router.register(r'asset-user-info', api.AssetUserExportViewSet, 'asset-user-info')
|
||||
|
||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
|
||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||
|
|
|
@ -99,7 +99,7 @@ class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView):
|
|||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.object.asset_set.all()
|
||||
self.queryset = self.object.assets.all()
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
|
@ -27,7 +27,7 @@ from django.forms.formsets import formset_factory
|
|||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.permissions import PermissionsMixin ,IsOrgAdmin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.const import (
|
||||
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
|
||||
)
|
||||
|
@ -74,8 +74,9 @@ class AssetUserListView(PermissionsMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserAssetListView(LoginRequiredMixin, TemplateView):
|
||||
class UserAssetListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/user_asset_list.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -214,10 +215,11 @@ class AssetDeleteView(PermissionsMixin, DeleteView):
|
|||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
|
||||
class AssetDetailView(LoginRequiredMixin, DetailView):
|
||||
class AssetDetailView(PermissionsMixin, DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_detail.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = Node.objects.exclude(assets=self.object)
|
||||
|
@ -231,7 +233,9 @@ class AssetDetailView(LoginRequiredMixin, DetailView):
|
|||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(LoginRequiredMixin, View):
|
||||
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 []
|
||||
|
|
|
@ -14,12 +14,11 @@ from django.views import View
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import ListView
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
|
||||
from audits.utils import get_excel_response, write_content_to_excel
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser
|
||||
|
||||
from orgs.utils import current_org
|
||||
from ops.views import CommandExecutionListView as UserCommandExecutionListView
|
||||
|
@ -253,7 +252,8 @@ class CommandExecutionListView(UserCommandExecutionListView):
|
|||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class LoginLogExportView(LoginRequiredMixin, View):
|
||||
class LoginLogExportView(PermissionsMixin, View):
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get(self, request):
|
||||
fields = [
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block modal_id %}mfa_auth_confirm{% endblock %}
|
||||
{% block modal_title%}{% trans "MFA confirm" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<style>
|
||||
.inmodal .modal-body {
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
<form class="form-horizontal" action="" style="padding-top: 20px">
|
||||
<div class="form-group mfa-field">
|
||||
<label for="mfa" class="col-sm-2 control-label">{% trans 'MFA' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="mfa" class="form-control input-sm" name="mfa">
|
||||
<span id="mfa_error" class="help-block">{% trans "Need otp auth for view auth" %}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
|
||||
|
||||
function showAuth() {
|
||||
$(".mfa-field").hide();
|
||||
$(".auth-field").show();
|
||||
|
||||
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + asset_id + "&username=" + username;
|
||||
$("#id_username_view").html(username);
|
||||
$("#id_hostname_view").html(host);
|
||||
var success = function (data) {
|
||||
var password = data.password;
|
||||
$("#id_password_view").val(password);
|
||||
};
|
||||
var error = function() {
|
||||
var msg = "{% trans 'Get auth info error' %}";
|
||||
toastr.error(msg)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
method: "GET",
|
||||
success: success,
|
||||
flash_message: false,
|
||||
error: error
|
||||
})
|
||||
}
|
||||
|
||||
var codeError = "{% trans 'Code error' %}";
|
||||
|
||||
$(document).ready(function () {
|
||||
}).on("click", ".btn-mfa", function () {
|
||||
var url = "{% url 'api-auth:user-otp-verify' %}";
|
||||
var data = {
|
||||
code: $("#mfa").val()
|
||||
};
|
||||
var success = function () {
|
||||
var now = new Date();
|
||||
lastMFATime = now.getTime() / 1000;
|
||||
$("#mfa_auth_confirm").modal("hide").trigger("success");
|
||||
};
|
||||
var error = function () {
|
||||
$("#mfa_error").addClass("text-danger").html(codeError);
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success: success,
|
||||
flash_message: false,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block modal_button %}
|
||||
<button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
|
||||
{% endblock %}
|
|
@ -48,7 +48,7 @@ class LogTailApi(generics.RetrieveAPIView):
|
|||
return line
|
||||
|
||||
def read_from_file(self):
|
||||
with open(self.log_path, 'r') as f:
|
||||
with open(self.log_path, 'rt', encoding='utf8') as f:
|
||||
offset = cache.get(self.mark, 0)
|
||||
f.seek(offset)
|
||||
data = f.read(self.buff_size).replace('\n', '\r\n')
|
||||
|
@ -79,7 +79,6 @@ class LogTailApi(generics.RetrieveAPIView):
|
|||
|
||||
|
||||
class ResourcesIDCacheApi(APIView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
spm = str(uuid.uuid4())
|
||||
resources_id = request.data.get('resources')
|
||||
|
|
|
@ -358,7 +358,7 @@ EMAIL_USE_SSL = False
|
|||
EMAIL_USE_TLS = False
|
||||
EMAIL_SUBJECT_PREFIX = '[JMS] '
|
||||
|
||||
#Email custom content
|
||||
# Email custom content
|
||||
EMAIL_CUSTOM_USER_CREATED_SUBJECT = ''
|
||||
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = ''
|
||||
EMAIL_CUSTOM_USER_CREATED_BODY = ''
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.utils import timezone
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from rest_framework.response import Response
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import HttpResponse
|
||||
|
@ -18,10 +17,12 @@ from users.models import User
|
|||
from assets.models import Asset
|
||||
from terminal.models import Session
|
||||
from orgs.utils import current_org
|
||||
from common.permissions import PermissionsMixin, IsValidUser
|
||||
|
||||
|
||||
class IndexView(LoginRequiredMixin, TemplateView):
|
||||
class IndexView(PermissionsMixin, TemplateView):
|
||||
template_name = 'index.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
session_week = None
|
||||
session_month = None
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -104,14 +104,15 @@ class JMSInventory(JMSBaseInventory):
|
|||
super().__init__(host_list=host_list)
|
||||
|
||||
def get_run_user_info(self, host):
|
||||
from assets.backends.multi import AssetUserManager
|
||||
from assets.backends import AssetUserManager
|
||||
|
||||
if not self.run_as:
|
||||
return {}
|
||||
|
||||
try:
|
||||
asset = self.assets.get(id=host.get('id'))
|
||||
run_user = AssetUserManager.get(self.run_as, asset)
|
||||
manager = AssetUserManager()
|
||||
run_user = manager.get(self.run_as, asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return {}
|
||||
|
|
|
@ -104,6 +104,13 @@ def hello(name, callback=None):
|
|||
print("Hello {}".format(name))
|
||||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean_periodic
|
||||
@register_as_period_task(interval=30)
|
||||
def hello123():
|
||||
print("Hello world")
|
||||
|
||||
|
||||
@shared_task
|
||||
def hello_callback(result):
|
||||
print(result)
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.conf import settings
|
|||
from django.views.generic import ListView, TemplateView
|
||||
|
||||
from common.permissions import (
|
||||
LoginRequiredMixin, PermissionsMixin, IsOrgAdmin, IsAuditor
|
||||
PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser
|
||||
)
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from ..models import CommandExecution
|
||||
|
@ -54,9 +54,10 @@ class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandExecutionStartView(LoginRequiredMixin, TemplateView):
|
||||
class CommandExecutionStartView(PermissionsMixin, TemplateView):
|
||||
template_name = 'ops/command_execution_create.html'
|
||||
form_class = CommandExecutionForm
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_user_system_users(self):
|
||||
from perms.utils import AssetPermissionUtil
|
||||
|
|
|
@ -646,6 +646,8 @@ 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)
|
||||
}
|
||||
})
|
||||
|
@ -927,8 +929,11 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
|
|||
}
|
||||
|
||||
// 解决input框中的资产和弹出表格中资产的显示不一致
|
||||
function initSelectedAssets2Table(){
|
||||
var inputAssets = $('#id_assets').val();
|
||||
function initSelectedAssets2Table(id){
|
||||
if (!id) {
|
||||
id = "#id_assets"
|
||||
}
|
||||
var inputAssets = $(id).val();
|
||||
var selectedAssets = asset_table2.selected.concat();
|
||||
|
||||
// input assets无,table assets选中,则取消勾选(再次click)
|
||||
|
|
|
@ -49,7 +49,7 @@ class TerminalUpdateView(PermissionsMixin, UpdateView):
|
|||
return context
|
||||
|
||||
|
||||
class TerminalDetailView(LoginRequiredMixin, PermissionsMixin, DetailView):
|
||||
class TerminalDetailView(PermissionsMixin, DetailView):
|
||||
model = Terminal
|
||||
template_name = 'terminal/terminal_detail.html'
|
||||
context_object_name = 'terminal'
|
||||
|
@ -97,7 +97,7 @@ class TerminalAcceptView(PermissionsMixin, JSONResponseMixin, UpdateView):
|
|||
return self.render_json_response(data)
|
||||
|
||||
|
||||
class TerminalConnectView(LoginRequiredMixin, PermissionsMixin, DetailView):
|
||||
class TerminalConnectView(PermissionsMixin, DetailView):
|
||||
"""
|
||||
Abandon
|
||||
"""
|
||||
|
@ -127,11 +127,11 @@ class TerminalConnectView(LoginRequiredMixin, PermissionsMixin, DetailView):
|
|||
return super(TerminalConnectView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class WebTerminalView(LoginRequiredMixin, View):
|
||||
class WebTerminalView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return redirect('/luna/?' + request.GET.urlencode())
|
||||
|
||||
|
||||
class WebSFTPView(LoginRequiredMixin, View):
|
||||
class WebSFTPView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return redirect('/coco/elfinder/sftp/?' + request.GET.urlencode())
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic import RedirectView
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import HttpResponseRedirect
|
||||
|
@ -15,6 +13,7 @@ from django.urls import reverse_lazy
|
|||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from common.permissions import PermissionsMixin, IsValidUser
|
||||
from ..models import User
|
||||
from ..utils import (
|
||||
send_reset_password_mail, get_password_check_rules, check_password_rules
|
||||
|
@ -120,8 +119,9 @@ class UserResetPasswordView(TemplateView):
|
|||
return HttpResponseRedirect(reverse('users:reset-password-success'))
|
||||
|
||||
|
||||
class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||
class UserFirstLoginView(PermissionsMixin, SessionWizardView):
|
||||
template_name = 'users/first_login.html'
|
||||
permission_classes = [IsValidUser]
|
||||
form_list = [
|
||||
forms.UserProfileForm,
|
||||
forms.UserPublicKeyForm,
|
||||
|
|
|
@ -10,7 +10,6 @@ import chardet
|
|||
from io import StringIO
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth import authenticate, login as auth_login
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.cache import cache
|
||||
|
@ -36,7 +35,7 @@ from common.const import (
|
|||
)
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from orgs.utils import current_org
|
||||
from .. import forms
|
||||
from ..models import User, UserGroup
|
||||
|
@ -379,8 +378,9 @@ class UserGrantedAssetView(PermissionsMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserProfileView(LoginRequiredMixin, TemplateView):
|
||||
class UserProfileView(PermissionsMixin, TemplateView):
|
||||
template_name = 'users/user_profile.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
mfa_setting = settings.SECURITY_MFA_AUTH
|
||||
|
@ -392,9 +392,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
|
||||
class UserProfileUpdateView(PermissionsMixin, UpdateView):
|
||||
template_name = 'users/user_profile_update.html'
|
||||
model = User
|
||||
permission_classes = [IsValidUser]
|
||||
form_class = forms.UserProfileForm
|
||||
success_url = reverse_lazy('users:user-profile')
|
||||
|
||||
|
@ -410,7 +411,7 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
|
||||
class UserPasswordUpdateView(PermissionsMixin, UpdateView):
|
||||
template_name = 'users/user_password_update.html'
|
||||
model = User
|
||||
form_class = forms.UserPasswordForm
|
||||
|
@ -451,10 +452,11 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
|
|||
return super().form_valid(form)
|
||||
|
||||
|
||||
class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
|
||||
class UserPublicKeyUpdateView(PermissionsMixin, UpdateView):
|
||||
template_name = 'users/user_pubkey_update.html'
|
||||
model = User
|
||||
form_class = forms.UserPublicKeyForm
|
||||
permission_classes = [IsValidUser]
|
||||
success_url = reverse_lazy('users:user-profile')
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
|
@ -469,7 +471,8 @@ class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserPublicKeyGenerateView(LoginRequiredMixin, View):
|
||||
class UserPublicKeyGenerateView(PermissionsMixin, View):
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver')
|
||||
|
|
|
@ -28,7 +28,6 @@ djangorestframework==3.9.4
|
|||
djangorestframework-bulk==0.2.1
|
||||
docutils==0.14
|
||||
ecdsa==0.13
|
||||
elasticsearch==6.1.1
|
||||
enum-compat==0.0.2
|
||||
ephem==3.7.6.0
|
||||
eventlet==0.24.1
|
||||
|
|
Loading…
Reference in New Issue