+18


![dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)

![autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)




FFXN
GitHub
yyh
盐粒 Yanli
autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Tianle
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Yunlu Wen
zyssyz123
Claude Opus 4.7
chariri
Asuka Minato
Copilot Autofix powered by AI
Nian
非法操作
Carmen Fernández Ruiz
wangxiaolei
QuantumGhost
L1nSn0w
Evan
Escape0707
Jingyi
Amr Sherif
ZHOU ZHICHEN
unknown
JzoNg
Xiyuan Chen
-LAN-
107bba0116
Signed-off-by: dependabot[bot] <[email protected]> Signed-off-by: EvanYao826 <[email protected]> Co-authored-by: yyh <[email protected]> Co-authored-by: 盐粒 Yanli <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Tianle <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yunlu Wen <[email protected]> Co-authored-by: zyssyz123 <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]> Co-authored-by: chariri <[email protected]> Co-authored-by: Asuka Minato <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> Co-authored-by: Nian <[email protected]> Co-authored-by: 非法操作 <[email protected]> Co-authored-by: Carmen Fernández Ruiz <[email protected]> Co-authored-by: wangxiaolei <[email protected]> Co-authored-by: QuantumGhost <[email protected]> Co-authored-by: L1nSn0w <[email protected]> Co-authored-by: Evan <[email protected]> Co-authored-by: Escape0707 <[email protected]> Co-authored-by: Jingyi <[email protected]> Co-authored-by: Amr Sherif <[email protected]> Co-authored-by: ZHOU ZHICHEN <[email protected]> Co-authored-by: unknown <[email protected]> Co-authored-by: JzoNg <[email protected]> Co-authored-by: Xiyuan Chen <[email protected]> Co-authored-by: -LAN- <[email protected]>
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""GET /openapi/v1/permitted-external-apps — external-subject app discovery (EE only).
|
|
|
|
`dfoe_` (External SSO) callers reach apps gated by ACL access-mode
|
|
(public / sso_verified). License-gated: CE deploys never enable the
|
|
EE blueprint chain so this module is unreachable there.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from flask import request
|
|
from flask_restx import Resource
|
|
from pydantic import ValidationError
|
|
from werkzeug.exceptions import UnprocessableEntity
|
|
|
|
from controllers.openapi import openapi_ns
|
|
from controllers.openapi._models import (
|
|
AppListRow,
|
|
PermittedExternalAppsListQuery,
|
|
PermittedExternalAppsListResponse,
|
|
)
|
|
from controllers.openapi.auth.composition import auth_router
|
|
from controllers.openapi.auth.data import AuthData, Edition
|
|
from extensions.ext_database import db
|
|
from libs.oauth_bearer import Scope, TokenType
|
|
from models import App
|
|
from services.account_service import TenantService
|
|
from services.app_service import AppService
|
|
from services.enterprise.app_permitted_service import list_permitted_apps
|
|
|
|
|
|
@openapi_ns.route("/permitted-external-apps")
|
|
class PermittedExternalAppsListApi(Resource):
|
|
@openapi_ns.response(
|
|
200, "Permitted external apps list", openapi_ns.models[PermittedExternalAppsListResponse.__name__]
|
|
)
|
|
@auth_router.guard(
|
|
scope=Scope.APPS_READ_PERMITTED_EXTERNAL,
|
|
allowed_token_types=frozenset({TokenType.OAUTH_EXTERNAL_SSO}),
|
|
edition=frozenset({Edition.EE}),
|
|
)
|
|
def get(self, *, auth_data: AuthData):
|
|
try:
|
|
query = PermittedExternalAppsListQuery.model_validate(request.args.to_dict(flat=True))
|
|
except ValidationError as exc:
|
|
raise UnprocessableEntity(exc.json())
|
|
|
|
page_result = list_permitted_apps(
|
|
page=query.page,
|
|
limit=query.limit,
|
|
mode=query.mode.value if query.mode else None,
|
|
name=query.name,
|
|
)
|
|
|
|
if not page_result.app_ids:
|
|
env = PermittedExternalAppsListResponse(
|
|
page=query.page, limit=query.limit, total=page_result.total, has_more=False, data=[]
|
|
)
|
|
return env.model_dump(mode="json"), 200
|
|
|
|
apps_by_id: dict[str, App] = {
|
|
str(a.id): a for a in AppService.find_visible_apps_by_ids(db.session, page_result.app_ids)
|
|
}
|
|
tenant_ids = list({str(a.tenant_id) for a in apps_by_id.values()})
|
|
tenants_by_id = {str(t.id): t for t in TenantService.get_tenants_by_ids(db.session, tenant_ids)}
|
|
|
|
items: list[AppListRow] = []
|
|
for app_id in page_result.app_ids:
|
|
app = apps_by_id.get(app_id)
|
|
if not app or app.status != "normal":
|
|
continue
|
|
tenant = tenants_by_id.get(str(app.tenant_id))
|
|
items.append(
|
|
AppListRow(
|
|
id=str(app.id),
|
|
name=app.name,
|
|
description=app.description,
|
|
mode=app.mode,
|
|
tags=[], # tenant-scoped; not surfaced cross-tenant
|
|
updated_at=app.updated_at.isoformat() if app.updated_at else None,
|
|
created_by_name=None, # cross-tenant author leak prevention
|
|
workspace_id=str(app.tenant_id),
|
|
workspace_name=tenant.name if tenant else None,
|
|
)
|
|
)
|
|
env = PermittedExternalAppsListResponse(
|
|
page=query.page,
|
|
limit=query.limit,
|
|
total=page_result.total,
|
|
has_more=query.page * query.limit < page_result.total,
|
|
data=items,
|
|
)
|
|
return env.model_dump(mode="json"), 200
|