feat: migrate ORM base to sqlmodel-ext, add file viewers and WOPI integration
All checks were successful
Test / test (push) Successful in 1m45s

- Migrate SQLModel base classes, mixins, and database management to
  external sqlmodel-ext package; remove sqlmodels/base/, sqlmodels/mixin/,
  and sqlmodels/database.py
- Add file viewer/editor system with WOPI protocol support for
  collaborative editing (OnlyOffice, Collabora)
- Add enterprise edition license verification module (ee/)
- Add Dockerfile multi-stage build with Cython compilation support
- Add new dependencies: sqlmodel-ext, cryptography, whatthepatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 14:23:17 +08:00
parent 53b757de7a
commit ccadfe57cd
81 changed files with 5106 additions and 4837 deletions

86
ee/license.py Normal file
View File

@@ -0,0 +1,86 @@
"""
RSA-PSS 许可证验签核心(编译为 .so 后公钥藏入二进制)
此文件只包含纯函数和常量,不包含 SQLModel 类。
"""
import base64
from datetime import datetime, timezone
import orjson
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
_PUBLIC_KEY_PEM: bytes = b"""-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyNltXQ/Nuechx3kjj3T5
oR6pZvTmpsDowqqxXJy7FXUI8d7XprhV+HrBQPsrT/Ngo9FwW3XyiK10m1WrzpGW
eaf9990Z5Z2naEn5TzGrh71p/D7mZcNGVumo9uAuhtNEemm6xB3FoyGYZj7X0cwA
VDvIiKAwYyRJX2LqVh1/tZM6tTO3oaGZXRMZzCNUPFSo4ZZudU3Boa5oQg08evu4
vaOqeFrMX47R3MSUmO9hOh+NS53XNqO0f0zw5sv95CtyR5qvJ4gpkgYaRCSQFd19
TnHU5saFVrH9jdADz1tdkMYcyYE+uJActZBapxCHSYB2tSCKWjDxeUFl/oY/ZFtY
l4MNz1ovkjNhpmR3g+I5fbvN0cxDIjnZ9vJ84ozGqTGT9s1jHaLbpLri/vhuT4F2
7kifXk8ImwtMZpZvzhmucH9/5VgcWKNuMATzEMif+YjFpuOGx8gc1XL1W/3q+dH0
EFESp+/knjcVIfwpAkIKyV7XvDgFHsif1SeI0zZMW4utowVvGocP1ZzK5BGNTk2z
CEtQDO7Rqo+UDckOJSG66VW3c2QO8o6uuy6fzx7q0MFEmUMwGf2iMVtR/KnXe99C
enOT0BpU1EQvqssErUqivDss7jm98iD8M/TCE7pFboqZ+SC9G+QAqNIQNFWh8bWA
R9hyXM/x5ysHd6MC4eEQnhMCAwEAAQ==
-----END PUBLIC KEY-----"""
class LicenseError(Exception):
"""许可证验证基础异常"""
class LicenseExpiredError(LicenseError):
"""许可证已过期"""
def verify_license(raw: str) -> dict:
"""
验证许可证字符串并返回载荷字典。
:param raw: 格式为 ``base64(json_payload).base64(signature)``
:returns: 解析后的载荷字典
:raises LicenseError: 格式无效或签名验证失败
:raises LicenseExpiredError: 许可证已过期
"""
parts = raw.strip().split(".")
if len(parts) != 2:
raise LicenseError("许可证格式无效:需要 payload.signature")
payload_b64, signature_b64 = parts
try:
payload_bytes = base64.urlsafe_b64decode(payload_b64)
signature = base64.urlsafe_b64decode(signature_b64)
except Exception as exc:
raise LicenseError(f"许可证 base64 解码失败: {exc}") from exc
public_key = serialization.load_pem_public_key(_PUBLIC_KEY_PEM)
try:
public_key.verify( # type: ignore[union-attr]
signature,
payload_bytes,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256(),
)
except Exception as exc:
raise LicenseError(f"许可证签名验证失败: {exc}") from exc
data: dict = orjson.loads(payload_bytes)
expires_at_str: str | None = data.get('expires_at')
if not expires_at_str:
raise LicenseError("许可证缺少 expires_at 字段")
expires_at = datetime.fromisoformat(expires_at_str)
if expires_at < datetime.now(timezone.utc):
raise LicenseExpiredError(
f"许可证已过期: {expires_at.isoformat()}"
)
return data