Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,11 +904,15 @@
"ROTATE_REFRESH_TOKEN": True,
"PKCE_REQUIRED": True,
"ALLOWED_CODE_CHALLENGE_METHODS": ["S256"],
"SCOPES": {"mcp": "MCP access"},
"SCOPES": {
"mcp": "MCP access",
"scim": "SCIM provisioning access",
},
"DEFAULT_SCOPES": ["mcp"],
"ALLOWED_GRANT_TYPES": [
"authorization_code",
"refresh_token",
"client_credentials",
],
}

Expand Down
6 changes: 5 additions & 1 deletion api/oauth2_metadata/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def authorization_server_metadata(request: HttpRequest) -> JsonResponse:
frontend_url: str = settings.FLAGSMITH_FRONTEND_URL.rstrip("/")
oauth2_settings: dict[str, Any] = settings.OAUTH2_PROVIDER
scopes: dict[str, str] = oauth2_settings.get("SCOPES", {})
allowed_grant_types: list[str] = oauth2_settings.get(
"ALLOWED_GRANT_TYPES",
["authorization_code", "refresh_token"],
)

metadata = {
"issuer": api_url,
Expand All @@ -39,7 +43,7 @@ def authorization_server_metadata(request: HttpRequest) -> JsonResponse:
"introspection_endpoint": f"{api_url}/o/introspect/",
"scopes_supported": list(scopes.keys()),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"grant_types_supported": allowed_grant_types,
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
Expand Down
53 changes: 52 additions & 1 deletion api/tests/unit/oauth2_metadata/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_metadata_endpoint__unauthenticated__returns_200_with_rfc8414_json(
assert data["revocation_endpoint"] == "https://api.flagsmith.com/o/revoke_token/"
assert data["introspection_endpoint"] == "https://api.flagsmith.com/o/introspect/"
assert data["response_types_supported"] == ["code"]
assert data["grant_types_supported"] == ["authorization_code", "refresh_token"]
assert data["grant_types_supported"] == ["authorization_code", "refresh_token","client_credentials"]
assert data["code_challenge_methods_supported"] == ["S256"]
assert "none" in data["token_endpoint_auth_methods_supported"]
assert data["introspection_endpoint_auth_methods_supported"] == ["none"]
Expand Down Expand Up @@ -108,3 +108,54 @@ def test_metadata_endpoint__post_request__returns_405() -> None:

# Then
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED


def test_metadata_endpoint__grant_types__derived_from_allowed_grant_types_setting(
client: Client,
settings: SettingsWrapper,
) -> None:
# Given
settings.OAUTH2_PROVIDER = {
**settings.OAUTH2_PROVIDER,
"ALLOWED_GRANT_TYPES": ["authorization_code", "client_credentials"],
}

# When
response = client.get(reverse(METADATA_URL))

# Then
data = response.json()
assert data["grant_types_supported"] == ["authorization_code", "client_credentials"]


def test_metadata_endpoint__grant_types__include_client_credentials_by_default(
client: Client,
settings: SettingsWrapper,
) -> None:
# Given
# Use real settings which now include client_credentials
settings.FLAGSMITH_API_URL = "https://api.flagsmith.com"
settings.FLAGSMITH_FRONTEND_URL = "https://app.flagsmith.com"

# When
response = client.get(reverse(METADATA_URL))

# Then
data = response.json()
assert "client_credentials" in data["grant_types_supported"]


def test_metadata_endpoint__scim_scope__present_in_scopes_supported(
client: Client,
settings: SettingsWrapper,
) -> None:
# Given
settings.FLAGSMITH_API_URL = "https://api.flagsmith.com"
settings.FLAGSMITH_FRONTEND_URL = "https://app.flagsmith.com"

# When
response = client.get(reverse(METADATA_URL))

# Then
data = response.json()
assert "scim" in data["scopes_supported"]
Loading