Feat/webapp verified sso 260: add token exchange for public app (#20731)

This commit is contained in:
Xiyuan Chen 2025-06-06 15:49:08 +09:00 committed by GitHub
parent 26f291396d
commit e312894bc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 12 deletions

View File

@ -1,18 +1,19 @@
import uuid
from datetime import UTC, datetime, timedelta
from flask import request
from flask_restful import Resource
from werkzeug.exceptions import NotFound, Unauthorized
from configs import dify_config
from controllers.web import api
from controllers.web.error import WebAppAuthRequiredError
from extensions.ext_database import db
from flask import request
from flask_restful import Resource
from libs.passport import PassportService
from models.model import App, EndUser, Site
from services.enterprise.enterprise_service import EnterpriseService
from services.feature_service import FeatureService
from werkzeug.exceptions import NotFound, Unauthorized
from api.services.webapp_auth_service import WebAppAuthService, WebAppAuthType
class PassportResource(Resource):
@ -98,7 +99,9 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded:
user_id = enterprise_user_decoded.get("user_id")
end_user_id = enterprise_user_decoded.get("end_user_id")
session_id = enterprise_user_decoded.get("session_id")
auth_type = enterprise_user_decoded.get("auth_type")
user_auth_type = enterprise_user_decoded.get("auth_type")
if not user_auth_type:
raise Unauthorized("Missing auth_type in the token.")
site = db.session.query(Site).filter(Site.code == app_code, Site.status == "normal").first()
if not site:
@ -108,13 +111,15 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded:
if not app_model or app_model.status != "normal" or not app_model.enable_site:
raise NotFound()
if not auth_type:
raise Unauthorized("Missing auth_type in the token.")
settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code)
if settings.access_mode == "sso_verified" and auth_type != "external":
app_auth_type = WebAppAuthService.get_app_auth_type(app_code=app_code)
if app_auth_type == WebAppAuthType.PUBLIC:
return _exchange_for_public_app_token(app_model, site)
elif app_auth_type == WebAppAuthType.EXTERNAL and user_auth_type != "external":
raise WebAppAuthRequiredError("Please login as external user.")
elif settings.access_mode in ["private", "private_all"] and auth_type == "external":
elif app_auth_type == WebAppAuthType.INTERNAL and user_auth_type != "internal":
raise WebAppAuthRequiredError("Please login as internal user.")
end_user = None
if end_user_id:
end_user = db.session.query(EndUser).filter(EndUser.id == end_user_id).first()
@ -140,7 +145,6 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded:
)
db.session.add(end_user)
db.session.commit()
exp_dt = datetime.now(UTC) + timedelta(hours=dify_config.ACCESS_TOKEN_EXPIRE_MINUTES * 24)
exp = int(exp_dt.timestamp())
payload = {
@ -150,7 +154,7 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded:
"app_code": site.code,
"user_id": user_id,
"end_user_id": end_user.id,
"auth_type": auth_type,
"auth_type": user_auth_type,
"granted_at": int(datetime.now(UTC).timestamp()),
"token_source": "webapp",
"exp": exp,
@ -161,6 +165,33 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded:
}
def _exchange_for_public_app_token(app_model, site):
end_user = EndUser(
tenant_id=app_model.tenant_id,
app_id=app_model.id,
type="browser",
is_anonymous=True,
session_id=generate_session_id(),
)
db.session.add(end_user)
db.session.commit()
payload = {
"iss": site.app_id,
"sub": "Web API Passport",
"app_id": site.app_id,
"app_code": site.code,
"end_user_id": end_user.id,
}
tk = PassportService().issue(payload)
return {
"access_token": tk,
}
def generate_session_id():
"""
Generate a unique session ID.

View File

@ -1,3 +1,4 @@
import enum
import random
from datetime import UTC, datetime, timedelta
from typing import Any, Optional, cast
@ -17,6 +18,14 @@ from tasks.mail_email_code_login import send_email_code_login_mail_task
from werkzeug.exceptions import Unauthorized
class WebAppAuthType(enum.StrEnum):
"""Enum for web app authentication types."""
PUBLIC = "public"
INTERNAL = "internal"
EXTERNAL = "external"
class WebAppAuthService:
"""Service for web app authentication."""
@ -141,3 +150,25 @@ class WebAppAuthService:
if webapp_settings and webapp_settings.access_mode in modes_requiring_permission_check:
return True
return False
@classmethod
def get_app_auth_type(cls, app_code: str | None = None, access_mode: str | None = None) -> WebAppAuthType:
"""
Get the authentication type for the app based on its access mode.
"""
if not app_code and not access_mode:
raise ValueError("Either app_code or access_mode must be provided.")
if access_mode:
if access_mode == "public":
return WebAppAuthType.PUBLIC
elif access_mode in ["private", "private_all"]:
return WebAppAuthType.INTERNAL
elif access_mode == "sso_verified":
return WebAppAuthType.EXTERNAL
if app_code:
webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code)
return cls.get_app_auth_type(access_mode=webapp_settings.access_mode)
raise ValueError("Could not determine app authentication type.")