- #
 - #  Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
 - #
 - #  Licensed under the Apache License, Version 2.0 (the "License");
 - #  you may not use this file except in compliance with the License.
 - #  You may obtain a copy of the License at
 - #
 - #      http://www.apache.org/licenses/LICENSE-2.0
 - #
 - #  Unless required by applicable law or agreed to in writing, software
 - #  distributed under the License is distributed on an "AS IS" BASIS,
 - #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 - #  See the License for the specific language governing permissions and
 - #  limitations under the License.
 - #
 - 
 - import jwt
 - import requests
 - from .oauth import OAuthClient
 - 
 - 
 - class OIDCClient(OAuthClient):
 -     def __init__(self, config):
 -         """
 -         Initialize the OIDCClient with the provider's configuration.
 -         Use `issuer` as the single source of truth for configuration discovery.
 -         """
 -         self.issuer = config.get("issuer")
 -         if not self.issuer:
 -             raise ValueError("Missing issuer in configuration.")
 - 
 -         oidc_metadata = self._load_oidc_metadata(self.issuer)
 -         config.update({
 -             'issuer': oidc_metadata['issuer'],
 -             'jwks_uri': oidc_metadata['jwks_uri'], 
 -             'authorization_url': oidc_metadata['authorization_endpoint'],
 -             'token_url': oidc_metadata['token_endpoint'],
 -             'userinfo_url': oidc_metadata['userinfo_endpoint']
 -         })
 - 
 -         super().__init__(config)
 -         self.issuer = config['issuer']
 -         self.jwks_uri = config['jwks_uri']
 - 
 - 
 -     def _load_oidc_metadata(self, issuer):
 -         """
 -         Load OIDC metadata from `/.well-known/openid-configuration`.
 -         """
 -         try:
 -             metadata_url = f"{issuer}/.well-known/openid-configuration"
 -             response = requests.get(metadata_url, timeout=7)
 -             response.raise_for_status()
 -             return response.json()
 -         except requests.exceptions.RequestException as e:
 -             raise ValueError(f"Failed to fetch OIDC metadata: {e}")
 - 
 - 
 -     def parse_id_token(self, id_token):
 -         """
 -         Parse and validate OIDC ID Token (JWT format) with signature verification.
 -         """
 -         try:
 -             # Decode JWT header without verifying signature
 -             headers = jwt.get_unverified_header(id_token)
 -             
 -             # OIDC usually uses `RS256` for signing
 -             alg = headers.get("alg", "RS256")
 - 
 -             # Use PyJWT's PyJWKClient to fetch JWKS and find signing key
 -             jwks_cli = jwt.PyJWKClient(self.jwks_uri)
 -             signing_key = jwks_cli.get_signing_key_from_jwt(id_token).key
 - 
 -             # Decode and verify signature
 -             decoded_token = jwt.decode(
 -                 id_token,
 -                 key=signing_key,
 -                 algorithms=[alg],  
 -                 audience=str(self.client_id),
 -                 issuer=self.issuer,
 -             )
 -             return decoded_token
 -         except Exception as e:
 -             raise ValueError(f"Error parsing ID Token: {e}")
 - 
 - 
 -     def fetch_user_info(self, access_token, id_token=None, **kwargs):
 -         """
 -         Fetch user info.
 -         """
 -         user_info = {}
 -         if id_token:
 -             user_info = self.parse_id_token(id_token)
 -         user_info.update(super().fetch_user_info(access_token).to_dict())
 -         return self.normalize_user_info(user_info)
 - 
 - 
 -     def normalize_user_info(self, user_info):
 -         return super().normalize_user_info(user_info)
 
 
  |