Files
dify/api/tests/unit_tests/services/openapi/test_mint_policy.py

127 lines
3.7 KiB
Python

"""Tests for the mint-policy validator.
Cross-checks the (subject_type, prefix, scopes) triple a caller intends
to mint against ``MINTABLE_PROFILES``. The validator's defense-in-depth
value kicks in when a caller wires scopes or prefix from a non-canonical
source — the well-formed canonical path is the no-violation case.
"""
from __future__ import annotations
import pytest
from libs.oauth_bearer import MINTABLE_PROFILES, Scope, SubjectType
from services.openapi.mint_policy import MintPolicyViolation, validate_mint_policy
def test_canonical_account_profile_passes():
profile = MINTABLE_PROFILES[SubjectType.ACCOUNT]
validate_mint_policy(
subject_type=profile.subject_type,
prefix=profile.prefix,
scopes=profile.scopes,
)
def test_canonical_external_sso_profile_passes():
profile = MINTABLE_PROFILES[SubjectType.EXTERNAL_SSO]
validate_mint_policy(
subject_type=profile.subject_type,
prefix=profile.prefix,
scopes=profile.scopes,
)
def test_wrong_prefix_rejected():
with pytest.raises(MintPolicyViolation) as exc:
validate_mint_policy(
subject_type=SubjectType.ACCOUNT,
prefix="dfoe_", # SSO prefix on an account subject
scopes=frozenset({Scope.FULL}),
)
assert "prefix" in str(exc.value)
def test_wrong_scopes_rejected():
with pytest.raises(MintPolicyViolation) as exc:
validate_mint_policy(
subject_type=SubjectType.ACCOUNT,
prefix="dfoa_",
scopes=frozenset({Scope.APPS_RUN}), # account should be {FULL}
)
assert "scopes" in str(exc.value)
def test_external_sso_with_full_scope_rejected():
with pytest.raises(MintPolicyViolation):
validate_mint_policy(
subject_type=SubjectType.EXTERNAL_SSO,
prefix="dfoe_",
scopes=frozenset({Scope.FULL}), # FULL never applies to dfoe_
)
def test_message_carries_both_drift_reasons():
"""Mismatched prefix AND mismatched scopes both surface in one error."""
with pytest.raises(MintPolicyViolation) as exc:
validate_mint_policy(
subject_type=SubjectType.ACCOUNT,
prefix="dfoe_",
scopes=frozenset({Scope.APPS_RUN}),
)
msg = str(exc.value)
assert "prefix" in msg
assert "scopes" in msg
def test_license_required_decorator_skips_on_ce():
from unittest.mock import patch
from services.openapi.license_gate import license_required
@license_required
def view():
return "ok"
with patch("services.openapi.license_gate.dify_config") as cfg:
cfg.ENTERPRISE_ENABLED = False
assert view() == "ok"
def test_license_required_decorator_403_on_invalid_ee_license():
from unittest.mock import patch
from werkzeug.exceptions import Forbidden
from services.openapi.license_gate import license_required
@license_required
def view():
return "ok"
with (
patch("services.openapi.license_gate.dify_config") as cfg,
patch("services.openapi.license_gate._is_license_valid", return_value=False),
):
cfg.ENTERPRISE_ENABLED = True
with pytest.raises(Forbidden) as exc:
view()
assert "license_required" in exc.value.description
def test_license_required_decorator_passes_on_valid_ee_license():
from unittest.mock import patch
from services.openapi.license_gate import license_required
@license_required
def view():
return "ok"
with (
patch("services.openapi.license_gate.dify_config") as cfg,
patch("services.openapi.license_gate._is_license_valid", return_value=True),
):
cfg.ENTERPRISE_ENABLED = True
assert view() == "ok"