From f038423ce28140e1fbd783a70f500405cf4ca739 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 14 Oct 2016 20:18:34 +0800 Subject: [PATCH 01/12] Add isdangerous --- apps/common/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/common/utils.py b/apps/common/utils.py index da83cfca7..ecbb293d6 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -7,6 +7,7 @@ from itertools import chain import string import logging +from itsdangerous import TimedJSONWebSignatureSerializer from django.shortcuts import reverse as dj_reverse from django.conf import settings from django.core import signing From a62a2178d02d9a7c6c8582e7b6cf34591107d784 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 15 Oct 2016 00:49:59 +0800 Subject: [PATCH 02/12] Add user backend --- apps/common/utils.py | 26 +++++++++++++++++++---- apps/users/backends.py | 47 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 apps/users/backends.py diff --git a/apps/common/utils.py b/apps/common/utils.py index ecbb293d6..a5af4f66c 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -7,15 +7,19 @@ from itertools import chain import string import logging -from itsdangerous import TimedJSONWebSignatureSerializer +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 +SIGNER = TimestampSigner(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 @@ -44,13 +48,27 @@ def decrypt(*args, **kwargs): return '' +def sign(value): + return SIGNER.sign(value) + + +def unsign(value, max_age=3600): + try: + return SIGNER.unsign(value, max_age=max_age) + except (BadSignature, SignatureExpired): + return None + + 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): diff --git a/apps/users/backends.py b/apps/users/backends.py new file mode 100644 index 000000000..ac4d67ddd --- /dev/null +++ b/apps/users/backends.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import authentication, exceptions +from django.utils.translation import ugettext as _ + +from common.utils import unsign +from .models import User + + +class APPSignAuthentication(authentication.BaseAuthentication): + keyword = 'Sign' + model = User + + 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, key): + try: + token = self.model.objects.select_related('user').get(key=key) + except self.model.DoesNotExist: + raise exceptions.AuthenticationFailed(_('Invalid token.')) + + if not token.user.is_active: + raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) + + +if __name__ == '__main__': + pass From 0446f449e9be30e266294384b6f48d046f421eb5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 15 Oct 2016 16:04:54 +0800 Subject: [PATCH 03/12] Add auth and permission backends --- apps/jumpserver/settings.py | 5 ++-- apps/users/api.py | 9 +++++-- apps/users/backends.py | 54 ++++++++++++++++++++++++++++++------- apps/users/models.py | 18 ++++++++++--- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index b57a1f07a..13029b815 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -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.AppSignAuthentication', ), } # This setting is required to override the Django's main loop, when running in diff --git a/apps/users/api.py b/apps/users/api.py index 4302cec6e..67ce7ae4f 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -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, IsAppUser, IsValidUser, IsSuperUserOrAppUser logger = get_logger(__name__) @@ -84,6 +85,10 @@ class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView): class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): queryset = User.objects.all() serializer_class = UserBulkUpdateSerializer + permission_classes = (IsSuperUserOrAppUser,) + + def get(self, request, *args, **kwargs): + return super(UserListUpdateApi, self).get(request, *args, **kwargs) class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): diff --git a/apps/users/backends.py b/apps/users/backends.py index ac4d67ddd..f4b98980a 100644 --- a/apps/users/backends.py +++ b/apps/users/backends.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- # -from rest_framework import authentication, exceptions +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 +from common.utils import unsign, get_object_or_none from .models import User -class APPSignAuthentication(authentication.BaseAuthentication): +class AppSignAuthentication(authentication.BaseAuthentication): keyword = 'Sign' model = User @@ -30,17 +31,50 @@ class APPSignAuthentication(authentication.BaseAuthentication): 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, key): - try: - token = self.model.objects.select_related('user').get(key=key) - except self.model.DoesNotExist: - raise exceptions.AuthenticationFailed(_('Invalid token.')) + def authenticate_credentials(self, sign): + app = unsign(sign, max_age=300) + if app: + user = get_object_or_none(self.model, username=app, role='App') + else: + raise exceptions.AuthenticationFailed(_('Invalid sign.')) - if not token.user.is_active: + if not user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) + return user, 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 IsAppUser(IsValidUser, permissions.BasePermission): + """Allows access only to app user """ + + def has_permission(self, request, view): + return super(IsAppUser, self).has_permission(request, view) \ + and request.user.is_app_user + + +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 IsSuperUserOrAppUser(IsValidUser, permissions.BasePermission): + """Allows access between superuser and app user""" + + def has_permission(self, request, view): + return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ + and (request.user.is_superuser or request.user.is_app_user) if __name__ == '__main__': diff --git a/apps/users/models.py b/apps/users/models.py index ebe45afaa..c7464f4fb 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -69,6 +69,7 @@ class User(AbstractUser): ROLE_CHOICES = ( ('Admin', _('Administrator')), ('User', _('User')), + ('App', _('Application')), ) username = models.CharField(max_length=20, unique=True, verbose_name=_('Username')) @@ -148,9 +149,18 @@ class User(AbstractUser): else: self.role = 'User' + is_admin = is_superuser + + @property + def is_app_user(self): + if self.role == 'App': + return True + else: + return False + @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 @@ -185,14 +195,14 @@ class User(AbstractUser): 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: From 9960a6cd2166f91ccf823e288e9ab3493c8e4177 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 15 Oct 2016 17:14:56 +0800 Subject: [PATCH 04/12] Plan create a new app: terminal --- apps/jumpserver/settings.py | 2 +- apps/terminal/__init__.py | 0 apps/terminal/admin.py | 3 +++ apps/terminal/apps.py | 7 +++++++ apps/terminal/models.py | 5 +++++ apps/terminal/tests.py | 3 +++ apps/terminal/views.py | 3 +++ apps/users/api.py | 22 ++++++++++++++++++++++ apps/users/models.py | 11 ----------- apps/users/serializers.py | 31 ++++++++++++++++++++++--------- 10 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 apps/terminal/__init__.py create mode 100644 apps/terminal/admin.py create mode 100644 apps/terminal/apps.py create mode 100644 apps/terminal/models.py create mode 100644 apps/terminal/tests.py create mode 100644 apps/terminal/views.py diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 13029b815..1a7b8b72e 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -54,7 +54,7 @@ INSTALLED_APPS = [ 'users.apps.UsersConfig', 'assets.apps.AssetsConfig', 'perms.apps.PermsConfig', - # 'terminal.apps.TerminalConfig', + 'terminal.apps.TerminalConfig', 'ops.apps.OpsConfig', 'audits.apps.AuditsConfig', 'common.apps.CommonConfig', diff --git a/apps/terminal/__init__.py b/apps/terminal/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/terminal/admin.py b/apps/terminal/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/apps/terminal/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/terminal/apps.py b/apps/terminal/apps.py new file mode 100644 index 000000000..c81fa232b --- /dev/null +++ b/apps/terminal/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class TerminalConfig(AppConfig): + name = 'terminal' diff --git a/apps/terminal/models.py b/apps/terminal/models.py new file mode 100644 index 000000000..bd4b2abe9 --- /dev/null +++ b/apps/terminal/models.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + +from django.db import models + +# Create your models here. diff --git a/apps/terminal/tests.py b/apps/terminal/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/apps/terminal/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/terminal/views.py b/apps/terminal/views.py new file mode 100644 index 000000000..91ea44a21 --- /dev/null +++ b/apps/terminal/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/users/api.py b/apps/users/api.py index 67ce7ae4f..3017bd40d 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -21,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): @@ -109,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 diff --git a/apps/users/models.py b/apps/users/models.py index c7464f4fb..c289bacca 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -69,7 +69,6 @@ class User(AbstractUser): ROLE_CHOICES = ( ('Admin', _('Administrator')), ('User', _('User')), - ('App', _('Application')), ) username = models.CharField(max_length=20, unique=True, verbose_name=_('Username')) @@ -149,15 +148,6 @@ class User(AbstractUser): else: self.role = 'User' - is_admin = is_superuser - - @property - def is_app_user(self): - if self.role == 'App': - return True - else: - return False - @property def is_staff(self): if self.is_authenticated and self.is_valid: @@ -188,7 +178,6 @@ 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): diff --git a/apps/users/serializers.py b/apps/users/serializers.py index cfce66ab7..fa1e57b79 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -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 From 3383b2b535a9feb06da7ef19de22c1c6cb31d108 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 15 Oct 2016 18:28:49 +0800 Subject: [PATCH 05/12] Add terminal mode --- apps/audits/models.py | 6 ++++-- apps/terminal/models.py | 26 +++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/audits/models.py b/apps/audits/models.py index eb6e5baeb..731cc989b 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -9,8 +9,9 @@ from django.utils.translation import ugettext_lazy as _ class LoginLog(models.Model): LOGIN_TYPE_CHOICE = ( - ('S', 'ssh'), - ('W', 'web'), + ('W', 'Web'), + ('T', 'Terminal'), + ('WT', 'Web Terminal') ) username = models.CharField(max_length=20, verbose_name=_('Username')) @@ -19,6 +20,7 @@ class LoginLog(models.Model): 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')) + from_terminal = models.ForeignKey date_login = models.DateTimeField(auto_now=True, verbose_name=_('Date login')) date_logout = models.DateTimeField(null=True, verbose_name=_('Date logout')) diff --git a/apps/terminal/models.py b/apps/terminal/models.py index bd4b2abe9..54449cd84 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -1,5 +1,29 @@ from __future__ import unicode_literals from django.db import models +from django.utils.translation import ugettext_lazy as _ -# Create your models here. +from users.models import User + + +class Terminal(models.Model): + name = models.CharField(max_length=30, 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')) + mail_to = models.ManyToManyField(User, verbose_name=_('Mail to ')) + date_created = models.DateTimeField(auto_now=True) + comment = models.TextField(verbose_name=_('Comment')) + + class Meta: + db_table = 'terminal' + ordering = ['name'] + + +class TerminalHeatbeat(models.Model): + terminal = models.ForeignKey(Terminal, on_delete=models.SET_NULL) + date_timestamp = models.IntegerField() + + class Meta: + db_table = 'terminal_heatbeat' From 26a8bce2c36972e1aa76630aee28dba93413e704 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 15 Oct 2016 23:34:02 +0800 Subject: [PATCH 06/12] Change auto_now to auto_now_add --- apps/assets/models.py | 20 ++++++++++---------- apps/audits/models.py | 8 ++++---- apps/perms/models.py | 2 +- apps/terminal/models.py | 13 ++++++++++--- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/assets/models.py b/apps/assets/models.py index b4a40fe64..2f7daf361 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -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_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' diff --git a/apps/audits/models.py b/apps/audits/models.py index 731cc989b..2a920e55d 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -10,18 +10,18 @@ from django.utils.translation import ugettext_lazy as _ class LoginLog(models.Model): LOGIN_TYPE_CHOICE = ( ('W', 'Web'), - ('T', 'Terminal'), + ('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')) 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')) from_terminal = models.ForeignKey - date_login = models.DateTimeField(auto_now=True, verbose_name=_('Date login')) + date_login = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login')) date_logout = models.DateTimeField(null=True, verbose_name=_('Date logout')) class Meta: @@ -44,7 +44,7 @@ class ProxyLog(models.Model): 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(auto_now_add=True, verbose_name=_('Date start')) date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished')) def __unicode__(self): diff --git a/apps/perms/models.py b/apps/perms/models.py index fd4189d87..b771d086d 100644 --- a/apps/perms/models.py +++ b/apps/perms/models.py @@ -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): diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 54449cd84..48caef6f5 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -7,13 +7,20 @@ from users.models import User class Terminal(models.Model): + TYPE_CHOICES = ( + ('S', 'SSH Terminal'), + ('WT', 'Web Terminal') + ) name = models.CharField(max_length=30, 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')) - mail_to = models.ManyToManyField(User, verbose_name=_('Mail to ')) - date_created = models.DateTimeField(auto_now=True) + type = models.CharField(choices=TYPE_CHOICES, max_length=2, verbose_name=_('Terminal type')) + ssh_host = models.CharField(max_length=100, verbose_name=_('SSH host')) + ssh_port = models.IntegerField(verbose_name=_('SSH port')) + mail_to = models.ManyToManyField(User, verbose_name=_('Mail to')) + date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(verbose_name=_('Comment')) class Meta: @@ -22,7 +29,7 @@ class Terminal(models.Model): class TerminalHeatbeat(models.Model): - terminal = models.ForeignKey(Terminal, on_delete=models.SET_NULL) + terminal = models.ForeignKey(Terminal, on_delete=models.CASCADE) date_timestamp = models.IntegerField() class Meta: From 4531157c72a1138871f04d15c913126a93225314 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 16 Oct 2016 22:12:13 +0800 Subject: [PATCH 07/12] may be some wrong --- apps/{terminal => apps}/__init__.py | 0 apps/{terminal => apps}/admin.py | 0 apps/apps/api.py | 14 ++++ apps/{terminal => apps}/apps.py | 4 +- apps/{terminal => apps}/models.py | 5 +- apps/apps/serializers.py | 17 ++++ .../templates/terminal/terminal_list.html | 79 +++++++++++++++++++ apps/{terminal => apps}/tests.py | 0 apps/apps/urls.py | 18 +++++ apps/apps/views.py | 17 ++++ apps/assets/models.py | 2 +- apps/jumpserver/settings.py | 2 +- apps/jumpserver/urls.py | 1 + apps/static/js/record.js | 2 +- apps/static/js/term.js | 46 +++++------ apps/static/js/webterminal.js | 14 ++-- apps/static/js/wssh.js | 2 +- apps/templates/_nav.html | 8 +- apps/terminal/views.py | 3 - apps/users/api.py | 4 +- apps/users/templates/users/user_list.html | 14 ++-- 21 files changed, 198 insertions(+), 54 deletions(-) rename apps/{terminal => apps}/__init__.py (100%) rename apps/{terminal => apps}/admin.py (100%) create mode 100644 apps/apps/api.py rename apps/{terminal => apps}/apps.py (58%) rename apps/{terminal => apps}/models.py (87%) create mode 100644 apps/apps/serializers.py create mode 100644 apps/apps/templates/terminal/terminal_list.html rename apps/{terminal => apps}/tests.py (100%) create mode 100644 apps/apps/urls.py create mode 100644 apps/apps/views.py delete mode 100644 apps/terminal/views.py diff --git a/apps/terminal/__init__.py b/apps/apps/__init__.py similarity index 100% rename from apps/terminal/__init__.py rename to apps/apps/__init__.py diff --git a/apps/terminal/admin.py b/apps/apps/admin.py similarity index 100% rename from apps/terminal/admin.py rename to apps/apps/admin.py diff --git a/apps/apps/api.py b/apps/apps/api.py new file mode 100644 index 000000000..7232ad5ea --- /dev/null +++ b/apps/apps/api.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework.generics import ListCreateAPIView + +from .models import Terminal +from .serializers import TerminalSerializer + + +class TerminalApi(ListCreateAPIView): + queryset = Terminal.objects.all() + serializer_class = TerminalSerializer + + diff --git a/apps/terminal/apps.py b/apps/apps/apps.py similarity index 58% rename from apps/terminal/apps.py rename to apps/apps/apps.py index c81fa232b..b5d690ba9 100644 --- a/apps/terminal/apps.py +++ b/apps/apps/apps.py @@ -3,5 +3,5 @@ from __future__ import unicode_literals from django.apps import AppConfig -class TerminalConfig(AppConfig): - name = 'terminal' +class AppsConfig(AppConfig): + name = 'apps' diff --git a/apps/terminal/models.py b/apps/apps/models.py similarity index 87% rename from apps/terminal/models.py rename to apps/apps/models.py index 48caef6f5..e11b457bf 100644 --- a/apps/terminal/models.py +++ b/apps/apps/models.py @@ -17,14 +17,13 @@ class Terminal(models.Model): 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')) - ssh_host = models.CharField(max_length=100, verbose_name=_('SSH host')) - ssh_port = models.IntegerField(verbose_name=_('SSH port')) + url = models.CharField(max_length=100, verbose_name=_('URL to login')) mail_to = models.ManyToManyField(User, verbose_name=_('Mail to')) date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(verbose_name=_('Comment')) class Meta: - db_table = 'terminal' + db_table = 'apps' ordering = ['name'] diff --git a/apps/apps/serializers.py b/apps/apps/serializers.py new file mode 100644 index 000000000..8f786abcc --- /dev/null +++ b/apps/apps/serializers.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import serializers + +from .models import Terminal + + +class TerminalSerializer(serializers.ModelSerializer): + class Meta: + model = Terminal + fields = ['name', 'ip', 'type', 'url', 'comment', 'is_active', + 'get_type_display'] + + +if __name__ == '__main__': + pass diff --git a/apps/apps/templates/terminal/terminal_list.html b/apps/apps/templates/terminal/terminal_list.html new file mode 100644 index 000000000..e6f63f3b3 --- /dev/null +++ b/apps/apps/templates/terminal/terminal_list.html @@ -0,0 +1,79 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block custom_head_css_js %} +{{ block.super }} + +{% endblock %} +{% block table_search %}{% endblock %} +{% block table_container %} +{##} + + + + + + + + + + + + + + +
+
+ +
+
{% trans 'Name' %}{% trans 'IP' %}{% trans 'Type' %}{% trans 'url' %}{% trans 'Active' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/terminal/tests.py b/apps/apps/tests.py similarity index 100% rename from apps/terminal/tests.py rename to apps/apps/tests.py diff --git a/apps/apps/urls.py b/apps/apps/urls.py new file mode 100644 index 000000000..2d6afd518 --- /dev/null +++ b/apps/apps/urls.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# + +from django.conf.urls import url + +import views +import api + +app_name = 'apps' + +urlpatterns = [ + url(r'^apps$', views.TerminalListView.as_view(), name='apps-list'), +] + +urlpatterns += [ + url(r'^v1/apps/$', api.TerminalApi.as_view(), name='apps-list-create-api'), +] diff --git a/apps/apps/views.py b/apps/apps/views.py new file mode 100644 index 000000000..720232109 --- /dev/null +++ b/apps/apps/views.py @@ -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 = 'apps/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 diff --git a/apps/assets/models.py b/apps/assets/models.py index 2f7daf361..5ceca9647 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -366,7 +366,7 @@ class Asset(models.Model): class Tag(models.Model): name = models.CharField(max_length=64, unique=True, verbose_name=_('Name')) - created_time = models.DateTimeField(auto_now_add_add=True, verbose_name=_('Create time')) + 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 __unicode__(self): diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 1a7b8b72e..28c8b7820 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -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', + 'apps.apps.TerminalConfig', 'rest_framework', 'rest_framework.authtoken', 'bootstrapform', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index f4222aa49..912b3c5e3 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -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/)?apps/', include('apps.urls')), ] diff --git a/apps/static/js/record.js b/apps/static/js/record.js index ba334e0c2..ad24c4ab0 100644 --- a/apps/static/js/record.js +++ b/apps/static/js/record.js @@ -121,7 +121,7 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) { timelist = timelist.sort(function(a, b){return a-b}); totalTime = totalTime * 1000; document.getElementById("afterScrubberText").innerHTML = buildTimeString(totalTime); - term.open(document.getElementById('terminal')); + term.open(document.getElementById('apps')); timer = setInterval(advance, TICK); }) diff --git a/apps/static/js/term.js b/apps/static/js/term.js index c29cad454..45850da2e 100644 --- a/apps/static/js/term.js +++ b/apps/static/js/term.js @@ -588,7 +588,7 @@ Terminal.bindKeys = function(document) { }, true); // If we click somewhere other than a - // terminal, unfocus the terminal. + // apps, unfocus the apps. on(document, 'mousedown', function(ev) { if (!Terminal.focus) return; @@ -742,7 +742,7 @@ Terminal.insertStyle = function(document, bg, fg) { // textContent doesn't work well with IE for