Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/tests/module_identity/security/test_protocol_exception_handler.py
T
insistence a89aec54aa feat: 新增 OAuth 2.0 / OIDC 统一认证中心 (#129)
* feat: 新增统一认证中心

* perf: 优化代码

* fix: 修复问题

* perf: 优化页面

* fix: 修复部分接口缺失Request参数的问题

* perf: 优化访问控制机制

* perf: 优化代码

* fix: 修复 Logout Token 缺少 exp

* perf&fix: 优化和修复

* perf: 优化代码

* perf: 优化代码

* fix: 修复测试问题
2026-10-01 22:56:05 +08:00

89 lines
3.2 KiB
Python

from fastapi import FastAPI, status
from fastapi.testclient import TestClient
from exceptions.exception import OAuthProtocolException, OidcInteractionException
from exceptions.handle import handle_exception
def _client() -> TestClient:
app = FastAPI()
handle_exception(app)
@app.get('/invalid-client')
async def invalid_client() -> None:
raise OAuthProtocolException(
'invalid_client',
'Client authentication failed',
status.HTTP_401_UNAUTHORIZED,
headers={'Cache-Control': 'public, max-age=3600', 'Pragma': 'cache'},
)
@app.get('/authorize-error')
async def authorize_error() -> None:
raise OAuthProtocolException(
'access_denied',
redirect_uri=(
'https://client.example/callback?tenant=one&tenant=two&error=old&error_description=old'
'&error_uri=https%3A%2F%2Fevil.example&code=old&state=old&iss=https%3A%2F%2Fevil.example'
),
state='opaque-state',
redirect_uri_verified=True,
issuer='https://auth.example.com',
)
@app.get('/invalid-redirect')
async def invalid_redirect() -> None:
raise OAuthProtocolException(
'access_denied',
redirect_uri='https://client.example/callback#fragment',
redirect_uri_verified=True,
)
@app.get('/interaction')
async def interaction() -> None:
raise OidcInteractionException('interaction-1', 'Interaction expired')
return TestClient(app)
def test_invalid_client_uses_standard_oauth_json() -> None:
response = _client().get('/invalid-client')
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert response.json()['error'] == 'invalid_client'
assert response.headers['cache-control'] == 'no-store'
assert response.headers['pragma'] == 'no-cache'
assert response.headers['www-authenticate'] == 'Basic realm="oauth2/token"'
assert 'success' not in response.json()
def test_verified_redirect_replaces_protocol_parameters_and_preserves_application_query() -> None:
response = _client().get('/authorize-error', follow_redirects=False)
assert response.status_code == status.HTTP_303_SEE_OTHER
assert response.headers['location'] == (
'https://client.example/callback?tenant=one&tenant=two&error=access_denied&state=opaque-state'
'&iss=https%3A%2F%2Fauth.example.com'
)
assert response.headers['cache-control'] == 'no-store'
assert response.headers['pragma'] == 'no-cache'
def test_invalid_validated_redirect_fails_closed_locally() -> None:
response = _client().get('/invalid-redirect', follow_redirects=False)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert 'location' not in response.headers
assert response.json()['error'] == 'server_error'
def test_interaction_exception_keeps_business_response_boundary() -> None:
response = _client().get('/interaction')
assert response.status_code == status.HTTP_200_OK
assert response.json()['code'] is not None
assert response.json()['data'] == 'interaction-1'
assert 'error' not in response.json()
assert response.headers['cache-control'] == 'no-store'
assert response.headers['pragma'] == 'no-cache'