Fix/webapp remove code (#26436)

Co-authored-by: GareArc <chen4851@purdue.edu>
This commit is contained in:
Garfield Dai 2025-09-29 15:58:41 +08:00 committed by GitHub
parent 1277a57641
commit 5073ce6e22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 55 additions and 43 deletions

View File

@ -15,7 +15,6 @@ from libs.datetime_utils import naive_utc_now
from libs.login import current_user, login_required from libs.login import current_user, login_required
from models import Account, App, InstalledApp, RecommendedApp from models import Account, App, InstalledApp, RecommendedApp
from services.account_service import TenantService from services.account_service import TenantService
from services.app_service import AppService
from services.enterprise.enterprise_service import EnterpriseService from services.enterprise.enterprise_service import EnterpriseService
from services.feature_service import FeatureService from services.feature_service import FeatureService
@ -68,31 +67,26 @@ class InstalledAppsListApi(Resource):
# Pre-filter out apps without setting or with sso_verified # Pre-filter out apps without setting or with sso_verified
filtered_installed_apps = [] filtered_installed_apps = []
app_id_to_app_code = {}
for installed_app in installed_app_list: for installed_app in installed_app_list:
app_id = installed_app["app"].id app_id = installed_app["app"].id
webapp_setting = webapp_settings.get(app_id) webapp_setting = webapp_settings.get(app_id)
if not webapp_setting or webapp_setting.access_mode == "sso_verified": if not webapp_setting or webapp_setting.access_mode == "sso_verified":
continue continue
app_code = AppService.get_app_code_by_id(str(app_id))
app_id_to_app_code[app_id] = app_code
filtered_installed_apps.append(installed_app) filtered_installed_apps.append(installed_app)
app_codes = list(app_id_to_app_code.values())
# Batch permission check # Batch permission check
app_ids = [installed_app["app"].id for installed_app in filtered_installed_apps]
permissions = EnterpriseService.WebAppAuth.batch_is_user_allowed_to_access_webapps( permissions = EnterpriseService.WebAppAuth.batch_is_user_allowed_to_access_webapps(
user_id=user_id, user_id=user_id,
app_codes=app_codes, app_ids=app_ids,
) )
# Keep only allowed apps # Keep only allowed apps
res = [] res = []
for installed_app in filtered_installed_apps: for installed_app in filtered_installed_apps:
app_id = installed_app["app"].id app_id = installed_app["app"].id
app_code = app_id_to_app_code[app_id] if permissions.get(app_id):
if permissions.get(app_code):
res.append(installed_app) res.append(installed_app)
installed_app_list = res installed_app_list = res

View File

@ -11,7 +11,6 @@ from controllers.console.wraps import account_initialization_required
from extensions.ext_database import db from extensions.ext_database import db
from libs.login import login_required from libs.login import login_required
from models import InstalledApp from models import InstalledApp
from services.app_service import AppService
from services.enterprise.enterprise_service import EnterpriseService from services.enterprise.enterprise_service import EnterpriseService
from services.feature_service import FeatureService from services.feature_service import FeatureService
@ -57,10 +56,9 @@ def user_allowed_to_access_app(view: Callable[Concatenate[InstalledApp, P], R] |
feature = FeatureService.get_system_features() feature = FeatureService.get_system_features()
if feature.webapp_auth.enabled: if feature.webapp_auth.enabled:
app_id = installed_app.app_id app_id = installed_app.app_id
app_code = AppService.get_app_code_by_id(app_id)
res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp( res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(
user_id=str(current_user.id), user_id=str(current_user.id),
app_code=app_code, app_id=app_id,
) )
if not res: if not res:
raise AppAccessDeniedError() raise AppAccessDeniedError()

View File

@ -160,9 +160,8 @@ class AppWebAuthPermission(Resource):
args = parser.parse_args() args = parser.parse_args()
app_id = args["appId"] app_id = args["appId"]
app_code = AppService.get_app_code_by_id(app_id)
res = True res = True
if WebAppAuthService.is_app_require_permission_check(app_id=app_id): if WebAppAuthService.is_app_require_permission_check(app_id=app_id):
res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(str(user_id), app_code) res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(str(user_id), app_id)
return {"result": res} return {"result": res}

View File

@ -12,6 +12,7 @@ from controllers.web.error import WebAppAuthRequiredError
from extensions.ext_database import db from extensions.ext_database import db
from libs.passport import PassportService from libs.passport import PassportService
from models.model import App, EndUser, Site from models.model import App, EndUser, Site
from services.app_service import AppService
from services.enterprise.enterprise_service import EnterpriseService from services.enterprise.enterprise_service import EnterpriseService
from services.feature_service import FeatureService from services.feature_service import FeatureService
from services.webapp_auth_service import WebAppAuthService, WebAppAuthType from services.webapp_auth_service import WebAppAuthService, WebAppAuthType
@ -38,7 +39,7 @@ class PassportResource(Resource):
if app_code is None: if app_code is None:
raise Unauthorized("X-App-Code header is missing.") raise Unauthorized("X-App-Code header is missing.")
app_id = AppService.get_app_id_by_code(app_code)
# exchange token for enterprise logined web user # exchange token for enterprise logined web user
enterprise_user_decoded = decode_enterprise_webapp_user_id(web_app_access_token) enterprise_user_decoded = decode_enterprise_webapp_user_id(web_app_access_token)
if enterprise_user_decoded: if enterprise_user_decoded:
@ -48,7 +49,7 @@ class PassportResource(Resource):
) )
if system_features.webapp_auth.enabled: if system_features.webapp_auth.enabled:
app_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code) app_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=app_id)
if not app_settings or not app_settings.access_mode == "public": if not app_settings or not app_settings.access_mode == "public":
raise WebAppAuthRequiredError() raise WebAppAuthRequiredError()

View File

@ -13,6 +13,7 @@ from controllers.web.error import WebAppAuthAccessDeniedError, WebAppAuthRequire
from extensions.ext_database import db from extensions.ext_database import db
from libs.passport import PassportService from libs.passport import PassportService
from models.model import App, EndUser, Site from models.model import App, EndUser, Site
from services.app_service import AppService
from services.enterprise.enterprise_service import EnterpriseService, WebAppSettings from services.enterprise.enterprise_service import EnterpriseService, WebAppSettings
from services.feature_service import FeatureService from services.feature_service import FeatureService
from services.webapp_auth_service import WebAppAuthService from services.webapp_auth_service import WebAppAuthService
@ -37,7 +38,11 @@ def validate_jwt_token(view: Callable[Concatenate[App, EndUser, P], R] | None =
def decode_jwt_token(): def decode_jwt_token():
system_features = FeatureService.get_system_features() system_features = FeatureService.get_system_features()
app_code = str(request.headers.get("X-App-Code")) app_code = request.headers.get("X-App-Code")
if not app_code:
app_code = None
else:
app_code = str(app_code)
try: try:
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
if auth_header is None: if auth_header is None:
@ -51,15 +56,30 @@ def decode_jwt_token():
if auth_scheme != "bearer": if auth_scheme != "bearer":
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.") raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
# Check for invalid token values
if tk in ["undefined", "null", "None", ""]:
raise Unauthorized("Invalid token provided.")
decoded = PassportService().verify(tk) decoded = PassportService().verify(tk)
app_code = decoded.get("app_code") # Preserve app_code from header if JWT token doesn't contain one
jwt_app_code = decoded.get("app_code")
if jwt_app_code:
app_code = jwt_app_code
app_id = decoded.get("app_id") app_id = decoded.get("app_id")
# Validate required fields from JWT token
if not app_id:
raise Unauthorized("Invalid token: missing app_id.")
if not app_code:
raise Unauthorized("Invalid token: missing app_code.")
with Session(db.engine, expire_on_commit=False) as session: with Session(db.engine, expire_on_commit=False) as session:
app_model = session.scalar(select(App).where(App.id == app_id)) app_model = session.scalar(select(App).where(App.id == app_id))
site = session.scalar(select(Site).where(Site.code == app_code)) site = session.scalar(select(Site).where(Site.code == app_code))
if not app_model: if not app_model:
raise NotFound() raise NotFound()
if not app_code or not site: if not site:
raise BadRequest("Site URL is no longer valid.") raise BadRequest("Site URL is no longer valid.")
if app_model.enable_site is False: if app_model.enable_site is False:
raise BadRequest("Site is disabled.") raise BadRequest("Site is disabled.")
@ -72,7 +92,12 @@ def decode_jwt_token():
app_web_auth_enabled = False app_web_auth_enabled = False
webapp_settings = None webapp_settings = None
if system_features.webapp_auth.enabled: if system_features.webapp_auth.enabled:
webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code) if not app_code:
raise BadRequest("App code is required for webapp authentication.")
if app_code in ["undefined", "null", "None", ""]:
raise BadRequest("Invalid app code provided.")
app_id = AppService.get_app_id_by_code(app_code)
webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id)
if not webapp_settings: if not webapp_settings:
raise NotFound("Web app settings not found.") raise NotFound("Web app settings not found.")
app_web_auth_enabled = webapp_settings.access_mode != "public" app_web_auth_enabled = webapp_settings.access_mode != "public"
@ -87,8 +112,11 @@ def decode_jwt_token():
if system_features.webapp_auth.enabled: if system_features.webapp_auth.enabled:
if not app_code: if not app_code:
raise Unauthorized("Please re-login to access the web app.") raise Unauthorized("Please re-login to access the web app.")
if app_code in ["undefined", "null", "None", ""]:
raise Unauthorized("Invalid app code provided.")
app_id = AppService.get_app_id_by_code(app_code)
app_web_auth_enabled = ( app_web_auth_enabled = (
EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=str(app_code)).access_mode != "public" EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=app_id).access_mode != "public"
) )
if app_web_auth_enabled: if app_web_auth_enabled:
raise WebAppAuthRequiredError() raise WebAppAuthRequiredError()
@ -129,7 +157,10 @@ def _validate_user_accessibility(
raise WebAppAuthRequiredError("Web app settings not found.") raise WebAppAuthRequiredError("Web app settings not found.")
if WebAppAuthService.is_app_require_permission_check(access_mode=webapp_settings.access_mode): if WebAppAuthService.is_app_require_permission_check(access_mode=webapp_settings.access_mode):
if not EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(user_id, app_code=app_code): if not app_code or app_code in ["undefined", "null", "None", ""]:
raise WebAppAuthAccessDeniedError("Invalid app code for permission check.")
app_id = AppService.get_app_id_by_code(app_code)
if not EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(user_id, app_id):
raise WebAppAuthAccessDeniedError() raise WebAppAuthAccessDeniedError()
auth_type = decoded.get("auth_type") auth_type = decoded.get("auth_type")

View File

@ -46,17 +46,17 @@ class EnterpriseService:
class WebAppAuth: class WebAppAuth:
@classmethod @classmethod
def is_user_allowed_to_access_webapp(cls, user_id: str, app_code: str): def is_user_allowed_to_access_webapp(cls, user_id: str, app_id: str):
params = {"userId": user_id, "appCode": app_code} params = {"userId": user_id, "appId": app_id}
data = EnterpriseRequest.send_request("GET", "/webapp/permission", params=params) data = EnterpriseRequest.send_request("GET", "/webapp/permission", params=params)
return data.get("result", False) return data.get("result", False)
@classmethod @classmethod
def batch_is_user_allowed_to_access_webapps(cls, user_id: str, app_codes: list[str]): def batch_is_user_allowed_to_access_webapps(cls, user_id: str, app_ids: list[str]):
if not app_codes: if not app_ids:
return {} return {}
body = {"userId": user_id, "appCodes": app_codes} body = {"userId": user_id, "appIds": app_ids}
data = EnterpriseRequest.send_request("POST", "/webapp/permission/batch", json=body) data = EnterpriseRequest.send_request("POST", "/webapp/permission/batch", json=body)
if not data: if not data:
raise ValueError("No data found.") raise ValueError("No data found.")
@ -92,16 +92,6 @@ class EnterpriseService:
return ret return ret
@classmethod
def get_app_access_mode_by_code(cls, app_code: str) -> WebAppSettings:
if not app_code:
raise ValueError("app_code must be provided.")
params = {"appCode": app_code}
data = EnterpriseRequest.send_request("GET", "/webapp/access-mode/code", params=params)
if not data:
raise ValueError("No data found.")
return WebAppSettings(**data)
@classmethod @classmethod
def update_app_access_mode(cls, app_id: str, access_mode: str): def update_app_access_mode(cls, app_id: str, access_mode: str):
if not app_id: if not app_id:

View File

@ -172,7 +172,8 @@ class WebAppAuthService:
return WebAppAuthType.EXTERNAL return WebAppAuthType.EXTERNAL
if app_code: if app_code:
webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code) app_id = AppService.get_app_id_by_code(app_code)
webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id)
return cls.get_app_auth_type(access_mode=webapp_settings.access_mode) return cls.get_app_auth_type(access_mode=webapp_settings.access_mode)
raise ValueError("Could not determine app authentication type.") raise ValueError("Could not determine app authentication type.")

View File

@ -35,9 +35,7 @@ class TestWebAppAuthService:
mock_enterprise_service.WebAppAuth.get_app_access_mode_by_id.return_value = type( mock_enterprise_service.WebAppAuth.get_app_access_mode_by_id.return_value = type(
"MockWebAppAuth", (), {"access_mode": "private"} "MockWebAppAuth", (), {"access_mode": "private"}
)() )()
mock_enterprise_service.WebAppAuth.get_app_access_mode_by_code.return_value = type( # Note: get_app_access_mode_by_code method was removed in refactoring
"MockWebAppAuth", (), {"access_mode": "private"}
)()
yield { yield {
"passport_service": mock_passport_service, "passport_service": mock_passport_service,
@ -866,7 +864,7 @@ class TestWebAppAuthService:
mock_webapp_auth = type("MockWebAppAuth", (), {"access_mode": "sso_verified"})() mock_webapp_auth = type("MockWebAppAuth", (), {"access_mode": "sso_verified"})()
mock_external_service_dependencies[ mock_external_service_dependencies[
"enterprise_service" "enterprise_service"
].WebAppAuth.get_app_access_mode_by_code.return_value = mock_webapp_auth ].WebAppAuth.get_app_access_mode_by_id.return_value = mock_webapp_auth
# Act: Execute authentication type determination # Act: Execute authentication type determination
result = WebAppAuthService.get_app_auth_type(app_code="mock_app_code") result = WebAppAuthService.get_app_auth_type(app_code="mock_app_code")
@ -877,7 +875,7 @@ class TestWebAppAuthService:
# Verify mock service was called correctly # Verify mock service was called correctly
mock_external_service_dependencies[ mock_external_service_dependencies[
"enterprise_service" "enterprise_service"
].WebAppAuth.get_app_access_mode_by_code.assert_called_once_with("mock_app_code") ].WebAppAuth.get_app_access_mode_by_id.assert_called_once_with("mock_app_id")
def test_get_app_auth_type_no_parameters(self, db_session_with_containers, mock_external_service_dependencies): def test_get_app_auth_type_no_parameters(self, db_session_with_containers, mock_external_service_dependencies):
""" """