• 老广's avatar
    Dev beta (#3048) · 164f48e1
    老广 authored
    * [Update] 统一url地址
    
    * [Update] 修改api
    
    * [Update] 使用规范的签名
    
    * [Update] 修改url
    
    * [Update] 修改swagger
    
    * [Update] 添加serializer class避免报错
    
    * [Update] 修改token
    
    * [Update] 支持api key
    
    * [Update] 支持生成api key
    
    * [Update] 修改api重定向
    
    * [Update] 修改翻译
    
    * [Update] 添加说明文档
    
    * [Update] 修复浏览器关闭后session不失效的问题
    
    * [Update] 修改一些内容
    
    * [Update] 修改 jms脚本
    
    * [Update] 修改重定向
    
    * [Update] 修改搜索trim
    
    * [Update] 修改搜索trim
    
    * [Update] 添加sys log
    
    * [Bugfix] 修改登陆错误
    
    * [Update] 优化User操作private_token的接口 (#3091)
    
    * [Update] 优化User操作private_token的接口
    
    * [Update] 优化User操作private_token的接口 2
    
    * [Bugfix] 解决授权了一个节点,当移动节点后,被移动的节点下的资产会放到未分组节点下的问题
    
    * [Update] 升级jquery
    
    * [Update] 默认使用page
    
    * [Update] 修改使用Orgmodel view set
    
    * [Update] 支持 nv的硬盘 https://github.com/jumpserver/jumpserver/issues/1804
    
    * [UPdate] 解决命令执行宽度问题
    
    * [Update] 优化节点
    
    * [Update] 修改nodes过多时创建比较麻烦
    
    * [Update] 修改导入
    
    * [Update] 节点获取更新
    
    * [Update] 修改nodes
    
    * [Update] nodes显示full value
    
    * [Update] 统一使用nodes select2 函数
    
    * [Update] 修改磁盘大小小数
    
    * [Update] 修改 Node service
    
    * [Update] 优化授权节点
    
    * [Update] 修改 node permission
    
    * [Update] 修改asset permission
    
    * [Stash]
    
    * [Update] 修改node assets api
    
    * [Update] 修改tree service,支持资产数量
    
    * [Update] 修改暂时完成
    
    * [Update] 修改一些bug
    Unverified
    164f48e1
auth.py 6.67 KB
# -*- coding: utf-8 -*-
#

import uuid
import time

from django.core.cache import cache
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _

from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView

from common.utils import get_logger, get_request_ip
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
from orgs.mixins.api import RootOrgViewMixin
from users.serializers import UserSerializer
from users.models import User
from assets.models import Asset, SystemUser
from audits.models import UserLoginLog as LoginLog
from users.utils import (
    check_otp_code, increase_login_failed_count,
    is_block_login, clean_failed_count
)
from .. import const
from ..utils import check_user_valid
from ..serializers import OtpVerifySerializer
from ..signals import post_auth_success, post_auth_failed

logger = get_logger(__name__)
__all__ = [
    'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
    'UserOtpVerifyApi',
]


class UserAuthApi(RootOrgViewMixin, APIView):
    permission_classes = (AllowAny,)
    serializer_class = UserSerializer

    def post(self, request):
        # limit login
        username = request.data.get('username')
        ip = request.data.get('remote_addr', None)
        ip = ip or get_request_ip(request)

        if is_block_login(username, ip):
            msg = _("Log in frequently and try again later")
            logger.warn(msg + ': ' + username + ':' + ip)
            return Response({'msg': msg}, status=401)

        user, msg = self.check_user_valid(request)
        if not user:
            username = request.data.get('username', '')
            self.send_auth_signal(success=False, username=username, reason=msg)
            increase_login_failed_count(username, ip)
            return Response({'msg': msg}, status=401)

        if not user.otp_enabled:
            self.send_auth_signal(success=True, user=user)
            # 登陆成功,清除原来的缓存计数
            clean_failed_count(username, ip)
            token, expired_at = user.create_bearer_token(request)
            return Response(
                {'token': token, 'user': self.serializer_class(user).data}
            )

        seed = uuid.uuid4().hex
        cache.set(seed, user, 300)
        return Response(
            {
                'code': 101,
                'msg': _('Please carry seed value and '
                         'conduct MFA secondary certification'),
                'otp_url': reverse('api-auth:user-otp-auth'),
                'seed': seed,
                'user': self.serializer_class(user).data
            }, status=300
        )

    @staticmethod
    def check_user_valid(request):
        username = request.data.get('username', '')
        password = request.data.get('password', '')
        public_key = request.data.get('public_key', '')
        user, msg = check_user_valid(
            username=username, password=password,
            public_key=public_key
        )
        return user, msg

    def send_auth_signal(self, success=True, user=None, username='', reason=''):
        if success:
            post_auth_success.send(sender=self.__class__, user=user, request=self.request)
        else:
            post_auth_failed.send(
                sender=self.__class__, username=username,
                request=self.request, reason=reason
            )


class UserConnectionTokenApi(RootOrgViewMixin, APIView):
    permission_classes = (IsOrgAdminOrAppUser,)

    def post(self, request):
        user_id = request.data.get('user', '')
        asset_id = request.data.get('asset', '')
        system_user_id = request.data.get('system_user', '')
        token = str(uuid.uuid4())
        user = get_object_or_404(User, id=user_id)
        asset = get_object_or_404(Asset, id=asset_id)
        system_user = get_object_or_404(SystemUser, id=system_user_id)
        value = {
            'user': user_id,
            'username': user.username,
            'asset': asset_id,
            'hostname': asset.hostname,
            'system_user': system_user_id,
            'system_user_name': system_user.name
        }
        cache.set(token, value, timeout=20)
        return Response({"token": token}, status=201)

    def get(self, request):
        token = request.query_params.get('token')
        user_only = request.query_params.get('user-only', None)
        value = cache.get(token, None)

        if not value:
            return Response('', status=404)

        if not user_only:
            return Response(value)
        else:
            return Response({'user': value['user']})

    def get_permissions(self):
        if self.request.query_params.get('user-only', None):
            self.permission_classes = (AllowAny,)
        return super().get_permissions()


class UserOtpAuthApi(RootOrgViewMixin, APIView):
    permission_classes = (AllowAny,)
    serializer_class = UserSerializer

    def post(self, request):
        otp_code = request.data.get('otp_code', '')
        seed = request.data.get('seed', '')
        user = cache.get(seed, None)
        if not user:
            return Response(
                {'msg': _('Please verify the user name and password first')},
                status=401
            )
        if not check_otp_code(user.otp_secret_key, otp_code):
            self.send_auth_signal(success=False, username=user.username, reason=const.mfa_failed)
            return Response({'msg': _('MFA certification failed')}, status=401)
        self.send_auth_signal(success=True, user=user)
        token, expired_at = user.create_bearer_token(request)
        data = {'token': token, 'user': self.serializer_class(user).data}
        return Response(data)

    def send_auth_signal(self, success=True, user=None, username='', reason=''):
        if success:
            post_auth_success.send(sender=self.__class__, user=user, request=self.request)
        else:
            post_auth_failed.send(
                sender=self.__class__, username=username,
                request=self.request, reason=reason
            )


class UserOtpVerifyApi(CreateAPIView):
    permission_classes = (IsValidUser,)
    serializer_class = OtpVerifySerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        code = serializer.validated_data["code"]

        if request.user.check_otp(code):
            request.session["MFA_VERIFY_TIME"] = int(time.time())
            return Response({"ok": "1"})
        else:
            return Response({"error": "Code not valid"}, status=400)