• 老广'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
signature.py 3.88 KB
from rest_framework import authentication
from rest_framework import exceptions

from httpsig import HeaderVerifier, utils

"""
Reusing failure exceptions serves several purposes:

    1. Lack of useful information regarding the failure inhibits attackers
    from learning about valid keyIDs or other forms of information leakage.
    Using the same actual object for any failure makes preventing such
    leakage through mistakenly-distinct error messages less likely.

    2. In an API scenario, the object is created once and raised many times
    rather than generated on every failure, which could lead to higher loads
    or memory usage in high-volume attack scenarios.

"""
FAILED = exceptions.AuthenticationFailed('Invalid signature.')


class SignatureAuthentication(authentication.BaseAuthentication):
    """
    DRF authentication class for HTTP Signature support.

    You must subclass this class in your own project and implement the
    `fetch_user_data(self, keyId, algorithm)` method, returning a tuple of
    the User object and a bytes object containing the user's secret. Note
    that key_id and algorithm are DIRTY as they are supplied by the client
    and so must be verified in your subclass!

    You may set the following class properties in your subclass to configure
    authentication for your particular use case:

    :param www_authenticate_realm:  Default: "api"
    :param required_headers:        Default: ["(request-target)", "date"]
    """

    www_authenticate_realm = "api"
    required_headers = ["(request-target)", "date"]

    def fetch_user_data(self, key_id, algorithm=None):
        """Retuns a tuple (User, secret) or (None, None)."""
        raise NotImplementedError()

    def authenticate_header(self, request):
        """
        DRF sends this for unauthenticated responses if we're the primary
        authenticator.
        """
        h = " ".join(self.required_headers)
        return 'Signature realm="%s",headers="%s"' % (
        self.www_authenticate_realm, h)

    def authenticate(self, request):
        """
        Perform the actual authentication.

        Note that the exception raised is always the same. This is so that we
        don't leak information about in/valid keyIds and other such useful
        things.
        """
        auth_header = authentication.get_authorization_header(request)
        if not auth_header or len(auth_header) == 0:
            return None

        method, fields = utils.parse_authorization_header(auth_header)

        # Ignore foreign Authorization headers.
        if method.lower() != 'signature':
            return None

        # Verify basic header structure.
        if len(fields) == 0:
            raise FAILED

        # Ensure all required fields were included.
        if len({"keyid", "algorithm", "signature"} - set(fields.keys())) > 0:
            raise FAILED

        # Fetch the secret associated with the keyid
        user, secret = self.fetch_user_data(
            fields["keyid"],
            algorithm=fields["algorithm"]
        )

        if not (user and secret):
            raise FAILED

        # Gather all request headers and translate them as stated in the Django docs:
        # https://docs.djangoproject.com/en/1.6/ref/request-response/#django.http.HttpRequest.META
        headers = {}
        for key in request.META.keys():
            if key.startswith("HTTP_") or \
                    key in ("CONTENT_TYPE", "CONTENT_LENGTH"):
                header = key[5:].lower().replace('_', '-')
                headers[header] = request.META[key]

        # Verify headers
        hs = HeaderVerifier(
            headers,
            secret,
            required_headers=self.required_headers,
            method=request.method.lower(),
            path=request.get_full_path()
        )

        # All of that just to get to this.
        if not hs.verify():
            raise FAILED

        return user, fields["keyid"]