選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

oidc.py 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #
  2. # Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import jwt
  17. import requests
  18. from .oauth import OAuthClient
  19. class OIDCClient(OAuthClient):
  20. def __init__(self, config):
  21. """
  22. Initialize the OIDCClient with the provider's configuration.
  23. Use `issuer` as the single source of truth for configuration discovery.
  24. """
  25. self.issuer = config.get("issuer")
  26. if not self.issuer:
  27. raise ValueError("Missing issuer in configuration.")
  28. oidc_metadata = self._load_oidc_metadata(self.issuer)
  29. config.update({
  30. 'issuer': oidc_metadata['issuer'],
  31. 'jwks_uri': oidc_metadata['jwks_uri'],
  32. 'authorization_url': oidc_metadata['authorization_endpoint'],
  33. 'token_url': oidc_metadata['token_endpoint'],
  34. 'userinfo_url': oidc_metadata['userinfo_endpoint']
  35. })
  36. super().__init__(config)
  37. self.issuer = config['issuer']
  38. self.jwks_uri = config['jwks_uri']
  39. def _load_oidc_metadata(self, issuer):
  40. """
  41. Load OIDC metadata from `/.well-known/openid-configuration`.
  42. """
  43. try:
  44. metadata_url = f"{issuer}/.well-known/openid-configuration"
  45. response = requests.get(metadata_url, timeout=7)
  46. response.raise_for_status()
  47. return response.json()
  48. except requests.exceptions.RequestException as e:
  49. raise ValueError(f"Failed to fetch OIDC metadata: {e}")
  50. def parse_id_token(self, id_token):
  51. """
  52. Parse and validate OIDC ID Token (JWT format) with signature verification.
  53. """
  54. try:
  55. # Decode JWT header without verifying signature
  56. headers = jwt.get_unverified_header(id_token)
  57. # OIDC usually uses `RS256` for signing
  58. alg = headers.get("alg", "RS256")
  59. # Use PyJWT's PyJWKClient to fetch JWKS and find signing key
  60. jwks_url = f"{self.issuer}/.well-known/jwks.json"
  61. jwks_cli = jwt.PyJWKClient(jwks_url)
  62. signing_key = jwks_cli.get_signing_key_from_jwt(id_token).key
  63. # Decode and verify signature
  64. decoded_token = jwt.decode(
  65. id_token,
  66. key=signing_key,
  67. algorithms=[alg],
  68. audience=str(self.client_id),
  69. issuer=self.issuer,
  70. )
  71. return decoded_token
  72. except Exception as e:
  73. raise ValueError(f"Error parsing ID Token: {e}")
  74. def fetch_user_info(self, access_token, id_token=None, **kwargs):
  75. """
  76. Fetch user info.
  77. """
  78. user_info = {}
  79. if id_token:
  80. user_info = self.parse_id_token(id_token)
  81. user_info.update(super().fetch_user_info(access_token).to_dict())
  82. return self.normalize_user_info(user_info)
  83. def normalize_user_info(self, user_info):
  84. return super().normalize_user_info(user_info)