updates
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .claims import ClientMetadataClaims
|
||||
|
||||
__all__ = ["ClientMetadataClaims"]
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,355 @@
|
||||
from authlib.common.urls import is_valid_url
|
||||
from authlib.jose import BaseClaims
|
||||
from authlib.jose.errors import InvalidClaimError
|
||||
|
||||
|
||||
class ClientMetadataClaims(BaseClaims):
|
||||
REGISTERED_CLAIMS = [
|
||||
"token_endpoint_auth_signing_alg",
|
||||
"application_type",
|
||||
"sector_identifier_uri",
|
||||
"subject_type",
|
||||
"id_token_signed_response_alg",
|
||||
"id_token_encrypted_response_alg",
|
||||
"id_token_encrypted_response_enc",
|
||||
"userinfo_signed_response_alg",
|
||||
"userinfo_encrypted_response_alg",
|
||||
"userinfo_encrypted_response_enc",
|
||||
"default_max_age",
|
||||
"require_auth_time",
|
||||
"default_acr_values",
|
||||
"initiate_login_uri",
|
||||
"request_object_signing_alg",
|
||||
"request_object_encryption_alg",
|
||||
"request_object_encryption_enc",
|
||||
"request_uris",
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
self._validate_essential_claims()
|
||||
self.validate_token_endpoint_auth_signing_alg()
|
||||
self.validate_application_type()
|
||||
self.validate_sector_identifier_uri()
|
||||
self.validate_subject_type()
|
||||
self.validate_id_token_signed_response_alg()
|
||||
self.validate_id_token_encrypted_response_alg()
|
||||
self.validate_id_token_encrypted_response_enc()
|
||||
self.validate_userinfo_signed_response_alg()
|
||||
self.validate_userinfo_encrypted_response_alg()
|
||||
self.validate_userinfo_encrypted_response_enc()
|
||||
self.validate_default_max_age()
|
||||
self.validate_require_auth_time()
|
||||
self.validate_default_acr_values()
|
||||
self.validate_initiate_login_uri()
|
||||
self.validate_request_object_signing_alg()
|
||||
self.validate_request_object_encryption_alg()
|
||||
self.validate_request_object_encryption_enc()
|
||||
self.validate_request_uris()
|
||||
|
||||
def _validate_uri(self, key):
|
||||
uri = self.get(key)
|
||||
uris = uri if isinstance(uri, list) else [uri]
|
||||
for uri in uris:
|
||||
if uri and not is_valid_url(uri):
|
||||
raise InvalidClaimError(key)
|
||||
|
||||
@classmethod
|
||||
def get_claims_options(self, metadata):
|
||||
"""Generate claims options validation from Authorization Server metadata."""
|
||||
options = {}
|
||||
|
||||
if acr_values_supported := metadata.get("acr_values_supported"):
|
||||
|
||||
def _validate_default_acr_values(claims, value):
|
||||
return not value or set(value).issubset(set(acr_values_supported))
|
||||
|
||||
options["default_acr_values"] = {"validate": _validate_default_acr_values}
|
||||
|
||||
values_mapping = {
|
||||
"token_endpoint_auth_signing_alg_values_supported": "token_endpoint_auth_signing_alg",
|
||||
"subject_types_supported": "subject_type",
|
||||
"id_token_signing_alg_values_supported": "id_token_signed_response_alg",
|
||||
"id_token_encryption_alg_values_supported": "id_token_encrypted_response_alg",
|
||||
"id_token_encryption_enc_values_supported": "id_token_encrypted_response_enc",
|
||||
"userinfo_signing_alg_values_supported": "userinfo_signed_response_alg",
|
||||
"userinfo_encryption_alg_values_supported": "userinfo_encrypted_response_alg",
|
||||
"userinfo_encryption_enc_values_supported": "userinfo_encrypted_response_enc",
|
||||
"request_object_signing_alg_values_supported": "request_object_signing_alg",
|
||||
"request_object_encryption_alg_values_supported": "request_object_encryption_alg",
|
||||
"request_object_encryption_enc_values_supported": "request_object_encryption_enc",
|
||||
}
|
||||
|
||||
def make_validator(metadata_claim_values):
|
||||
def _validate(claims, value):
|
||||
return not value or value in metadata_claim_values
|
||||
|
||||
return _validate
|
||||
|
||||
for metadata_claim_name, request_claim_name in values_mapping.items():
|
||||
if metadata_claim_values := metadata.get(metadata_claim_name):
|
||||
options[request_claim_name] = {
|
||||
"validate": make_validator(metadata_claim_values)
|
||||
}
|
||||
|
||||
return options
|
||||
|
||||
def validate_token_endpoint_auth_signing_alg(self):
|
||||
"""JWS [JWS] alg algorithm [JWA] that MUST be used for signing the JWT [JWT]
|
||||
used to authenticate the Client at the Token Endpoint for the private_key_jwt
|
||||
and client_secret_jwt authentication methods.
|
||||
|
||||
All Token Requests using these authentication methods from this Client MUST be
|
||||
rejected, if the JWT is not signed with this algorithm. Servers SHOULD support
|
||||
RS256. The value none MUST NOT be used. The default, if omitted, is that any
|
||||
algorithm supported by the OP and the RP MAY be used.
|
||||
"""
|
||||
if self.get("token_endpoint_auth_signing_alg") == "none":
|
||||
raise InvalidClaimError("token_endpoint_auth_signing_alg")
|
||||
|
||||
self._validate_claim_value("token_endpoint_auth_signing_alg")
|
||||
|
||||
def validate_application_type(self):
|
||||
"""Kind of the application.
|
||||
|
||||
The default, if omitted, is web. The defined values are native or web. Web
|
||||
Clients using the OAuth Implicit Grant Type MUST only register URLs using the
|
||||
https scheme as redirect_uris; they MUST NOT use localhost as the hostname.
|
||||
Native Clients MUST only register redirect_uris using custom URI schemes or
|
||||
loopback URLs using the http scheme; loopback URLs use localhost or the IP
|
||||
loopback literals 127.0.0.1 or [::1] as the hostname. Authorization Servers MAY
|
||||
place additional constraints on Native Clients. Authorization Servers MAY
|
||||
reject Redirection URI values using the http scheme, other than the loopback
|
||||
case for Native Clients. The Authorization Server MUST verify that all the
|
||||
registered redirect_uris conform to these constraints. This prevents sharing a
|
||||
Client ID across different types of Clients.
|
||||
"""
|
||||
self.setdefault("application_type", "web")
|
||||
if self.get("application_type") not in ("web", "native"):
|
||||
raise InvalidClaimError("application_type")
|
||||
|
||||
self._validate_claim_value("application_type")
|
||||
|
||||
def validate_sector_identifier_uri(self):
|
||||
"""URL using the https scheme to be used in calculating Pseudonymous Identifiers
|
||||
by the OP.
|
||||
|
||||
The URL references a file with a single JSON array of redirect_uri values.
|
||||
Please see Section 5. Providers that use pairwise sub (subject) values SHOULD
|
||||
utilize the sector_identifier_uri value provided in the Subject Identifier
|
||||
calculation for pairwise identifiers.
|
||||
"""
|
||||
self._validate_uri("sector_identifier_uri")
|
||||
|
||||
def validate_subject_type(self):
|
||||
"""subject_type requested for responses to this Client.
|
||||
|
||||
The subject_types_supported discovery parameter contains a list of the supported
|
||||
subject_type values for the OP. Valid types include pairwise and public.
|
||||
"""
|
||||
self._validate_claim_value("subject_type")
|
||||
|
||||
def validate_id_token_signed_response_alg(self):
|
||||
"""JWS alg algorithm [JWA] REQUIRED for signing the ID Token issued to this
|
||||
Client.
|
||||
|
||||
The value none MUST NOT be used as the ID Token alg value unless the Client uses
|
||||
only Response Types that return no ID Token from the Authorization Endpoint
|
||||
(such as when only using the Authorization Code Flow). The default, if omitted,
|
||||
is RS256. The public key for validating the signature is provided by retrieving
|
||||
the JWK Set referenced by the jwks_uri element from OpenID Connect Discovery 1.0
|
||||
[OpenID.Discovery].
|
||||
"""
|
||||
if self.get(
|
||||
"id_token_signed_response_alg"
|
||||
) == "none" and "id_token" in self.get("response_type", ""):
|
||||
raise InvalidClaimError("id_token_signed_response_alg")
|
||||
|
||||
self.setdefault("id_token_signed_response_alg", "RS256")
|
||||
self._validate_claim_value("id_token_signed_response_alg")
|
||||
|
||||
def validate_id_token_encrypted_response_alg(self):
|
||||
"""JWE alg algorithm [JWA] REQUIRED for encrypting the ID Token issued to this
|
||||
Client.
|
||||
|
||||
If this is requested, the response will be signed then encrypted, with the
|
||||
result being a Nested JWT, as defined in [JWT]. The default, if omitted, is that
|
||||
no encryption is performed.
|
||||
"""
|
||||
self._validate_claim_value("id_token_encrypted_response_alg")
|
||||
|
||||
def validate_id_token_encrypted_response_enc(self):
|
||||
"""JWE enc algorithm [JWA] REQUIRED for encrypting the ID Token issued to this
|
||||
Client.
|
||||
|
||||
If id_token_encrypted_response_alg is specified, the default
|
||||
id_token_encrypted_response_enc value is A128CBC-HS256. When
|
||||
id_token_encrypted_response_enc is included, id_token_encrypted_response_alg
|
||||
MUST also be provided.
|
||||
"""
|
||||
if self.get("id_token_encrypted_response_enc") and not self.get(
|
||||
"id_token_encrypted_response_alg"
|
||||
):
|
||||
raise InvalidClaimError("id_token_encrypted_response_enc")
|
||||
|
||||
if self.get("id_token_encrypted_response_alg"):
|
||||
self.setdefault("id_token_encrypted_response_enc", "A128CBC-HS256")
|
||||
|
||||
self._validate_claim_value("id_token_encrypted_response_enc")
|
||||
|
||||
def validate_userinfo_signed_response_alg(self):
|
||||
"""JWS alg algorithm [JWA] REQUIRED for signing UserInfo Responses.
|
||||
|
||||
If this is specified, the response will be JWT [JWT] serialized, and signed
|
||||
using JWS. The default, if omitted, is for the UserInfo Response to return the
|
||||
Claims as a UTF-8 [RFC3629] encoded JSON object using the application/json
|
||||
content-type.
|
||||
"""
|
||||
self._validate_claim_value("userinfo_signed_response_alg")
|
||||
|
||||
def validate_userinfo_encrypted_response_alg(self):
|
||||
"""JWE [JWE] alg algorithm [JWA] REQUIRED for encrypting UserInfo Responses.
|
||||
|
||||
If both signing and encryption are requested, the response will be signed then
|
||||
encrypted, with the result being a Nested JWT, as defined in [JWT]. The default,
|
||||
if omitted, is that no encryption is performed.
|
||||
"""
|
||||
self._validate_claim_value("userinfo_encrypted_response_alg")
|
||||
|
||||
def validate_userinfo_encrypted_response_enc(self):
|
||||
"""JWE enc algorithm [JWA] REQUIRED for encrypting UserInfo Responses.
|
||||
|
||||
If userinfo_encrypted_response_alg is specified, the default
|
||||
userinfo_encrypted_response_enc value is A128CBC-HS256. When
|
||||
userinfo_encrypted_response_enc is included, userinfo_encrypted_response_alg
|
||||
MUST also be provided.
|
||||
"""
|
||||
if self.get("userinfo_encrypted_response_enc") and not self.get(
|
||||
"userinfo_encrypted_response_alg"
|
||||
):
|
||||
raise InvalidClaimError("userinfo_encrypted_response_enc")
|
||||
|
||||
if self.get("userinfo_encrypted_response_alg"):
|
||||
self.setdefault("userinfo_encrypted_response_enc", "A128CBC-HS256")
|
||||
|
||||
self._validate_claim_value("userinfo_encrypted_response_enc")
|
||||
|
||||
def validate_default_max_age(self):
|
||||
"""Default Maximum Authentication Age.
|
||||
|
||||
Specifies that the End-User MUST be actively authenticated if the End-User was
|
||||
authenticated longer ago than the specified number of seconds. The max_age
|
||||
request parameter overrides this default value. If omitted, no default Maximum
|
||||
Authentication Age is specified.
|
||||
"""
|
||||
if self.get("default_max_age") is not None and not isinstance(
|
||||
self["default_max_age"], (int, float)
|
||||
):
|
||||
raise InvalidClaimError("default_max_age")
|
||||
|
||||
self._validate_claim_value("default_max_age")
|
||||
|
||||
def validate_require_auth_time(self):
|
||||
"""Boolean value specifying whether the auth_time Claim in the ID Token is
|
||||
REQUIRED.
|
||||
|
||||
It is REQUIRED when the value is true. (If this is false, the auth_time Claim
|
||||
can still be dynamically requested as an individual Claim for the ID Token using
|
||||
the claims request parameter described in Section 5.5.1 of OpenID Connect Core
|
||||
1.0 [OpenID.Core].) If omitted, the default value is false.
|
||||
"""
|
||||
self.setdefault("require_auth_time", False)
|
||||
if self.get("require_auth_time") is not None and not isinstance(
|
||||
self["require_auth_time"], bool
|
||||
):
|
||||
raise InvalidClaimError("require_auth_time")
|
||||
|
||||
self._validate_claim_value("require_auth_time")
|
||||
|
||||
def validate_default_acr_values(self):
|
||||
"""Default requested Authentication Context Class Reference values.
|
||||
|
||||
Array of strings that specifies the default acr values that the OP is being
|
||||
requested to use for processing requests from this Client, with the values
|
||||
appearing in order of preference. The Authentication Context Class satisfied by
|
||||
the authentication performed is returned as the acr Claim Value in the issued ID
|
||||
Token. The acr Claim is requested as a Voluntary Claim by this parameter. The
|
||||
acr_values_supported discovery element contains a list of the supported acr
|
||||
values supported by the OP. Values specified in the acr_values request parameter
|
||||
or an individual acr Claim request override these default values.
|
||||
"""
|
||||
self._validate_claim_value("default_acr_values")
|
||||
|
||||
def validate_initiate_login_uri(self):
|
||||
"""RI using the https scheme that a third party can use to initiate a login by
|
||||
the RP, as specified in Section 4 of OpenID Connect Core 1.0 [OpenID.Core].
|
||||
|
||||
The URI MUST accept requests via both GET and POST. The Client MUST understand
|
||||
the login_hint and iss parameters and SHOULD support the target_link_uri
|
||||
parameter.
|
||||
"""
|
||||
self._validate_uri("initiate_login_uri")
|
||||
|
||||
def validate_request_object_signing_alg(self):
|
||||
"""JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request Objects
|
||||
sent to the OP.
|
||||
|
||||
All Request Objects from this Client MUST be rejected, if not signed with this
|
||||
algorithm. Request Objects are described in Section 6.1 of OpenID Connect Core
|
||||
1.0 [OpenID.Core]. This algorithm MUST be used both when the Request Object is
|
||||
passed by value (using the request parameter) and when it is passed by reference
|
||||
(using the request_uri parameter). Servers SHOULD support RS256. The value none
|
||||
MAY be used. The default, if omitted, is that any algorithm supported by the OP
|
||||
and the RP MAY be used.
|
||||
"""
|
||||
self._validate_claim_value("request_object_signing_alg")
|
||||
|
||||
def validate_request_object_encryption_alg(self):
|
||||
"""JWE [JWE] alg algorithm [JWA] the RP is declaring that it may use for
|
||||
encrypting Request Objects sent to the OP.
|
||||
|
||||
This parameter SHOULD be included when symmetric encryption will be used, since
|
||||
this signals to the OP that a client_secret value needs to be returned from
|
||||
which the symmetric key will be derived, that might not otherwise be returned.
|
||||
The RP MAY still use other supported encryption algorithms or send unencrypted
|
||||
Request Objects, even when this parameter is present. If both signing and
|
||||
encryption are requested, the Request Object will be signed then encrypted, with
|
||||
the result being a Nested JWT, as defined in [JWT]. The default, if omitted, is
|
||||
that the RP is not declaring whether it might encrypt any Request Objects.
|
||||
"""
|
||||
self._validate_claim_value("request_object_encryption_alg")
|
||||
|
||||
def validate_request_object_encryption_enc(self):
|
||||
"""JWE enc algorithm [JWA] the RP is declaring that it may use for encrypting
|
||||
Request Objects sent to the OP.
|
||||
|
||||
If request_object_encryption_alg is specified, the default
|
||||
request_object_encryption_enc value is A128CBC-HS256. When
|
||||
request_object_encryption_enc is included, request_object_encryption_alg MUST
|
||||
also be provided.
|
||||
"""
|
||||
if self.get("request_object_encryption_enc") and not self.get(
|
||||
"request_object_encryption_alg"
|
||||
):
|
||||
raise InvalidClaimError("request_object_encryption_enc")
|
||||
|
||||
if self.get("request_object_encryption_alg"):
|
||||
self.setdefault("request_object_encryption_enc", "A128CBC-HS256")
|
||||
|
||||
self._validate_claim_value("request_object_encryption_enc")
|
||||
|
||||
def validate_request_uris(self):
|
||||
"""Array of request_uri values that are pre-registered by the RP for use at the
|
||||
OP.
|
||||
|
||||
These URLs MUST use the https scheme unless the target Request Object is signed
|
||||
in a way that is verifiable by the OP. Servers MAY cache the contents of the
|
||||
files referenced by these URIs and not retrieve them at the time they are used
|
||||
in a request. OPs can require that request_uri values used be pre-registered
|
||||
with the require_request_uri_registration discovery parameter. If the contents
|
||||
of the request file could ever change, these URI values SHOULD include the
|
||||
base64url-encoded SHA-256 hash value of the file contents referenced by the URI
|
||||
as the value of the URI fragment. If the fragment value used for a URI changes,
|
||||
that signals the server that its cached value for that URI with the old fragment
|
||||
value is no longer valid.
|
||||
"""
|
||||
self._validate_uri("request_uris")
|
||||
Reference in New Issue
Block a user