fix(HEL-494): 网站市场客户端改为纯中枢 Facade,并迁移 iFinD 凭据到中枢
生产 gateway 不再读取 Tushare token 或实例化 TushareProvider/TushareClient;问财凭据经带鉴权的中枢接口加密入库,避免发版后 iFinD 未配置。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
100752f43c
commit
8a7d1f3698
@@ -14,6 +14,8 @@ services:
|
||||
DATAHUB_TOKEN: "${DATAHUB_TOKEN:?DATAHUB_TOKEN must be set}"
|
||||
DATAHUB_ADMIN_PASSWORD: "${DATAHUB_ADMIN_PASSWORD:?DATAHUB_ADMIN_PASSWORD must be set}"
|
||||
TUSHARE_TOKEN: "${TUSHARE_TOKEN:-}"
|
||||
IFIND_REFRESH_TOKEN: "${IFIND_REFRESH_TOKEN:-}"
|
||||
IFIND_ACCESS_TOKEN: "${IFIND_ACCESS_TOKEN:-}"
|
||||
DATAHUB_DB_PATH: /app/data/datahub.db
|
||||
DATAHUB_BACKUP_DIR: /app/data/backups
|
||||
TZ: Asia/Shanghai
|
||||
|
||||
@@ -76,6 +76,11 @@ class HubRequestHandler(BaseHTTPRequestHandler):
|
||||
payload = self.hub.api.query_api(body)
|
||||
self._json(payload, HTTPStatus.OK)
|
||||
return
|
||||
if path == "/v1/credentials/ifind" and method == "POST":
|
||||
body = self._read_json()
|
||||
payload = self.hub.put_ifind_credentials(body)
|
||||
self._json(payload, HTTPStatus.OK)
|
||||
return
|
||||
payload = self.hub.api.handle(path, parse_query(query))
|
||||
self._json(payload, HTTPStatus.OK)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from datahub.adapters.ifind import IfindAdapter
|
||||
from datahub.adapters.tushare import TushareAdapter
|
||||
@@ -52,6 +53,17 @@ class Hub:
|
||||
self.admin = AdminAPI(self.db, self.pipeline, self.scheduler, self.auth, ifind=self.ifind)
|
||||
self.static_dir = Path(__file__).resolve().parents[1] / "admin"
|
||||
|
||||
def put_ifind_credentials(self, body: dict[str, Any] | None) -> dict[str, Any]:
|
||||
from datahub.serving import envelope
|
||||
|
||||
payload = dict(body or {})
|
||||
refresh = str(payload.get("ifind_refresh_token") or "").strip()
|
||||
access = str(payload.get("ifind_access_token") or "").strip()
|
||||
self.auth.store_credential("ifind_refresh_token", refresh)
|
||||
self.auth.store_credential("ifind_access_token", access)
|
||||
self.ifind.set_credentials(refresh, access)
|
||||
return envelope(self.ifind.status(), {"source": "ifind"})
|
||||
|
||||
def start(self) -> None:
|
||||
if self.settings.scheduler_enabled:
|
||||
self.scheduler.start()
|
||||
|
||||
@@ -61,6 +61,18 @@ class ApiContractTests(unittest.TestCase):
|
||||
self.hub.stop()
|
||||
self.tmp.cleanup()
|
||||
|
||||
def _post(self, path: str, body: dict, token: str | None = None) -> tuple[int, dict]:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if token is not None:
|
||||
headers["X-Datahub-Token"] = token
|
||||
raw = json.dumps(body).encode("utf-8")
|
||||
req = Request(self.base + path, data=raw, headers=headers, method="POST")
|
||||
try:
|
||||
with urlopen(req, timeout=5) as resp:
|
||||
return resp.status, json.loads(resp.read().decode())
|
||||
except HTTPError as exc:
|
||||
return exc.code, json.loads(exc.read().decode())
|
||||
|
||||
def _get(self, path: str, token: str | None = None) -> tuple[int, dict]:
|
||||
headers = {}
|
||||
if token is not None:
|
||||
@@ -145,6 +157,28 @@ class ApiContractTests(unittest.TestCase):
|
||||
self.assertNotIn("tushare-secret-token-xyz", blob)
|
||||
self.assertNotIn(self.token, blob)
|
||||
|
||||
def test_ifind_credentials_require_token_and_update_adapter(self) -> None:
|
||||
status, body = self._post(
|
||||
"/v1/credentials/ifind",
|
||||
{"ifind_refresh_token": "refresh-secret", "ifind_access_token": "access-secret"},
|
||||
token=None,
|
||||
)
|
||||
self.assertEqual(status, 401)
|
||||
self.assertEqual(body["error"]["code"], "UNAUTHORIZED")
|
||||
status, body = self._post(
|
||||
"/v1/credentials/ifind",
|
||||
{"ifind_refresh_token": "refresh-secret", "ifind_access_token": "access-secret"},
|
||||
token=self.token,
|
||||
)
|
||||
self.assertEqual(status, 200, body)
|
||||
self.assertTrue(body["data"]["configured"])
|
||||
self.assertTrue(body["data"]["access_ready"])
|
||||
self.assertTrue(self.hub.ifind.configured)
|
||||
self.assertEqual(self.hub.auth.load_credential("ifind_refresh_token"), "refresh-secret")
|
||||
blob = json.dumps(body)
|
||||
self.assertNotIn("refresh-secret", blob)
|
||||
self.assertNotIn("access-secret", blob)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user