mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-08-14 12:27:09 +00:00
* [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
113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
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"]
|