mirror of https://github.com/jumpserver/jumpserver
Finish token access api
parent
92251f2a45
commit
315af35296
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"""
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
Loading…
Reference in New Issue