-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow OIDC fallback to id_token for group support
Fixes #5464
- Loading branch information
Showing
7 changed files
with
235 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,7 @@ hfids | |
hostname | ||
httpx | ||
human_friendly_id | ||
id_token | ||
idempotency | ||
include_in_menu | ||
Infrahub | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from typing import Any | ||
|
||
import httpx | ||
|
||
from infrahub.services.adapters.http import InfrahubHTTP | ||
|
||
|
||
class MemoryHTTP(InfrahubHTTP): | ||
def __init__(self) -> None: | ||
self._get_response: dict[str, httpx.Response] = {} | ||
self._post_response: dict[str, httpx.Response] = {} | ||
|
||
async def get( | ||
self, | ||
url: str, | ||
headers: dict[str, Any] | None = None, | ||
) -> httpx.Response: | ||
return self._get_response[url] | ||
|
||
async def post( | ||
self, | ||
url: str, | ||
data: Any | None = None, | ||
json: Any | None = None, | ||
headers: dict[str, Any] | None = None, | ||
verify: bool | None = None, | ||
) -> httpx.Response: | ||
return self._post_response[url] | ||
|
||
def add_get_response(self, url: str, response: httpx.Response) -> None: | ||
self._get_response[url] = response | ||
|
||
def add_post_response(self, url: str, response: httpx.Response) -> None: | ||
self._post_response[url] = response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import json | ||
import time | ||
import uuid | ||
from typing import Any | ||
|
||
import httpx | ||
from jwcrypto import jwk, jwt | ||
from pydantic_core import Url | ||
|
||
from infrahub.api.oidc import OIDCDiscoveryConfig, _get_id_token_groups | ||
from infrahub.services import InfrahubServices | ||
from tests.adapters.http import MemoryHTTP | ||
|
||
|
||
async def test_get_id_token_groups_for_oidc() -> None: | ||
memory_http = MemoryHTTP() | ||
service = InfrahubServices(http=memory_http) | ||
client_id = "testing-oicd-1234" | ||
|
||
helper = OIDCTestHelper() | ||
token_response = helper.generate_token_response( | ||
username="testuser", | ||
groups=["operators"], | ||
client_id=client_id, | ||
issuer=str(OIDC_CONFIG.issuer), | ||
) | ||
|
||
memory_http.add_get_response( | ||
url=str(OIDC_CONFIG.jwks_uri), | ||
response=httpx.Response(status_code=200, content=json.dumps(helper.jwks_payload)), | ||
) | ||
|
||
groups = await _get_id_token_groups( | ||
oidc_config=OIDC_CONFIG, | ||
service=service, | ||
payload=token_response, | ||
client_id=client_id, | ||
) | ||
|
||
assert groups == ["operators"] | ||
|
||
|
||
async def test_get_id_token_groups_for_oidc_no_id_token() -> None: | ||
memory_http = MemoryHTTP() | ||
service = InfrahubServices(http=memory_http) | ||
client_id = "testing-oicd-1234" | ||
|
||
helper = OIDCTestHelper() | ||
token_response = helper.generate_token_response( | ||
username="testuser", | ||
groups=["operators"], | ||
client_id=client_id, | ||
issuer=str(OIDC_CONFIG.issuer), | ||
) | ||
token_response.pop("id_token") | ||
|
||
memory_http.add_get_response( | ||
url=str(OIDC_CONFIG.jwks_uri), | ||
response=httpx.Response(status_code=200, content=json.dumps(helper.jwks_payload)), | ||
) | ||
|
||
groups = await _get_id_token_groups( | ||
oidc_config=OIDC_CONFIG, | ||
service=service, | ||
payload=token_response, | ||
client_id=client_id, | ||
) | ||
|
||
assert groups == [] | ||
|
||
|
||
class OIDCTestHelper: | ||
def __init__(self) -> None: | ||
self.key: jwk.JWK = jwk.JWK.generate(kty="RSA", size=2048) | ||
self.kid = str(uuid.uuid4()) | ||
|
||
self.jwks_payload = { | ||
"keys": [ | ||
{ | ||
**json.loads(self.key.export_public()), | ||
"kid": self.kid, | ||
} | ||
] | ||
} | ||
|
||
def generate_token_response(self, username: str, groups: list[str], client_id: str, issuer: str) -> dict[str, Any]: | ||
current_time = int(time.time()) | ||
expiration_time = current_time + 600 | ||
|
||
id_token = jwt.JWT( | ||
header={"alg": "RS256", "kid": self.kid}, | ||
claims={ | ||
"sub": str(uuid.uuid4()), | ||
"aud": client_id, | ||
"iss": issuer, | ||
"exp": expiration_time, | ||
"iat": current_time, | ||
"auth_time": current_time, | ||
"name": username, | ||
"groups": groups, | ||
}, | ||
) | ||
id_token.make_signed_token(self.key) | ||
|
||
return { | ||
"access_token": id_token.serialize(), | ||
"expires_in": 600, | ||
"refresh_expires_in": 1800, | ||
"id_token": id_token.serialize(), | ||
"token_type": "Bearer", | ||
"scope": "openid profile email", | ||
} | ||
|
||
|
||
OIDC_CONFIG = OIDCDiscoveryConfig( | ||
issuer=Url("https://oidc.example.com/realms/infrahub-oidc"), | ||
authorization_endpoint=Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/auth"), | ||
token_endpoint=Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/token"), | ||
userinfo_endpoint=Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/userinfo"), | ||
jwks_uri=Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/certs"), | ||
revocation_endpoint=Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/revoke"), | ||
registration_endpoint=Url("https://oidc.example.com/realms/infrahub-oidc/clients-registrations/openid-connect"), | ||
introspection_endpoint=Url( | ||
"https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/token/introspect" | ||
), | ||
end_session_endpoint=Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/logout"), | ||
frontchannel_logout_supported=True, | ||
frontchannel_logout_session_supported=True, | ||
grant_types_supported=["authorization_code", "implicit"], | ||
response_types_supported=["code", "id_token", "token"], | ||
subject_types_supported=["public"], | ||
id_token_signing_alg_values_supported=["RS256"], | ||
scopes_supported=["openid", "profile", "email"], | ||
token_endpoint_auth_methods_supported=["client_secret_basic"], | ||
claims_supported=["sub", "name", "email"], | ||
acr_values_supported=["1"], | ||
request_parameter_supported=True, | ||
request_uri_parameter_supported=True, | ||
require_request_uri_registration=True, | ||
code_challenge_methods_supported=["S256"], | ||
tls_client_certificate_bound_access_tokens=True, | ||
mtls_endpoint_aliases={ | ||
"token_endpoint": Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/token"), | ||
"revocation_endpoint": Url("https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/revoke"), | ||
"introspection_endpoint": Url( | ||
"https://oidc.example.com/realms/infrahub-oidc/protocol/openid-connect/token/introspect" | ||
), | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Allow OIDC providers to fall back to id_token for group membership reports if they are not provided within the userinfo URL. This allows for group support using Azure. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters