- 新增图像生成接口,支持试用、积分和自定义API Key模式 - 实现生成图片结果异步上传至MinIO存储,带重试机制 - 优化积分预扣除和异常退还逻辑,保障用户积分准确 - 添加获取生成历史记录接口,支持时间范围和分页 - 提供本地字典配置接口,支持模型、比例、提示模板和尺寸 - 实现图片批量上传接口,支持S3兼容对象存储 feat(admin): 增加管理员角色管理与权限分配接口 - 实现角色列表查询、角色创建、更新及删除功能 - 增加权限列表查询接口 - 实现用户角色分配接口,便于统一管理用户权限 - 增加系统字典增删查改接口,支持分类过滤和排序 - 权限控制全面覆盖管理接口,保证安全访问 feat(auth): 完善用户登录注册及权限相关接口与页面 - 实现手机号验证码发送及校验功能,保障注册安全 - 支持手机号注册、登录及退出接口,集成日志记录 - 增加修改密码功能,验证原密码后更新 - 提供动态导航菜单接口,基于权限展示不同菜单 - 实现管理界面路由及日志、角色、字典管理页面访问权限控制 - 添加系统日志查询接口,支持关键词和等级筛选 feat(app): 初始化Flask应用并配置蓝图与数据库 - 创建应用程序工厂,加载配置,初始化数据库和Redis客户端 - 注册认证、API及管理员蓝图,整合路由 - 根路由渲染主页模板 - 应用上下文中自动创建数据库表,保证运行环境准备完毕 feat(database): 提供数据库创建与迁移支持脚本 - 新增数据库创建脚本,支持自动检测是否已存在 - 添加数据库表初始化脚本,支持创建和删除所有表 - 实现RBAC权限初始化,包含基础权限和角色创建 - 新增字段手动修复脚本,添加用户API Key和积分字段 - 强制迁移脚本支持清理连接和修复表结构,初始化默认数据及角色分配 feat(config): 新增系统配置参数 - 配置数据库、Redis、Session和MinIO相关参数 - 添加AI接口地址及试用Key配置 - 集成阿里云短信服务配置及开发模式相关参数 feat(extensions): 初始化数据库、Redis和MinIO客户端 - 创建全局SQLAlchemy数据库实例和Redis客户端 - 配置基于boto3的MinIO兼容S3客户端 chore(logs): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
407 lines
15 KiB
Python
407 lines
15 KiB
Python
from __future__ import annotations
|
|
|
|
import collections.abc as cabc
|
|
import json
|
|
import typing as t
|
|
|
|
from .encoding import want_bytes
|
|
from .exc import BadPayload
|
|
from .exc import BadSignature
|
|
from .signer import _make_keys_list
|
|
from .signer import Signer
|
|
|
|
if t.TYPE_CHECKING:
|
|
import typing_extensions as te
|
|
|
|
# This should be either be str or bytes. To avoid having to specify the
|
|
# bound type, it falls back to a union if structural matching fails.
|
|
_TSerialized = te.TypeVar(
|
|
"_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes]
|
|
)
|
|
else:
|
|
# Still available at runtime on Python < 3.13, but without the default.
|
|
_TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes])
|
|
|
|
|
|
class _PDataSerializer(t.Protocol[_TSerialized]):
|
|
def loads(self, payload: _TSerialized, /) -> t.Any: ...
|
|
# A signature with additional arguments is not handled correctly by type
|
|
# checkers right now, so an overload is used below for serializers that
|
|
# don't match this strict protocol.
|
|
def dumps(self, obj: t.Any, /) -> _TSerialized: ...
|
|
|
|
|
|
# Use TypeIs once it's available in typing_extensions or 3.13.
|
|
def is_text_serializer(
|
|
serializer: _PDataSerializer[t.Any],
|
|
) -> te.TypeGuard[_PDataSerializer[str]]:
|
|
"""Checks whether a serializer generates text or binary."""
|
|
return isinstance(serializer.dumps({}), str)
|
|
|
|
|
|
class Serializer(t.Generic[_TSerialized]):
|
|
"""A serializer wraps a :class:`~itsdangerous.signer.Signer` to
|
|
enable serializing and securely signing data other than bytes. It
|
|
can unsign to verify that the data hasn't been changed.
|
|
|
|
The serializer provides :meth:`dumps` and :meth:`loads`, similar to
|
|
:mod:`json`, and by default uses :mod:`json` internally to serialize
|
|
the data to bytes.
|
|
|
|
The secret key should be a random string of ``bytes`` and should not
|
|
be saved to code or version control. Different salts should be used
|
|
to distinguish signing in different contexts. See :doc:`/concepts`
|
|
for information about the security of the secret key and salt.
|
|
|
|
:param secret_key: The secret key to sign and verify with. Can be a
|
|
list of keys, oldest to newest, to support key rotation.
|
|
:param salt: Extra key to combine with ``secret_key`` to distinguish
|
|
signatures in different contexts.
|
|
:param serializer: An object that provides ``dumps`` and ``loads``
|
|
methods for serializing data to a string. Defaults to
|
|
:attr:`default_serializer`, which defaults to :mod:`json`.
|
|
:param serializer_kwargs: Keyword arguments to pass when calling
|
|
``serializer.dumps``.
|
|
:param signer: A ``Signer`` class to instantiate when signing data.
|
|
Defaults to :attr:`default_signer`, which defaults to
|
|
:class:`~itsdangerous.signer.Signer`.
|
|
:param signer_kwargs: Keyword arguments to pass when instantiating
|
|
the ``Signer`` class.
|
|
:param fallback_signers: List of signer parameters to try when
|
|
unsigning with the default signer fails. Each item can be a dict
|
|
of ``signer_kwargs``, a ``Signer`` class, or a tuple of
|
|
``(signer, signer_kwargs)``. Defaults to
|
|
:attr:`default_fallback_signers`.
|
|
|
|
.. versionchanged:: 2.0
|
|
Added support for key rotation by passing a list to
|
|
``secret_key``.
|
|
|
|
.. versionchanged:: 2.0
|
|
Removed the default SHA-512 fallback signer from
|
|
``default_fallback_signers``.
|
|
|
|
.. versionchanged:: 1.1
|
|
Added support for ``fallback_signers`` and configured a default
|
|
SHA-512 fallback. This fallback is for users who used the yanked
|
|
1.0.0 release which defaulted to SHA-512.
|
|
|
|
.. versionchanged:: 0.14
|
|
The ``signer`` and ``signer_kwargs`` parameters were added to
|
|
the constructor.
|
|
"""
|
|
|
|
#: The default serialization module to use to serialize data to a
|
|
#: string internally. The default is :mod:`json`, but can be changed
|
|
#: to any object that provides ``dumps`` and ``loads`` methods.
|
|
default_serializer: _PDataSerializer[t.Any] = json
|
|
|
|
#: The default ``Signer`` class to instantiate when signing data.
|
|
#: The default is :class:`itsdangerous.signer.Signer`.
|
|
default_signer: type[Signer] = Signer
|
|
|
|
#: The default fallback signers to try when unsigning fails.
|
|
default_fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
] = []
|
|
|
|
# Serializer[str] if no data serializer is provided, or if it returns str.
|
|
@t.overload
|
|
def __init__(
|
|
self: Serializer[str],
|
|
secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
|
|
salt: str | bytes | None = b"itsdangerous",
|
|
serializer: None | _PDataSerializer[str] = None,
|
|
serializer_kwargs: dict[str, t.Any] | None = None,
|
|
signer: type[Signer] | None = None,
|
|
signer_kwargs: dict[str, t.Any] | None = None,
|
|
fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
]
|
|
| None = None,
|
|
): ...
|
|
|
|
# Serializer[bytes] with a bytes data serializer positional argument.
|
|
@t.overload
|
|
def __init__(
|
|
self: Serializer[bytes],
|
|
secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
|
|
salt: str | bytes | None,
|
|
serializer: _PDataSerializer[bytes],
|
|
serializer_kwargs: dict[str, t.Any] | None = None,
|
|
signer: type[Signer] | None = None,
|
|
signer_kwargs: dict[str, t.Any] | None = None,
|
|
fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
]
|
|
| None = None,
|
|
): ...
|
|
|
|
# Serializer[bytes] with a bytes data serializer keyword argument.
|
|
@t.overload
|
|
def __init__(
|
|
self: Serializer[bytes],
|
|
secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
|
|
salt: str | bytes | None = b"itsdangerous",
|
|
*,
|
|
serializer: _PDataSerializer[bytes],
|
|
serializer_kwargs: dict[str, t.Any] | None = None,
|
|
signer: type[Signer] | None = None,
|
|
signer_kwargs: dict[str, t.Any] | None = None,
|
|
fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
]
|
|
| None = None,
|
|
): ...
|
|
|
|
# Fall back with a positional argument. If the strict signature of
|
|
# _PDataSerializer doesn't match, fall back to a union, requiring the user
|
|
# to specify the type.
|
|
@t.overload
|
|
def __init__(
|
|
self,
|
|
secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
|
|
salt: str | bytes | None,
|
|
serializer: t.Any,
|
|
serializer_kwargs: dict[str, t.Any] | None = None,
|
|
signer: type[Signer] | None = None,
|
|
signer_kwargs: dict[str, t.Any] | None = None,
|
|
fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
]
|
|
| None = None,
|
|
): ...
|
|
|
|
# Fall back with a keyword argument.
|
|
@t.overload
|
|
def __init__(
|
|
self,
|
|
secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
|
|
salt: str | bytes | None = b"itsdangerous",
|
|
*,
|
|
serializer: t.Any,
|
|
serializer_kwargs: dict[str, t.Any] | None = None,
|
|
signer: type[Signer] | None = None,
|
|
signer_kwargs: dict[str, t.Any] | None = None,
|
|
fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
]
|
|
| None = None,
|
|
): ...
|
|
|
|
def __init__(
|
|
self,
|
|
secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
|
|
salt: str | bytes | None = b"itsdangerous",
|
|
serializer: t.Any | None = None,
|
|
serializer_kwargs: dict[str, t.Any] | None = None,
|
|
signer: type[Signer] | None = None,
|
|
signer_kwargs: dict[str, t.Any] | None = None,
|
|
fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
]
|
|
| None = None,
|
|
):
|
|
#: The list of secret keys to try for verifying signatures, from
|
|
#: oldest to newest. The newest (last) key is used for signing.
|
|
#:
|
|
#: This allows a key rotation system to keep a list of allowed
|
|
#: keys and remove expired ones.
|
|
self.secret_keys: list[bytes] = _make_keys_list(secret_key)
|
|
|
|
if salt is not None:
|
|
salt = want_bytes(salt)
|
|
# if salt is None then the signer's default is used
|
|
|
|
self.salt = salt
|
|
|
|
if serializer is None:
|
|
serializer = self.default_serializer
|
|
|
|
self.serializer: _PDataSerializer[_TSerialized] = serializer
|
|
self.is_text_serializer: bool = is_text_serializer(serializer)
|
|
|
|
if signer is None:
|
|
signer = self.default_signer
|
|
|
|
self.signer: type[Signer] = signer
|
|
self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {}
|
|
|
|
if fallback_signers is None:
|
|
fallback_signers = list(self.default_fallback_signers)
|
|
|
|
self.fallback_signers: list[
|
|
dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
|
|
] = fallback_signers
|
|
self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {}
|
|
|
|
@property
|
|
def secret_key(self) -> bytes:
|
|
"""The newest (last) entry in the :attr:`secret_keys` list. This
|
|
is for compatibility from before key rotation support was added.
|
|
"""
|
|
return self.secret_keys[-1]
|
|
|
|
def load_payload(
|
|
self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None
|
|
) -> t.Any:
|
|
"""Loads the encoded object. This function raises
|
|
:class:`.BadPayload` if the payload is not valid. The
|
|
``serializer`` parameter can be used to override the serializer
|
|
stored on the class. The encoded ``payload`` should always be
|
|
bytes.
|
|
"""
|
|
if serializer is None:
|
|
use_serializer = self.serializer
|
|
is_text = self.is_text_serializer
|
|
else:
|
|
use_serializer = serializer
|
|
is_text = is_text_serializer(serializer)
|
|
|
|
try:
|
|
if is_text:
|
|
return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type]
|
|
|
|
return use_serializer.loads(payload) # type: ignore[arg-type]
|
|
except Exception as e:
|
|
raise BadPayload(
|
|
"Could not load the payload because an exception"
|
|
" occurred on unserializing the data.",
|
|
original_error=e,
|
|
) from e
|
|
|
|
def dump_payload(self, obj: t.Any) -> bytes:
|
|
"""Dumps the encoded object. The return value is always bytes.
|
|
If the internal serializer returns text, the value will be
|
|
encoded as UTF-8.
|
|
"""
|
|
return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
|
|
|
|
def make_signer(self, salt: str | bytes | None = None) -> Signer:
|
|
"""Creates a new instance of the signer to be used. The default
|
|
implementation uses the :class:`.Signer` base class.
|
|
"""
|
|
if salt is None:
|
|
salt = self.salt
|
|
|
|
return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs)
|
|
|
|
def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]:
|
|
"""Iterates over all signers to be tried for unsigning. Starts
|
|
with the configured signer, then constructs each signer
|
|
specified in ``fallback_signers``.
|
|
"""
|
|
if salt is None:
|
|
salt = self.salt
|
|
|
|
yield self.make_signer(salt)
|
|
|
|
for fallback in self.fallback_signers:
|
|
if isinstance(fallback, dict):
|
|
kwargs = fallback
|
|
fallback = self.signer
|
|
elif isinstance(fallback, tuple):
|
|
fallback, kwargs = fallback
|
|
else:
|
|
kwargs = self.signer_kwargs
|
|
|
|
for secret_key in self.secret_keys:
|
|
yield fallback(secret_key, salt=salt, **kwargs)
|
|
|
|
def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized:
|
|
"""Returns a signed string serialized with the internal
|
|
serializer. The return value can be either a byte or unicode
|
|
string depending on the format of the internal serializer.
|
|
"""
|
|
payload = want_bytes(self.dump_payload(obj))
|
|
rv = self.make_signer(salt).sign(payload)
|
|
|
|
if self.is_text_serializer:
|
|
return rv.decode("utf-8") # type: ignore[return-value]
|
|
|
|
return rv # type: ignore[return-value]
|
|
|
|
def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None:
|
|
"""Like :meth:`dumps` but dumps into a file. The file handle has
|
|
to be compatible with what the internal serializer expects.
|
|
"""
|
|
f.write(self.dumps(obj, salt))
|
|
|
|
def loads(
|
|
self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any
|
|
) -> t.Any:
|
|
"""Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
|
|
signature validation fails.
|
|
"""
|
|
s = want_bytes(s)
|
|
last_exception = None
|
|
|
|
for signer in self.iter_unsigners(salt):
|
|
try:
|
|
return self.load_payload(signer.unsign(s))
|
|
except BadSignature as err:
|
|
last_exception = err
|
|
|
|
raise t.cast(BadSignature, last_exception)
|
|
|
|
def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any:
|
|
"""Like :meth:`loads` but loads from a file."""
|
|
return self.loads(f.read(), salt)
|
|
|
|
def loads_unsafe(
|
|
self, s: str | bytes, salt: str | bytes | None = None
|
|
) -> tuple[bool, t.Any]:
|
|
"""Like :meth:`loads` but without verifying the signature. This
|
|
is potentially very dangerous to use depending on how your
|
|
serializer works. The return value is ``(signature_valid,
|
|
payload)`` instead of just the payload. The first item will be a
|
|
boolean that indicates if the signature is valid. This function
|
|
never fails.
|
|
|
|
Use it for debugging only and if you know that your serializer
|
|
module is not exploitable (for example, do not use it with a
|
|
pickle serializer).
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
return self._loads_unsafe_impl(s, salt)
|
|
|
|
def _loads_unsafe_impl(
|
|
self,
|
|
s: str | bytes,
|
|
salt: str | bytes | None,
|
|
load_kwargs: dict[str, t.Any] | None = None,
|
|
load_payload_kwargs: dict[str, t.Any] | None = None,
|
|
) -> tuple[bool, t.Any]:
|
|
"""Low level helper function to implement :meth:`loads_unsafe`
|
|
in serializer subclasses.
|
|
"""
|
|
if load_kwargs is None:
|
|
load_kwargs = {}
|
|
|
|
try:
|
|
return True, self.loads(s, salt=salt, **load_kwargs)
|
|
except BadSignature as e:
|
|
if e.payload is None:
|
|
return False, None
|
|
|
|
if load_payload_kwargs is None:
|
|
load_payload_kwargs = {}
|
|
|
|
try:
|
|
return (
|
|
False,
|
|
self.load_payload(e.payload, **load_payload_kwargs),
|
|
)
|
|
except BadPayload:
|
|
return False, None
|
|
|
|
def load_unsafe(
|
|
self, f: t.IO[t.Any], salt: str | bytes | None = None
|
|
) -> tuple[bool, t.Any]:
|
|
"""Like :meth:`loads_unsafe` but loads from a file.
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
return self.loads_unsafe(f.read(), salt=salt)
|