Finish token access api

pull/530/head
ibuler 2016-10-31 18:58:23 +08:00
parent 92251f2a45
commit 315af35296
7 changed files with 110 additions and 36 deletions

View File

@ -267,10 +267,11 @@ REST_FRAMEWORK = {
'users.backends.IsValidUser',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'users.backends.TerminalAuthentication',
'users.backends.AccessTokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'users.backends.TerminalAuthentication',
),
}
# This setting is required to override the Django's main loop, when running in

View File

@ -1,14 +1,20 @@
# ~*~ coding: utf-8 ~*~
#
from django.shortcuts import get_object_or_404
import base64
from django.shortcuts import get_object_or_404
from django.core.cache import cache
from django.conf import settings
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework import authentication
from common.mixins import BulkDeleteApiMixin
from common.utils import get_logger
from .utils import check_user_valid, token_gen
from .models import User, UserGroup
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
@ -113,21 +119,26 @@ class DeleteUserFromGroupApi(generics.DestroyAPIView):
instance.users.remove(user)
class AppUserRegisterApi(generics.CreateAPIView):
"""App send a post request to register a app user
class UserTokenApi(APIView):
permission_classes = ()
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
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.
def post(self, request, *args, **kwargs):
username = request.data.get('username', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
remote_addr = request.META.get('REMOTE_ADDR', '')
remote_addr = base64.b64encode(remote_addr).replace('=', '')
user = check_user_valid(username=username, password=password, public_key=public_key)
if user:
token = cache.get('%s_%s' % (user.id, remote_addr))
if not token:
token = token_gen(user)
cache.set(token, user.id, self.expiration)
cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration)
return Response({'token': token})
else:
return Response({'msg': 'Invalid password or public key or user is not active or expired'})
Save fields:
username:
name: name + request.ip
email: username + '@app.org'
role: App
"""
pass

View File

@ -1,13 +1,17 @@
# -*- coding: utf-8 -*-
#
import base64
from django.core.cache import cache
from django.conf import settings
from django.utils.translation import ugettext as _
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
from .models import User
class TerminalAuthentication(authentication.BaseAuthentication):
@ -47,6 +51,47 @@ class TerminalAuthentication(authentication.BaseAuthentication):
return terminal, None
class AccessTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Token'
model = User
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
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 token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Sign string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = auth[1].decode()
except UnicodeError:
msg = _('Invalid token header. Sign string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(token, request)
def authenticate_credentials(self, token, request):
user_id = cache.get(token)
user = get_object_or_none(User, id=user_id)
if not user:
msg = _('Invalid token')
raise exceptions.AuthenticationFailed(msg)
remote_addr = request.META.get('REMOTE_ADDR', '')
remote_addr = base64.b16encode(remote_addr).replace('=', '')
cache.set(token, user_id, self.expiration)
cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration)
return user, None
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired"""

View File

@ -193,6 +193,11 @@ class User(AbstractUser):
return True
return False
def check_public_key(self, public_key):
if self.public_key == public_key:
return True
return False
def generate_reset_token(self):
return signing.dumps({'reset': self.id, 'email': self.email})

View File

@ -36,6 +36,7 @@ urlpatterns = [
urlpatterns += [
url(r'^v1/users/$', api.UserListUpdateApi.as_view(), name='user-bulk-update-api'),
url(r'^v1/users/token$', api.UserTokenApi.as_view(), name='user-token-api'),
url(r'^v1/users/(?P<pk>\d+)/$', api.UserDetailApi.as_view(), name='user-patch-api'),
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import logging
import os
import re
import uuid
from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin
@ -206,18 +207,20 @@ def validate_ssh_pk(text):
return startState([n.strip() for n in text.splitlines()])
def check_user_is_valid(**kwargs):
def check_user_valid(**kwargs):
password = kwargs.pop('password', None)
public_key = kwargs.pop('public_key', None)
user = get_object_or_none(User, **kwargs)
if password and not user.check_password(password):
user = None
if public_key and not user.public_key == public_key:
user = None
if user and user.is_valid:
if user is None or not user.is_valid:
return None
if password and user.check_password(password):
return user
if public_key and user.public_key == public_key:
return user
return None
def token_gen(*args, **kwargs):
return uuid.uuid4().get_hex()

View File

@ -10,6 +10,7 @@
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_DIR = os.path.join(BASE_DIR, 'logs')
class Config:
@ -23,7 +24,6 @@ class Config:
# It's used to identify your site, When we send a create mail to user, we only know login url is /login/
# But we should know the absolute url like: http://jms.jumpserver.org/login/, so SITE_URL is
# HTTP_PROTOCOL://HOST[:PORT]
# Todo: May be use :method: get_current_site more grace, bug restful api unknown ok or not
SITE_URL = 'http://localhost'
# Django security setting, if your disable debug model, you should setting that
@ -53,7 +53,7 @@ class Config:
# When Django start it will bind this host and port
# ./manage.py runserver 127.0.0.1:8080
# Todo: Gunicorn or uwsgi run may be use it
HTTP_LISTEN_HOST = '0.0.0.0'
HTTP_BIND_HOST = '127.0.0.1'
HTTP_LISTEN_PORT = 8080
# Use Redis as broker for celery and web socket
@ -61,6 +61,9 @@ class Config:
REDIS_PORT = 6379
# REDIS_PASSWORD = ''
# Api token expiration when create
TOKEN_EXPIRATION = 3600
# Email SMTP setting, we only support smtp send mail
# EMAIL_HOST = 'smtp.qq.com'
# EMAIL_PORT = 25
@ -70,14 +73,10 @@ class Config:
# EMAIL_USE_TLS = False # If port is 587, set True
# EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
# SSH use password or public key for auth
SSH_PASSWORD_AUTH = False
SSH_PUBLIC_KEY_AUTH = True
def __init__(self):
pass
def __getattr__(self, item):
def __getattr__(self, key):
return None
@ -86,6 +85,14 @@ class DevelopmentConfig(Config):
DISPLAY_PER_PAGE = 20
DB_ENGINE = 'sqlite'
DB_NAME = os.path.join(BASE_DIR, 'db.sqlite3')
EMAIL_HOST = 'smtp.exmail.qq.com'
EMAIL_PORT = 465
EMAIL_HOST_USER = 'ask@jumpserver.org'
EMAIL_HOST_PASSWORD = 'xfDf4x1n'
EMAIL_USE_SSL = True # If port is 465, set True
EMAIL_USE_TLS = False # If port is 587, set True
EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
SITE_URL = 'http://localhost:8080'
class ProductionConfig(Config):
@ -106,3 +113,4 @@ config = {
}
env = 'development'