mirror of
https://github.com/langgenius/dify.git
synced 2026-08-29 03:45:08 +08:00
refactor(api): reuse web app access queries for permissions (#40984)
This commit is contained in:
+18
-23
@@ -7,33 +7,29 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
from werkzeug.exceptions import Unauthorized
|
||||
|
||||
from constants import HEADER_NAME_APP_CODE
|
||||
from controllers.common import fields
|
||||
from controllers.common.errors import InvalidArgumentError
|
||||
from controllers.common.fields import AccessModeResponse, Parameters
|
||||
from controllers.common.fields import AccessModeResponse, BooleanResultResponse, Parameters
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||
from controllers.web import web_ns
|
||||
from controllers.web.error import (
|
||||
AgentNotPublishedError,
|
||||
AppUnavailableError,
|
||||
WebAppAccessServiceUnavailableError,
|
||||
WebAppAuthRequiredError,
|
||||
WebAppNotFoundError,
|
||||
)
|
||||
from controllers.web.wraps import WebApiResource
|
||||
from extensions.ext_application_services import application_services
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import dump_response
|
||||
from libs.passport import PassportService
|
||||
from libs.token import extract_webapp_passport
|
||||
from models.model import App, EndUser
|
||||
from services.app_definition_query_service import AppDefinitionNotPublishedError, AppDefinitionUnavailableError
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
from services.feature_service import FeatureService
|
||||
from services.webapp_access_query_service import (
|
||||
WebAppAccessAppNotFoundError,
|
||||
WebAppAccessReferenceRequiredError,
|
||||
WebAppAccessUnavailableError,
|
||||
)
|
||||
from services.webapp_auth_service import WebAppAuthService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -64,7 +60,7 @@ register_response_schema_models(
|
||||
Parameters,
|
||||
AppMetaResponse,
|
||||
AccessModeResponse,
|
||||
fields.BooleanResultResponse,
|
||||
BooleanResultResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -165,21 +161,23 @@ class AppWebAuthPermission(Resource):
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
500: "Internal Server Error",
|
||||
503: "Web App Access Service Unavailable",
|
||||
}
|
||||
)
|
||||
@web_ns.response(200, "Success", web_ns.models[fields.BooleanResultResponse.__name__])
|
||||
@web_ns.response(200, "Success", web_ns.models[BooleanResultResponse.__name__])
|
||||
def get(self):
|
||||
user_id = "visitor"
|
||||
app_code = request.headers.get(HEADER_NAME_APP_CODE)
|
||||
app_id = request.args.get("appId")
|
||||
if not app_id or not app_code:
|
||||
raise ValueError("appId must be provided")
|
||||
|
||||
require_permission_check = WebAppAuthService.is_app_require_permission_check(
|
||||
app_id=app_id, session=db.session()
|
||||
)
|
||||
if not require_permission_check:
|
||||
return {"result": True}
|
||||
webapp_access = application_services().webapp_access
|
||||
try:
|
||||
requires_permission_check = webapp_access.requires_permission_check(app_id)
|
||||
except WebAppAccessUnavailableError:
|
||||
raise WebAppAccessServiceUnavailableError() from None
|
||||
if not requires_permission_check:
|
||||
return dump_response(BooleanResultResponse, {"result": True})
|
||||
|
||||
try:
|
||||
tk = extract_webapp_passport(app_code, request)
|
||||
@@ -188,16 +186,13 @@ class AppWebAuthPermission(Resource):
|
||||
decoded = PassportService().verify(tk)
|
||||
user_id = decoded.get("user_id", "visitor")
|
||||
except Unauthorized:
|
||||
raise
|
||||
raise WebAppAuthRequiredError() from None
|
||||
except Exception:
|
||||
logger.exception("Unexpected error during auth verification")
|
||||
raise
|
||||
|
||||
features = FeatureService.get_system_features()
|
||||
if not features.webapp_auth.enabled:
|
||||
return {"result": True}
|
||||
|
||||
res = True
|
||||
if WebAppAuthService.is_app_require_permission_check(app_id=app_id, session=db.session()):
|
||||
res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(str(user_id), app_id)
|
||||
return {"result": res}
|
||||
try:
|
||||
is_allowed = webapp_access.is_user_allowed(user_id=str(user_id), app_id=app_id)
|
||||
except WebAppAccessUnavailableError:
|
||||
raise WebAppAccessServiceUnavailableError() from None
|
||||
return dump_response(BooleanResultResponse, {"result": is_allowed})
|
||||
|
||||
@@ -69,6 +69,13 @@ def _get_enterprise_webapp_access_mode(app_id: str) -> WebAppAccessMode:
|
||||
raise WebAppAccessUnavailableError from e
|
||||
|
||||
|
||||
def _is_user_allowed_to_access_webapp(user_id: str, app_id: str) -> bool:
|
||||
try:
|
||||
return EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(user_id, app_id)
|
||||
except (EnterpriseServiceError, httpx.RequestError, json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
raise WebAppAccessUnavailableError from e
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ApplicationServices:
|
||||
account_activation: AccountActivationService
|
||||
@@ -120,6 +127,7 @@ def build_application_services(
|
||||
access=WebAppAccessQueryRepository(session_factory=database_client),
|
||||
webapp_auth_enabled=FeatureService.is_webapp_auth_enabled(),
|
||||
access_mode_for_app=_get_enterprise_webapp_access_mode,
|
||||
is_user_allowed_for_app=_is_user_allowed_to_access_webapp,
|
||||
),
|
||||
explore_banner_queries=ExploreBannerQueryService(
|
||||
banners=ExploreBannerQueryRepository(client=database_client),
|
||||
|
||||
@@ -847,6 +847,7 @@ Check if user has permission to access a web application.
|
||||
| 400 | Bad Request | |
|
||||
| 401 | Unauthorized | |
|
||||
| 500 | Internal Server Error | |
|
||||
| 503 | Web App Access Service Unavailable | |
|
||||
|
||||
### [POST] /workflows/run
|
||||
**Run workflow**
|
||||
|
||||
@@ -5,6 +5,8 @@ from typing import Protocol
|
||||
|
||||
from enums import WebAppAccessMode
|
||||
|
||||
_PERMISSION_CHECK_MODES = frozenset({WebAppAccessMode.PRIVATE, WebAppAccessMode.PRIVATE_ALL})
|
||||
|
||||
|
||||
class WebAppAccessQuery(Protocol):
|
||||
def find_app_id_by_code(self, app_code: str) -> str | None: ...
|
||||
@@ -29,10 +31,12 @@ class WebAppAccessQueryService:
|
||||
access: WebAppAccessQuery,
|
||||
webapp_auth_enabled: bool,
|
||||
access_mode_for_app: Callable[[str], WebAppAccessMode],
|
||||
is_user_allowed_for_app: Callable[[str, str], bool],
|
||||
) -> None:
|
||||
self._access = access
|
||||
self._webapp_auth_enabled = webapp_auth_enabled
|
||||
self._access_mode_for_app = access_mode_for_app
|
||||
self._is_user_allowed_for_app = is_user_allowed_for_app
|
||||
|
||||
def get_access_mode(self, *, app_id: str | None, app_code: str | None) -> WebAppAccessMode:
|
||||
if not self._webapp_auth_enabled:
|
||||
@@ -47,3 +51,12 @@ class WebAppAccessQueryService:
|
||||
raise WebAppAccessReferenceRequiredError("appId or appCode must be provided")
|
||||
|
||||
return self._access_mode_for_app(app_id)
|
||||
|
||||
def requires_permission_check(self, app_id: str) -> bool:
|
||||
return self._access_mode_for_app(app_id) in _PERMISSION_CHECK_MODES
|
||||
|
||||
def is_user_allowed(self, *, user_id: str, app_id: str) -> bool:
|
||||
if not self._webapp_auth_enabled:
|
||||
return True
|
||||
|
||||
return self._is_user_allowed_for_app(user_id, app_id)
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from werkzeug.exceptions import Unauthorized
|
||||
|
||||
from controllers.common.errors import InvalidArgumentError
|
||||
from controllers.web.app import AppAccessMode, AppMeta, AppParameterApi, AppWebAuthPermission
|
||||
@@ -14,6 +15,7 @@ from controllers.web.error import (
|
||||
AgentNotPublishedError,
|
||||
AppUnavailableError,
|
||||
WebAppAccessServiceUnavailableError,
|
||||
WebAppAuthRequiredError,
|
||||
WebAppNotFoundError,
|
||||
)
|
||||
from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict
|
||||
@@ -194,14 +196,151 @@ class TestAppAccessMode:
|
||||
# AppWebAuthPermission
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestAppWebAuthPermission:
|
||||
@patch("controllers.web.app.WebAppAuthService.is_app_require_permission_check", return_value=False)
|
||||
def test_returns_true_when_no_permission_check_required(self, mock_check: MagicMock, app: Flask) -> None:
|
||||
with app.test_request_context("/webapp/permission?appId=app-1", headers={"X-App-Code": "code1"}):
|
||||
@patch("controllers.web.app.application_services")
|
||||
def test_returns_true_without_reading_passport_when_no_permission_check_required(
|
||||
self, application_services: MagicMock, app: Flask
|
||||
) -> None:
|
||||
webapp_access = MagicMock()
|
||||
webapp_access.requires_permission_check.return_value = False
|
||||
application_services.return_value = SimpleNamespace(webapp_access=webapp_access)
|
||||
|
||||
with (
|
||||
app.test_request_context("/webapp/permission?appId=app-1", headers={"X-App-Code": "code1"}),
|
||||
patch("controllers.web.app.extract_webapp_passport") as extract_passport,
|
||||
):
|
||||
result = AppWebAuthPermission().get()
|
||||
|
||||
assert result == {"result": True}
|
||||
webapp_access.requires_permission_check.assert_called_once_with("app-1")
|
||||
webapp_access.is_user_allowed.assert_not_called()
|
||||
extract_passport.assert_not_called()
|
||||
|
||||
def test_raises_when_missing_app_id(self, app: Flask) -> None:
|
||||
with app.test_request_context("/webapp/permission", headers={"X-App-Code": "code1"}):
|
||||
@pytest.mark.parametrize(
|
||||
("decoded", "expected_user_id", "allowed"),
|
||||
[
|
||||
pytest.param({"user_id": "user-1"}, "user-1", True, id="identified-user"),
|
||||
pytest.param({}, "visitor", False, id="visitor-fallback"),
|
||||
],
|
||||
)
|
||||
@patch("controllers.web.app.application_services")
|
||||
def test_checks_private_app_permission(
|
||||
self,
|
||||
application_services: MagicMock,
|
||||
decoded: dict[str, str],
|
||||
expected_user_id: str,
|
||||
allowed: bool,
|
||||
app: Flask,
|
||||
) -> None:
|
||||
webapp_access = MagicMock()
|
||||
webapp_access.requires_permission_check.return_value = True
|
||||
webapp_access.is_user_allowed.return_value = allowed
|
||||
application_services.return_value = SimpleNamespace(webapp_access=webapp_access)
|
||||
|
||||
with (
|
||||
app.test_request_context("/webapp/permission?appId=app-1", headers={"X-App-Code": "code1"}),
|
||||
patch("controllers.web.app.extract_webapp_passport", return_value="passport") as extract_passport,
|
||||
patch("controllers.web.app.PassportService") as passport_service,
|
||||
):
|
||||
passport_service.return_value.verify.return_value = decoded
|
||||
result = AppWebAuthPermission().get()
|
||||
|
||||
assert result == {"result": allowed}
|
||||
webapp_access.requires_permission_check.assert_called_once_with("app-1")
|
||||
extract_passport.assert_called_once()
|
||||
passport_service.return_value.verify.assert_called_once_with("passport")
|
||||
webapp_access.is_user_allowed.assert_called_once_with(user_id=expected_user_id, app_id="app-1")
|
||||
|
||||
@pytest.mark.parametrize("failing_method", ["requires_permission_check", "is_user_allowed"])
|
||||
@patch("controllers.web.app.application_services")
|
||||
def test_maps_access_dependency_failure_to_service_unavailable(
|
||||
self, application_services: MagicMock, failing_method: str, app: Flask
|
||||
) -> None:
|
||||
webapp_access = MagicMock()
|
||||
webapp_access.requires_permission_check.return_value = True
|
||||
if failing_method == "requires_permission_check":
|
||||
webapp_access.requires_permission_check.side_effect = WebAppAccessUnavailableError()
|
||||
else:
|
||||
webapp_access.is_user_allowed.side_effect = WebAppAccessUnavailableError()
|
||||
application_services.return_value = SimpleNamespace(webapp_access=webapp_access)
|
||||
|
||||
passport_service = MagicMock()
|
||||
passport_service.return_value.verify.return_value = {"user_id": "user-1"}
|
||||
with (
|
||||
app.test_request_context("/webapp/permission?appId=app-1", headers={"X-App-Code": "code1"}),
|
||||
patch("controllers.web.app.extract_webapp_passport", return_value="passport"),
|
||||
patch("controllers.web.app.PassportService", passport_service),
|
||||
pytest.raises(WebAppAccessServiceUnavailableError) as raised,
|
||||
):
|
||||
AppWebAuthPermission().get()
|
||||
|
||||
assert raised.value.data == {
|
||||
"code": "web_app_access_unavailable",
|
||||
"message": "Web app access service is unavailable.",
|
||||
"status": 503,
|
||||
}
|
||||
|
||||
@patch("controllers.web.app.application_services")
|
||||
def test_private_app_requires_passport(self, application_services: MagicMock, app: Flask) -> None:
|
||||
webapp_access = MagicMock()
|
||||
webapp_access.requires_permission_check.return_value = True
|
||||
application_services.return_value = SimpleNamespace(webapp_access=webapp_access)
|
||||
|
||||
with (
|
||||
app.test_request_context("/webapp/permission?appId=app-1", headers={"X-App-Code": "code1"}),
|
||||
patch("controllers.web.app.extract_webapp_passport", return_value=None),
|
||||
pytest.raises(WebAppAuthRequiredError) as raised,
|
||||
):
|
||||
AppWebAuthPermission().get()
|
||||
|
||||
assert raised.value.data == {
|
||||
"code": "web_sso_auth_required",
|
||||
"message": "Web app authentication required.",
|
||||
"status": 401,
|
||||
}
|
||||
webapp_access.is_user_allowed.assert_not_called()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description",
|
||||
["Token has expired.", "Invalid token signature.", "Invalid token."],
|
||||
)
|
||||
@patch("controllers.web.app.application_services")
|
||||
def test_invalid_passport_is_normalized_to_web_app_auth_required(
|
||||
self, application_services: MagicMock, description: str, app: Flask
|
||||
) -> None:
|
||||
webapp_access = MagicMock()
|
||||
webapp_access.requires_permission_check.return_value = True
|
||||
application_services.return_value = SimpleNamespace(webapp_access=webapp_access)
|
||||
invalid_passport = Unauthorized(description)
|
||||
|
||||
with (
|
||||
app.test_request_context("/webapp/permission?appId=app-1", headers={"X-App-Code": "code1"}),
|
||||
patch("controllers.web.app.extract_webapp_passport", return_value="passport"),
|
||||
patch("controllers.web.app.PassportService") as passport_service,
|
||||
):
|
||||
passport_service.return_value.verify.side_effect = invalid_passport
|
||||
with pytest.raises(WebAppAuthRequiredError) as raised:
|
||||
AppWebAuthPermission().get()
|
||||
|
||||
assert raised.value.data == {
|
||||
"code": "web_sso_auth_required",
|
||||
"message": "Web app authentication required.",
|
||||
"status": 401,
|
||||
}
|
||||
webapp_access.is_user_allowed.assert_not_called()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "headers"),
|
||||
[
|
||||
pytest.param("/webapp/permission", {"X-App-Code": "code1"}, id="missing-app-id"),
|
||||
pytest.param("/webapp/permission?appId=app-1", {}, id="missing-app-code"),
|
||||
],
|
||||
)
|
||||
@patch("controllers.web.app.application_services")
|
||||
def test_raises_when_app_reference_is_missing(
|
||||
self, application_services: MagicMock, path: str, headers: dict[str, str], app: Flask
|
||||
) -> None:
|
||||
with app.test_request_context(path, headers=headers):
|
||||
with pytest.raises(ValueError, match="appId"):
|
||||
AppWebAuthPermission().get()
|
||||
|
||||
application_services.assert_not_called()
|
||||
|
||||
@@ -300,3 +300,49 @@ def test_build_application_services_does_not_hide_unknown_enterprise_errors(
|
||||
services.webapp_access.get_access_mode(app_id="app-1", app_code=None)
|
||||
|
||||
assert raised.value is failure
|
||||
|
||||
|
||||
def test_build_application_services_wires_webapp_permission(
|
||||
sqlite_session_factory: sessionmaker[Session],
|
||||
) -> None:
|
||||
with (
|
||||
patch(
|
||||
"extensions.ext_application_services.FeatureService.is_webapp_auth_enabled", return_value=True
|
||||
) as enabled,
|
||||
patch(
|
||||
"extensions.ext_application_services.EnterpriseService.WebAppAuth.get_app_access_mode_by_id",
|
||||
return_value=SimpleNamespace(access_mode="private"),
|
||||
) as get_access_mode,
|
||||
patch(
|
||||
"extensions.ext_application_services.EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp",
|
||||
return_value=False,
|
||||
) as is_user_allowed,
|
||||
):
|
||||
services = ext_application_services.build_application_services(
|
||||
database_client=sqlite_session_factory,
|
||||
deployment_edition=DeploymentEdition.COMMUNITY,
|
||||
initialization_password="",
|
||||
redis=MagicMock(spec=RedisClientWrapper),
|
||||
)
|
||||
requires_permission = services.webapp_access.requires_permission_check("app-1")
|
||||
allowed = services.webapp_access.is_user_allowed(user_id="user-1", app_id="app-1")
|
||||
|
||||
assert requires_permission is True
|
||||
assert allowed is False
|
||||
enabled.assert_called_once_with()
|
||||
get_access_mode.assert_called_once_with("app-1")
|
||||
is_user_allowed.assert_called_once_with("user-1", "app-1")
|
||||
|
||||
|
||||
def test_webapp_permission_adapter_maps_connection_failure() -> None:
|
||||
failure = httpx.ConnectError("connection failed")
|
||||
with (
|
||||
patch(
|
||||
"extensions.ext_application_services.EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp",
|
||||
side_effect=failure,
|
||||
),
|
||||
pytest.raises(WebAppAccessUnavailableError) as raised,
|
||||
):
|
||||
ext_application_services._is_user_allowed_to_access_webapp("user-1", "app-1")
|
||||
|
||||
assert raised.value.__cause__ is failure
|
||||
|
||||
@@ -16,21 +16,25 @@ def _service(
|
||||
access: MagicMock,
|
||||
enabled: bool = True,
|
||||
access_mode: WebAppAccessMode = WebAppAccessMode.PRIVATE,
|
||||
) -> tuple[WebAppAccessQueryService, MagicMock]:
|
||||
allowed: bool = True,
|
||||
) -> tuple[WebAppAccessQueryService, MagicMock, MagicMock]:
|
||||
access_mode_for_app = MagicMock(return_value=access_mode)
|
||||
is_user_allowed_for_app = MagicMock(return_value=allowed)
|
||||
return (
|
||||
WebAppAccessQueryService(
|
||||
access=access,
|
||||
webapp_auth_enabled=enabled,
|
||||
access_mode_for_app=access_mode_for_app,
|
||||
is_user_allowed_for_app=is_user_allowed_for_app,
|
||||
),
|
||||
access_mode_for_app,
|
||||
is_user_allowed_for_app,
|
||||
)
|
||||
|
||||
|
||||
def test_disabled_auth_returns_public_before_resolving_app() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, access_mode_for_app = _service(access=access, enabled=False)
|
||||
service, access_mode_for_app, _ = _service(access=access, enabled=False)
|
||||
|
||||
assert service.get_access_mode(app_id=None, app_code=None) is WebAppAccessMode.PUBLIC
|
||||
access.find_app_id_by_code.assert_not_called()
|
||||
@@ -39,7 +43,7 @@ def test_disabled_auth_returns_public_before_resolving_app() -> None:
|
||||
|
||||
def test_enabled_auth_reads_access_mode_by_app_id() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, access_mode_for_app = _service(access=access)
|
||||
service, access_mode_for_app, _ = _service(access=access)
|
||||
|
||||
assert service.get_access_mode(app_id="app-1", app_code=None) is WebAppAccessMode.PRIVATE
|
||||
access.find_app_id_by_code.assert_not_called()
|
||||
@@ -49,7 +53,7 @@ def test_enabled_auth_reads_access_mode_by_app_id() -> None:
|
||||
def test_app_code_takes_precedence_over_app_id() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
access.find_app_id_by_code.return_value = "resolved-id"
|
||||
service, access_mode_for_app = _service(access=access, access_mode=WebAppAccessMode.SSO_VERIFIED)
|
||||
service, access_mode_for_app, _ = _service(access=access, access_mode=WebAppAccessMode.SSO_VERIFIED)
|
||||
|
||||
assert service.get_access_mode(app_id="ignored-id", app_code="code-1") is WebAppAccessMode.SSO_VERIFIED
|
||||
access.find_app_id_by_code.assert_called_once_with("code-1")
|
||||
@@ -59,7 +63,7 @@ def test_app_code_takes_precedence_over_app_id() -> None:
|
||||
def test_missing_app_code_raises_not_found() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
access.find_app_id_by_code.return_value = None
|
||||
service, access_mode_for_app = _service(access=access)
|
||||
service, access_mode_for_app, _ = _service(access=access)
|
||||
|
||||
with pytest.raises(WebAppAccessAppNotFoundError):
|
||||
service.get_access_mode(app_id="must-not-fallback", app_code="missing-code")
|
||||
@@ -69,7 +73,7 @@ def test_missing_app_code_raises_not_found() -> None:
|
||||
|
||||
def test_enabled_auth_requires_app_id_or_code() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, access_mode_for_app = _service(access=access)
|
||||
service, access_mode_for_app, _ = _service(access=access)
|
||||
|
||||
with pytest.raises(WebAppAccessReferenceRequiredError, match="^appId or appCode must be provided$"):
|
||||
service.get_access_mode(app_id=None, app_code=None)
|
||||
@@ -81,7 +85,7 @@ def test_repository_failure_is_not_hidden() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
failure = TypeError("repository bug")
|
||||
access.find_app_id_by_code.side_effect = failure
|
||||
service, _ = _service(access=access)
|
||||
service, _, _ = _service(access=access)
|
||||
|
||||
with pytest.raises(TypeError) as raised:
|
||||
service.get_access_mode(app_id=None, app_code="code-1")
|
||||
@@ -91,7 +95,7 @@ def test_repository_failure_is_not_hidden() -> None:
|
||||
|
||||
def test_access_mode_failure_is_not_hidden() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, access_mode_for_app = _service(access=access)
|
||||
service, access_mode_for_app, _ = _service(access=access)
|
||||
failure = TypeError("adapter bug")
|
||||
access_mode_for_app.side_effect = failure
|
||||
|
||||
@@ -99,3 +103,48 @@ def test_access_mode_failure_is_not_hidden() -> None:
|
||||
service.get_access_mode(app_id="app-1", app_code=None)
|
||||
|
||||
assert raised.value is failure
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("access_mode", "expected"),
|
||||
[
|
||||
pytest.param(WebAppAccessMode.PUBLIC, False, id="public"),
|
||||
pytest.param(WebAppAccessMode.SSO_VERIFIED, False, id="sso-verified"),
|
||||
pytest.param(WebAppAccessMode.PRIVATE, True, id="private"),
|
||||
pytest.param(WebAppAccessMode.PRIVATE_ALL, True, id="private-all"),
|
||||
],
|
||||
)
|
||||
def test_requires_permission_check_for_private_modes(access_mode: WebAppAccessMode, expected: bool) -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, access_mode_for_app, _ = _service(access=access, access_mode=access_mode)
|
||||
|
||||
assert service.requires_permission_check("app-1") is expected
|
||||
access_mode_for_app.assert_called_once_with("app-1")
|
||||
|
||||
|
||||
def test_disabled_auth_still_reads_configured_mode_before_passport() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, access_mode_for_app, _ = _service(
|
||||
access=access,
|
||||
enabled=False,
|
||||
access_mode=WebAppAccessMode.PRIVATE,
|
||||
)
|
||||
|
||||
assert service.requires_permission_check("app-1") is True
|
||||
access_mode_for_app.assert_called_once_with("app-1")
|
||||
|
||||
|
||||
def test_disabled_auth_allows_after_passport_without_querying_user_permission() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, _, is_user_allowed_for_app = _service(access=access, enabled=False, allowed=False)
|
||||
|
||||
assert service.is_user_allowed(user_id="user-1", app_id="app-1") is True
|
||||
is_user_allowed_for_app.assert_not_called()
|
||||
|
||||
|
||||
def test_enabled_auth_delegates_user_permission() -> None:
|
||||
access: MagicMock = create_autospec(WebAppAccessQuery, instance=True, spec_set=True)
|
||||
service, _, is_user_allowed_for_app = _service(access=access, allowed=False)
|
||||
|
||||
assert service.is_user_allowed(user_id="user-1", app_id="app-1") is False
|
||||
is_user_allowed_for_app.assert_called_once_with("user-1", "app-1")
|
||||
|
||||
@@ -1568,6 +1568,7 @@ export type GetWebappPermissionErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
500: unknown
|
||||
503: unknown
|
||||
}
|
||||
|
||||
export type GetWebappPermissionResponses = {
|
||||
|
||||
Reference in New Issue
Block a user