mirror of https://github.com/jumpserver/jumpserver
Merge with audits
commit
afb923737c
|
@ -2,17 +2,20 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
from rest_framework import viewsets, serializers, generics
|
||||
from .models import AssetGroup, Asset, IDC, AssetExtend
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView
|
||||
from .serializers import *
|
||||
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from common.utils import get_object_or_none, signer
|
||||
from .hands import IsSuperUserOrTerminalUser, IsSuperUser
|
||||
from .models import AssetGroup, Asset, IDC, SystemUser
|
||||
from .serializers import AssetBulkUpdateSerializer
|
||||
|
||||
|
||||
class AssetGroupSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
# exclude = [
|
||||
# 'password', 'first_name', 'last_name', 'secret_key_otp',
|
||||
# 'private_key', 'public_key', 'avatar',
|
||||
# ]
|
||||
|
||||
|
||||
class AssetSerializer(serializers.ModelSerializer):
|
||||
|
@ -45,10 +48,44 @@ class IDCViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
"""API endpoint that allows IDC to be viewed or edited."""
|
||||
queryset = IDC.objects.all()
|
||||
serializer_class = IDCSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
|
||||
class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = AssetBulkUpdateSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserAuthApi(APIView):
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
system_user_id = request.query_params.get('system_user_id', -1)
|
||||
system_user_username = request.query_params.get('system_user_username', '')
|
||||
|
||||
system_user = get_object_or_none(SystemUser, id=system_user_id, username=system_user_username)
|
||||
|
||||
if system_user:
|
||||
if system_user.password:
|
||||
password = signer.sign(system_user.password)
|
||||
else:
|
||||
password = signer.sign('')
|
||||
|
||||
if system_user.private_key:
|
||||
private_key = signer.sign(system_user.private_key)
|
||||
else:
|
||||
private_key = signer.sign(None)
|
||||
|
||||
response = {
|
||||
'id': system_user.id,
|
||||
'password': password,
|
||||
'private_key': private_key,
|
||||
}
|
||||
|
||||
return Response(response)
|
||||
else:
|
||||
return Response({'msg': 'error system user id or username'}, status=401)
|
||||
|
||||
|
||||
|
|
|
@ -38,16 +38,14 @@ class AssetCreateForm(forms.ModelForm):
|
|||
self.instance.tags.clear()
|
||||
self.instance.tags.add(*tuple(tags))
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
|
||||
tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all())
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'type', 'comment', 'admin_user', 'system_users', 'idc', 'groups',
|
||||
'other_ip', 'remote_card_ip', 'mac_address', 'brand', 'cpu', 'memory', 'disk', 'os', 'cabinet_no',
|
||||
'cabinet_pos', 'number', 'status', 'env', 'sn', 'tags',
|
||||
]
|
||||
tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all())
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')}),
|
||||
|
@ -60,6 +58,8 @@ class AssetCreateForm(forms.ModelForm):
|
|||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'system_users': _('System user will be granted for user to login assets (using ansible create automatic)'),
|
||||
'admin_user': _('Admin user should be exist on asset already, And have sudo ALL permission'),
|
||||
'tags': '最多5个标签,单个标签最长8个汉字,按回车确认'
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@ class SystemUserForm(forms.ModelForm):
|
|||
# Todo: Validate private key file, and generate public key
|
||||
# Todo: Auto generate private key and public key
|
||||
if private_key_file:
|
||||
system_user.private_key = private_key_file.read()
|
||||
system_user.private_key = private_key_file.read().strip()
|
||||
system_user.save()
|
||||
return self.instance
|
||||
|
||||
|
@ -264,6 +264,7 @@ class SystemUserForm(forms.ModelForm):
|
|||
'auth_update': 'Auto update system user ssh key',
|
||||
}
|
||||
|
||||
|
||||
class AssetTagForm(forms.ModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(queryset=Asset.objects.all(),
|
||||
label=_('Asset'),
|
||||
|
|
|
@ -12,4 +12,5 @@
|
|||
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.backends import IsSuperUserOrTerminalUser, IsSuperUser
|
||||
from users.models import User, UserGroup
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.core import serializers
|
|||
import logging
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import encrypt, decrypt
|
||||
from common.utils import signer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -111,23 +111,23 @@ class AdminUser(models.Model):
|
|||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
self._password = encrypt(password_raw)
|
||||
self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
return decrypt(self._private_key)
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
self._private_key = encrypt(private_key_raw)
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return decrypt(self._public_key)
|
||||
return signer.unsign(self._public_key)
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
self._public_key = encrypt(public_key_raw)
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
class Meta:
|
||||
db_table = 'admin_user'
|
||||
|
@ -179,27 +179,27 @@ class SystemUser(models.Model):
|
|||
|
||||
@property
|
||||
def password(self):
|
||||
return decrypt(self._password)
|
||||
return signer.unsign(self._password)
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
self._password = encrypt(password_raw)
|
||||
self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
return decrypt(self._private_key)
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
self._private_key = encrypt(private_key_raw)
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return decrypt(self._public_key)
|
||||
return signer.unsign(self._public_key)
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
self._public_key = encrypt(public_key_raw)
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
def get_assets_inherit_from_asset_groups(self):
|
||||
assets = set()
|
||||
|
@ -289,10 +289,10 @@ def get_default_idc():
|
|||
|
||||
|
||||
class Asset(models.Model):
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'))
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP'))
|
||||
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP'))
|
||||
hostname = models.CharField(max_length=128, blank=True, verbose_name=_('Hostname'))
|
||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
|
||||
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
|
||||
|
|
|
@ -5,6 +5,7 @@ from .models import AssetGroup, Asset, IDC, AssetExtend
|
|||
from common.mixins import BulkDeleteApiMixin
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
|
||||
|
||||
|
||||
class AssetBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
# group_display = serializers.SerializerMethodField()
|
||||
# active_display = serializers.SerializerMethodField()
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
<li class="active">
|
||||
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<li>
|
||||
<a href="" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset login log' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
@ -54,6 +57,14 @@
|
|||
<td>{% trans 'IP' %}:</td>
|
||||
<td><b>{{ asset.ip }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
{% if asset.admin_user %}
|
||||
<td><b>{{ asset.admin_user.name }}</b></td>
|
||||
{% else %}
|
||||
<td><b>None</b></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Other IP' %}:</td>
|
||||
<td><b>{{ asset.other_ip }}</b></td>
|
||||
|
@ -173,7 +184,15 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td>{% trans 'Repush system users' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
<button type="button" class="btn btn-primary btn-xs" id="btn_reset_pk" style="width: 54px;">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -215,6 +234,41 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Asset groups' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select system user' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for system_user in system_users_remain %}
|
||||
<option value="{{ system_user.id }}">{{ system_user.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-warning btn-sm">{% trans 'Associate' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
{% for system_user in system_users %}
|
||||
<tr>
|
||||
<td ><b>{{ system_user.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-xs" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="active"><a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a></li>
|
||||
<li><a href="" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset group perm' %}</a></li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-group-update' pk=asset_group.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
<i class="fa fa-bar-chart-o"></i> {% trans 'Associate assets and asset groups' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -64,10 +64,10 @@ urlpatterns = [
|
|||
]
|
||||
|
||||
urlpatterns += [
|
||||
#json
|
||||
url(r'^v1/assets/$', api.AssetViewSet.as_view({'get':'list'}), name='assets-list-api'),
|
||||
url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update-api'),
|
||||
url(r'^v1/idc/$', api.IDCViewSet.as_view({'get':'list'}), name='idc-list-json'),
|
||||
url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -44,8 +44,6 @@ class AssetListView(AdminUserRequiredMixin, ListView):
|
|||
return super(AssetListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView):
|
||||
model = Asset
|
||||
tag_type = 'asset'
|
||||
|
@ -182,12 +180,16 @@ class AssetDetailView(DetailView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
asset_groups = self.object.groups.all()
|
||||
system_users = self.object.system_users.all()
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Asset detail',
|
||||
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
|
||||
if asset_group not in asset_groups],
|
||||
'asset_groups': asset_groups,
|
||||
'system_users_remain': [system_user for system_user in SystemUser.objects.all()
|
||||
if system_user not in system_users],
|
||||
'system_users': system_users,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetDetailView, self).get_context_data(**kwargs)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from users.models import User
|
||||
from assets.models import Asset, SystemUser
|
||||
from users.backends import IsSuperUserOrTerminalUser
|
||||
from terminal.models import Terminal
|
||||
|
|
|
@ -71,7 +71,7 @@ class ProxyLog(models.Model):
|
|||
|
||||
|
||||
class CommandLog(models.Model):
|
||||
proxy_log = models.ForeignKey(ProxyLog, on_delete=models.CASCADE, related_name='command_log')
|
||||
proxy_log = models.ForeignKey(ProxyLog, on_delete=models.CASCADE, related_name='commands')
|
||||
command_no = models.IntegerField()
|
||||
command = models.CharField(max_length=1000, blank=True)
|
||||
output = models.TextField(blank=True)
|
||||
|
@ -82,7 +82,10 @@ class CommandLog(models.Model):
|
|||
|
||||
@property
|
||||
def output_decode(self):
|
||||
try:
|
||||
return base64.b64decode(self.output).replace('\n', '<br />')
|
||||
except UnicodeDecodeError:
|
||||
return 'UnicodeDecodeError'
|
||||
|
||||
class Meta:
|
||||
db_table = 'command_log'
|
||||
|
|
|
@ -14,7 +14,8 @@ 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', 'time', 'command_length', "commands_dict"]
|
||||
'log_file', 'was_failed', 'is_finished', 'date_start', 'date_finished', 'time',
|
||||
'command_length', "commands_dict"]
|
||||
|
||||
@staticmethod
|
||||
def get_time(obj):
|
||||
|
@ -25,7 +26,7 @@ class ProxyLogSerializer(serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def get_command_length(obj):
|
||||
return len(obj.command_log.all())
|
||||
return len(obj.commands.all())
|
||||
|
||||
|
||||
class CommandLogSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -3,10 +3,61 @@
|
|||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
{# <a href="{% url 'perms:asset-permission-create' %}" class="btn btn-sm btn-primary "> {% trans "Create permission" %} </a>#}
|
||||
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for user in user_list %}
|
||||
<option value="{{ user.username }}" {% if user.username == username %} selected {% endif %}>{{ user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="ip">
|
||||
<option value="">{% trans 'Asset' %}</option>
|
||||
{% for asset in asset_list %}
|
||||
<option value="{{ asset.ip }}" {% if asset.ip == ip %} selected {% endif %}>{{ asset.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>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<table class="footable table table-stripped toggle-arrow-tiny" data-page="false">
|
||||
<thead>
|
||||
|
@ -15,6 +66,8 @@
|
|||
<th>Command</th>
|
||||
<th>Username</th>
|
||||
<th>IP</th>
|
||||
<th>System user</th>
|
||||
<th>Proxy log</th>
|
||||
<th>Datetime</th>
|
||||
<th data-hide="all">Output</th>
|
||||
</tr>
|
||||
|
@ -22,10 +75,12 @@
|
|||
<tbody>
|
||||
{% for command in command_list %}
|
||||
<tr>
|
||||
<td>{{ command.command_no }}</td>
|
||||
<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>
|
||||
</tr>
|
||||
|
@ -39,6 +94,13 @@
|
|||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.footable').footable();
|
||||
$('.select2').select2();
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,108 +1,123 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
{{ block.super }}
|
||||
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
|
||||
<link href="{% static "css/plugins/layer/layer.css" %}" rel="stylesheet">
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
{#<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>#}
|
||||
<table class="table table-striped table-bordered table-hover " id="proxy_log_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<div class="checkbox checkbox-default">
|
||||
<input type="checkbox" class="ipt_check_all">
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||
</div>
|
||||
</th>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'Select user' %}</option>
|
||||
{% for user in user_list %}
|
||||
<option value="{{ user.username }}" {% if user.username == username %} selected {% endif %}>{{ user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="ip">
|
||||
<option value="">{% trans 'Select asset' %}</option>
|
||||
{% for asset in asset_list %}
|
||||
<option value="{{ asset.ip }}" {% if asset.ip == ip %} selected {% endif %}>{{ asset.ip }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="system_user">
|
||||
<option value="">{% trans 'System user' %}</option>
|
||||
{% for su in system_user_list %}
|
||||
<option value="{{ su.username }}" {% if su.username == system_user %} selected {% endif %}>{{ su.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% 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 'System user' %}</th>
|
||||
{# <th class="text-center">{% trans 'Login type' %}</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 'Date start' %}</th>
|
||||
<th class="text-center">{% trans 'Time' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% 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 %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script src="{% static "js/plugins/layer/layer.js" %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var options = {
|
||||
ele: $('#proxy_log_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "users:user-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData) {
|
||||
$(td).html('<a url="{% url "audits:proxy-log-commands-list" pk=99991938 %}" class="commands">99991937</a>'
|
||||
.replace('99991937', cellData)
|
||||
.replace('99991938',rowData.id))
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 9, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "audits:proxy-log-detail" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Detail" %}</a>'
|
||||
.replace('99991937', cellData);
|
||||
var delete_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_delete" data-uid="99991937" data-name="99991938">{% trans "Delete" %}</a>'
|
||||
.replace('99991937', cellData)
|
||||
.replace('99991938', rowData.name);
|
||||
$(td).html(detail_btn + delete_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "audits:proxy-log-list-create-api" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "ip"},
|
||||
{data: "system_user"}, {data: "command_length"}, {data: 'was_failed'},
|
||||
{data: "is_finished"}, {data: "date_start"}, {data: 'time'}, {data: 'id'}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}).on('click', '.commands', function () {
|
||||
var url = $(this).attr('url');
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '很多时候,我们想最大化看,比如像这个页面。',
|
||||
shadeClose: true,
|
||||
shade: false,
|
||||
maxmin: true, //开启最大化最小化按钮
|
||||
area: ['893px', '600px'],
|
||||
content: url
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"order": []
|
||||
});
|
||||
$('.select2').select2();
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -1,23 +1,74 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
import datetime
|
||||
|
||||
from django.views.generic import ListView, UpdateView, DeleteView, DetailView, TemplateView
|
||||
from django.views.generic.edit import SingleObjectMixin
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse_lazy
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import ProxyLog, CommandLog
|
||||
from .utils import AdminUserRequiredMixin
|
||||
from .hands import User, Asset, SystemUser
|
||||
|
||||
|
||||
class ProxyLogListView(TemplateView):
|
||||
seven_days_ago_s = (datetime.datetime.now()-datetime.timedelta(7)).strftime('%m/%d/%Y')
|
||||
now_s = datetime.datetime.now().strftime('%m/%d/%Y')
|
||||
|
||||
|
||||
class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
||||
model = ProxyLog
|
||||
template_name = 'audits/proxy_log_list.html'
|
||||
context_object_name = 'proxy_log_list'
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super(ProxyLogListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.username = username = self.request.GET.get('username', '')
|
||||
self.ip = ip = self.request.GET.get('ip', '')
|
||||
self.system_user = system_user = self.request.GET.get('system_user', '')
|
||||
self.date_from_s = date_from_s = self.request.GET.get('date_from', '%s' % seven_days_ago_s)
|
||||
self.date_to_s = date_to_s = self.request.GET.get('date_to', '%s' % now_s)
|
||||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, '%m/%d/%Y')
|
||||
self.queryset = self.queryset.filter(date_start__gt=date_from)
|
||||
if date_to_s:
|
||||
date_to = timezone.datetime.strptime(date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
|
||||
self.queryset = self.queryset.filter(date_start__lt=date_to)
|
||||
if username:
|
||||
self.queryset = self.queryset.filter(username=username)
|
||||
if ip:
|
||||
self.queryset = self.queryset.filter(ip=ip)
|
||||
if system_user:
|
||||
self.queryset = self.queryset.filter(system_user=system_user)
|
||||
if keyword:
|
||||
self.queryset = self.queryset.filter(Q(username__contains=keyword) |
|
||||
Q(name__icontains=keyword) |
|
||||
Q(hostname__icontains=keyword) |
|
||||
Q(ip__icontains=keyword) |
|
||||
Q(system_user__icontains=keyword)).distinct()
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ProxyLogListView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Audits'), 'action': _('Proxy log list')})
|
||||
return context
|
||||
context = {
|
||||
'app': _('Audits'),
|
||||
'action': _('Proxy log list'),
|
||||
'user_list': User.objects.all().order_by('username'),
|
||||
'asset_list': Asset.objects.all().order_by('ip'),
|
||||
'system_user_list': SystemUser.objects.all().order_by('name'),
|
||||
'keyword': self.keyword,
|
||||
'date_from': self.date_from_s,
|
||||
'date_to': self.date_to_s,
|
||||
'username': self.username,
|
||||
'ip': self.ip,
|
||||
'system_user': self.system_user,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(ProxyLogListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ProxyLogDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
|
@ -29,7 +80,7 @@ class ProxyLogDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
|||
return super(ProxyLogDetailView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return list(self.object.command_log.all())
|
||||
return list(self.object.commands.all())
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -58,22 +109,46 @@ class CommandLogListView(AdminUserRequiredMixin, ListView):
|
|||
context_object_name = 'command_list'
|
||||
|
||||
def get_queryset(self):
|
||||
# Todo: Default order by lose asset connection num
|
||||
self.queryset = super(CommandLogListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.sort = sort = self.request.GET.get('sort', '-datetime')
|
||||
self.username = username = self.request.GET.get('username', '')
|
||||
self.ip = ip = self.request.GET.get('ip', '')
|
||||
self.system_user = system_user = self.request.GET.get('system_user', '')
|
||||
self.date_from_s = date_from_s = self.request.GET.get('date_from', '%s' % seven_days_ago_s)
|
||||
self.date_to_s = date_to_s = self.request.GET.get('date_to', '%s' % now_s)
|
||||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, '%m/%d/%Y')
|
||||
self.queryset = self.queryset.filter(datetime__gt=date_from)
|
||||
if date_to_s:
|
||||
date_to = timezone.datetime.strptime(date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
|
||||
self.queryset = self.queryset.filter(datetime__lt=date_to)
|
||||
if username:
|
||||
self.queryset = self.queryset.filter(proxy_log__username=username)
|
||||
if ip:
|
||||
self.queryset = self.queryset.filter(proxy_log__ip=ip)
|
||||
if system_user:
|
||||
self.queryset = self.queryset.filter(proxy_log__system_user=system_user)
|
||||
if keyword:
|
||||
self.queryset = self.queryset.filter()
|
||||
|
||||
self.queryset = self.queryset.filter(command=keyword)
|
||||
if sort:
|
||||
self.queryset = self.queryset.order_by(sort)
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Audits',
|
||||
'action': 'Command log list'
|
||||
'app': _('Audits'),
|
||||
'action': _('Command log list'),
|
||||
'user_list': User.objects.all().order_by('username'),
|
||||
'asset_list': Asset.objects.all().order_by('ip'),
|
||||
'system_user_list': SystemUser.objects.all().order_by('name'),
|
||||
'keyword': self.keyword,
|
||||
'date_from': self.date_from_s,
|
||||
'date_to': self.date_to_s,
|
||||
'username': self.username,
|
||||
'ip': self.ip,
|
||||
'system_user': self.system_user,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(CommandLogListView, self).get_context_data(**kwargs)
|
||||
|
|
|
@ -8,7 +8,7 @@ import string
|
|||
import logging
|
||||
import datetime
|
||||
|
||||
from itsdangerous import Signer, TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, TimestampSigner, \
|
||||
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
|
||||
BadSignature, SignatureExpired
|
||||
from django.shortcuts import reverse as dj_reverse
|
||||
from django.conf import settings
|
||||
|
@ -34,31 +34,31 @@ def get_object_or_none(model, **kwargs):
|
|||
return obj
|
||||
|
||||
|
||||
def encrypt(*args, **kwargs):
|
||||
class Signer(object):
|
||||
def __init__(self, secret_key=SECRET_KEY):
|
||||
self.secret_key = secret_key
|
||||
|
||||
def sign(self, value):
|
||||
s = JSONWebSignatureSerializer(self.secret_key)
|
||||
return s.dumps(value)
|
||||
|
||||
def unsign(self, value):
|
||||
s = JSONWebSignatureSerializer(self.secret_key)
|
||||
try:
|
||||
return signing.dumps(*args, **kwargs)
|
||||
except signing.BadSignature:
|
||||
return ''
|
||||
return s.loads(value)
|
||||
except BadSignature:
|
||||
return None
|
||||
|
||||
def sign_t(self, value, expires_in=3600):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
|
||||
return s.dumps(value)
|
||||
|
||||
def decrypt(*args, **kwargs):
|
||||
def unsign_t(self, value):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key)
|
||||
try:
|
||||
return signing.loads(*args, **kwargs)
|
||||
except signing.BadSignature:
|
||||
return ''
|
||||
|
||||
|
||||
def sign(value, secret_key=SECRET_KEY):
|
||||
signer = TimestampSigner(secret_key)
|
||||
return signer.sign(value)
|
||||
|
||||
|
||||
def unsign(value, max_age=3600, secret_key=SECRET_KEY):
|
||||
signer = TimestampSigner(secret_key)
|
||||
try:
|
||||
return signer.unsign(value, max_age=max_age)
|
||||
return s.loads(value)
|
||||
except (BadSignature, SignatureExpired):
|
||||
return ''
|
||||
return None
|
||||
|
||||
|
||||
def date_expired_default():
|
||||
|
@ -69,10 +69,6 @@ def date_expired_default():
|
|||
return timezone.now() + timezone.timedelta(days=365*years)
|
||||
|
||||
|
||||
def sign(value):
|
||||
return SIGNER.sign(value)
|
||||
|
||||
|
||||
def combine_seq(s1, s2, callback=None):
|
||||
for s in (s1, s2):
|
||||
if not hasattr(s, '__iter__'):
|
||||
|
@ -165,3 +161,5 @@ def timesince(dt, since='', default="just now"):
|
|||
return "%d %s" % (period, singular if period == 1 else plural)
|
||||
return default
|
||||
|
||||
|
||||
signer = Signer()
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
[{"model": "users.usergroup", "pk": 1, "fields": {"name": "Default", "comment": "Default user group for all user", "date_created": "2016-09-05T11:39:25.770Z", "created_by": "System"}}, {"model": "users.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$30000$5ReHkQOQA2Hk$DIW0b5U/uK+U0xqjA3QpYvBcODNhm2MPCm7YWbQys3I=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-09-05T11:39:25.771Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": "", "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-08-19T11:39:25.771Z", "created_by": "System", "user_permissions": [], "groups": [1]}}]
|
File diff suppressed because one or more lines are too long
|
@ -62,7 +62,6 @@ INSTALLED_APPS = [
|
|||
'rest_framework.authtoken',
|
||||
'bootstrapform',
|
||||
'captcha',
|
||||
# 'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
|
@ -266,45 +265,13 @@ REST_FRAMEWORK = {
|
|||
'users.backends.IsValidUser',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'users.backends.TerminalAuthentication',
|
||||
'users.backends.AccessTokenAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
'users.backends.TerminalAuthentication',
|
||||
),
|
||||
}
|
||||
# This setting is required to override the Django's main loop, when running in
|
||||
# development mode, such as ./manage runserver
|
||||
# WSGI_APPLICATION = 'ws4redis.django_runserver.application'
|
||||
|
||||
# URL that distinguishes websocket connections from normal requests
|
||||
# WEBSOCKET_URL = '/ws/'
|
||||
|
||||
# WebSocket Redis
|
||||
# WS4REDIS_CONNECTION = {
|
||||
# 'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
||||
# 'port': CONFIG.REDIS_PORT or 6379,
|
||||
# 'db': 2,
|
||||
# }
|
||||
|
||||
# Set the number of seconds each message shall persisted
|
||||
# WS4REDIS_EXPIRE = 3600
|
||||
|
||||
# WS4REDIS_HEARTBEAT = 'love you'
|
||||
|
||||
# WS4REDIS_PREFIX = 'demo'
|
||||
|
||||
# SESSION_ENGINE = 'redis_sessions.session'
|
||||
|
||||
# SESSION_REDIS_PREFIX = 'session'
|
||||
|
||||
# SESSION_REDIS_HOST = CONFIG.REDIS_HOST
|
||||
|
||||
# SESSION_REDIS_PORT = CONFIG.REDIS_PORT
|
||||
|
||||
# SESSION_REDIS_PASSWORD = CONFIG.REDIS_PASSWORD
|
||||
|
||||
# SESSION_REDIS_DB = CONFIG.REDIS_DB
|
||||
|
||||
|
||||
# Custom User Auth model
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
@ -321,5 +288,3 @@ CELERY_RESULT_BACKEND = BROKER_URL
|
|||
CAPTCHA_IMAGE_SIZE = (75, 33)
|
||||
CAPTCHA_FOREGROUND_COLOR = '#001100'
|
||||
|
||||
#
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ urlpatterns = [
|
|||
url(r'^captcha/', include('captcha.urls')),
|
||||
url(r'^$', TemplateView.as_view(template_name='base.html'), name='index'),
|
||||
url(r'^(api/)?users/', include('users.urls')),
|
||||
url(r'^assets/', include('assets.urls')),
|
||||
url(r'^(api/)?assets/', include('assets.urls')),
|
||||
url(r'^(api/)?perms/', include('perms.urls')),
|
||||
url(r'^(api/)?audits/', include('audits.urls')),
|
||||
url(r'^(api/)?terminal/', include('terminal.urls')),
|
||||
|
|
|
@ -2,11 +2,20 @@
|
|||
#
|
||||
|
||||
from rest_framework.views import APIView, Response
|
||||
from users.backends import IsValidUser
|
||||
from rest_framework.generics import ListCreateAPIView
|
||||
from users.backends import IsValidUser, IsSuperUser
|
||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups
|
||||
from .models import AssetPermission
|
||||
from . import serializers
|
||||
|
||||
|
||||
class UserAssetsGrantedApi(APIView):
|
||||
class AssetPermissionListCreateApi(ListCreateAPIView):
|
||||
queryset = AssetPermission.objects.all()
|
||||
serializer_class = serializers.AssetPermissionSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class UserAssetsApi(APIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -34,3 +43,59 @@ class UserAssetsGrantedApi(APIView):
|
|||
|
||||
return Response(assets_json, status=200)
|
||||
|
||||
|
||||
class UserAssetsGroupsApi(APIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
asset_groups = {}
|
||||
user = request.user
|
||||
|
||||
if user:
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset in assets:
|
||||
for asset_group in asset.groups.all():
|
||||
if asset_group.id in asset_groups:
|
||||
asset_groups[asset_group.id]['asset_num'] += 1
|
||||
else:
|
||||
asset_groups[asset_group.id] = {
|
||||
'id': asset_group.id,
|
||||
'name': asset_group.name,
|
||||
'comment': asset_group.comment,
|
||||
'asset_num': 1
|
||||
}
|
||||
|
||||
asset_groups_json = asset_groups.values()
|
||||
return Response(asset_groups_json, status=200)
|
||||
|
||||
|
||||
class UserAssetsGroupAssetsApi(APIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# asset_group_id = request.query_params.get('asset_group_id', -1)
|
||||
asset_group_id = kwargs.get('pk', -1)
|
||||
# asset_group_name = request.query_params.get('asset_group_name', '')
|
||||
user = request.user
|
||||
assets_json = []
|
||||
|
||||
if user:
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset, system_users in assets.items():
|
||||
for asset_group in asset.groups.all():
|
||||
if str(asset_group.id) == asset_group_id: # and asset_group.name == asset_group_name:
|
||||
assets_json.append({
|
||||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'system_users': [
|
||||
{
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
} for system_user in system_users
|
||||
],
|
||||
'comment': asset.comment
|
||||
})
|
||||
return Response(assets_json, status=200)
|
||||
|
|
|
@ -6,9 +6,18 @@ 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
|
||||
|
@ -34,3 +43,4 @@ class AssetPermissionForm(forms.ModelForm):
|
|||
'asset_groups': '* Asset or Asset group at least one required',
|
||||
'system_users': '* required',
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
#
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
# from users.backends import IsValdiUser
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, AssetGroup, SystemUser
|
||||
|
||||
|
||||
def associate_system_users_with_assets(system_users, assets, asset_groups):
|
||||
for asset in assets:
|
||||
asset.system_users.add(*tuple(system_users))
|
||||
|
||||
for asset_group in asset_groups:
|
||||
asset_group.system_users.add(*tuple(system_users))
|
||||
|
|
|
@ -11,19 +11,19 @@ from common.utils import date_expired_default, combine_seq
|
|||
|
||||
|
||||
class AssetPermission(models.Model):
|
||||
PRIVATE_FOR_CHOICE = (
|
||||
('N', 'None'),
|
||||
('U', 'user'),
|
||||
('G', 'user group'),
|
||||
)
|
||||
# PRIVATE_FOR_CHOICE = (
|
||||
# ('N', 'None'),
|
||||
# ('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'))
|
||||
# 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'))
|
||||
|
|
|
@ -1,4 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from .models import AssetPermission
|
||||
|
||||
|
||||
class AssetPermissionSerializer(serializers.ModelSerializer):
|
||||
# users_amount = serializers.SerializerMethodField()
|
||||
# user_groups_amount = serializers.SerializerMethodField()
|
||||
# assets_amount = serializers.SerializerMethodField()
|
||||
# asset_groups_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = ['id', 'name', 'users', 'user_groups', 'assets', 'asset_groups',
|
||||
'system_users', 'is_active', 'comment', 'date_expired']
|
||||
|
||||
# @staticmethod
|
||||
# def get_users_amount(obj):
|
||||
# return obj.users.count()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_user_groups_amount(obj):
|
||||
# return obj.user_groups.count()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_assets_amount(obj):
|
||||
# return obj.assets.count()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_asset_groups_amount(obj):
|
||||
# return obj.asset_groups.count()
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datapicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
<a href="{% url 'perms:asset-permission-asset-list' pk=asset_permission.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'perms:asset-permission-update' pk=asset_permission.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -6,25 +6,21 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
|
||||
</th>
|
||||
<th class="text-center"><a href="{% url 'perms:asset-permission-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'User count' %}</th>
|
||||
<th class="text-center">{% trans 'User group count' %}</th>
|
||||
<th class="text-center">{% trans 'Asset count' %}</th>
|
||||
<th class="text-center">{% trans 'Asset group count' %}</th>
|
||||
<th class="text-center">{% trans 'System user count' %}</th>
|
||||
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=date_expired">{% trans 'Is valid' %}</a></th>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'User group' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Asset group' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">{% trans 'Is valid' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for asset_permission in asset_permission_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
<input type="checkbox" name="checked" value="{{ asset_permission.id }}">
|
||||
</td>
|
||||
<td class="text-center">{{ asset_permission.id }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'perms:asset-permission-detail' pk=asset_permission.id %}">
|
||||
{{ asset_permission.name }}
|
||||
|
@ -50,24 +46,14 @@
|
|||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_bottom_left %}
|
||||
<form id="" method="get" action="" class=" mail-search">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto">
|
||||
<option>{% trans 'Delete selected' %}</option>
|
||||
<option>{% trans 'Update selected' %}</option>
|
||||
<option>{% trans 'Deactive selected' %}</option>
|
||||
<option>{% trans 'Export selected' %}</option>
|
||||
</select>
|
||||
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='search_btn' type="submit" style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"order": []
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
|
@ -22,7 +22,13 @@ urlpatterns = [
|
|||
]
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^v1/user/assets/granted/$', api.UserAssetsGrantedApi.as_view(),
|
||||
name='user-assets-granted'),
|
||||
url(r'^v1/asset-permission/$', api.AssetPermissionListCreateApi.as_view(),
|
||||
name='asset-permission-list-create-api'),
|
||||
url(r'^v1/user/assets/$', api.UserAssetsApi.as_view(),
|
||||
name='user-assets'),
|
||||
url(r'^v1/user/asset-groups/$', api.UserAssetsGroupsApi.as_view(),
|
||||
name='user-asset-groups'),
|
||||
url(r'^v1/user/asset-groups/(?P<pk>[0-9]+)/assets/$', api.UserAssetsGroupAssetsApi.as_view(),
|
||||
name='user-asset-groups-assets'),
|
||||
]
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
|||
return super(AssetPermissionListView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
# Todo: Default order by lose asset connection num
|
||||
self.queryset = super(AssetPermissionListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.sort = sort = self.request.GET.get('sort', '-date_created')
|
||||
|
|
|
@ -34,7 +34,7 @@ th a {
|
|||
}
|
||||
|
||||
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: #1ab394;
|
||||
background-color: #1ab394 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
|
@ -283,9 +283,10 @@ jumpserver.initDataTable = function (options) {
|
|||
var ele = options.ele || $('.dataTable');
|
||||
var columnDefs = [
|
||||
{
|
||||
targets: 0, orderable: false,
|
||||
targets: 0,
|
||||
orderable: false,
|
||||
createdCell: function(td) {
|
||||
$(td).html('<div class="checkbox checkbox-default"><input type="checkbox" class="ipt_check"><label></label></div>');
|
||||
$(td).html('<input type="checkbox" class="ipt_check">');
|
||||
}
|
||||
},
|
||||
{className: 'text-center', targets: '_all'}
|
||||
|
|
|
@ -33,15 +33,19 @@
|
|||
<div class="" id="content_start">
|
||||
{% block content_left_head %} {% endblock %}
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search">
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search form-inline">
|
||||
{% block search_form %}
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block tags_list %}{% endblock %}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="">
|
||||
<li id="terminal">
|
||||
<a href="{% url 'terminal:terminal-list' %}">
|
||||
<i class="fa fa-desktop"></i><span class="nav-label">{% trans 'Terminal' %}</span><span class="label label-info pull-right"></span>
|
||||
</a>
|
||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIV
|
|||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from common.utils import unsign, get_object_or_none
|
||||
from common.utils import signer, get_object_or_none
|
||||
from .models import Terminal, TerminalHeatbeat
|
||||
from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
|
||||
from .hands import IsSuperUserOrTerminalUser
|
||||
|
@ -17,7 +17,7 @@ class TerminalCreateListApi(ListCreateAPIView):
|
|||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
name = unsign(request.data.get('name', ''))
|
||||
name = signer.unsign(request.data.get('name', ''))
|
||||
if name:
|
||||
terminal = get_object_or_none(Terminal, name=name)
|
||||
if terminal:
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datapicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
import base64
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from rest_framework import authentication
|
||||
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from common.utils import get_logger
|
||||
from .utils import check_user_valid, token_gen
|
||||
from .models import User, UserGroup
|
||||
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
|
||||
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
|
||||
|
@ -113,21 +119,25 @@ class DeleteUserFromGroupApi(generics.DestroyAPIView):
|
|||
instance.users.remove(user)
|
||||
|
||||
|
||||
class AppUserRegisterApi(generics.CreateAPIView):
|
||||
"""App send a post request to register a app user
|
||||
class UserTokenApi(APIView):
|
||||
permission_classes = ()
|
||||
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
|
||||
|
||||
request params contains `username_signed`, You can unsign it,
|
||||
username = unsign(username_signed), if you get the username,
|
||||
It's present it's a valid request, or return (401, Invalid request),
|
||||
then your should check if the user exist or not. If exist,
|
||||
return (200, register success), If not, you should be save it, and
|
||||
notice admin user, The user default is not active before admin user
|
||||
unblock it.
|
||||
def post(self, request, *args, **kwargs):
|
||||
username = request.data.get('username', '')
|
||||
password = request.data.get('password', '')
|
||||
public_key = request.data.get('public_key', '')
|
||||
remote_addr = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
Save fields:
|
||||
username:
|
||||
name: name + request.ip
|
||||
email: username + '@app.org'
|
||||
role: App
|
||||
"""
|
||||
pass
|
||||
remote_addr = base64.b64encode(remote_addr).replace('=', '')
|
||||
user = check_user_valid(username=username, password=password, public_key=public_key)
|
||||
if user:
|
||||
token = cache.get('%s_%s' % (user.id, remote_addr))
|
||||
if not token:
|
||||
token = token_gen(user)
|
||||
|
||||
cache.set(token, user.id, self.expiration)
|
||||
cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration)
|
||||
return Response({'token': token, 'id': user.id, 'username': user.username, 'name': user.name})
|
||||
else:
|
||||
return Response({'msg': 'Invalid password or public key or user is not active or expired'})
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import base64
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import authentication, exceptions, permissions
|
||||
from rest_framework.compat import is_authenticated
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import unsign, get_object_or_none
|
||||
|
||||
from common.utils import signer, get_object_or_none
|
||||
from .hands import Terminal
|
||||
from .models import User
|
||||
|
||||
|
||||
class TerminalAuthentication(authentication.BaseAuthentication):
|
||||
|
@ -35,7 +39,7 @@ class TerminalAuthentication(authentication.BaseAuthentication):
|
|||
return self.authenticate_credentials(sign)
|
||||
|
||||
def authenticate_credentials(self, sign):
|
||||
name = unsign(sign, max_age=300)
|
||||
name = signer.unsign(sign)
|
||||
if name:
|
||||
terminal = get_object_or_none(self.model, name=name)
|
||||
else:
|
||||
|
@ -47,6 +51,46 @@ class TerminalAuthentication(authentication.BaseAuthentication):
|
|||
return terminal, None
|
||||
|
||||
|
||||
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||
keyword = 'Token'
|
||||
model = User
|
||||
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = authentication.get_authorization_header(request).split()
|
||||
|
||||
if not auth or auth[0].lower() != self.keyword.lower().encode():
|
||||
return None
|
||||
|
||||
if len(auth) == 1:
|
||||
msg = _('Invalid token header. No credentials provided.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
elif len(auth) > 2:
|
||||
msg = _('Invalid token header. Sign string should not contain spaces.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
try:
|
||||
token = auth[1].decode()
|
||||
except UnicodeError:
|
||||
msg = _('Invalid token header. Sign string should not contain invalid characters.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
return self.authenticate_credentials(token, request)
|
||||
|
||||
def authenticate_credentials(self, token, request):
|
||||
user_id = cache.get(token)
|
||||
user = get_object_or_none(User, id=user_id)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
remote_addr = request.META.get('REMOTE_ADDR', '')
|
||||
remote_addr = base64.b16encode(remote_addr).replace('=', '')
|
||||
cache.set(token, user_id, self.expiration)
|
||||
cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration)
|
||||
|
||||
return user, None
|
||||
|
||||
|
||||
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
||||
"""Allows access to valid user, is active and not expired"""
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ class UserCreateForm(forms.ModelForm):
|
|||
]
|
||||
help_texts = {
|
||||
'username': '* required',
|
||||
'name': '* required',
|
||||
'email': '* required',
|
||||
}
|
||||
widgets = {
|
||||
|
@ -107,7 +108,7 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
|
|||
|
||||
def save(self, commit=True):
|
||||
self.instance = super(UserPrivateAssetPermissionForm, self).save(commit=commit)
|
||||
self.instance.private_for = 'U'
|
||||
# self.instance.private_for = 'U'
|
||||
self.instance.users = [self.user]
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
@ -115,7 +116,7 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = [
|
||||
'assets', 'asset_groups', 'system_users', 'private_for', 'name',
|
||||
'assets', 'asset_groups', 'system_users', 'name',
|
||||
]
|
||||
widgets = {
|
||||
'assets': forms.SelectMultiple(attrs={'class': 'select2',
|
||||
|
|
|
@ -15,7 +15,7 @@ from django.shortcuts import reverse
|
|||
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from common.utils import encrypt, decrypt, date_expired_default
|
||||
from common.utils import signer, date_expired_default
|
||||
from common.mixins import NoDeleteModelMixin
|
||||
|
||||
|
||||
|
@ -72,7 +72,7 @@ class User(AbstractUser):
|
|||
)
|
||||
|
||||
username = models.CharField(max_length=20, unique=True, verbose_name=_('Username'))
|
||||
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
|
||||
name = models.CharField(max_length=20, verbose_name=_('Name'))
|
||||
email = models.EmailField(max_length=30, unique=True, verbose_name=_('Email'))
|
||||
groups = models.ManyToManyField(UserGroup, related_name='users', blank=True, verbose_name=_('User group'))
|
||||
role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
|
||||
|
@ -120,19 +120,19 @@ class User(AbstractUser):
|
|||
|
||||
@property
|
||||
def private_key(self):
|
||||
return decrypt(self._private_key)
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
self._private_key = encrypt(private_key_raw)
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return decrypt(self._public_key)
|
||||
return signer.unsign(self._public_key)
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
self._public_key = encrypt(public_key_raw)
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
@property
|
||||
def is_superuser(self):
|
||||
|
@ -193,13 +193,18 @@ class User(AbstractUser):
|
|||
return True
|
||||
return False
|
||||
|
||||
def check_public_key(self, public_key):
|
||||
if self.public_key == public_key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def generate_reset_token(self):
|
||||
return signing.dumps({'reset': self.id, 'email': self.email})
|
||||
return signer.sign_t({'reset': self.id, 'email': self.email}, expires_in=3600)
|
||||
|
||||
@classmethod
|
||||
def validate_reset_token(cls, token, max_age=3600):
|
||||
def validate_reset_token(cls, token):
|
||||
try:
|
||||
data = signing.loads(token, max_age=max_age)
|
||||
data = signer.unsign_t(token)
|
||||
user_id = data.get('reset', None)
|
||||
user_email = data.get('email', '')
|
||||
user = cls.objects.get(id=user_id, email=user_email)
|
||||
|
@ -270,10 +275,10 @@ def generate_fake():
|
|||
model.generate_fake()
|
||||
|
||||
|
||||
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
|
||||
def create_auth_token(sender, instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
try:
|
||||
Token.objects.create(user=instance)
|
||||
except IntegrityError:
|
||||
pass
|
||||
# @receiver(post_save, sender=settings.AUTH_USER_MODEL)
|
||||
# def create_auth_token(sender, instance=None, created=False, **kwargs):
|
||||
# if created:
|
||||
# try:
|
||||
# Token.objects.create(user=instance)
|
||||
# except IntegrityError:
|
||||
# pass
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
|
||||
|
||||
from common.utils import unsign
|
||||
from common.utils import signer
|
||||
from .models import User, UserGroup
|
||||
|
||||
|
||||
|
@ -84,14 +84,3 @@ class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer
|
|||
def get_user_amount(obj):
|
||||
return obj.users.count()
|
||||
|
||||
|
||||
class AppUserRegisterSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=20)
|
||||
|
||||
def create(self, validated_data):
|
||||
sign = validated_data('username', '')
|
||||
username = unsign(sign)
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
{% csrf_token %}
|
||||
<h3>{% trans 'Account' %}</h3>
|
||||
{% block username %} {% endblock %}
|
||||
{{ form.email|bootstrap_horizontal }}
|
||||
{{ form.name|bootstrap_horizontal }}
|
||||
{{ form.email|bootstrap_horizontal }}
|
||||
{{ form.groups|bootstrap_horizontal }}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
@ -79,7 +79,7 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datapicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
|
|
@ -21,7 +21,8 @@ div.dataTables_wrapper div.dataTables_filter {
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<div class="checkbox checkbox-default"><input id="" type="checkbox" class="ipt_check_all"><label></label></div>
|
||||
{# <div><input id="" type="checkbox" class="ipt_check_all"><label></label></div>#}
|
||||
<input id="" type="checkbox" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
|
|
|
@ -36,6 +36,7 @@ urlpatterns = [
|
|||
|
||||
urlpatterns += [
|
||||
url(r'^v1/users/$', api.UserListUpdateApi.as_view(), name='user-bulk-update-api'),
|
||||
url(r'^v1/users/token/$', api.UserTokenApi.as_view(), name='user-token-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/$', api.UserDetailApi.as_view(), name='user-patch-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
|
@ -206,18 +207,20 @@ def validate_ssh_pk(text):
|
|||
return startState([n.strip() for n in text.splitlines()])
|
||||
|
||||
|
||||
def check_user_is_valid(**kwargs):
|
||||
def check_user_valid(**kwargs):
|
||||
password = kwargs.pop('password', None)
|
||||
public_key = kwargs.pop('public_key', None)
|
||||
user = get_object_or_none(User, **kwargs)
|
||||
|
||||
if password and not user.check_password(password):
|
||||
user = None
|
||||
|
||||
if public_key and not user.public_key == public_key:
|
||||
user = None
|
||||
|
||||
if user and user.is_valid:
|
||||
return user
|
||||
|
||||
if user is None or not user.is_valid:
|
||||
return None
|
||||
if password and user.check_password(password):
|
||||
return user
|
||||
if public_key and user.public_key == public_key:
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
def token_gen(*args, **kwargs):
|
||||
return uuid.uuid4().get_hex()
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||||
|
||||
|
||||
class Config:
|
||||
|
@ -23,7 +24,6 @@ class Config:
|
|||
# It's used to identify your site, When we send a create mail to user, we only know login url is /login/
|
||||
# But we should know the absolute url like: http://jms.jumpserver.org/login/, so SITE_URL is
|
||||
# HTTP_PROTOCOL://HOST[:PORT]
|
||||
# Todo: May be use :method: get_current_site more grace, bug restful api unknown ok or not
|
||||
SITE_URL = 'http://localhost'
|
||||
|
||||
# Django security setting, if your disable debug model, you should setting that
|
||||
|
@ -53,13 +53,21 @@ class Config:
|
|||
# When Django start it will bind this host and port
|
||||
# ./manage.py runserver 127.0.0.1:8080
|
||||
# Todo: Gunicorn or uwsgi run may be use it
|
||||
HTTP_LISTEN_HOST = '0.0.0.0'
|
||||
HTTP_BIND_HOST = '127.0.0.1'
|
||||
HTTP_LISTEN_PORT = 8080
|
||||
|
||||
# Use Redis as broker for celery and web socket
|
||||
REDIS_HOST = '127.0.0.1'
|
||||
REDIS_PORT = 6379
|
||||
# REDIS_PASSWORD = ''
|
||||
REDIS_PASSWORD = ''
|
||||
BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % {
|
||||
'password': REDIS_PASSWORD,
|
||||
'host': REDIS_HOST,
|
||||
'port': REDIS_PORT,
|
||||
}
|
||||
|
||||
# Api token expiration when create
|
||||
TOKEN_EXPIRATION = 3600
|
||||
|
||||
# Email SMTP setting, we only support smtp send mail
|
||||
# EMAIL_HOST = 'smtp.qq.com'
|
||||
|
@ -70,10 +78,6 @@ class Config:
|
|||
# EMAIL_USE_TLS = False # If port is 587, set True
|
||||
# EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
|
||||
|
||||
# SSH use password or public key for auth
|
||||
SSH_PASSWORD_AUTH = False
|
||||
SSH_PUBLIC_KEY_AUTH = True
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
@ -86,6 +90,14 @@ class DevelopmentConfig(Config):
|
|||
DISPLAY_PER_PAGE = 20
|
||||
DB_ENGINE = 'sqlite'
|
||||
DB_NAME = os.path.join(BASE_DIR, 'db.sqlite3')
|
||||
EMAIL_HOST = 'smtp.exmail.qq.com'
|
||||
EMAIL_PORT = 465
|
||||
EMAIL_HOST_USER = 'ask@jumpserver.org'
|
||||
EMAIL_HOST_PASSWORD = 'xfDf4x1n'
|
||||
EMAIL_USE_SSL = True # If port is 465, set True
|
||||
EMAIL_USE_TLS = False # If port is 587, set True
|
||||
EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
|
||||
SITE_URL = 'http://localhost:8080'
|
||||
|
||||
|
||||
class ProductionConfig(Config):
|
||||
|
@ -106,3 +118,4 @@ config = {
|
|||
}
|
||||
|
||||
env = 'development'
|
||||
|
||||
|
|
Loading…
Reference in New Issue