Merge with audits

pull/530/head
ibuler 2016-11-06 11:52:25 +08:00
commit afb923737c
51 changed files with 746 additions and 346 deletions

View File

@ -1,18 +1,21 @@
# ~*~ coding: utf-8 ~*~
from rest_framework import serializers
from rest_framework import viewsets, serializers,generics
from .models import AssetGroup, Asset, IDC, AssetExtend
from rest_framework import viewsets, serializers, generics
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView
from common.mixins import BulkDeleteApiMixin
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin,ListBulkCreateUpdateDestroyAPIView
from .serializers import *
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)

View File

@ -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'),

View File

@ -12,4 +12,5 @@
from users.utils import AdminUserRequiredMixin
from users.backends import IsSuperUserOrTerminalUser, IsSuperUser
from users.models import User, UserGroup

View File

@ -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',

View File

@ -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()

View File

@ -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">

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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'),
]

View File

@ -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],
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)

View File

@ -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

View File

@ -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):
return base64.b64decode(self.output).replace('\n', '<br />')
try:
return base64.b64decode(self.output).replace('\n', '<br />')
except UnicodeDecodeError:
return 'UnicodeDecodeError'
class Meta:
db_table = 'command_log'

View File

@ -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):

View File

@ -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 %}

View File

@ -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;
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %}
div.dataTables_wrapper div.dataTables_filter {
margin-left: 15px;
}
</style>
{% 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 '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_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">
</div>
</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>
{% 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 '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>
{% 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>
$(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
});
})
</script>
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('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 %}

View File

@ -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)

View File

@ -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):
try:
return signing.dumps(*args, **kwargs)
except signing.BadSignature:
return ''
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 decrypt(*args, **kwargs):
try:
return signing.loads(*args, **kwargs)
except signing.BadSignature:
return ''
def unsign(self, value):
s = JSONWebSignatureSerializer(self.secret_key)
try:
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 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)
except (BadSignature, SignatureExpired):
return ''
def unsign_t(self, value):
s = TimedJSONWebSignatureSerializer(self.secret_key)
try:
return s.loads(value)
except (BadSignature, SignatureExpired):
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

View File

@ -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

View File

@ -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'
#

View File

@ -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')),

View File

@ -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)

View File

@ -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',
}

View File

@ -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))

View File

@ -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'))

View File

@ -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()

View File

@ -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();

View File

@ -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">

View File

@ -6,32 +6,28 @@
{% 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 }}
</a>
</td>
<td class="text-center">{{ asset_permission.users.count}}</td>
<td class="text-center">{{ asset_permission.user_groups.count}}</td>
<td class="text-center">{{ asset_permission.users.count }}</td>
<td class="text-center">{{ asset_permission.user_groups.count }}</td>
<td class="text-center">{{ asset_permission.assets.count }}</td>
<td class="text-center">{{ asset_permission.asset_groups.count }}</td>
<td class="text-center">{{ asset_permission.system_users.count }}</td>
@ -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 %}

View File

@ -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'),
]

View File

@ -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')

View File

@ -34,7 +34,7 @@ th a {
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #1ab394;
background-color: #1ab394 !important;
color: white;
}

View File

@ -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'}

View File

@ -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 %}

View File

@ -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>

View File

@ -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:

View File

@ -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();

View File

@ -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'})

View File

@ -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"""

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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>

View File

@ -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'),

View File

@ -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:
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()

View File

@ -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'