merge: integrate ZSY customer service and profile capabilities
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
from app.core.conversation_privacy import sanitize_customer_service_message
|
||||
|
||||
|
||||
def test_customer_service_message_hides_sensitive_credentials_before_storage() -> None:
|
||||
"""敏感值不得进入客服会话持久化和后续异步处理链。"""
|
||||
message = (
|
||||
"登录密码: Secret123;验证码 123456;身份证 11010519491231002X;"
|
||||
"银行卡 6222021234567890123;手机号 13812345678"
|
||||
)
|
||||
|
||||
sanitized = sanitize_customer_service_message(message)
|
||||
|
||||
assert "Secret123" not in sanitized
|
||||
assert "123456" not in sanitized
|
||||
assert "11010519491231002X" not in sanitized
|
||||
assert "6222021234567890123" not in sanitized
|
||||
assert "13812345678" not in sanitized
|
||||
assert "登录密码" in sanitized
|
||||
assert "验证码" in sanitized
|
||||
|
||||
|
||||
def test_customer_service_message_keeps_ordinary_password_question_unchanged() -> None:
|
||||
"""普通业务咨询不应被误判为用户实际提交的密码。"""
|
||||
message = "忘记登录密码怎么办?"
|
||||
|
||||
assert sanitize_customer_service_message(message) == message
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
|
||||
from app.core.config import Settings
|
||||
from app.core.errors import UnauthorizedAgentError
|
||||
from app.core.security import JwtAuthenticator
|
||||
from app.core.security import JwtAuthenticator, VisitorTokenIssuer
|
||||
|
||||
# 开发专用密钥目录(tools/generate_jwt_keys.py 生成)。本测试需要真实密钥完成签发与验签,
|
||||
# 所以路径只在这里定义一次:换密钥目录时改这一处,避免多处硬编码各自漂移。
|
||||
@@ -43,6 +43,35 @@ def test_authenticate_valid_token() -> None:
|
||||
assert context.trace_id
|
||||
|
||||
|
||||
def test_authenticate_visitor_token_returns_limited_anonymous_context() -> None:
|
||||
now = datetime.now(UTC)
|
||||
private_key = Path("config/jwt/jwt-private.pem").read_text(encoding="utf-8")
|
||||
token = jwt.encode(
|
||||
{"sub": "2", "iss": "jr-auth", "aud": "jr-agent-platform", "iat": now,
|
||||
"nbf": now, "exp": now + timedelta(minutes=5), "jti": "visitor-jti-1",
|
||||
"visitor": True},
|
||||
private_key,
|
||||
algorithm="RS256",
|
||||
)
|
||||
|
||||
context = JwtAuthenticator(_settings()).authenticate(token)
|
||||
|
||||
assert context.roles == ("visitor",)
|
||||
assert context.permissions == ("agent:run", "knowledge:query")
|
||||
assert context.customer_ids == ()
|
||||
assert context.data_scope == "public"
|
||||
|
||||
|
||||
def test_visitor_token_issuer_creates_short_lived_limited_token() -> None:
|
||||
token, expires_at = VisitorTokenIssuer(_settings()).issue()
|
||||
|
||||
context = JwtAuthenticator(_settings()).authenticate(token)
|
||||
|
||||
assert context.roles == ("visitor",)
|
||||
assert context.permissions == ("agent:run", "knowledge:query")
|
||||
assert expires_at > datetime.now(UTC)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("subject", ["abc", "", "0", "-1", "1.5", "12", "1" * 21,
|
||||
"18446744073709551616"])
|
||||
def test_invalid_numeric_subject_is_unauthorized(subject):
|
||||
@@ -66,6 +95,37 @@ def test_signed_invalid_subject_returns_401_before_identity_query(monkeypatch):
|
||||
resolve.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_visitor_token_skips_identity_database_resolution(monkeypatch):
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.api.dependencies.auth import _authenticator, build_request_context
|
||||
|
||||
now = datetime.now(UTC)
|
||||
private_key = Path("config/jwt/jwt-private.pem").read_text(encoding="utf-8")
|
||||
token = jwt.encode(
|
||||
{"sub": "2", "iss": "jr-local", "aud": "jr-agent-platform", "iat": now,
|
||||
"nbf": now, "exp": now + timedelta(minutes=5), "jti": "visitor-jti-2",
|
||||
"visitor": True},
|
||||
private_key,
|
||||
algorithm="RS256",
|
||||
)
|
||||
resolve = AsyncMock()
|
||||
monkeypatch.setattr("app.service.identity_service.IdentityService.resolve", resolve)
|
||||
_authenticator.cache_clear()
|
||||
request = Request({"type": "http", "method": "GET", "path": "/", "headers": []})
|
||||
|
||||
context = await build_request_context(
|
||||
request, HTTPAuthorizationCredentials(scheme="Bearer", credentials=token)
|
||||
)
|
||||
|
||||
assert context.roles == ("visitor",)
|
||||
resolve.assert_not_awaited()
|
||||
|
||||
|
||||
def test_authenticate_rejects_expired_token() -> None:
|
||||
with pytest.raises(UnauthorizedAgentError):
|
||||
now = datetime.now(UTC)
|
||||
|
||||
Reference in New Issue
Block a user