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

@ -2,17 +2,20 @@
from rest_framework import serializers from rest_framework import serializers
from rest_framework import viewsets, serializers, generics from rest_framework import viewsets, serializers, generics
from .models import AssetGroup, Asset, IDC, AssetExtend from rest_framework.response import Response
from common.mixins import BulkDeleteApiMixin from rest_framework.views import APIView
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView 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 AssetGroupSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AssetGroup model = AssetGroup
# exclude = [
# 'password', 'first_name', 'last_name', 'secret_key_otp',
# 'private_key', 'public_key', 'avatar',
# ]
class AssetSerializer(serializers.ModelSerializer): class AssetSerializer(serializers.ModelSerializer):
@ -45,10 +48,44 @@ class IDCViewSet(viewsets.ReadOnlyModelViewSet):
"""API endpoint that allows IDC to be viewed or edited.""" """API endpoint that allows IDC to be viewed or edited."""
queryset = IDC.objects.all() queryset = IDC.objects.all()
serializer_class = IDCSerializer serializer_class = IDCSerializer
permission_classes = (IsSuperUser,)
class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = AssetBulkUpdateSerializer 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.clear()
self.instance.tags.add(*tuple(tags)) self.instance.tags.add(*tuple(tags))
class Meta: class Meta:
model = Asset model = Asset
tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all())
fields = [ fields = [
'hostname', 'ip', 'port', 'type', 'comment', 'admin_user', 'system_users', 'idc', 'groups', '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', 'other_ip', 'remote_card_ip', 'mac_address', 'brand', 'cpu', 'memory', 'disk', 'os', 'cabinet_no',
'cabinet_pos', 'number', 'status', 'env', 'sn', 'tags', 'cabinet_pos', 'number', 'status', 'env', 'sn', 'tags',
] ]
tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all())
widgets = { widgets = {
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'groups': forms.SelectMultiple(attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}), 'data-placeholder': _('Select asset groups')}),
@ -60,6 +58,8 @@ class AssetCreateForm(forms.ModelForm):
help_texts = { help_texts = {
'hostname': '* required', 'hostname': '* required',
'ip': '* 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个汉字按回车确认' 'tags': '最多5个标签单个标签最长8个汉字按回车确认'
} }
@ -243,7 +243,7 @@ class SystemUserForm(forms.ModelForm):
# Todo: Validate private key file, and generate public key # Todo: Validate private key file, and generate public key
# Todo: Auto generate private key and public key # Todo: Auto generate private key and public key
if private_key_file: if private_key_file:
system_user.private_key = private_key_file.read() system_user.private_key = private_key_file.read().strip()
system_user.save() system_user.save()
return self.instance return self.instance
@ -264,6 +264,7 @@ class SystemUserForm(forms.ModelForm):
'auth_update': 'Auto update system user ssh key', 'auth_update': 'Auto update system user ssh key',
} }
class AssetTagForm(forms.ModelForm): class AssetTagForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(queryset=Asset.objects.all(), assets = forms.ModelMultipleChoiceField(queryset=Asset.objects.all(),
label=_('Asset'), label=_('Asset'),

View File

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

View File

@ -7,7 +7,7 @@ from django.core import serializers
import logging import logging
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import encrypt, decrypt from common.utils import signer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -111,23 +111,23 @@ class AdminUser(models.Model):
@password.setter @password.setter
def password(self, password_raw): def password(self, password_raw):
self._password = encrypt(password_raw) self._password = signer.sign(password_raw)
@property @property
def private_key(self): def private_key(self):
return decrypt(self._private_key) return signer.unsign(self._private_key)
@private_key.setter @private_key.setter
def private_key(self, private_key_raw): def private_key(self, private_key_raw):
self._private_key = encrypt(private_key_raw) self._private_key = signer.sign(private_key_raw)
@property @property
def public_key(self): def public_key(self):
return decrypt(self._public_key) return signer.unsign(self._public_key)
@public_key.setter @public_key.setter
def public_key(self, public_key_raw): def public_key(self, public_key_raw):
self._public_key = encrypt(public_key_raw) self._public_key = signer.sign(public_key_raw)
class Meta: class Meta:
db_table = 'admin_user' db_table = 'admin_user'
@ -179,27 +179,27 @@ class SystemUser(models.Model):
@property @property
def password(self): def password(self):
return decrypt(self._password) return signer.unsign(self._password)
@password.setter @password.setter
def password(self, password_raw): def password(self, password_raw):
self._password = encrypt(password_raw) self._password = signer.sign(password_raw)
@property @property
def private_key(self): def private_key(self):
return decrypt(self._private_key) return signer.unsign(self._private_key)
@private_key.setter @private_key.setter
def private_key(self, private_key_raw): def private_key(self, private_key_raw):
self._private_key = encrypt(private_key_raw) self._private_key = signer.sign(private_key_raw)
@property @property
def public_key(self): def public_key(self):
return decrypt(self._public_key) return signer.unsign(self._public_key)
@public_key.setter @public_key.setter
def public_key(self, public_key_raw): 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): def get_assets_inherit_from_asset_groups(self):
assets = set() assets = set()
@ -289,10 +289,10 @@ def get_default_idc():
class Asset(models.Model): 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')) 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')) 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')) port = models.IntegerField(default=22, verbose_name=_('Port'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) 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', 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 common.mixins import BulkDeleteApiMixin
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
class AssetBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer): class AssetBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# group_display = serializers.SerializerMethodField() # group_display = serializers.SerializerMethodField()
# active_display = serializers.SerializerMethodField() # active_display = serializers.SerializerMethodField()

View File

@ -18,6 +18,9 @@
<li class="active"> <li class="active">
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a> <a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li> </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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">

View File

@ -22,6 +22,9 @@
<li> <li>
<a href="" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset login log' %}</a> <a href="" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset login log' %}</a>
</li> </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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -54,6 +57,14 @@
<td>{% trans 'IP' %}:</td> <td>{% trans 'IP' %}:</td>
<td><b>{{ asset.ip }}</b></td> <td><b>{{ asset.ip }}</b></td>
</tr> </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> <tr>
<td>{% trans 'Other IP' %}:</td> <td>{% trans 'Other IP' %}:</td>
<td><b>{{ asset.other_ip }}</b></td> <td><b>{{ asset.other_ip }}</b></td>
@ -173,7 +184,15 @@
</span> </span>
</td> </td>
</tr> </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> </tbody>
</table> </table>
</div> </div>
@ -215,6 +234,41 @@
</table> </table>
</div> </div>
</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> </div>
</div> </div>

View File

@ -15,9 +15,11 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"><a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a> <li class="active"><a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a></li>
</li>
<li><a href="" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset group perm' %}</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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">

View File

@ -23,6 +23,9 @@
<i class="fa fa-bar-chart-o"></i> {% trans 'Associate assets and asset groups' %} <i class="fa fa-bar-chart-o"></i> {% trans 'Associate assets and asset groups' %}
</a> </a>
</li> </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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">

View File

@ -64,10 +64,10 @@ urlpatterns = [
] ]
urlpatterns += [ urlpatterns += [
#json
url(r'^v1/assets/$', api.AssetViewSet.as_view({'get':'list'}), name='assets-list-api'), 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/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/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) return super(AssetListView, self).get_context_data(**kwargs)
class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView): class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView):
model = Asset model = Asset
tag_type = 'asset' tag_type = 'asset'
@ -182,12 +180,16 @@ class AssetDetailView(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
asset_groups = self.object.groups.all() asset_groups = self.object.groups.all()
system_users = self.object.system_users.all()
context = { context = {
'app': 'Assets', 'app': 'Assets',
'action': 'Asset detail', 'action': 'Asset detail',
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all() '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, '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) kwargs.update(context)
return super(AssetDetailView, self).get_context_data(**kwargs) return super(AssetDetailView, self).get_context_data(**kwargs)

View File

@ -1,5 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
from users.models import User
from assets.models import Asset, SystemUser
from users.backends import IsSuperUserOrTerminalUser from users.backends import IsSuperUserOrTerminalUser
from terminal.models import Terminal from terminal.models import Terminal

View File

@ -71,7 +71,7 @@ class ProxyLog(models.Model):
class CommandLog(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_no = models.IntegerField()
command = models.CharField(max_length=1000, blank=True) command = models.CharField(max_length=1000, blank=True)
output = models.TextField(blank=True) output = models.TextField(blank=True)
@ -82,7 +82,10 @@ class CommandLog(models.Model):
@property @property
def output_decode(self): def output_decode(self):
try:
return base64.b64decode(self.output).replace('\n', '<br />') return base64.b64decode(self.output).replace('\n', '<br />')
except UnicodeDecodeError:
return 'UnicodeDecodeError'
class Meta: class Meta:
db_table = 'command_log' db_table = 'command_log'

View File

@ -14,7 +14,8 @@ class ProxyLogSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.ProxyLog model = models.ProxyLog
fields = ['id', 'name', 'username', 'hostname', 'ip', 'system_user', 'login_type', 'terminal', 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 @staticmethod
def get_time(obj): def get_time(obj):
@ -25,7 +26,7 @@ class ProxyLogSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_command_length(obj): def get_command_length(obj):
return len(obj.command_log.all()) return len(obj.commands.all())
class CommandLogSerializer(serializers.ModelSerializer): class CommandLogSerializer(serializers.ModelSerializer):

View File

@ -3,10 +3,61 @@
{% load static %} {% load static %}
{% load common_tags %} {% load common_tags %}
{% block content_left_head %} {% 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"> <link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<style>
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %} {% 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 %} {% block table_container %}
<table class="footable table table-stripped toggle-arrow-tiny" data-page="false"> <table class="footable table table-stripped toggle-arrow-tiny" data-page="false">
<thead> <thead>
@ -15,6 +66,8 @@
<th>Command</th> <th>Command</th>
<th>Username</th> <th>Username</th>
<th>IP</th> <th>IP</th>
<th>System user</th>
<th>Proxy log</th>
<th>Datetime</th> <th>Datetime</th>
<th data-hide="all">Output</th> <th data-hide="all">Output</th>
</tr> </tr>
@ -22,10 +75,12 @@
<tbody> <tbody>
{% for command in command_list %} {% for command in command_list %}
<tr> <tr>
<td>{{ command.command_no }}</td> <td>{{ command.id }}</td>
<td>{{ command.command }}</td> <td>{{ command.command }}</td>
<td>{{ command.proxy_log.username }}</td> <td>{{ command.proxy_log.username }}</td>
<td>{{ command.proxy_log.ip }}</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.datetime }}</td>
<td>{{ command.output_decode |safe }}</td> <td>{{ command.output_decode |safe }}</td>
</tr> </tr>
@ -39,6 +94,13 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.footable').footable(); $('.footable').footable();
$('.select2').select2();
$('#date .input-daterange').datepicker({
dateFormat: 'mm/dd/yy',
keyboardNavigation: false,
forceParse: false,
autoclose: true
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,108 +1,123 @@
{% extends '_base_list.html' %} {% extends '_base_list.html' %}
{% load i18n static %} {% load i18n %}
{% block custom_head_css_js %} {% load static %}
{{ block.super }} {% load common_tags %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet"> {% block content_left_head %}
<link href="{% static "css/plugins/layer/layer.css" %}" rel="stylesheet"> <link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<style> <style>
div.dataTables_wrapper div.dataTables_filter, #search_btn {
.dataTables_length { margin-bottom: 0;
float: right !important;
}
div.dataTables_wrapper div.dataTables_filter {
margin-left: 15px;
} }
</style> </style>
{% endblock %} {% 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>#} {% block table_search %}
<table class="table table-striped table-bordered table-hover " id="proxy_log_list_table" > <form id="search_form" method="get" action="" class="pull-right form-inline">
<thead> <div class="form-group" id="date">
<tr> <div class="input-daterange input-group" id="datepicker">
<th class="text-center"> <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<div class="checkbox checkbox-default"> <input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
<input type="checkbox" class="ipt_check_all"> <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>
</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 'Username' %}</th>
<th class="text-center">{% trans 'IP' %}</th> <th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'System user' %}</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 'Command' %}</th>
<th class="text-center">{% trans 'Success' %}</th> <th class="text-center">{% trans 'Success' %}</th>
<th class="text-center">{% trans 'Finished' %}</th> <th class="text-center">{% trans 'Finished' %}</th>
<th class="text-center">{% trans 'Date start' %}</th> <th class="text-center">{% trans 'Date start' %}</th>
<th class="text-center">{% trans 'Time' %}</th> <th class="text-center">{% trans 'Time' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %} {% 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 %} {% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script src="{% static "js/plugins/layer/layer.js" %}"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
var options = { $('table').DataTable({
ele: $('#proxy_log_list_table'), "searching": false,
columnDefs: [ "paging": false,
{targets: 1, createdCell: function (td, cellData, rowData) { "order": []
var detail_btn = '<a href="{% url "users:user-detail" pk=99991937 %}">' + cellData + '</a>'; });
$(td).html(detail_btn.replace('99991937', rowData.id)); $('.select2').select2();
}}, $('#date .input-daterange').datepicker({
{targets: 4, createdCell: function (td, cellData, rowData) { dateFormat: 'mm/dd/yy',
if (cellData) { keyboardNavigation: false,
$(td).html('<a url="{% url "audits:proxy-log-commands-list" pk=99991938 %}" class="commands">99991937</a>' forceParse: false,
.replace('99991937', cellData) autoclose: true
.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>
{% endblock %} {% endblock %}

View File

@ -1,23 +1,74 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
import datetime
from django.views.generic import ListView, UpdateView, DeleteView, DetailView, TemplateView from django.views.generic import ListView, UpdateView, DeleteView, DetailView, TemplateView
from django.views.generic.edit import SingleObjectMixin from django.views.generic.edit import SingleObjectMixin
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils import timezone
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings from django.conf import settings
from django.db.models import Q
from .models import ProxyLog, CommandLog from .models import ProxyLog, CommandLog
from .utils import AdminUserRequiredMixin 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' 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): def get_context_data(self, **kwargs):
context = super(ProxyLogListView, self).get_context_data(**kwargs) context = {
context.update({'app': _('Audits'), 'action': _('Proxy log list')}) 'app': _('Audits'),
return context '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): class ProxyLogDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
@ -29,7 +80,7 @@ class ProxyLogDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
return super(ProxyLogDetailView, self).get(request, *args, **kwargs) return super(ProxyLogDetailView, self).get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
return list(self.object.command_log.all()) return list(self.object.commands.all())
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
@ -58,22 +109,46 @@ class CommandLogListView(AdminUserRequiredMixin, ListView):
context_object_name = 'command_list' context_object_name = 'command_list'
def get_queryset(self): def get_queryset(self):
# Todo: Default order by lose asset connection num
self.queryset = super(CommandLogListView, self).get_queryset() self.queryset = super(CommandLogListView, self).get_queryset()
self.keyword = keyword = self.request.GET.get('keyword', '') self.keyword = keyword = self.request.GET.get('keyword', '')
self.sort = sort = self.request.GET.get('sort', '-datetime') 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: if keyword:
self.queryset = self.queryset.filter() self.queryset = self.queryset.filter(command=keyword)
if sort: if sort:
self.queryset = self.queryset.order_by(sort) self.queryset = self.queryset.order_by(sort)
return self.queryset return self.queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': 'Audits', 'app': _('Audits'),
'action': 'Command log list' '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) kwargs.update(context)
return super(CommandLogListView, self).get_context_data(**kwargs) return super(CommandLogListView, self).get_context_data(**kwargs)

View File

@ -8,7 +8,7 @@ import string
import logging import logging
import datetime import datetime
from itsdangerous import Signer, TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, TimestampSigner, \ from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
BadSignature, SignatureExpired BadSignature, SignatureExpired
from django.shortcuts import reverse as dj_reverse from django.shortcuts import reverse as dj_reverse
from django.conf import settings from django.conf import settings
@ -34,31 +34,31 @@ def get_object_or_none(model, **kwargs):
return obj 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: try:
return signing.dumps(*args, **kwargs) return s.loads(value)
except signing.BadSignature: except BadSignature:
return '' 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: try:
return signing.loads(*args, **kwargs) return s.loads(value)
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)
except (BadSignature, SignatureExpired): except (BadSignature, SignatureExpired):
return '' return None
def date_expired_default(): def date_expired_default():
@ -69,10 +69,6 @@ def date_expired_default():
return timezone.now() + timezone.timedelta(days=365*years) return timezone.now() + timezone.timedelta(days=365*years)
def sign(value):
return SIGNER.sign(value)
def combine_seq(s1, s2, callback=None): def combine_seq(s1, s2, callback=None):
for s in (s1, s2): for s in (s1, s2):
if not hasattr(s, '__iter__'): 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 "%d %s" % (period, singular if period == 1 else plural)
return default 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', 'rest_framework.authtoken',
'bootstrapform', 'bootstrapform',
'captcha', 'captcha',
# 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
@ -266,45 +265,13 @@ REST_FRAMEWORK = {
'users.backends.IsValidUser', 'users.backends.IsValidUser',
), ),
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
'users.backends.TerminalAuthentication',
'users.backends.AccessTokenAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication', '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 # Custom User Auth model
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = 'users.User'
@ -321,5 +288,3 @@ CELERY_RESULT_BACKEND = BROKER_URL
CAPTCHA_IMAGE_SIZE = (75, 33) CAPTCHA_IMAGE_SIZE = (75, 33)
CAPTCHA_FOREGROUND_COLOR = '#001100' CAPTCHA_FOREGROUND_COLOR = '#001100'
#

View File

@ -23,7 +23,7 @@ urlpatterns = [
url(r'^captcha/', include('captcha.urls')), url(r'^captcha/', include('captcha.urls')),
url(r'^$', TemplateView.as_view(template_name='base.html'), name='index'), url(r'^$', TemplateView.as_view(template_name='base.html'), name='index'),
url(r'^(api/)?users/', include('users.urls')), 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/)?perms/', include('perms.urls')),
url(r'^(api/)?audits/', include('audits.urls')), url(r'^(api/)?audits/', include('audits.urls')),
url(r'^(api/)?terminal/', include('terminal.urls')), url(r'^(api/)?terminal/', include('terminal.urls')),

View File

@ -2,11 +2,20 @@
# #
from rest_framework.views import APIView, Response 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 .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,) permission_classes = (IsValidUser,)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -34,3 +43,59 @@ class UserAssetsGrantedApi(APIView):
return Response(assets_json, status=200) 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 .hands import User, UserGroup, Asset, AssetGroup, SystemUser
from .models import AssetPermission from .models import AssetPermission
from .hands import associate_system_users_with_assets
class AssetPermissionForm(forms.ModelForm): 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: class Meta:
model = AssetPermission model = AssetPermission
@ -34,3 +43,4 @@ class AssetPermissionForm(forms.ModelForm):
'asset_groups': '* Asset or Asset group at least one required', 'asset_groups': '* Asset or Asset group at least one required',
'system_users': '* required', 'system_users': '* required',
} }

View File

@ -2,7 +2,13 @@
# #
from users.utils import AdminUserRequiredMixin from users.utils import AdminUserRequiredMixin
# from users.backends import IsValdiUser
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, AssetGroup, SystemUser 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): class AssetPermission(models.Model):
PRIVATE_FOR_CHOICE = ( # PRIVATE_FOR_CHOICE = (
('N', 'None'), # ('N', 'None'),
('U', 'user'), # ('U', 'user'),
('G', 'user group'), # ('G', 'user group'),
) # )
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
users = models.ManyToManyField(User, related_name='asset_permissions', blank=True) users = models.ManyToManyField(User, related_name='asset_permissions', blank=True)
user_groups = models.ManyToManyField(UserGroup, 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) assets = models.ManyToManyField(Asset, related_name='granted_by_permissions', blank=True)
asset_groups = models.ManyToManyField(AssetGroup, 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') 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, # private_for = models.CharField(choices=PRIVATE_FOR_CHOICE, max_length=1, default='N', blank=True,
verbose_name=_('Private for')) # verbose_name=_('Private for'))
is_active = models.BooleanField(default=True, verbose_name=_('Active')) is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired')) 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')) created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))

View File

@ -1,4 +1,34 @@
# -*- coding: utf-8 -*- # -*- 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> </div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% 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> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();

View File

@ -28,6 +28,9 @@
<a href="{% url 'perms:asset-permission-asset-list' pk=asset_permission.id %}" class="text-center"> <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> <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a>
</li> </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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">

View File

@ -6,25 +6,21 @@
{% endblock %} {% endblock %}
{% block table_head %} {% block table_head %}
<th class="text-center"> <th class="text-center">{% trans 'ID' %}</th>
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')"> <th class="text-center">{% trans 'Name' %}</th>
</th> <th class="text-center">{% trans 'User' %}</th>
<th class="text-center"><a href="{% url 'perms:asset-permission-list' %}?sort=name">{% trans 'Name' %}</a></th> <th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'User count' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'User group count' %}</th> <th class="text-center">{% trans 'Asset group' %}</th>
<th class="text-center">{% trans 'Asset count' %}</th> <th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Asset group count' %}</th> <th class="text-center">{% trans 'Is valid' %}</th>
<th class="text-center">{% trans 'System user count' %}</th> <th class="text-center">{% trans 'Action' %}</th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=date_expired">{% trans 'Is valid' %}</a></th>
<th class="text-center"></th>
{% endblock %} {% endblock %}
{% block table_body %} {% block table_body %}
{% for asset_permission in asset_permission_list %} {% for asset_permission in asset_permission_list %}
<tr class="gradeX"> <tr class="gradeX">
<td class="text-center"> <td class="text-center">{{ asset_permission.id }}</td>
<input type="checkbox" name="checked" value="{{ asset_permission.id }}">
</td>
<td class="text-center"> <td class="text-center">
<a href="{% url 'perms:asset-permission-detail' pk=asset_permission.id %}"> <a href="{% url 'perms:asset-permission-detail' pk=asset_permission.id %}">
{{ asset_permission.name }} {{ asset_permission.name }}
@ -50,24 +46,14 @@
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
{% block content_bottom_left %} {% block custom_foot_js %}
<form id="" method="get" action="" class=" mail-search"> <script>
<div class="input-group"> $(document).ready(function() {
<select class="form-control m-b" style="width: auto"> $('table').DataTable({
<option>{% trans 'Delete selected' %}</option> "searching": false,
<option>{% trans 'Update selected' %}</option> "paging": false,
<option>{% trans 'Deactive selected' %}</option> "order": []
<option>{% trans 'Export selected' %}</option> })
</select> })
</script>
<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>
{% endblock %} {% endblock %}

View File

@ -22,7 +22,13 @@ urlpatterns = [
] ]
urlpatterns += [ urlpatterns += [
url(r'^v1/user/assets/granted/$', api.UserAssetsGrantedApi.as_view(), url(r'^v1/asset-permission/$', api.AssetPermissionListCreateApi.as_view(),
name='user-assets-granted'), 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) return super(AssetPermissionListView, self).get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
# Todo: Default order by lose asset connection num
self.queryset = super(AssetPermissionListView, self).get_queryset() self.queryset = super(AssetPermissionListView, self).get_queryset()
self.keyword = keyword = self.request.GET.get('keyword', '') self.keyword = keyword = self.request.GET.get('keyword', '')
self.sort = sort = self.request.GET.get('sort', '-date_created') 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] { .select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #1ab394; background-color: #1ab394 !important;
color: white; color: white;
} }

View File

@ -283,9 +283,10 @@ jumpserver.initDataTable = function (options) {
var ele = options.ele || $('.dataTable'); var ele = options.ele || $('.dataTable');
var columnDefs = [ var columnDefs = [
{ {
targets: 0, orderable: false, targets: 0,
orderable: false,
createdCell: function(td) { 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'} {className: 'text-center', targets: '_all'}

View File

@ -33,15 +33,19 @@
<div class="" id="content_start"> <div class="" id="content_start">
{% block content_left_head %} {% endblock %} {% block content_left_head %} {% endblock %}
{% block table_search %} {% 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"> <div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}"> <input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
</div>
<div class="input-group">
<div class="input-group-btn"> <div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary"> <button id='search_btn' type="submit" class="btn btn-sm btn-primary">
搜索 搜索
</button> </button>
</div> </div>
</div> </div>
{% endblock %}
</form> </form>
{% endblock %} {% endblock %}
{% block tags_list %}{% endblock %} {% block tags_list %}{% endblock %}

View File

@ -34,7 +34,7 @@
</li> </li>
</ul> </ul>
</li> </li>
<li id=""> <li id="terminal">
<a href="{% url 'terminal:terminal-list' %}"> <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> <i class="fa fa-desktop"></i><span class="nav-label">{% trans 'Terminal' %}</span><span class="label label-info pull-right"></span>
</a> </a>

View File

@ -5,7 +5,7 @@ from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIV
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework.permissions import AllowAny 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 .models import Terminal, TerminalHeatbeat
from .serializers import TerminalSerializer, TerminalHeatbeatSerializer from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
from .hands import IsSuperUserOrTerminalUser from .hands import IsSuperUserOrTerminalUser
@ -17,7 +17,7 @@ class TerminalCreateListApi(ListCreateAPIView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
name = unsign(request.data.get('name', '')) name = signer.unsign(request.data.get('name', ''))
if name: if name:
terminal = get_object_or_none(Terminal, name=name) terminal = get_object_or_none(Terminal, name=name)
if terminal: if terminal:

View File

@ -60,7 +60,7 @@
</div> </div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% 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> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();

View File

@ -1,14 +1,20 @@
# ~*~ coding: utf-8 ~*~ # ~*~ 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 import generics, status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework import authentication
from common.mixins import BulkDeleteApiMixin from common.mixins import BulkDeleteApiMixin
from common.utils import get_logger from common.utils import get_logger
from .utils import check_user_valid, token_gen
from .models import User, UserGroup from .models import User, UserGroup
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \ from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
@ -113,21 +119,25 @@ class DeleteUserFromGroupApi(generics.DestroyAPIView):
instance.users.remove(user) instance.users.remove(user)
class AppUserRegisterApi(generics.CreateAPIView): class UserTokenApi(APIView):
"""App send a post request to register a app user permission_classes = ()
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
request params contains `username_signed`, You can unsign it, def post(self, request, *args, **kwargs):
username = unsign(username_signed), if you get the username, username = request.data.get('username', '')
It's present it's a valid request, or return (401, Invalid request), password = request.data.get('password', '')
then your should check if the user exist or not. If exist, public_key = request.data.get('public_key', '')
return (200, register success), If not, you should be save it, and remote_addr = request.META.get('REMOTE_ADDR', '')
notice admin user, The user default is not active before admin user
unblock it.
Save fields: remote_addr = base64.b64encode(remote_addr).replace('=', '')
username: user = check_user_valid(username=username, password=password, public_key=public_key)
name: name + request.ip if user:
email: username + '@app.org' token = cache.get('%s_%s' % (user.id, remote_addr))
role: App if not token:
""" token = token_gen(user)
pass
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 -*- # -*- 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 import authentication, exceptions, permissions
from rest_framework.compat import is_authenticated 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 .hands import Terminal
from .models import User
class TerminalAuthentication(authentication.BaseAuthentication): class TerminalAuthentication(authentication.BaseAuthentication):
@ -35,7 +39,7 @@ class TerminalAuthentication(authentication.BaseAuthentication):
return self.authenticate_credentials(sign) return self.authenticate_credentials(sign)
def authenticate_credentials(self, sign): def authenticate_credentials(self, sign):
name = unsign(sign, max_age=300) name = signer.unsign(sign)
if name: if name:
terminal = get_object_or_none(self.model, name=name) terminal = get_object_or_none(self.model, name=name)
else: else:
@ -47,6 +51,46 @@ class TerminalAuthentication(authentication.BaseAuthentication):
return terminal, None 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): class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired""" """Allows access to valid user, is active and not expired"""

View File

@ -27,6 +27,7 @@ class UserCreateForm(forms.ModelForm):
] ]
help_texts = { help_texts = {
'username': '* required', 'username': '* required',
'name': '* required',
'email': '* required', 'email': '* required',
} }
widgets = { widgets = {
@ -107,7 +108,7 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
def save(self, commit=True): def save(self, commit=True):
self.instance = super(UserPrivateAssetPermissionForm, self).save(commit=commit) 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.users = [self.user]
self.instance.save() self.instance.save()
return self.instance return self.instance
@ -115,7 +116,7 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
class Meta: class Meta:
model = AssetPermission model = AssetPermission
fields = [ fields = [
'assets', 'asset_groups', 'system_users', 'private_for', 'name', 'assets', 'asset_groups', 'system_users', 'name',
] ]
widgets = { widgets = {
'assets': forms.SelectMultiple(attrs={'class': 'select2', '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 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 from common.mixins import NoDeleteModelMixin
@ -72,7 +72,7 @@ class User(AbstractUser):
) )
username = models.CharField(max_length=20, unique=True, verbose_name=_('Username')) 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')) email = models.EmailField(max_length=30, unique=True, verbose_name=_('Email'))
groups = models.ManyToManyField(UserGroup, related_name='users', blank=True, verbose_name=_('User group')) 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')) role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
@ -120,19 +120,19 @@ class User(AbstractUser):
@property @property
def private_key(self): def private_key(self):
return decrypt(self._private_key) return signer.unsign(self._private_key)
@private_key.setter @private_key.setter
def private_key(self, private_key_raw): def private_key(self, private_key_raw):
self._private_key = encrypt(private_key_raw) self._private_key = signer.sign(private_key_raw)
@property @property
def public_key(self): def public_key(self):
return decrypt(self._public_key) return signer.unsign(self._public_key)
@public_key.setter @public_key.setter
def public_key(self, public_key_raw): def public_key(self, public_key_raw):
self._public_key = encrypt(public_key_raw) self._public_key = signer.sign(public_key_raw)
@property @property
def is_superuser(self): def is_superuser(self):
@ -193,13 +193,18 @@ class User(AbstractUser):
return True return True
return False return False
def check_public_key(self, public_key):
if self.public_key == public_key:
return True
return False
def generate_reset_token(self): 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 @classmethod
def validate_reset_token(cls, token, max_age=3600): def validate_reset_token(cls, token):
try: try:
data = signing.loads(token, max_age=max_age) data = signer.unsign_t(token)
user_id = data.get('reset', None) user_id = data.get('reset', None)
user_email = data.get('email', '') user_email = data.get('email', '')
user = cls.objects.get(id=user_id, email=user_email) user = cls.objects.get(id=user_id, email=user_email)
@ -270,10 +275,10 @@ def generate_fake():
model.generate_fake() model.generate_fake()
@receiver(post_save, sender=settings.AUTH_USER_MODEL) # @receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs): # def create_auth_token(sender, instance=None, created=False, **kwargs):
if created: # if created:
try: # try:
Token.objects.create(user=instance) # Token.objects.create(user=instance)
except IntegrityError: # except IntegrityError:
pass # pass

View File

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
from common.utils import unsign from common.utils import signer
from .models import User, UserGroup from .models import User, UserGroup
@ -84,14 +84,3 @@ class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer
def get_user_amount(obj): def get_user_amount(obj):
return obj.users.count() 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 %} {% csrf_token %}
<h3>{% trans 'Account' %}</h3> <h3>{% trans 'Account' %}</h3>
{% block username %} {% endblock %} {% block username %} {% endblock %}
{{ form.email|bootstrap_horizontal }}
{{ form.name|bootstrap_horizontal }} {{ form.name|bootstrap_horizontal }}
{{ form.email|bootstrap_horizontal }}
{{ form.groups|bootstrap_horizontal }} {{ form.groups|bootstrap_horizontal }}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
@ -79,7 +79,7 @@
</div> </div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% 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> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();

View File

@ -21,7 +21,8 @@ div.dataTables_wrapper div.dataTables_filter {
<thead> <thead>
<tr> <tr>
<th class="text-center"> <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>
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Username' %}</th>

View File

@ -36,6 +36,7 @@ urlpatterns = [
urlpatterns += [ urlpatterns += [
url(r'^v1/users/$', api.UserListUpdateApi.as_view(), name='user-bulk-update-api'), 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+)/$', 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-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'), 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 logging
import os import os
import re import re
import uuid
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin 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()]) 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) password = kwargs.pop('password', None)
public_key = kwargs.pop('public_key', None) public_key = kwargs.pop('public_key', None)
user = get_object_or_none(User, **kwargs) user = get_object_or_none(User, **kwargs)
if password and not user.check_password(password): if user is None or not user.is_valid:
user = None
if public_key and not user.public_key == public_key:
user = None
if user and user.is_valid:
return user
return None 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 import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_DIR = os.path.join(BASE_DIR, 'logs')
class Config: 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/ # 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 # But we should know the absolute url like: http://jms.jumpserver.org/login/, so SITE_URL is
# HTTP_PROTOCOL://HOST[:PORT] # 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' SITE_URL = 'http://localhost'
# Django security setting, if your disable debug model, you should setting that # 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 # When Django start it will bind this host and port
# ./manage.py runserver 127.0.0.1:8080 # ./manage.py runserver 127.0.0.1:8080
# Todo: Gunicorn or uwsgi run may be use it # 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 HTTP_LISTEN_PORT = 8080
# Use Redis as broker for celery and web socket # Use Redis as broker for celery and web socket
REDIS_HOST = '127.0.0.1' REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379 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 SMTP setting, we only support smtp send mail
# EMAIL_HOST = 'smtp.qq.com' # EMAIL_HOST = 'smtp.qq.com'
@ -70,10 +78,6 @@ class Config:
# EMAIL_USE_TLS = False # If port is 587, set True # EMAIL_USE_TLS = False # If port is 587, set True
# EMAIL_SUBJECT_PREFIX = '[Jumpserver] ' # EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
# SSH use password or public key for auth
SSH_PASSWORD_AUTH = False
SSH_PUBLIC_KEY_AUTH = True
def __init__(self): def __init__(self):
pass pass
@ -86,6 +90,14 @@ class DevelopmentConfig(Config):
DISPLAY_PER_PAGE = 20 DISPLAY_PER_PAGE = 20
DB_ENGINE = 'sqlite' DB_ENGINE = 'sqlite'
DB_NAME = os.path.join(BASE_DIR, 'db.sqlite3') 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): class ProductionConfig(Config):
@ -106,3 +118,4 @@ config = {
} }
env = 'development' env = 'development'