Commit 45dcb261 authored by ibuler's avatar ibuler

Merge branch 'audits'

parents 4e1f9c97 bb9a0672
......@@ -19,7 +19,7 @@ class IDC(models.Model):
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
date_created = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date added'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date added'))
operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
......@@ -62,7 +62,7 @@ class AssetExtend(models.Model):
key = models.CharField(max_length=64, verbose_name=_('KEY'))
value = models.CharField(max_length=64, verbose_name=_('VALUE'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_("Created by"))
date_created = models.DateTimeField(auto_now=True, null=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
......@@ -98,7 +98,7 @@ class AdminUser(models.Model):
_public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
as_default = models.BooleanField(default=False, verbose_name=_('As default'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now=True, null=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
def __unicode__(self):
......@@ -169,7 +169,7 @@ class SystemUser(models.Model):
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
home = models.CharField(max_length=64, blank=True, verbose_name=_('Home'))
uid = models.IntegerField(null=True, blank=True, verbose_name=_('Uid'))
date_created = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True)
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(max_length=128, blank=True, verbose_name=_('Comment'))
......@@ -243,7 +243,7 @@ class AssetGroup(models.Model):
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date added'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date added'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
......@@ -321,7 +321,7 @@ class Asset(models.Model):
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
date_created = models.DateTimeField(auto_now=True, null=True, blank=True, verbose_name=_('Date added'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date added'))
comment = models.TextField(max_length=128, null=True, blank=True, verbose_name=_('Comment'))
tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
......@@ -365,15 +365,15 @@ class Asset(models.Model):
class Tag(models.Model):
name = models.CharField('标签名', max_length=64,unique=True)
created_time = models.DateTimeField('创建时间', auto_now_add=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
created_time = models.DateTimeField(auto_now_add=True, verbose_name=_('Create time'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
def __str__(self):
return self.name
def __unicode__(self):
return self.name
__str__ = __unicode__
class Meta:
db_table = 'tag'
......
......@@ -6,18 +6,46 @@ from rest_framework import generics
import serializers
from .models import ProxyLog
from .models import ProxyLog, CommandLog
from .hands import IsSuperUserOrTerminalUser, Terminal
class ProxyLogListCreateApi(generics.ListCreateAPIView):
"""User proxy to backend server need call this api.
params: {
"username": "",
"name": "",
"hostname": "",
"ip": "",
"terminal", "",
"login_type": "",
"system_user": "",
"was_failed": "",
"date_start": ""
}
some params we need generate: {
"log_file", "", # No use now, may be think more about monitor and record
}
"""
queryset = ProxyLog.objects.all()
serializer_class = serializers.ProxyLogSerializer
permission_classes = (IsSuperUserOrTerminalUser,)
def perform_create(self, serializer):
# Todo: May be save log_file
super(ProxyLogListCreateApi, self).perform_create(serializer)
class ProxyLogDetailApi(generics.RetrieveUpdateDestroyAPIView):
queryset = ProxyLog.objects.all()
serializer_class = serializers.ProxyLogSerializer
permission_classes = (IsSuperUserOrTerminalUser,)
class CommandLogCreateApi(generics.CreateAPIView):
class CommandLogCreateApi(generics.ListCreateAPIView):
queryset = CommandLog.objects.all()
serializer_class = serializers.CommandLogSerializer
permission_classes = (IsSuperUserOrTerminalUser,)
# ~*~ coding: utf-8 ~*~
#
from users.backends import IsSuperUserOrTerminalUser
from terminal.models import Terminal
......@@ -9,17 +9,20 @@ from django.utils.translation import ugettext_lazy as _
class LoginLog(models.Model):
LOGIN_TYPE_CHOICE = (
('S', 'ssh'),
('W', 'web'),
('W', 'Web'),
('S', 'SSH Terminal'),
('WT', 'Web Terminal')
)
username = models.CharField(max_length=20, verbose_name=_('Username'))
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=1, verbose_name=_('Login type'))
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
terminal = models.CharField(max_length=32, verbose_name=_('Terminal'))
login_ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
login_city = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Login city'))
user_agent = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('User agent'))
date_login = models.DateTimeField(auto_now=True, verbose_name=_('Date login'))
from_terminal = models.ForeignKey
date_login = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
date_logout = models.DateTimeField(null=True, verbose_name=_('Date logout'))
class Meta:
......@@ -29,8 +32,8 @@ class LoginLog(models.Model):
class ProxyLog(models.Model):
LOGIN_TYPE_CHOICE = (
('S', 'ssh'),
('W', 'web'),
('S', 'SSH Terminal'),
('WT', 'Web Terminal'),
)
username = models.CharField(max_length=20, verbose_name=_('Username'))
......@@ -38,11 +41,13 @@ class ProxyLog(models.Model):
hostname = models.CharField(max_length=128, blank=True, verbose_name=_('Hostname'))
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'))
system_user = models.CharField(max_length=20, verbose_name=_('System user'))
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=1, verbose_name=_('Login type'))
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, blank=True,
null=True, verbose_name=_('Login type'))
terminal = models.CharField(max_length=32, blank=True, null=True, verbose_name=_('Terminal'))
log_file = models.CharField(max_length=1000, blank=True, null=True)
was_failed = models.BooleanField(default=False, verbose_name=_('Did connect failed'))
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
date_start = models.DateTimeField(auto_now=True, verbose_name=_('Date start'))
date_start = models.DateTimeField(verbose_name=_('Date start'))
date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished'))
def __unicode__(self):
......@@ -55,14 +60,14 @@ class ProxyLog(models.Model):
class CommandLog(models.Model):
proxy_log = models.ForeignKey(ProxyLog, on_delete=models.CASCADE, related_name='command_log')
command_no = models.IntegerField()
command = models.CharField(max_length=1000, blank=True)
output = models.TextField(blank=True)
date_start = models.DateTimeField(null=True)
date_finished = models.DateTimeField(null=True)
datetime = models.DateTimeField(null=True)
def __unicode__(self):
return '%s: %s' % (self.id, self.command)
class Meta:
db_table = 'command_log'
ordering = ['-date_start', 'command']
ordering = ['command_no', 'command']
......@@ -16,5 +16,4 @@ class CommandLogSerializer(serializers.ModelSerializer):
model = models.CommandLog
if __name__ == '__main__':
pass
......@@ -7,14 +7,18 @@ from itertools import chain
import string
import logging
from itsdangerous import Signer, TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, TimestampSigner, \
BadSignature, SignatureExpired
from django.shortcuts import reverse as dj_reverse
from django.conf import settings
from django.core import signing
from django.utils import timezone
SECRET_KEY = settings.SECRET_KEY
def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None, external=False):
url = dj_reverse(viewname, urlconf=urlconf, args=args, kwargs=kwargs, current_app=current_app)
def reverse(view_name, urlconf=None, args=None, kwargs=None, current_app=None, external=False):
url = dj_reverse(view_name, urlconf=urlconf, args=args, kwargs=kwargs, current_app=current_app)
if external:
url = settings.SITE_URL.strip('/') + url
......@@ -43,13 +47,29 @@ def decrypt(*args, **kwargs):
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):
return ''
def date_expired_default():
try:
years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS)
except TypeError:
years = 70
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):
......
......@@ -33,7 +33,7 @@ except ImportError:
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = CONFIG.SECRET_KEY or '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x'
SECRET_KEY = CONFIG.SECRET_KEY
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = CONFIG.DEBUG or False
......@@ -54,10 +54,10 @@ INSTALLED_APPS = [
'users.apps.UsersConfig',
'assets.apps.AssetsConfig',
'perms.apps.PermsConfig',
# 'terminal.apps.TerminalConfig',
'ops.apps.OpsConfig',
'audits.apps.AuditsConfig',
'common.apps.CommonConfig',
'terminal.apps.TerminalConfig',
'rest_framework',
'rest_framework.authtoken',
'bootstrapform',
......@@ -68,7 +68,6 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'ws4redis',
]
......@@ -264,12 +263,14 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser',
# 'rest_framework.permissions.IsAuthenticated',
'users.backends.IsValidUser',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'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
......
......@@ -26,6 +26,7 @@ urlpatterns = [
url(r'^assets/', include('assets.urls')),
url(r'^perms/', include('perms.urls')),
url(r'^(api/)?audits/', include('audits.urls')),
url(r'^(api/)?terminal/', include('terminal.urls')),
]
......
......@@ -27,7 +27,7 @@ class AssetPermission(models.Model):
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now=True, verbose_name=_('Date created'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
def __unicode__(self):
......
......@@ -4447,7 +4447,7 @@ extend(SVGRenderer.prototype, {
* START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
* *
* For applications and websites that don't need IE support, like platform *
* targeted mobile apps and web apps, this code can be removed. *
* targeted mobile terminal and web terminal, this code can be removed. *
* *
*****************************************************************************/
......
......@@ -4217,19 +4217,19 @@ Terminal.prototype.setMode = function(params) {
// focusout: ^[[O
this.sendFocus = true;
break;
case 1005: // utf8 ext mode mouse
case 1005: // utf8 terminal mode mouse
this.utfMouse = true;
// for wide terminals
// simply encodes large values as utf8 characters
break;
case 1006: // sgr ext mode mouse
case 1006: // sgr terminal mode mouse
this.sgrMouse = true;
// for wide terminals
// does not add 32 to fields
// press: ^[[<b;x;yM
// release: ^[[<b;x;ym
break;
case 1015: // urxvt ext mode mouse
case 1015: // urxvt terminal mode mouse
this.urxvtMouse = true;
// for wide terminals
// numbers for fields
......@@ -4406,13 +4406,13 @@ Terminal.prototype.resetMode = function(params) {
case 1004: // send focusin/focusout events
this.sendFocus = false;
break;
case 1005: // utf8 ext mode mouse
case 1005: // utf8 terminal mode mouse
this.utfMouse = false;
break;
case 1006: // sgr ext mode mouse
case 1006: // sgr terminal mode mouse
this.sgrMouse = false;
break;
case 1015: // urxvt ext mode mouse
case 1015: // urxvt terminal mode mouse
this.urxvtMouse = false;
break;
case 25: // hide cursor
......@@ -6030,7 +6030,7 @@ Terminal.charsets = {};
// DEC Special Character and Line Drawing Set.
// http://vt100.net/docs/vt102-ug/table5-13.html
// A lot of curses apps use this if they see TERM=xterm.
// A lot of curses terminal use this if they see TERM=xterm.
// testing: echo -e '\e(0a\e(B'
// The xterm output sometimes seems to conflict with the
// reference above. xterm seems in line with the reference
......
......@@ -32,11 +32,13 @@
<li id="asset-permission">
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
</li>
{# <li id="user-group">#}
{# <a href="">{% trans 'User group perm' %}</a>#}
{# </li>#}
</ul>
</li>
<li id="">
<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>
</li>
<li id="">
<a href="">
<i class="fa fa-files-o"></i><span class="nav-label">{% trans 'Audits' %}</span><span class="label label-info pull-right"></span>
......
from django.contrib import admin
# Register your models here.
# -*- coding: utf-8 -*-
#
from rest_framework.generics import ListCreateAPIView, CreateAPIView
from rest_framework.views import APIView, Response
from rest_framework.permissions import AllowAny
from common.utils import unsign, get_object_or_none
from .models import Terminal, TerminalHeatbeat
from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
from .hands import IsSuperUserOrTerminalUser
class TerminalApi(ListCreateAPIView):
queryset = Terminal.objects.all()
serializer_class = TerminalSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
name = unsign(request.data.get('name', ''))
if name:
terminal = get_object_or_none(Terminal, name=name)
if terminal:
if terminal.is_accepted and terminal.is_active:
return Response(data={'data': {'name': name, 'id': terminal.id},
'msg': 'Success'},
status=200)
else:
return Response(data={'data': {'name': name, 'ip': terminal.ip},
'msg': 'Need admin accept or active it'},
status=203)
else:
ip = request.META.get('X-Real-IP') or request.META.get('REMOTE_ADDR')
terminal = Terminal.objects.create(name=name, ip=ip)
return Response(data={'data': {'name': name, 'ip': terminal.ip},
'msg': 'Need admin accept or active it'},
status=204)
else:
return Response(data={'msg': 'Secrete key invalid'}, status=401)
class TerminalHeatbeatApi(CreateAPIView):
model = TerminalHeatbeat
serializer_class = TerminalHeatbeatSerializer
permission_classes = (IsSuperUserOrTerminalUser,)
from __future__ import unicode_literals
from django.apps import AppConfig
class TerminalConfig(AppConfig):
name = 'terminal'
# -*- coding: utf-8 -*-
#
from users.backends import IsSuperUserOrTerminalUser
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
from users.models import User
class Terminal(models.Model):
TYPE_CHOICES = (
('S', 'SSH Terminal'),
('WT', 'Web Terminal')
)
name = models.CharField(max_length=30, unique=True, verbose_name=_('Name'))
ip = models.GenericIPAddressField(verbose_name=_('From ip'))
is_active = models.BooleanField(default=False, verbose_name=_('Is active'))
is_bound_ip = models.BooleanField(default=False, verbose_name=_('Is bound ip'))
heatbeat_interval = models.IntegerField(default=60, verbose_name=_('Heatbeat interval'))
type = models.CharField(choices=TYPE_CHOICES, max_length=2, verbose_name=_('Terminal type'))
url = models.CharField(max_length=100, verbose_name=_('URL to login'))
mail_to = models.ManyToManyField(User, verbose_name=_('Mail to'))
is_accepted = models.BooleanField(default=False, verbose_name=_('Is accepted'))
date_created = models.DateTimeField(auto_now_add=True)
comment = models.TextField(verbose_name=_('Comment'))
def is_valid(self):
return self.is_active and self.is_accepted
@property
def is_superuser(self):
return False
@property
def is_terminal(self):
return True
class Meta:
db_table = 'terminal'
ordering = ['name']
class TerminalHeatbeat(models.Model):
terminal = models.ForeignKey(Terminal, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'terminal_heatbeat'
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from .models import Terminal, TerminalHeatbeat
class TerminalSerializer(serializers.ModelSerializer):
class Meta:
model = Terminal
fields = ['name', 'ip', 'type', 'url', 'comment', 'is_active', 'is_accepted',
'get_type_display']
class TerminalHeatbeatSerializer(serializers.ModelSerializer):
class Meta:
model = TerminalHeatbeat
fields = ['terminal']
if __name__ == '__main__':
pass
{% extends '_base_list.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
{{ block.super }}
<style>
div.dataTables_wrapper div.dataTables_filter,
.dataTables_length {
float: right !important;
}
div.dataTables_wrapper div.dataTables_filter {
margin-left: 15px;
}
</style>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
{#<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>#}
<table class="table table-striped table-bordered table-hover " id="terminal_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 'Name' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'url' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script>
$(document).ready(function(){
var options = {
ele: $('#terminal_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) {#}
{# var innerHtml = cellData.length > 8 ? cellData.substring(0, 8) + '...': cellData;#}
{# $(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');#}
{# }},#}
{# {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: 7, createdCell: function (td, cellData, rowData) {#}
{# var update_btn = '<a href="{% url "users:user-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);#}
{# var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);#}
{# if (rowData.id === 1) {#}
{# $(td).html(update_btn)#}
{# } else {#}
{# $(td).html(update_btn + del_btn)#}
{# }}],#}
{# ],#}
ajax_url: '{% url "terminal:terminal-list-create-api" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "ip" }, {data: "get_type_display" }, {data: "url" },
{data: "is_active" }, {data: "ip"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
</script>
{% endblock %}
from django.test import TestCase
# Create your tests here.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from django.conf.urls import url
import views
import api
app_name = 'terminal'
urlpatterns = [
url(r'^terminal$', views.TerminalListView.as_view(), name='terminal-list'),
]
urlpatterns += [
url(r'^v1/terminal/$', api.TerminalApi.as_view(), name='terminal-list-create-api'),
url(r'^v1/terminal-heatbeat/$', api.TerminalHeatbeatApi.as_view(), name='terminal-heatbeat-api'),
]
# ~*~ coding: utf-8 ~*~
#
from django.views.generic import ListView
from django.utils.translation import ugettext as _
from .models import Terminal
class TerminalListView(ListView):
model = Terminal
template_name = 'terminal/terminal_list.html'
def get_context_data(self, **kwargs):
context = super(TerminalListView, self).get_context_data(**kwargs)
context.update({'app': _('Terminal'), 'action': _('Terminal list')})
return context
......@@ -7,11 +7,12 @@ from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from common.mixins import BulkDeleteApiMixin
from common.utils import get_logger
from .models import User, UserGroup
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
from common.mixins import BulkDeleteApiMixin
from common.utils import get_logger
from .backends import IsSuperUser, IsTerminalUser, IsValidUser, IsSuperUserOrTerminalUser
logger = get_logger(__name__)
......@@ -20,11 +21,13 @@ logger = get_logger(__name__)
class UserDetailApi(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserDetailSerializer
permission_classes = (IsSuperUser,)
class UserAndGroupEditApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserAndGroupSerializer
permission_classes = (IsSuperUser,)
class UserResetPasswordApi(generics.UpdateAPIView):
......@@ -84,6 +87,10 @@ class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserBulkUpdateSerializer
permission_classes = (IsSuperUserOrTerminalUser,)
# def get(self, request, *args, **kwargs):
# return super(UserListUpdateApi, self).get(request, *args, **kwargs)
class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
......@@ -104,3 +111,23 @@ class DeleteUserFromGroupApi(generics.DestroyAPIView):
user_id = kwargs.get('uid')
user = get_object_or_404(User, id=user_id)
instance.users.remove(user)
class AppUserRegisterApi(generics.CreateAPIView):
"""App send a post request to register a app user
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.
Save fields:
username:
name: name + request.ip
email: username + '@app.org'
role: App
"""
pass
# -*- coding: utf-8 -*-
#
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 .hands import Terminal
class TerminalAuthentication(authentication.BaseAuthentication):
keyword = 'Sign'
model = Terminal
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 sign header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid sign header. Sign string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
sign = auth[1].decode()
except UnicodeError:
msg = _('Invalid token header. Sign string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(sign)
def authenticate_credentials(self, sign):
name = unsign(sign, max_age=300)
if name:
terminal = get_object_or_none(self.model, name=name)
else:
raise exceptions.AuthenticationFailed(_('Invalid sign.'))
if not terminal.is_active:
raise exceptions.AuthenticationFailed(_('Terminal inactive or deleted.'))
terminal.is_authenticated = True
return terminal, None
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired"""
def has_permission(self, request, view):
return super(IsValidUser, self).has_permission(request, view) \
and request.user.is_valid
class IsTerminalUser(IsValidUser, permissions.BasePermission):
"""Allows access only to app user """
def has_permission(self, request, view):
return super(IsTerminalUser, self).has_permission(request, view) \
and isinstance(request.user, Terminal)
class IsSuperUser(IsValidUser, permissions.BasePermission):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
class IsSuperUserOrTerminalUser(IsValidUser, permissions.BasePermission):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
return super(IsSuperUserOrTerminalUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_terminal)
if __name__ == '__main__':
pass
......@@ -12,3 +12,4 @@
from perms.models import AssetPermission
from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
from terminal.models import Terminal
\ No newline at end of file
......@@ -141,6 +141,10 @@ class User(AbstractUser):
else:
return False
@property
def is_terminal(self):
return False
@is_superuser.setter
def is_superuser(self, value):
if value is True:
......@@ -150,7 +154,7 @@ class User(AbstractUser):
@property
def is_staff(self):
if self.is_authenticated and self.is_active and not self.is_expired and self.is_superuser:
if self.is_authenticated and self.is_valid:
return True
else:
return False
......@@ -178,21 +182,20 @@ class User(AbstractUser):
token = Token.objects.get(user=self)
except Token.DoesNotExist:
token = Token.objects.create(user=self)
return token.key
def refresh_private_token(self):
Token.objects.filter(user=self).delete()
return Token.objects.create(user=self)
def generate_reset_token(self):
return signing.dumps({'reset': self.id, 'email': self.email})
def is_member_of(self, user_group):
if user_group in self.groups.all():
return True
return False
def generate_reset_token(self):
return signing.dumps({'reset': self.id, 'email': self.email})
@classmethod
def validate_reset_token(cls, token, max_age=3600):
try:
......
......@@ -5,23 +5,23 @@ 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 .models import User, UserGroup
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
class UserPKUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', '_public_key']
def validate__public_key(self, value):
@staticmethod
def validate__public_key(value):
from sshpubkeys import SSHKey
from sshpubkeys.exceptions import InvalidKeyException
ssh = SSHKey(value)
......@@ -45,7 +45,6 @@ class UserAndGroupSerializer(serializers.ModelSerializer):
class GroupDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserGroup
fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users']
......@@ -63,16 +62,17 @@ class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer)
'enable_otp', 'comment', 'groups', 'get_role_display',
'group_display', 'active_display']
def get_group_display(self, obj):
@staticmethod
def get_group_display(obj):
return " ".join([group.name for group in obj.groups.all()])
def get_active_display(self, obj):
# TODO: user ative state
@staticmethod
def get_active_display(obj):
# TODO: user active state
return not (obj.is_expired and obj.is_active)
class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
user_amount = serializers.SerializerMethodField()
class Meta:
......@@ -80,5 +80,18 @@ class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer
list_serializer_class = BulkListSerializer
fields = ['id', 'name', 'comment', 'user_amount']
def get_user_amount(self, obj):
@staticmethod
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
......@@ -23,12 +23,12 @@ div.dataTables_wrapper div.dataTables_filter {
<th class="text-center">
<div class="checkbox checkbox-default"><input id="" type="checkbox" class="ipt_check_all"><label></label></div>
</th>
<th class="text-center">{% trans 'Name' %}</a></th>
<th class="text-center">{% trans 'Username' %}</a></th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Role' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Asset num' %}</th>
<th class="text-center">{% trans 'Active' %}</a></th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
......@@ -165,7 +165,7 @@ $(document).ready(function(){
var fail = function() {
var msg = "{% trans 'User Deleting failed.' %}";
swal("{% trans 'User Delete' %}", msg, "error");
}
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
......@@ -208,15 +208,15 @@ $(document).ready(function(){
post_list.push(content);
});
if (post_list === []) {
return false;
};
return false
}
var the_url = "{% url 'users:user-bulk-update-api' %}";
var success = function() {
var msg = "{% trans 'The selected users has been updated successfully.' %}";
swal("{% trans 'User Updated' %}", msg, "success");
$('#user_list_table').DataTable().ajax.reload();
jumpserver.checked = false;
}
};
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
$('#user_bulk_update_modal').modal('hide');
}).on('click', '#btn_user_import', function() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment