mirror of https://github.com/jumpserver/jumpserver
Finish access key auth
parent
c5ab49c515
commit
2707012325
|
@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404
|
|||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_object_or_none, signer
|
||||
from .hands import IsSuperUserOrTerminalUser, IsSuperUser
|
||||
from .hands import IsSuperUserOrAppUser, IsSuperUser
|
||||
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
|
||||
from . import serializers
|
||||
|
||||
|
@ -18,6 +18,7 @@ class AssetViewSet(IDInFilterMixin, viewsets.ModelViewSet):
|
|||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
filter_fields = ('id', 'ip', 'hostname')
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(AssetViewSet, self).get_queryset()
|
||||
|
@ -90,7 +91,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
|||
|
||||
|
||||
class SystemUserAuthApi(APIView):
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
system_user_id = request.query_params.get('system_user_id', -1)
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.permissions import IsSuperUserOrTerminalUser, IsSuperUser
|
||||
from users.permissions import IsSuperUserOrAppUser, IsSuperUser
|
||||
from users.models import User, UserGroup
|
||||
|
|
|
@ -7,7 +7,7 @@ from rest_framework import generics, viewsets
|
|||
from rest_framework.views import APIView, Response
|
||||
|
||||
from . import models, serializers
|
||||
from .hands import IsSuperUserOrTerminalUser, Terminal
|
||||
from .hands import IsSuperUserOrAppUser, Terminal
|
||||
|
||||
|
||||
class ProxyLogViewSet(viewsets.ModelViewSet):
|
||||
|
@ -32,13 +32,13 @@ class ProxyLogViewSet(viewsets.ModelViewSet):
|
|||
|
||||
queryset = models.ProxyLog.objects.all()
|
||||
serializer_class = serializers.ProxyLogSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class CommandLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = models.CommandLog.objects.all()
|
||||
serializer_class = serializers.CommandLogSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
# class CommandLogTitleApi(APIView):
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
from users.utils import AdminUserRequiredMixin
|
||||
from users.models import User
|
||||
from assets.models import Asset, SystemUser
|
||||
from users.permissions import IsSuperUserOrTerminalUser
|
||||
from users.permissions import IsSuperUserOrAppUser
|
||||
from terminal.models import Terminal
|
||||
|
|
|
@ -2,6 +2,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
"""
|
||||
兼容Python版本
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
is_py2 = (sys.version_info[0] == 2)
|
||||
is_py3 = (sys.version_info[0] == 3)
|
||||
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except (ImportError, SyntaxError):
|
||||
import json
|
||||
|
||||
|
||||
if is_py2:
|
||||
|
||||
def to_bytes(data):
|
||||
"""若输入为unicode, 则转为utf-8编码的bytes;其他则原样返回。"""
|
||||
if isinstance(data, unicode):
|
||||
return data.encode('utf-8')
|
||||
else:
|
||||
return data
|
||||
|
||||
def to_string(data):
|
||||
"""把输入转换为str对象"""
|
||||
return to_bytes(data)
|
||||
|
||||
def to_unicode(data):
|
||||
"""把输入转换为unicode,要求输入是unicode或者utf-8编码的bytes。"""
|
||||
if isinstance(data, bytes):
|
||||
return data.decode('utf-8')
|
||||
else:
|
||||
return data
|
||||
|
||||
def stringify(input):
|
||||
if isinstance(input, dict):
|
||||
return dict([(stringify(key), stringify(value)) for key,value in input.iteritems()])
|
||||
elif isinstance(input, list):
|
||||
return [stringify(element) for element in input]
|
||||
elif isinstance(input, unicode):
|
||||
return input.encode('utf-8')
|
||||
else:
|
||||
return input
|
||||
|
||||
builtin_str = str
|
||||
bytes = str
|
||||
str = unicode
|
||||
|
||||
|
||||
elif is_py3:
|
||||
|
||||
def to_bytes(data):
|
||||
"""若输入为str(即unicode),则转为utf-8编码的bytes;其他则原样返回"""
|
||||
if isinstance(data, str):
|
||||
return data.encode(encoding='utf-8')
|
||||
else:
|
||||
return data
|
||||
|
||||
def to_string(data):
|
||||
"""若输入为bytes,则认为是utf-8编码,并返回str"""
|
||||
if isinstance(data, bytes):
|
||||
return data.decode('utf-8')
|
||||
else:
|
||||
return data
|
||||
|
||||
def to_unicode(data):
|
||||
"""把输入转换为unicode,要求输入是unicode或者utf-8编码的bytes。"""
|
||||
return to_string(data)
|
||||
|
||||
def stringify(input):
|
||||
return input
|
||||
|
||||
builtin_str = str
|
||||
bytes = bytes
|
||||
str = str
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
from six import string_types
|
||||
import base64
|
||||
import os
|
||||
from itertools import chain
|
||||
import string
|
||||
import logging
|
||||
import datetime
|
||||
import paramiko
|
||||
import time
|
||||
import hashlib
|
||||
from email.utils import formatdate
|
||||
import calendar
|
||||
import threading
|
||||
|
||||
import paramiko
|
||||
import sshpubkeys
|
||||
|
@ -16,7 +21,6 @@ from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerial
|
|||
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
|
||||
|
||||
try:
|
||||
|
@ -24,6 +28,8 @@ try:
|
|||
except ImportError:
|
||||
import StringIO
|
||||
|
||||
from .compat import to_bytes, to_string
|
||||
|
||||
SECRET_KEY = settings.SECRET_KEY
|
||||
|
||||
|
||||
|
@ -255,4 +261,63 @@ def setattr_bulk(seq, key, value):
|
|||
return map(set_attr, seq)
|
||||
|
||||
|
||||
def content_md5(data):
|
||||
"""计算data的MD5值,经过Base64编码并返回str类型。
|
||||
|
||||
返回值可以直接作为HTTP Content-Type头部的值
|
||||
"""
|
||||
m = hashlib.md5(to_bytes(data))
|
||||
return to_string(base64.b64encode(m.digest()))
|
||||
|
||||
_STRPTIME_LOCK = threading.Lock()
|
||||
|
||||
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
|
||||
|
||||
|
||||
def to_unixtime(time_string, format_string):
|
||||
with _STRPTIME_LOCK:
|
||||
return int(calendar.timegm(time.strptime(time_string, format_string)))
|
||||
|
||||
|
||||
def http_date(timeval=None):
|
||||
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
|
||||
但不能使用strftime,因为strftime的结果是和locale相关的。
|
||||
"""
|
||||
return formatdate(timeval, usegmt=True)
|
||||
|
||||
|
||||
def http_to_unixtime(time_string):
|
||||
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
|
||||
|
||||
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
|
||||
"""
|
||||
return to_unixtime(time_string, _GMT_FORMAT)
|
||||
|
||||
|
||||
def iso8601_to_unixtime(time_string):
|
||||
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
|
||||
return to_unixtime(time_string, _ISO8601_FORMAT)
|
||||
|
||||
|
||||
def http_to_unixtime(time_string):
|
||||
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
|
||||
|
||||
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
|
||||
"""
|
||||
return to_unixtime(time_string, "%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
|
||||
def make_signature(access_key_secret, date=None):
|
||||
if isinstance(date, int):
|
||||
date_gmt = http_date(date)
|
||||
elif date is None:
|
||||
date_gmt = http_date(int(time.time()))
|
||||
else:
|
||||
date_gmt = date
|
||||
|
||||
data = str(access_key_secret) + "\n" + date_gmt
|
||||
return content_md5(data)
|
||||
|
||||
|
||||
signer = Signer()
|
|
@ -272,13 +272,12 @@ REST_FRAMEWORK = {
|
|||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
# or allow read-only access for unauthenticated users.
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'users.permissions.IsValidUser',
|
||||
'users.permissions.IsSuperUser',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'users.authentication.TerminalAuthentication',
|
||||
'users.authentication.AccessKeyAuthentication',
|
||||
'users.authentication.AccessTokenAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
),
|
||||
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
|
||||
|
|
|
@ -11,7 +11,7 @@ from rest_framework.permissions import AllowAny
|
|||
from common.utils import signer, get_object_or_none
|
||||
from .models import Terminal, TerminalHeatbeat
|
||||
from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
|
||||
from .hands import IsSuperUserOrTerminalUser, User
|
||||
from .hands import IsSuperUserOrAppUser, User
|
||||
|
||||
|
||||
class TerminalRegister(ListCreateAPIView):
|
||||
|
@ -62,13 +62,13 @@ class TerminalViewSet(viewsets.ModelViewSet):
|
|||
class TerminalHeatbeatApi(ListCreateAPIView):
|
||||
queryset = TerminalHeatbeat.objects.all()
|
||||
serializer_class = TerminalHeatbeatSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class TerminalHeatbeatViewSet(viewsets.ModelViewSet):
|
||||
queryset = TerminalHeatbeat.objects.all()
|
||||
serializer_class = TerminalHeatbeatSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
terminal = request.user
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
#
|
||||
|
||||
from users.models import User
|
||||
from users.permissions import IsSuperUserOrTerminalUser
|
||||
from audits.models import ProxyLog
|
||||
from users.permissions import IsSuperUserOrAppUser
|
||||
from audits.models import ProxyLog
|
|
@ -16,7 +16,7 @@ from common.utils import get_logger
|
|||
from .utils import check_user_valid, get_or_refresh_token
|
||||
from .models import User, UserGroup
|
||||
from .hands import write_login_log_async
|
||||
from .permissions import IsSuperUser, IsTerminalUser, IsValidUser, IsSuperUserOrTerminalUser
|
||||
from .permissions import IsSuperUser, IsAppUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from . import serializers
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
|
@ -12,7 +14,7 @@ from django.utils.six import text_type
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import HTTP_HEADER_ENCODING
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from common.utils import get_object_or_none, make_signature, http_to_unixtime
|
||||
from .utils import get_or_refresh_token
|
||||
from .models import User, AccessKey
|
||||
|
||||
|
@ -22,7 +24,6 @@ def get_request_date_header(request):
|
|||
if isinstance(date, text_type):
|
||||
# Work around django test client oddness
|
||||
date = date.encode(HTTP_HEADER_ENCODING)
|
||||
|
||||
return date
|
||||
|
||||
|
||||
|
@ -54,18 +55,30 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
access_key_id = sign[0]
|
||||
secret = sign[1]
|
||||
date =
|
||||
request_signature = sign[1]
|
||||
|
||||
return self.authenticate_credentials(sign)
|
||||
|
||||
def authenticate_credentials(self, access_key_id, secret, datetime):
|
||||
access_key_id = sign[0]
|
||||
secret = sign[1]
|
||||
return self.authenticate_credentials(request, access_key_id, request_signature)
|
||||
|
||||
def authenticate_credentials(self, request, access_key_id, request_signature):
|
||||
access_key = get_object_or_none(AccessKey, id=access_key_id)
|
||||
request_date = get_request_date_header(request)
|
||||
if access_key is None or not access_key.user:
|
||||
raise exceptions.AuthenticationFailed(_('Invalid signature.'))
|
||||
access_key_secret = access_key.secret
|
||||
|
||||
print(request_date)
|
||||
|
||||
try:
|
||||
request_unix_time = http_to_unixtime(request_date)
|
||||
except ValueError:
|
||||
raise exceptions.AuthenticationFailed(_('HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT'))
|
||||
|
||||
if int(time.time()) - request_unix_time > 15*60:
|
||||
raise exceptions.AuthenticationFailed(_('Expired, more than 15 minutes'))
|
||||
|
||||
signature = make_signature(access_key_secret, request_date)
|
||||
if not signature == request_signature:
|
||||
raise exceptions.AuthenticationFailed(_('Invalid signature. %s: %s' % (signature, request_signature)))
|
||||
|
||||
if not access_key.user.is_active:
|
||||
raise exceptions.AuthenticationFailed(_('User disabled.'))
|
||||
|
|
|
@ -101,8 +101,8 @@ class User(AbstractUser):
|
|||
return False
|
||||
|
||||
@property
|
||||
def is_terminal(self):
|
||||
return False
|
||||
def is_app(self):
|
||||
return self.role == 'App'
|
||||
|
||||
@is_superuser.setter
|
||||
def is_superuser(self, value):
|
||||
|
|
|
@ -23,12 +23,12 @@ class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
|||
and request.user.is_valid
|
||||
|
||||
|
||||
class IsTerminalUser(IsValidUser, permissions.BasePermission):
|
||||
class IsAppUser(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)
|
||||
return super(IsAppUser, self).has_permission(request, view) \
|
||||
and request.user.is_app()
|
||||
|
||||
|
||||
class IsSuperUser(IsValidUser, permissions.BasePermission):
|
||||
|
@ -39,12 +39,12 @@ class IsSuperUser(IsValidUser, permissions.BasePermission):
|
|||
and request.user.is_superuser
|
||||
|
||||
|
||||
class IsSuperUserOrTerminalUser(IsValidUser, permissions.BasePermission):
|
||||
class IsSuperUserOrAppUser(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)
|
||||
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
|
||||
and (request.user.is_superuser or request.user.is_app)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Loading…
Reference in New Issue