mirror of https://github.com/jumpserver/jumpserver
Merge branch 'master' of code.jumpserver.org:Jumpserver/jumpserver
Mergepull/530/head
commit
80baecc8be
|
@ -19,7 +19,7 @@ class IDC(models.Model):
|
||||||
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
|
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
|
||||||
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
|
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
|
||||||
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
|
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'))
|
operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator'))
|
||||||
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
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'))
|
key = models.CharField(max_length=64, verbose_name=_('KEY'))
|
||||||
value = models.CharField(max_length=64, verbose_name=_('VALUE'))
|
value = models.CharField(max_length=64, verbose_name=_('VALUE'))
|
||||||
created_by = models.CharField(max_length=32, blank=True, verbose_name=_("Created by"))
|
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'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
def __unicode__(self):
|
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'))
|
_public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||||
as_default = models.BooleanField(default=False, verbose_name=_('As default'))
|
as_default = models.BooleanField(default=False, verbose_name=_('As default'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
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'))
|
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
@ -169,7 +169,7 @@ class SystemUser(models.Model):
|
||||||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||||
home = models.CharField(max_length=64, blank=True, verbose_name=_('Home'))
|
home = models.CharField(max_length=64, blank=True, verbose_name=_('Home'))
|
||||||
uid = models.IntegerField(null=True, blank=True, verbose_name=_('Uid'))
|
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'))
|
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
||||||
comment = models.TextField(max_length=128, blank=True, verbose_name=_('Comment'))
|
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'))
|
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||||
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
|
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
|
||||||
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
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'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
def __unicode__(self):
|
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'))
|
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'))
|
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'))
|
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'))
|
comment = models.TextField(max_length=128, null=True, blank=True, verbose_name=_('Comment'))
|
||||||
tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
|
tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
|
||||||
|
|
||||||
|
@ -365,15 +365,15 @@ class Asset(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Tag(models.Model):
|
class Tag(models.Model):
|
||||||
name = models.CharField('标签名', max_length=64,unique=True)
|
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
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'))
|
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
__str__ = __unicode__
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'tag'
|
db_table = 'tag'
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,46 @@ from rest_framework import generics
|
||||||
|
|
||||||
import serializers
|
import serializers
|
||||||
|
|
||||||
from .models import ProxyLog
|
from .models import ProxyLog, CommandLog
|
||||||
|
from .hands import IsSuperUserOrTerminalUser, Terminal
|
||||||
|
|
||||||
|
|
||||||
class ProxyLogListCreateApi(generics.ListCreateAPIView):
|
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()
|
queryset = ProxyLog.objects.all()
|
||||||
serializer_class = serializers.ProxyLogSerializer
|
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):
|
class ProxyLogDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
queryset = ProxyLog.objects.all()
|
queryset = ProxyLog.objects.all()
|
||||||
serializer_class = serializers.ProxyLogSerializer
|
serializer_class = serializers.ProxyLogSerializer
|
||||||
|
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||||
|
|
||||||
|
|
||||||
class CommandLogCreateApi(generics.CreateAPIView):
|
class CommandLogCreateApi(generics.ListCreateAPIView):
|
||||||
|
queryset = CommandLog.objects.all()
|
||||||
serializer_class = serializers.CommandLogSerializer
|
serializer_class = serializers.CommandLogSerializer
|
||||||
|
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# ~*~ 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):
|
class LoginLog(models.Model):
|
||||||
LOGIN_TYPE_CHOICE = (
|
LOGIN_TYPE_CHOICE = (
|
||||||
('S', 'ssh'),
|
('W', 'Web'),
|
||||||
('W', 'web'),
|
('S', 'SSH Terminal'),
|
||||||
|
('WT', 'Web Terminal')
|
||||||
)
|
)
|
||||||
|
|
||||||
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
||||||
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
|
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_ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||||
login_city = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Login city'))
|
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'))
|
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'))
|
date_logout = models.DateTimeField(null=True, verbose_name=_('Date logout'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -29,8 +32,8 @@ class LoginLog(models.Model):
|
||||||
|
|
||||||
class ProxyLog(models.Model):
|
class ProxyLog(models.Model):
|
||||||
LOGIN_TYPE_CHOICE = (
|
LOGIN_TYPE_CHOICE = (
|
||||||
('S', 'ssh'),
|
('S', 'SSH Terminal'),
|
||||||
('W', 'web'),
|
('WT', 'Web Terminal'),
|
||||||
)
|
)
|
||||||
|
|
||||||
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
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'))
|
hostname = models.CharField(max_length=128, blank=True, verbose_name=_('Hostname'))
|
||||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'))
|
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'))
|
||||||
system_user = models.CharField(max_length=20, verbose_name=_('System user'))
|
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)
|
log_file = models.CharField(max_length=1000, blank=True, null=True)
|
||||||
was_failed = models.BooleanField(default=False, verbose_name=_('Did connect failed'))
|
was_failed = models.BooleanField(default=False, verbose_name=_('Did connect failed'))
|
||||||
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
|
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'))
|
date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished'))
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
@ -55,14 +60,14 @@ 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='command_log')
|
||||||
|
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)
|
||||||
date_start = models.DateTimeField(null=True)
|
datetime = models.DateTimeField(null=True)
|
||||||
date_finished = models.DateTimeField(null=True)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '%s: %s' % (self.id, self.command)
|
return '%s: %s' % (self.id, self.command)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'command_log'
|
db_table = 'command_log'
|
||||||
ordering = ['-date_start', 'command']
|
ordering = ['command_no', 'command']
|
||||||
|
|
|
@ -16,5 +16,4 @@ class CommandLogSerializer(serializers.ModelSerializer):
|
||||||
model = models.CommandLog
|
model = models.CommandLog
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
pass
|
|
||||||
|
|
|
@ -7,14 +7,18 @@ from itertools import chain
|
||||||
import string
|
import string
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from itsdangerous import Signer, TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, TimestampSigner, \
|
||||||
|
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
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.utils import timezone
|
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:
|
if external:
|
||||||
url = settings.SITE_URL.strip('/') + url
|
url = settings.SITE_URL.strip('/') + url
|
||||||
|
@ -43,13 +47,29 @@ def decrypt(*args, **kwargs):
|
||||||
return ''
|
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():
|
def date_expired_default():
|
||||||
try:
|
try:
|
||||||
years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS)
|
years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
years = 70
|
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):
|
def combine_seq(s1, s2, callback=None):
|
||||||
|
|
|
@ -33,7 +33,7 @@ except ImportError:
|
||||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# 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!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = CONFIG.DEBUG or False
|
DEBUG = CONFIG.DEBUG or False
|
||||||
|
@ -54,10 +54,10 @@ INSTALLED_APPS = [
|
||||||
'users.apps.UsersConfig',
|
'users.apps.UsersConfig',
|
||||||
'assets.apps.AssetsConfig',
|
'assets.apps.AssetsConfig',
|
||||||
'perms.apps.PermsConfig',
|
'perms.apps.PermsConfig',
|
||||||
# 'terminal.apps.TerminalConfig',
|
|
||||||
'ops.apps.OpsConfig',
|
'ops.apps.OpsConfig',
|
||||||
'audits.apps.AuditsConfig',
|
'audits.apps.AuditsConfig',
|
||||||
'common.apps.CommonConfig',
|
'common.apps.CommonConfig',
|
||||||
|
'terminal.apps.TerminalConfig',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'bootstrapform',
|
'bootstrapform',
|
||||||
|
@ -68,7 +68,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'ws4redis',
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -264,12 +263,14 @@ REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
# or allow read-only access for unauthenticated users.
|
# or allow read-only access for unauthenticated users.
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'rest_framework.permissions.IsAdminUser',
|
# 'rest_framework.permissions.IsAuthenticated',
|
||||||
|
'users.backends.IsValidUser',
|
||||||
),
|
),
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
'users.backends.TerminalAuthentication',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
# This setting is required to override the Django's main loop, when running in
|
# 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'^assets/', include('assets.urls')),
|
||||||
url(r'^perms/', include('perms.urls')),
|
url(r'^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')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class AssetPermission(models.Model):
|
||||||
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'))
|
||||||
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)
|
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
|
|
@ -4447,7 +4447,7 @@ extend(SVGRenderer.prototype, {
|
||||||
* START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
|
* START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
|
||||||
* *
|
* *
|
||||||
* For applications and websites that don't need IE support, like platform *
|
* 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
|
// focusout: ^[[O
|
||||||
this.sendFocus = true;
|
this.sendFocus = true;
|
||||||
break;
|
break;
|
||||||
case 1005: // utf8 ext mode mouse
|
case 1005: // utf8 terminal mode mouse
|
||||||
this.utfMouse = true;
|
this.utfMouse = true;
|
||||||
// for wide terminals
|
// for wide terminals
|
||||||
// simply encodes large values as utf8 characters
|
// simply encodes large values as utf8 characters
|
||||||
break;
|
break;
|
||||||
case 1006: // sgr ext mode mouse
|
case 1006: // sgr terminal mode mouse
|
||||||
this.sgrMouse = true;
|
this.sgrMouse = true;
|
||||||
// for wide terminals
|
// for wide terminals
|
||||||
// does not add 32 to fields
|
// does not add 32 to fields
|
||||||
// press: ^[[<b;x;yM
|
// press: ^[[<b;x;yM
|
||||||
// release: ^[[<b;x;ym
|
// release: ^[[<b;x;ym
|
||||||
break;
|
break;
|
||||||
case 1015: // urxvt ext mode mouse
|
case 1015: // urxvt terminal mode mouse
|
||||||
this.urxvtMouse = true;
|
this.urxvtMouse = true;
|
||||||
// for wide terminals
|
// for wide terminals
|
||||||
// numbers for fields
|
// numbers for fields
|
||||||
|
@ -4406,13 +4406,13 @@ Terminal.prototype.resetMode = function(params) {
|
||||||
case 1004: // send focusin/focusout events
|
case 1004: // send focusin/focusout events
|
||||||
this.sendFocus = false;
|
this.sendFocus = false;
|
||||||
break;
|
break;
|
||||||
case 1005: // utf8 ext mode mouse
|
case 1005: // utf8 terminal mode mouse
|
||||||
this.utfMouse = false;
|
this.utfMouse = false;
|
||||||
break;
|
break;
|
||||||
case 1006: // sgr ext mode mouse
|
case 1006: // sgr terminal mode mouse
|
||||||
this.sgrMouse = false;
|
this.sgrMouse = false;
|
||||||
break;
|
break;
|
||||||
case 1015: // urxvt ext mode mouse
|
case 1015: // urxvt terminal mode mouse
|
||||||
this.urxvtMouse = false;
|
this.urxvtMouse = false;
|
||||||
break;
|
break;
|
||||||
case 25: // hide cursor
|
case 25: // hide cursor
|
||||||
|
@ -6030,7 +6030,7 @@ Terminal.charsets = {};
|
||||||
|
|
||||||
// DEC Special Character and Line Drawing Set.
|
// DEC Special Character and Line Drawing Set.
|
||||||
// http://vt100.net/docs/vt102-ug/table5-13.html
|
// 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'
|
// testing: echo -e '\e(0a\e(B'
|
||||||
// The xterm output sometimes seems to conflict with the
|
// The xterm output sometimes seems to conflict with the
|
||||||
// reference above. xterm seems in line with the reference
|
// reference above. xterm seems in line with the reference
|
||||||
|
|
|
@ -32,11 +32,13 @@
|
||||||
<li id="asset-permission">
|
<li id="asset-permission">
|
||||||
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
|
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{# <li id="user-group">#}
|
|
||||||
{# <a href="">{% trans 'User group perm' %}</a>#}
|
|
||||||
{# </li>#}
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</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="">
|
<li id="">
|
||||||
<a href="">
|
<a href="">
|
||||||
<i class="fa fa-files-o"></i><span class="nav-label">{% trans 'Audits' %}</span><span class="label label-info pull-right"></span>
|
<i class="fa fa-files-o"></i><span class="nav-label">{% trans 'Audits' %}</span><span class="label label-info pull-right"></span>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1,46 @@
|
||||||
|
# -*- 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,)
|
|
@ -0,0 +1,7 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalConfig(AppConfig):
|
||||||
|
name = 'terminal'
|
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from users.backends import IsSuperUserOrTerminalUser
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
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'
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- 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
|
|
@ -0,0 +1,79 @@
|
||||||
|
{% 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 %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/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'),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# ~*~ 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.response import Response
|
||||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||||
|
|
||||||
|
from common.mixins import BulkDeleteApiMixin
|
||||||
|
from common.utils import get_logger
|
||||||
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
|
||||||
from common.mixins import BulkDeleteApiMixin
|
from .backends import IsSuperUser, IsTerminalUser, IsValidUser, IsSuperUserOrTerminalUser
|
||||||
from common.utils import get_logger
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -20,11 +21,13 @@ logger = get_logger(__name__)
|
||||||
class UserDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
class UserDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserDetailSerializer
|
serializer_class = UserDetailSerializer
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
|
||||||
class UserAndGroupEditApi(generics.RetrieveUpdateAPIView):
|
class UserAndGroupEditApi(generics.RetrieveUpdateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserAndGroupSerializer
|
serializer_class = UserAndGroupSerializer
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
|
||||||
class UserResetPasswordApi(generics.UpdateAPIView):
|
class UserResetPasswordApi(generics.UpdateAPIView):
|
||||||
|
@ -84,6 +87,10 @@ class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserBulkUpdateSerializer
|
serializer_class = UserBulkUpdateSerializer
|
||||||
|
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||||
|
|
||||||
|
# def get(self, request, *args, **kwargs):
|
||||||
|
# return super(UserListUpdateApi, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||||
|
@ -104,3 +111,23 @@ class DeleteUserFromGroupApi(generics.DestroyAPIView):
|
||||||
user_id = kwargs.get('uid')
|
user_id = kwargs.get('uid')
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
instance.users.remove(user)
|
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
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- 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.models import AssetPermission
|
||||||
from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
|
from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
|
||||||
|
from terminal.models import Terminal
|
|
@ -141,6 +141,10 @@ class User(AbstractUser):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_terminal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
@is_superuser.setter
|
@is_superuser.setter
|
||||||
def is_superuser(self, value):
|
def is_superuser(self, value):
|
||||||
if value is True:
|
if value is True:
|
||||||
|
@ -150,7 +154,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_staff(self):
|
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
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -178,21 +182,20 @@ class User(AbstractUser):
|
||||||
token = Token.objects.get(user=self)
|
token = Token.objects.get(user=self)
|
||||||
except Token.DoesNotExist:
|
except Token.DoesNotExist:
|
||||||
token = Token.objects.create(user=self)
|
token = Token.objects.create(user=self)
|
||||||
|
|
||||||
return token.key
|
return token.key
|
||||||
|
|
||||||
def refresh_private_token(self):
|
def refresh_private_token(self):
|
||||||
Token.objects.filter(user=self).delete()
|
Token.objects.filter(user=self).delete()
|
||||||
return Token.objects.create(user=self)
|
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):
|
def is_member_of(self, user_group):
|
||||||
if user_group in self.groups.all():
|
if user_group in self.groups.all():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def generate_reset_token(self):
|
||||||
|
return signing.dumps({'reset': self.id, 'email': self.email})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_reset_token(cls, token, max_age=3600):
|
def validate_reset_token(cls, token, max_age=3600):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -5,23 +5,23 @@ 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 .models import User, UserGroup
|
from .models import User, UserGroup
|
||||||
|
|
||||||
|
|
||||||
class UserDetailSerializer(serializers.ModelSerializer):
|
class UserDetailSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
|
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
|
||||||
|
|
||||||
|
|
||||||
class UserPKUpdateSerializer(serializers.ModelSerializer):
|
class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['id', '_public_key']
|
fields = ['id', '_public_key']
|
||||||
|
|
||||||
def validate__public_key(self, value):
|
@staticmethod
|
||||||
|
def validate__public_key(value):
|
||||||
from sshpubkeys import SSHKey
|
from sshpubkeys import SSHKey
|
||||||
from sshpubkeys.exceptions import InvalidKeyException
|
from sshpubkeys.exceptions import InvalidKeyException
|
||||||
ssh = SSHKey(value)
|
ssh = SSHKey(value)
|
||||||
|
@ -45,7 +45,6 @@ class UserAndGroupSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class GroupDetailSerializer(serializers.ModelSerializer):
|
class GroupDetailSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserGroup
|
model = UserGroup
|
||||||
fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users']
|
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',
|
'enable_otp', 'comment', 'groups', 'get_role_display',
|
||||||
'group_display', 'active_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()])
|
return " ".join([group.name for group in obj.groups.all()])
|
||||||
|
|
||||||
def get_active_display(self, obj):
|
@staticmethod
|
||||||
# TODO: user ative state
|
def get_active_display(obj):
|
||||||
|
# TODO: user active state
|
||||||
return not (obj.is_expired and obj.is_active)
|
return not (obj.is_expired and obj.is_active)
|
||||||
|
|
||||||
|
|
||||||
class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
|
|
||||||
user_amount = serializers.SerializerMethodField()
|
user_amount = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -80,5 +80,18 @@ class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
fields = ['id', 'name', 'comment', 'user_amount']
|
fields = ['id', 'name', 'comment', 'user_amount']
|
||||||
|
|
||||||
def get_user_amount(self, obj):
|
@staticmethod
|
||||||
|
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
|
||||||
|
|
|
@ -23,12 +23,12 @@ div.dataTables_wrapper div.dataTables_filter {
|
||||||
<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 class="checkbox checkbox-default"><input id="" type="checkbox" class="ipt_check_all"><label></label></div>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center">{% trans 'Name' %}</a></th>
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
<th class="text-center">{% trans 'Username' %}</a></th>
|
<th class="text-center">{% trans 'Username' %}</th>
|
||||||
<th class="text-center">{% trans 'Role' %}</th>
|
<th class="text-center">{% trans 'Role' %}</th>
|
||||||
<th class="text-center">{% trans 'User group' %}</th>
|
<th class="text-center">{% trans 'User group' %}</th>
|
||||||
<th class="text-center">{% trans 'Asset num' %}</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>
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -165,7 +165,7 @@ $(document).ready(function(){
|
||||||
var fail = function() {
|
var fail = function() {
|
||||||
var msg = "{% trans 'User Deleting failed.' %}";
|
var msg = "{% trans 'User Deleting failed.' %}";
|
||||||
swal("{% trans 'User Delete' %}", msg, "error");
|
swal("{% trans 'User Delete' %}", msg, "error");
|
||||||
}
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
|
@ -208,15 +208,15 @@ $(document).ready(function(){
|
||||||
post_list.push(content);
|
post_list.push(content);
|
||||||
});
|
});
|
||||||
if (post_list === []) {
|
if (post_list === []) {
|
||||||
return false;
|
return false
|
||||||
};
|
}
|
||||||
var the_url = "{% url 'users:user-bulk-update-api' %}";
|
var the_url = "{% url 'users:user-bulk-update-api' %}";
|
||||||
var success = function() {
|
var success = function() {
|
||||||
var msg = "{% trans 'The selected users has been updated successfully.' %}";
|
var msg = "{% trans 'The selected users has been updated successfully.' %}";
|
||||||
swal("{% trans 'User Updated' %}", msg, "success");
|
swal("{% trans 'User Updated' %}", msg, "success");
|
||||||
$('#user_list_table').DataTable().ajax.reload();
|
$('#user_list_table').DataTable().ajax.reload();
|
||||||
jumpserver.checked = false;
|
jumpserver.checked = false;
|
||||||
}
|
};
|
||||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
||||||
$('#user_bulk_update_modal').modal('hide');
|
$('#user_bulk_update_modal').modal('hide');
|
||||||
}).on('click', '#btn_user_import', function() {
|
}).on('click', '#btn_user_import', function() {
|
||||||
|
|
Loading…
Reference in New Issue