Commit a79c3dd1 authored by ibuler's avatar ibuler

[Fixture] 添加command log backends, 未来支持es

parent 0869931e
# -*- coding: utf-8 -*-
#
from collections import OrderedDict
from django.core.cache import cache
from django.conf import settings
import copy
......@@ -35,7 +36,7 @@ class TerminalRegisterView(ListCreateAPIView):
if serializer.is_valid():
terminal = serializer.save()
app_user, access_key = terminal.create_related_app_user()
data = {}
data = OrderedDict()
data['terminal'] = copy.deepcopy(serializer.data)
data['user'] = app_user.to_json()
data['access_key_id'] = access_key.id
......
......@@ -167,6 +167,16 @@ class SystemUser(models.Model):
def asset_group_amount(self):
return self.asset_groups.count()
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'auth_method': self.auth_method,
'auto_push': self.auto_push,
}
class Meta:
ordering = ['name']
......
......@@ -233,9 +233,6 @@ class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list')
#ordering = '-id'
# Todo: Asset group create template select assets so hard, need be resolve next
def get_context_data(self, **kwargs):
context = {
......@@ -249,7 +246,8 @@ class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
def form_valid(self, form):
asset_group = form.save()
assets_id_list = self.request.POST.getlist('assets', [])
assets = [get_object_or_404(Asset, id=int(asset_id)) for asset_id in assets_id_list]
assets = [get_object_or_404(Asset, id=int(asset_id))
for asset_id in assets_id_list]
asset_group.created_by = self.request.user.username or 'Admin'
asset_group.assets.add(*tuple(assets))
asset_group.save()
......@@ -284,7 +282,8 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
'app': _('Assets'),
'action': _('Asset group detail'),
'assets_remain': assets_remain,
'assets': [asset for asset in Asset.objects.all() if asset not in assets_remain],
'assets': [asset for asset in Asset.objects.all()
if asset not in assets_remain],
'system_users': system_users,
'system_users_remain': system_users_remain,
}
......@@ -349,10 +348,9 @@ class IDCCreateView(AdminUserRequiredMixin, CreateView):
return super(IDCCreateView, self).get_context_data(**kwargs)
def form_valid(self, form):
IDC = form.save(commit=False)
IDC.created_by = self.request.user.username or 'System'
IDC.save()
# IDC_add_success_next(user)
idc = form.save(commit=False)
idc.created_by = self.request.user.username or 'System'
idc.save()
return super(IDCCreateView, self).form_valid(form)
......
......@@ -5,11 +5,12 @@
from __future__ import absolute_import, unicode_literals
from rest_framework import generics, viewsets
from rest_framework.views import APIView, Response
from rest_framework_bulk import BulkModelViewSet
from audits.backends import command_store
from audits.backends.command.serializers import CommandLogSerializer
from . import models, serializers
from .hands import IsSuperUserOrAppUser, Terminal, IsAppUser
from .hands import IsSuperUserOrAppUser, IsAppUser
class ProxyLogReceiveView(generics.CreateAPIView):
......@@ -47,8 +48,21 @@ class ProxyLogViewSet(viewsets.ModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
class CommandLogViewSet(viewsets.ModelViewSet):
queryset = models.CommandLog.objects.all()
serializer_class = serializers.CommandLogSerializer
class CommandLogViewSet(BulkModelViewSet):
"""接受app发送来的command log, 格式如下
{
"proxy_log_id": 23,
"user": "admin",
"asset": "localhost",
"system_user": "web",
"command_no": 1,
"command": "whoami",
"output": "d2hvbWFp", # base64.b64encode(s)
"timestamp": 1485238673.0
}
"""
queryset = command_store.all()
serializer_class = CommandLogSerializer
permission_classes = (IsSuperUserOrAppUser,)
from importlib import import_module
from django.conf import settings
command_engine = import_module(settings.COMMAND_STORE_BACKEND)
command_store = command_engine.CommandStore()
from .command.serializers import CommandLogSerializer
# coding: utf-8
import abc
class CommandBase(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def save(self, proxy_log_id, user, asset, system_user,
command_no, command, output, timestamp):
pass
@abc.abstractmethod
def filter(self, date_from=None, date_to=None, user='',
asset='', system_user='', command=''):
pass
# ~*~ coding: utf-8 ~*~
from .base import CommandBase
from audits.models import CommandLog
class CommandStore(CommandBase):
model = CommandLog
queryset = []
def save(self, proxy_log_id, user, asset, system_user,
command_no, command, output, timestamp):
self.model.objects.create(
proxy_log_id=proxy_log_id, user=user, asset=asset,
system_user=system_user, command_no=command_no,
command=command, output=output, timestamp=timestamp
)
def filter(self, date_from_ts=None, date_to_ts=None, user='',
asset='', system_user='', command='', proxy_log_id=''):
filter_kwargs = {}
if date_from_ts:
filter_kwargs['timestamp__gte'] = date_from_ts
if date_to_ts:
filter_kwargs['timestamp__lte'] = date_to_ts
if user:
filter_kwargs['user'] = user
if asset:
filter_kwargs['asset'] = asset
if system_user:
filter_kwargs['system_user'] = system_user
if command:
filter_kwargs['command__icontains'] = command
if proxy_log_id:
filter_kwargs['proxy_log_id'] = proxy_log_id
if filter_kwargs:
self.queryset = self.model.objects.filter(**filter_kwargs)
return self.queryset
def all(self):
"""返回所有数据"""
return self.model.objects.iterator()
# ~*~ coding: utf-8 ~*~
import base64
from rest_framework import serializers
from audits.models import CommandLog
from audits.backends import command_store
class CommandLogSerializer(serializers.ModelSerializer):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
class Meta:
model = CommandLog
fields = '__all__'
def save(self):
try:
output = self.validated_data['output']
self.validated_data['output'] = base64.b64decode(output)
except IndexError:
pass
return command_store.save(**dict(self.validated_data))
......@@ -24,7 +24,8 @@ class LoginLog(models.Model):
verbose_name=_('Login city'))
user_agent = models.CharField(max_length=100, blank=True, null=True,
verbose_name=_('User agent'))
date_login = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
date_login = models.DateTimeField(auto_now_add=True,
verbose_name=_('Date login'))
class Meta:
db_table = 'login_log'
......@@ -37,56 +38,52 @@ class ProxyLog(models.Model):
('WT', 'Web Terminal'),
)
username = models.CharField(max_length=20, verbose_name=_('Username'))
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
hostname = models.CharField(max_length=128, blank=True, verbose_name=_('Hostname'))
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'))
system_user = models.CharField(max_length=20, verbose_name=_('System user'))
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, blank=True,
null=True, verbose_name=_('Login type'))
terminal = models.CharField(max_length=32, blank=True, null=True, verbose_name=_('Terminal'))
user = models.CharField(max_length=32, verbose_name=_('User'))
asset = models.CharField(max_length=32, verbose_name=_('Asset'))
system_user = models.CharField(max_length=32, verbose_name=_('System user'))
login_type = models.CharField(
choices=LOGIN_TYPE_CHOICE, max_length=2, blank=True,
null=True, verbose_name=_('Login type'))
terminal = models.CharField(
max_length=32, blank=True, null=True, verbose_name=_('Terminal'))
log_file = models.CharField(max_length=1000, blank=True, null=True)
was_failed = models.BooleanField(default=False, verbose_name=_('Did connect failed'))
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
date_start = models.DateTimeField(auto_created=True, verbose_name=_('Date start'))
date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished'))
is_failed = models.BooleanField(
default=False, verbose_name=_('Did connect failed'))
is_finished = models.BooleanField(
default=False, verbose_name=_('Is finished'))
date_start = models.DateTimeField(
auto_created=True, verbose_name=_('Date start'))
date_finished = models.DateTimeField(
null=True, verbose_name=_('Date finished'))
def __unicode__(self):
return '%s-%s-%s-%s' % (self.username, self.hostname, self.system_user, self.id)
@property
def commands_dict(self):
commands = self.command_log.all()
return [{
"command_no": command.command_no,
"command": command.command,
"output": command.output_decode,
"datetime": command.datetime,
} for command in commands]
return '%s-%s-%s' % (self.user, self.asset, self.system_user)
class Meta:
ordering = ['-date_start', 'username']
ordering = ['-date_start', 'user']
class CommandLog(models.Model):
proxy_log = models.ForeignKey(ProxyLog, on_delete=models.CASCADE,
related_name='commands')
proxy_log_id = models.IntegerField()
user = models.CharField(max_length=48, db_index=True)
asset = models.CharField(max_length=128, db_index=True)
system_user = models.CharField(max_length=48, db_index=True)
command_no = models.IntegerField()
command = models.CharField(max_length=1000, blank=True)
command = models.CharField(max_length=1000, blank=True, db_index=True)
output = models.TextField(blank=True)
datetime = models.DateTimeField(null=True)
timestamp = models.FloatField(null=True, db_index=True)
def __unicode__(self):
return '%s: %s' % (self.id, self.command)
@property
def output_decode(self):
try:
return base64.b64decode(self.output).decode('utf-8') \
.replace('\n', '<br />')
except UnicodeDecodeError:
return 'UnicodeDecodeError'
class Meta:
db_table = 'command_log'
ordering = ['command_no', 'command']
class RecordLog(models.Model):
proxy_log_id = models.IntegerField()
output = models.TextField(verbose_name=_('Output'))
timestamp = models.FloatField(null=True)
def __unicode__(self):
return 'Record: %s' % self.proxy_log_id
......@@ -13,10 +13,7 @@ class ProxyLogSerializer(serializers.ModelSerializer):
class Meta:
model = models.ProxyLog
fields = ['id', 'name', 'username', 'hostname', 'ip', 'system_user',
'login_type', 'terminal', 'log_file', 'was_failed',
'is_finished', 'date_start', 'date_finished', 'time',
'command_length', "commands_dict"]
fields = '__all__'
@staticmethod
def get_time(obj):
......@@ -27,10 +24,5 @@ class ProxyLogSerializer(serializers.ModelSerializer):
@staticmethod
def get_command_length(obj):
return len(obj.commands.all())
return 2
class CommandLogSerializer(serializers.ModelSerializer):
class Meta:
model = models.CommandLog
fields = '__all__'
......@@ -23,32 +23,31 @@
</div>
</div>
<div class="input-group">
<select class="select2 form-control" name="username">
<select class="select2 form-control" name="user">
<option value="">{% trans 'User' %}</option>
{% for user in user_list %}
<option value="{{ user.username }}" {% if user.username == username %} selected {% endif %}>{{ user.username }}</option>
{% for u in user_list %}
<option value="{{ u.username }}" {% if user == u.username %} selected {% endif %}>{{ u.username }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<select class="select2 form-control" name="ip">
<select class="select2 form-control" name="asset">
<option value="">{% trans 'Asset' %}</option>
{% for asset in asset_list %}
<option value="{{ asset.ip }}" {% if asset.ip == ip %} selected {% endif %}>{{ asset.ip }}</option>
{% for a in asset_list %}
<option value="{{ a.ip }}" {% if asset == a.ip %} selected {% endif %}>{{ a.ip }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<select class="select2 form-control" name="system_user">
{# <option value="">{{ system_user }}</option>#}
<option value="">{% trans 'System user' %}</option>
{% for system_user in system_user_list %}
<option value="{{ system_user.username }}" {% if system_user.username == system_user %} selected {% endif %}>{{ system_user.username }}</option>
{% for s in system_user_list %}
<option value="{{ s.username }}" {% if s.username == system_user %} selected {% endif %}>{{ s.username }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
<input type="text" class="form-control input-sm" name="command" placeholder="Command" value="{{ command }}">
</div>
<div class="input-group">
<div class="input-group-btn">
......@@ -78,12 +77,12 @@
<tr>
<td>{{ command.id }}</td>
<td>{{ command.command }}</td>
<td>{{ command.proxy_log.username }}</td>
<td>{{ command.proxy_log.ip }}</td>
<td>{{ command.proxy_log.system_user }}</td>
<td><a href="{% url 'audits:proxy-log-detail' pk=command.proxy_log.id %}">{{ command.proxy_log.id}}</a></td>
<td>{{ command.datetime }}</td>
<td>{{ command.output_decode|safe }}</td>
<td>{{ command.user }}</td>
<td>{{ command.asset }}</td>
<td>{{ command.system_user }}</td>
<td><a href="{% url 'audits:proxy-log-detail' pk=command.proxy_log_id %}">{{ command.proxy_log_id}}</a></td>
<td>{{ command.timestamp|ts_to_date }}</td>
<td><pre style="border: none; background: none">{{ command.output|to_html|safe }}</pre></td>
</tr>
{% endfor %}
</tbody>
......
......@@ -19,7 +19,7 @@
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="col-sm-11" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Command log list' %} <b>{{ user_object.name }}</b></span>
......@@ -52,8 +52,8 @@
<tr>
<td>{{ command.command_no }}</td>
<td>{{ command.command }}</td>
<td>{{ command.output_decode|safe }}</td>
<td>{{ command.datetime }}</td>
<td><pre style="border: none;background: none">{{ command.output|to_html|safe}}</pre></td>
<td>{{ command.timestamp|ts_to_date}}</td>
</tr>
{% endfor %}
</tbody>
......
......@@ -47,7 +47,7 @@
</select>
</div>
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Command" value="{{ keyword }}">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Keyword" value="{{ keyword }}">
</div>
<div class="input-group">
<div class="input-group-btn">
......@@ -61,44 +61,53 @@
{% block table_head %}
<th class="text-center">{% trans 'ID' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Command' %}</th>
<th class="text-center">{% trans 'Success' %}</th>
<th class="text-center">{% trans 'Finished' %}</th>
<th class="text-center">{% trans 'R/M' %}</th>
<th class="text-center">{% trans 'Date start' %}</th>
<th class="text-center">{% trans 'Time' %}</th>
{% endblock %}
{% block table_body %}
{% for proxy_log in proxy_log_list %}
<tr class="gradeX">
<td class="text-center">
<a href="{% url 'audits:proxy-log-detail' pk=proxy_log.id %}">{{ proxy_log.id }}</a>
</td>
<td class="text-center">{{ proxy_log.username }}</td>
<td class="text-center">{{ proxy_log.ip }}</td>
<td class="text-center">{{ proxy_log.system_user }}</td>
<td class="text-center">{{ proxy_log.commands.all|length}}</td>
<td class="text-center">
{% if proxy_log.was_failed %}
<i class="fa fa-times text-danger"></i>
{% else %}
<i class="fa fa-check text-navy"></i>
{% endif %}
</td>
<td class="text-center">
{% if proxy_log.is_finished %}
<i class="fa fa-check text-navy"></i>
{% else %}
<i class="fa fa-times text-danger"></i>
{% endif %}
</td>
<td class="text-center">{{ proxy_log.date_start }}</td>
<td class="text-center">{{ proxy_log.date_finished|timeuntil:proxy_log.date_start }}</td>
</tr>
{% endfor %}
{% for proxy_log in proxy_log_list %}
<tr class="gradeX">
<td class="text-center">
<a href="{% url 'audits:proxy-log-detail' pk=proxy_log.id %}">{{ proxy_log.id }}</a>
</td>
<td class="text-center">{{ proxy_log.user }}</td>
<td class="text-center">{{ proxy_log.asset }}</td>
<td class="text-center">{{ proxy_log.system_user }}</td>
<td class="text-center">{{ proxy_log.commands.all|length}}</td>
<td class="text-center">
{% if proxy_log.is_failed %}
<i class="fa fa-times text-danger"></i>
{% else %}
<i class="fa fa-check text-navy"></i>
{% endif %}
</td>
{% if proxy_log.is_finished %}
<td class="text-center">
<i class="fa fa-check text-navy"></i>
</td>
<td class="text-center">
<a><span class="text-navy"><i class="fa fa-play-circle"></i></span></a>
</td>
{% else %}
<td class="text-center">
<i class="fa fa-times text-danger"></i>
</td>
<td class="text-center">
<a><span class="text-danger"><i class="fa fa-eye"></i></span></a>
</td>
{% endif %}
<td class="text-center">{{ proxy_log.date_start }}</td>
<td class="text-center">{{ proxy_log.date_finished|timeuntil:proxy_log.date_start }}</td>
</tr>
{% endfor %}
{% endblock %}
{% block custom_foot_js %}
......
......@@ -10,7 +10,8 @@ router.register(r'v1/proxy-log', api.ProxyLogViewSet, 'proxy-log')
router.register(r'v1/command-log', api.CommandLogViewSet, 'command-log')
urlpatterns = [
url(r'^v1/proxy-log/receive/$', api.ProxyLogReceiveView.as_view(), name='proxy-log-receive'),
url(r'^v1/proxy-log/receive/$', api.ProxyLogReceiveView.as_view(),
name='proxy-log-receive'),
]
urlpatterns += router.urls
This diff is collapsed.
......@@ -3,7 +3,8 @@
from django import template
from django.utils import timezone
from django.conf import settings
from django.utils.html import escape
from audits.backends import command_store
register = template.Library()
......@@ -41,10 +42,30 @@ def join_attr(seq, attr=None, sep=None):
sep = ', '
if attr is not None:
seq = [getattr(obj, attr) for obj in seq]
print(seq)
return sep.join(seq)
@register.filter
def int_to_str(value):
return str(value)
\ No newline at end of file
return str(value)
@register.filter
def ts_to_date(ts):
try:
ts = float(ts)
except TypeError:
ts = 0
dt = timezone.datetime.fromtimestamp(ts).\
replace(tzinfo=timezone.get_current_timezone())
return dt.strftime('%Y-%m-%d %H:%M:%S')
@register.filter
def to_html(s):
return escape(s).replace('\n', '<br />')
@register.filter
def proxy_log_commands(log_id):
return command_store.filter(proxy_log_id=log_id)
\ No newline at end of file
......@@ -33,8 +33,10 @@ from .compat import to_bytes, to_string
SECRET_KEY = settings.SECRET_KEY
def reverse(view_name, urlconf=None, args=None, kwargs=None, current_app=None, external=False):
url = dj_reverse(view_name, urlconf=urlconf, args=args, kwargs=kwargs, current_app=current_app)
def reverse(view_name, urlconf=None, args=None, kwargs=None,
current_app=None, external=False):
url = dj_reverse(view_name, urlconf=urlconf, args=args,
kwargs=kwargs, current_app=current_app)
if external:
url = settings.SITE_URL.strip('/') + url
......@@ -44,8 +46,8 @@ def reverse(view_name, urlconf=None, args=None, kwargs=None, current_app=None, e
def get_object_or_none(model, **kwargs):
try:
obj = model.objects.get(**kwargs)
except model.DoesNotExist:
obj = None
except (model.FieldError, model.DoesNotExist):
return None
return obj
......
# ~*~ coding: utf-8 ~*~
import pytz
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
class TimezoneMiddleware(MiddlewareMixin):
def process_request(self, request):
tzname = request.META.get('TZ')
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
timezone.deactivate()
......@@ -79,6 +79,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'jumpserver.middleware.TimezoneMiddleware',
]
ROOT_URLCONF = 'jumpserver.urls'
......@@ -323,3 +324,5 @@ CACHES = {
CAPTCHA_IMAGE_SIZE = (75, 33)
CAPTCHA_FOREGROUND_COLOR = '#001100'
COMMAND_STORE_BACKEND = 'audits.backends.command.db'
......@@ -14,6 +14,7 @@ from .models import AssetPermission
from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, \
AssetGroup, AssetGroupSerializer, SystemUser
from . import serializers
from .utils import associate_system_users_and_assets
class AssetPermissionViewSet(viewsets.ModelViewSet):
......@@ -35,11 +36,32 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
queryset = get_user_group_asset_permissions(user_group)
return queryset
# Todo: 忘记为何要重写get_serializer_class了
def get_serializer_class(self):
if getattr(self, 'user_id', ''):
return serializers.UserAssetPermissionSerializer
return serializers.AssetPermissionSerializer
def associate_system_users_and_assets(self, serializer):
assets = serializer.validated_data.get('assets', [])
asset_groups = serializer.validated_data.get('asset_groups', [])
system_users = serializer.validated_data.get('system_users', [])
if serializer.partial:
instance = self.get_object()
assets.extend(list(instance.assets.all()))
asset_groups.extend(list(instance.asset_groups.all()))
system_users.extend(list(instance.system_users.all()))
print('Run')
associate_system_users_and_assets(system_users, assets, asset_groups)
def perform_create(self, serializer):
self.associate_system_users_and_assets(serializer)
return super(AssetPermissionViewSet, self).perform_create(serializer)
def perform_update(self, serializer):
self.associate_system_users_and_assets(serializer)
return super(AssetPermissionViewSet, self).perform_update(serializer)
class RevokeUserAssetPermission(APIView):
permission_classes = (IsSuperUser,)
......@@ -58,6 +80,27 @@ class RevokeUserAssetPermission(APIView):
return Response({'msg': 'failed'}, status=404)
class RemoveSystemUserAssetPermission(APIView):
"""将系统用户从授权中移除, Detail页面会调用"""
permission_classes = (IsSuperUser,)
def put(self, request, *args, **kwargs):
response = []
asset_permission_id = kwargs.pop('pk')
system_users_id = request.data.get('system_users')
print(system_users_id)
asset_permission = get_object_or_404(
AssetPermission, id=asset_permission_id)
if not isinstance(system_users_id, list):
system_users_id = [system_users_id]
for system_user_id in system_users_id:
system_user = get_object_or_none(SystemUser, id=system_user_id)
if system_user:
asset_permission.system_users.remove(system_user)
response.append(system_user.to_json())
return Response(response, status=200)
class RevokeUserGroupAssetPermission(APIView):
permission_classes = (IsSuperUser,)
......
......@@ -6,19 +6,9 @@ from django.utils.translation import ugettext_lazy as _
# from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
from .models import AssetPermission
from .hands import associate_system_users_with_assets
class AssetPermissionForm(forms.ModelForm):
def save(self, commit=True):
instance = super(AssetPermissionForm, self).save(commit=commit)
assets = instance.assets.all()
asset_groups = instance.asset_groups.all()
system_users = instance.system_users.all()
associate_system_users_with_assets(system_users, assets, asset_groups)
return instance
class Meta:
model = AssetPermission
fields = [
......@@ -48,4 +38,3 @@ class AssetPermissionForm(forms.ModelForm):
'asset_groups': '* Asset or Asset group at least one required',
'system_users': '* required',
}
......@@ -7,9 +7,9 @@ from assets.models import Asset, AssetGroup, SystemUser
from assets.serializers import AssetGrantedSerializer, AssetGroupSerializer
def associate_system_users_with_assets(system_users, assets, asset_groups):
def push_system_user(assets, system_user):
print('Push system user %s' % system_user.name)
for asset in assets:
asset.system_users.add(*tuple(system_users))
print('\tAsset: %s' % asset.ip)
for asset_group in asset_groups:
asset_group.system_users.add(*tuple(system_users))
......@@ -4,6 +4,7 @@ import functools
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.db.models.signals import m2m_changed
from users.models import User, UserGroup
from assets.models import Asset, AssetGroup, SystemUser
......@@ -16,18 +17,26 @@ class AssetPermission(models.Model):
# ('U', 'user'),
# ('G', 'user group'),
# )
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
users = models.ManyToManyField(User, related_name='asset_permissions', blank=True)
user_groups = models.ManyToManyField(UserGroup, related_name='asset_permissions', blank=True)
assets = models.ManyToManyField(Asset, related_name='granted_by_permissions', blank=True)
asset_groups = models.ManyToManyField(AssetGroup, related_name='granted_by_permissions', blank=True)
system_users = models.ManyToManyField(SystemUser, related_name='granted_by_permissions')
# private_for = models.CharField(choices=PRIVATE_FOR_CHOICE, max_length=1, default='N', blank=True,
# verbose_name=_('Private for'))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
name = models.CharField(
max_length=128, unique=True, verbose_name=_('Name'))
users = models.ManyToManyField(
User, related_name='asset_permissions', blank=True)
user_groups = models.ManyToManyField(
UserGroup, related_name='asset_permissions', blank=True)
assets = models.ManyToManyField(
Asset, related_name='granted_by_permissions', blank=True)
asset_groups = models.ManyToManyField(
AssetGroup, related_name='granted_by_permissions', blank=True)
system_users = models.ManyToManyField(
SystemUser, related_name='granted_by_permissions')
is_active = models.BooleanField(
default=True, verbose_name=_('Active'))
date_expired = models.DateTimeField(
default=date_expired_default, verbose_name=_('Date expired'))
created_by = models.CharField(
max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(
auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
def __unicode__(self):
......@@ -68,3 +77,15 @@ class AssetPermission(models.Model):
class Meta:
db_table = 'asset_permission'
# def change_permission(sender, **kwargs):
# print('Sender: %s' % sender)
# for k, v in kwargs.items():
# print('%s: %s' % (k, v))
# print()
#
# m2m_changed.connect(change_permission, sender=AssetPermission.assets.through)
......@@ -169,16 +169,16 @@
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-small" id="btn_add_user_group">{% trans 'Join' %}</button>
<button type="button" class="btn btn-info btn-small" id="btn_add_system_user">{% trans 'Join' %}</button>
</td>
</tr>
</form>
{% for system_user in system_users %}
<tr>
<td ><b class="bdg_user_group" data-gid={{ system_user.id }}>{{ system_user.name }}</b></td>
<td ><b class="bdg-system-user" data-uid={{ system_user.id }}>{{ system_user.name }}</b></td>
<td>
<button class="btn btn-danger btn-xs btn_delete_user_group" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger btn-xs btn-del" data-uid="{{ system_user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
......@@ -196,8 +196,47 @@
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.system_users_selected = {};
function addSystemUser(system_users) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}";
var body = {
system_users: Object.assign([], system_users)
};
var success = function(data) {
window.location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function removeSystemUser(system_users, tr) {
var the_url = "{% url 'api-perms:remove-system-user-asset-permission' pk=asset_permission.id %}";
var body = {
system_users: system_users
};
var success = function (data) {
tr.remove()
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PUT',
success: success
})
}
$(document).ready(function () {
$('.select2').select2();
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.system_users_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.system_users_selected[data.id]
})
}).on('click', '.btn-delete-perm', function () {
var $this = $(this);
var name = "{{ asset_permission.name }}";
......@@ -205,6 +244,28 @@
var the_url = '{% url "api-perms:asset-permission-detail" pk=99991937 %}'.replace('99991937', uid);
var redirect_url = "{% url 'perms:asset-permission-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn_add_system_user', function () {
if (Object.keys(jumpserver.system_users_selected).length === 0) {
return false;
}
var system_users = $('.bdg-system-user').map(function() {
return $(this).data('uid');
}).get();
$.map(jumpserver.system_users_selected, function(value, index) {
system_users.push(parseInt(index));
$('#opt_' + index).remove();
});
addSystemUser(system_users)
}).on('click', '.btn-del', function () {
var $this = $(this);
var $uid = $this.data('uid');
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg-system-user');
var $system_user = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + $uid + '" id="opt_' + $uid + '">' + $system_user + '</option>'
);
removeSystemUser($uid, $tr)
})
</script>
{% endblock %}
\ No newline at end of file
......@@ -108,7 +108,7 @@
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select user' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
<select data-placeholder="{% trans 'Select user' %}" class="select2 user" style="width: 100%" multiple="" tabindex="4">
{% for user in users_remain %}
<option value="{{ user.id }}">{{ user.name }}: {{ user.username }}</option>
{% endfor %}
......@@ -117,7 +117,7 @@
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm">{% trans 'Add' %}</button>
<button type="button" class="btn btn-primary btn-sm btn-add-user">{% trans 'Add' %}</button>
</td>
</tr>
</form>
......@@ -136,7 +136,7 @@
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select user groups' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
<select data-placeholder="{% trans 'Select user groups' %}" class="select2 user-group" style="width: 100%" multiple="" tabindex="4">
{% for user_group in user_groups_remain %}
<option value="{{ user_group.id }}" id="opt_{{ user_group.id }}">{{ user_group.name }}</option>
{% endfor %}
......@@ -145,7 +145,7 @@
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-small" id="btn_add_user_group">{% trans 'Join' %}</button>
<button type="button" class="btn btn-info btn-small" id="btn_add_user_group">{% trans 'Add' %}</button>
</td>
</tr>
</form>
......@@ -172,25 +172,29 @@
{% endblock %}
{% block custom_foot_js %}
<script>
{# function switch_user_status(obj) {#}
{# var status = $(obj).prop('checked');#}
{##}
{# $.ajax({#}
{# url: "{% url 'users:user-active-api' pk=user.id %}",#}
{# type: "PUT",#}
{# data: {#}
{# 'is_active': status#}
{# },#}
{# success: function (data, status) {#}
{# console.log(data)#}
{# },#}
{# error: function () {#}
{# console.log('error')#}
{# }#}
{# })#}
{# }#}
jumpserver.users_selected = {};
jumpserver.user_groups_selected = {};
$(document).ready(function () {
$('.select2').select2();
});
$('.select2.user').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.users_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.users_selected[data.id]
});
$('.select2.user-group').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.user_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.user_groups_selected[data.id]
})
}).on('click', '.btn-add-user', function () {
console.log(jumpserver.users_selected)
})
</script>
{% endblock %}
\ No newline at end of file
from django.test import TestCase
# Create your tests here.
from django.contrib.sessions.backends import file, db, cache
from django.contrib.auth.views import login
\ No newline at end of file
......@@ -50,7 +50,12 @@ urlpatterns = [
# 验证用户是否有某个资产和系统用户的权限
url(r'v1/asset-permission/user/validate/$',
api.ValidateUserAssetPermissionView.as_view(),
name='validate-user-asset-permission')
name='validate-user-asset-permission'),
# 删除asset permission中的某个系统用户
url(r'^v1/asset-permissions/(?P<pk>[0-9]+)/system-user/remove/$',
api.RemoveSystemUserAssetPermission.as_view(),
name='remove-system-user-asset-permission'),
]
urlpatterns += router.urls
......
# coding: utf-8
from __future__ import absolute_import, unicode_literals
from common.utils import setattr_bulk
from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
from .hands import User, UserGroup, Asset, AssetGroup, SystemUser, \
push_system_user
def get_user_group_granted_asset_groups(user_group):
"""Return asset groups granted of the user group
:param user_group: Instance of :class: ``UserGroup``
:return: {asset_group1: {system_user1, }, asset_group2: {system_user1, system_user2}}
:return: {asset_group1: {system_user1, },
asset_group2: {system_user1, system_user2}}
"""
asset_groups = {}
asset_permissions = user_group.asset_permissions.all()
......@@ -50,7 +54,8 @@ def get_user_granted_asset_groups_direct(user):
"""Return asset groups granted of the user direct nor inherit from user group
:param user: Instance of :class: ``User``
:return: {asset_group: {system_user1, }, asset_group2: {system_user1, system_user2]}
:return: {asset_group: {system_user1, },
asset_group2: {system_user1, system_user2]}
"""
asset_groups = {}
asset_permissions_direct = user.asset_permissions.all()
......@@ -72,7 +77,8 @@ def get_user_granted_asset_groups_inherit_from_user_groups(user):
"""Return asset groups granted of the user and inherit from user group
:param user: Instance of :class: ``User``
:return: {asset_group: {system_user1, }, asset_group2: {system_user1, system_user2]}
:return: {asset_group: {system_user1, },
asset_group2: {system_user1, system_user2]}
"""
asset_groups = {}
user_groups = user.groups.all()
......@@ -103,7 +109,8 @@ def get_user_granted_asset_groups(user):
:return: {asset1: {system_user1, system_user2}, asset2: {...}}
"""
asset_groups_inherit_from_user_groups = get_user_granted_asset_groups_inherit_from_user_groups(user)
asset_groups_inherit_from_user_groups = \
get_user_granted_asset_groups_inherit_from_user_groups(user)
asset_groups_direct = get_user_granted_asset_groups_direct(user)
asset_groups = asset_groups_inherit_from_user_groups
......@@ -211,3 +218,27 @@ def get_user_groups_granted_in_asset_group(asset):
def get_users_granted_in_asset_group(asset):
pass
def associate_system_users_and_assets(system_users, assets, asset_groups):
"""关联系统用户和资产, 目的是保存它们的关系, 然后新加入的资产或系统
用户时,推送系统用户到资产
Todo: 这里需要最终Api定下来更改一下, 现在策略是以系统用户为核心推送, 一个系统用户
推送一次
"""
assets_all = set(assets)
for asset_group in asset_groups:
assets_all |= set(asset_group.assets.all())
for system_user in system_users:
assets_need_push = []
if system_user.auto_push:
assets_need_push.extend(
[asset for asset in assets_all
if asset not in system_user.assets.all()
]
)
system_user.assets.add(*(tuple(assets_all)))
push_system_user(assets_need_push, system_user)
......@@ -6,16 +6,18 @@ import functools
from django.utils.translation import ugettext as _
from django.conf import settings
from django.db.models import Q
from django.views.generic import TemplateView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.views.generic import ListView, CreateView, UpdateView
from django.views.generic.edit import DeleteView, FormView
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin
from common.utils import search_object_attr
from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, Asset, AssetGroup
from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
Asset, AssetGroup
from .models import AssetPermission
from .forms import AssetPermissionForm
from .utils import associate_system_users_and_assets
class AssetPermissionListView(AdminUserRequiredMixin, ListView):
......@@ -79,6 +81,16 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
self.object.name,))
return success_message
def form_valid(self, form):
assets = form.cleaned_data['assets']
asset_groups = form.cleaned_data['asset_groups']
system_users = form.cleaned_data['system_users']
associate_system_users_and_assets(system_users, assets, asset_groups)
response = super(AssetPermissionCreateView, self).form_valid(form)
self.object.created_by = self.request.user.name
self.object.save()
return response
class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
model = AssetPermission
......@@ -100,6 +112,13 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
kwargs={'pk': self.object.pk})
return success_url
def form_valid(self, form):
assets = form.cleaned_data['assets']
asset_groups = form.cleaned_data['asset_groups']
system_users = form.cleaned_data['system_users']
associate_system_users_and_assets(system_users, assets, asset_groups)
return super(AssetPermissionUpdateView, self).form_valid(form)
class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'perms/asset_permission_detail.html'
......
......@@ -187,28 +187,28 @@ function activeNav() {
}
function APIUpdateAttr(props) {
// props = {url: .., body: , success: , error: , method: ,}
props = props || {};
var success_message = props.success_message || 'Update Successfully!';
var fail_message = props.fail_message || 'Error occurred while updating.';
$.ajax({
url: props.url,
type: props.method || "PATCH",
data: props.body,
contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json"
}).done(function(data, textStatue, jqXHR) {
toastr.success(success_message);
if (typeof props.success === 'function') {
return props.success(data);
}
}).fail(function(jqXHR, textStatue, errorThrown) {
toastr.error(fail_message);
if (typeof props.error === 'function') {
return props.error(errorThrown);
}
});
// props = {url: .., body: , success: , error: , method: ,}
props = props || {};
var success_message = props.success_message || 'Update Successfully!';
var fail_message = props.fail_message || 'Error occurred while updating.';
$.ajax({
url: props.url,
type: props.method || "PATCH",
data: props.body,
contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json"
}).done(function(data, textStatue, jqXHR) {
toastr.success(success_message);
if (typeof props.success === 'function') {
return props.success(data);
}
}).fail(function(jqXHR, textStatue, errorThrown) {
toastr.error(fail_message);
if (typeof props.error === 'function') {
return props.error(errorThrown);
}
});
// return true;
}
......
......@@ -107,7 +107,6 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
if not access_key.user.is_active:
raise exceptions.AuthenticationFailed(_('User disabled.'))
return access_key.user, None
......
......@@ -31,7 +31,9 @@ class UserCreateUpdateForm(forms.ModelForm):
'email': '* required',
}
widgets = {
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Join user groups')}),
'groups': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Join user groups')}),
}
......@@ -41,29 +43,11 @@ class UserBulkImportForm(forms.ModelForm):
fields = ['username', 'email', 'enable_otp', 'role']
# class UserUpdateForm(forms.ModelForm):
#
# class Meta:
# model = User
# fields = [
# 'name', 'email', 'groups', 'wechat',
# 'phone', 'enable_otp', 'role', 'date_expired', 'comment',
# ]
# help_texts = {
# 'username': '* required',
# 'email': '* required',
# 'groups': '* required'
# }
# widgets = {
# 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Join user groups')}),
# }
class UserGroupForm(forms.ModelForm):
class Meta:
model = UserGroup
fields = [
'name', 'comment',
'name', 'comment'
]
help_texts = {
'name': '* required'
......@@ -87,7 +71,8 @@ class UserKeyForm(forms.Form):
def clean_public_key(self):
public_key = self.cleaned_data['public_key']
if self.user.public_key and public_key == self.user.public_key:
raise forms.ValidationError(_('Public key should not be the same as your old one.'))
raise forms.ValidationError(_('Public key should not be the '
'same as your old one.'))
if not validate_ssh_public_key(public_key):
raise forms.ValidationError(_('Not a valid ssh public key'))
......@@ -97,7 +82,8 @@ class UserKeyForm(forms.Form):
class UserPrivateAssetPermissionForm(forms.ModelForm):
def save(self, commit=True):
self.instance = super(UserPrivateAssetPermissionForm, self).save(commit=commit)
self.instance = super(UserPrivateAssetPermissionForm, self)\
.save(commit=commit)
self.instance.users = [self.user]
self.instance.save()
return self.instance
......@@ -108,19 +94,23 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
'assets', 'asset_groups', 'system_users', 'name',
]
widgets = {
'assets': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select assets')}),
'asset_groups': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'system_users': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select system users')}),
'assets': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select assets')}),
'asset_groups': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'system_users': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select system users')}),
}
class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
def save(self, commit=True):
self.instance = super(UserGroupPrivateAssetPermissionForm, self).save(commit=commit)
self.instance = super(UserGroupPrivateAssetPermissionForm, self)\
.save(commit=commit)
self.instance.user_groups = [self.user_group]
self.instance.save()
return self.instance
......@@ -131,12 +121,15 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
'assets', 'asset_groups', 'system_users', 'name',
]
widgets = {
'assets': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select assets')}),
'asset_groups': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'system_users': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select system users')}),
'assets': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select assets')}),
'asset_groups': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'system_users': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select system users')}),
}
......
......@@ -44,6 +44,7 @@ class UserGroupCreateView(AdminUserRequiredMixin, CreateView):
'users': users})
return context
# 需要添加组下用户, 而user并不是group的多对多,所以需要手动建立关系
def form_valid(self, form):
user_group = form.save()
users_id_list = self.request.POST.getlist('users', [])
......
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