Finish access key auth

pull/530/head
ibuler 2016-12-25 13:15:28 +08:00
parent c5ab49c515
commit 2707012325
13 changed files with 190 additions and 37 deletions

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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',),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.'))

View File

@ -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):

View File

@ -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__':