mirror of
https://github.com/langgenius/dify.git
synced 2026-05-21 09:17:27 +08:00
Ports service_api/app/{completion,workflow}.py to bearer-authed
/openapi/v1/apps/<app_id>/{info,chat-messages,completion-messages,workflows/run}.
Architecture:
- New controllers/openapi/auth/ package: Pipeline + Step protocol over
one mutable Context. Endpoints attach via @APP_PIPELINE.guard(scope=...)
— single attachment point; forgetting auth is structurally impossible.
- Pipeline order: BearerCheck -> ScopeCheck -> AppResolver -> AppAuthzCheck
-> CallerMount.
- Strategies vary along independent axes: AclStrategy (EE webapp-auth inner
API) vs MembershipStrategy (CE TenantAccountJoin); AccountMounter vs
EndUserMounter dispatched by SubjectType.
- App is in URL path (not header). Each non-GET has typed Pydantic Request;
each non-SSE response has typed Pydantic Response. Bearer-as-identity:
body 'user' field stripped, ignored if present.
Adds InvokeFrom.OPENAPI enum variant. Emits app.run.openapi audit log
on successful invocation via standard logger extra={"audit": True, ...}
convention.
47 lines
1.2 KiB
Python
47 lines
1.2 KiB
Python
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from flask import Flask
|
|
from flask_restx import Api
|
|
|
|
|
|
def _client():
|
|
from controllers.openapi import app_info # noqa: F401
|
|
from controllers.openapi import openapi_ns
|
|
|
|
app = Flask(__name__)
|
|
api = Api(app)
|
|
api.add_namespace(openapi_ns, path="/openapi/v1")
|
|
return app.test_client()
|
|
|
|
|
|
def test_app_info_returns_response_model(bypass_pipeline):
|
|
app_obj = SimpleNamespace(
|
|
id="app1",
|
|
name="X",
|
|
description="d",
|
|
mode="chat",
|
|
author_name="alice",
|
|
tags=[SimpleNamespace(name="prod")],
|
|
)
|
|
with patch("controllers.openapi.app_info._unpack_app", return_value=app_obj):
|
|
r = _client().get("/openapi/v1/apps/app1/info")
|
|
assert r.status_code == 200
|
|
body = r.get_json()
|
|
assert body == {
|
|
"id": "app1",
|
|
"name": "X",
|
|
"description": "d",
|
|
"mode": "chat",
|
|
"author_name": "alice",
|
|
"tags": ["prod"],
|
|
}
|
|
|
|
|
|
def test_app_info_response_model_validates():
|
|
from controllers.openapi.app_info import AppInfoResponse
|
|
|
|
m = AppInfoResponse(id="x", name="N", mode="chat")
|
|
assert m.tags == []
|
|
assert m.description is None
|