mirror of
https://github.com/langgenius/dify.git
synced 2025-07-03 23:28:18 +00:00
230 lines
7.7 KiB
Python
230 lines
7.7 KiB
Python
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 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 services.webapp_auth_service import WebAppAuthService, WebAppAuthType
|
|
|
|
|
|
class PassportResource(Resource):
|
|
"""Base resource for passport."""
|
|
|
|
def get(self):
|
|
system_features = FeatureService.get_system_features()
|
|
app_code = request.headers.get("X-App-Code")
|
|
user_id = request.args.get("user_id")
|
|
web_app_access_token = request.args.get("web_app_access_token")
|
|
|
|
if app_code is None:
|
|
raise Unauthorized("X-App-Code header is missing.")
|
|
|
|
# exchange token for enterprise logined web user
|
|
enterprise_user_decoded = decode_enterprise_webapp_user_id(web_app_access_token)
|
|
if enterprise_user_decoded:
|
|
# a web user has already logged in, exchange a token for this app without redirecting to the login page
|
|
return exchange_token_for_existing_web_user(
|
|
app_code=app_code, enterprise_user_decoded=enterprise_user_decoded
|
|
)
|
|
|
|
if system_features.webapp_auth.enabled:
|
|
app_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code)
|
|
if not app_settings or not app_settings.access_mode == "public":
|
|
raise WebAppAuthRequiredError()
|
|
|
|
# get site from db and check if it is normal
|
|
site = db.session.query(Site).filter(Site.code == app_code, Site.status == "normal").first()
|
|
if not site:
|
|
raise NotFound()
|
|
# get app from db and check if it is normal and enable_site
|
|
app_model = db.session.query(App).filter(App.id == site.app_id).first()
|
|
if not app_model or app_model.status != "normal" or not app_model.enable_site:
|
|
raise NotFound()
|
|
|
|
if user_id:
|
|
end_user = (
|
|
db.session.query(EndUser).filter(EndUser.app_id == app_model.id, EndUser.session_id == user_id).first()
|
|
)
|
|
|
|
if end_user:
|
|
pass
|
|
else:
|
|
end_user = EndUser(
|
|
tenant_id=app_model.tenant_id,
|
|
app_id=app_model.id,
|
|
type="browser",
|
|
is_anonymous=True,
|
|
session_id=user_id,
|
|
)
|
|
db.session.add(end_user)
|
|
db.session.commit()
|
|
else:
|
|
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": app_code,
|
|
"end_user_id": end_user.id,
|
|
}
|
|
|
|
tk = PassportService().issue(payload)
|
|
|
|
return {
|
|
"access_token": tk,
|
|
}
|
|
|
|
|
|
api.add_resource(PassportResource, "/passport")
|
|
|
|
|
|
def decode_enterprise_webapp_user_id(jwt_token: str | None):
|
|
"""
|
|
Decode the enterprise user session from the Authorization header.
|
|
"""
|
|
if not jwt_token:
|
|
return None
|
|
|
|
decoded = PassportService().verify(jwt_token)
|
|
source = decoded.get("token_source")
|
|
if not source or source != "webapp_login_token":
|
|
raise Unauthorized("Invalid token source. Expected 'webapp_login_token'.")
|
|
return decoded
|
|
|
|
|
|
def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded: dict):
|
|
"""
|
|
Exchange a token for an existing web user session.
|
|
"""
|
|
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")
|
|
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:
|
|
raise NotFound()
|
|
|
|
app_model = db.session.query(App).filter(App.id == site.app_id).first()
|
|
if not app_model or app_model.status != "normal" or not app_model.enable_site:
|
|
raise NotFound()
|
|
|
|
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, enterprise_user_decoded)
|
|
elif app_auth_type == WebAppAuthType.EXTERNAL and user_auth_type != "external":
|
|
raise WebAppAuthRequiredError("Please login as external user.")
|
|
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()
|
|
if session_id:
|
|
end_user = (
|
|
db.session.query(EndUser)
|
|
.filter(
|
|
EndUser.session_id == session_id,
|
|
EndUser.tenant_id == app_model.tenant_id,
|
|
EndUser.app_id == app_model.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not end_user:
|
|
if not session_id:
|
|
raise NotFound("Missing session_id for existing web user.")
|
|
end_user = EndUser(
|
|
tenant_id=app_model.tenant_id,
|
|
app_id=app_model.id,
|
|
type="browser",
|
|
is_anonymous=True,
|
|
session_id=session_id,
|
|
)
|
|
db.session.add(end_user)
|
|
db.session.commit()
|
|
exp_dt = datetime.now(UTC) + timedelta(minutes=dify_config.ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
exp = int(exp_dt.timestamp())
|
|
payload = {
|
|
"iss": site.id,
|
|
"sub": "Web API Passport",
|
|
"app_id": site.app_id,
|
|
"app_code": site.code,
|
|
"user_id": user_id,
|
|
"end_user_id": end_user.id,
|
|
"auth_type": user_auth_type,
|
|
"granted_at": int(datetime.now(UTC).timestamp()),
|
|
"token_source": "webapp",
|
|
"exp": exp,
|
|
}
|
|
token: str = PassportService().issue(payload)
|
|
return {
|
|
"access_token": token,
|
|
}
|
|
|
|
|
|
def _exchange_for_public_app_token(app_model, site, token_decoded):
|
|
user_id = token_decoded.get("user_id")
|
|
end_user = None
|
|
if user_id:
|
|
end_user = (
|
|
db.session.query(EndUser).filter(EndUser.app_id == app_model.id, EndUser.session_id == user_id).first()
|
|
)
|
|
|
|
if not end_user:
|
|
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.
|
|
"""
|
|
while True:
|
|
session_id = str(uuid.uuid4())
|
|
existing_count = db.session.query(EndUser).filter(EndUser.session_id == session_id).count()
|
|
if existing_count == 0:
|
|
return session_id
|