Commit 9cd75390 authored by BaiJiangJie's avatar BaiJiangJie Committed by 老广

[Update] Auth Info (#2806)

* [Update] 修改支持auth info导出

* [Update] 统一认证查看

* [Update] 修改auth book manager

* [Update] 修改auth info

* [Update] 完成修改auth info

* [Update] 优化api
parent 8adaf629
......@@ -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 = {
......
# -*- 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
from .manager import AssetUserManager
# -*- coding: utf-8 -*-
#
from ..models import AdminUser
from .asset_user import AssetUserBackend
class AdminUserBackend(AssetUserBackend):
model = AdminUser
backend = 'AdminUser'
# -*- 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
# -*- 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
# -*- 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
......
# -*- 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")
# -*- 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")
# -*- 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")
# -*- 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
# -*- 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
# -*- 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
# -*- 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
cache.set(self._conn_cache_key, value, 3600)
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
@property
def keyword(self):
return '{}_#_{}'.format(self.username, str(self.asset.id))
cache.set(self._conn_cache_key, _connectivity, 3600)
@property
def hostname(self):
return self.asset.hostname
@property
def keyword(self):
return {'username': self.username, 'asset': self.asset}
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
......
{% 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 %}
{% 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 %}
......
{% 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>
\ No newline at end of file
......@@ -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 %}
\ No newline at end of file
......@@ -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 %}
\ No newline at end of file
......@@ -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 %}
\ No newline at end of file
......@@ -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 = [
......
{% 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
......
This diff is collapsed.
......@@ -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())
# ~*~ 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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment