venv added, updated
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
from authlib.common.urls import urlparse, is_valid_url
|
||||
from authlib.common.security import is_secure_transport
|
||||
|
||||
|
||||
class AuthorizationServerMetadata(dict):
|
||||
"""Define Authorization Server Metadata via `Section 2`_ in RFC8414_.
|
||||
|
||||
.. _RFC8414: https://tools.ietf.org/html/rfc8414
|
||||
.. _`Section 2`: https://tools.ietf.org/html/rfc8414#section-2
|
||||
"""
|
||||
REGISTRY_KEYS = [
|
||||
'issuer', 'authorization_endpoint', 'token_endpoint',
|
||||
'jwks_uri', 'registration_endpoint', 'scopes_supported',
|
||||
'response_types_supported', 'response_modes_supported',
|
||||
'grant_types_supported', 'token_endpoint_auth_methods_supported',
|
||||
'token_endpoint_auth_signing_alg_values_supported',
|
||||
'service_documentation', 'ui_locales_supported',
|
||||
'op_policy_uri', 'op_tos_uri', 'revocation_endpoint',
|
||||
'revocation_endpoint_auth_methods_supported',
|
||||
'revocation_endpoint_auth_signing_alg_values_supported',
|
||||
'introspection_endpoint',
|
||||
'introspection_endpoint_auth_methods_supported',
|
||||
'introspection_endpoint_auth_signing_alg_values_supported',
|
||||
'code_challenge_methods_supported',
|
||||
]
|
||||
|
||||
def validate_issuer(self):
|
||||
"""REQUIRED. The authorization server's issuer identifier, which is
|
||||
a URL that uses the "https" scheme and has no query or fragment
|
||||
components.
|
||||
"""
|
||||
issuer = self.get('issuer')
|
||||
|
||||
#: 1. REQUIRED
|
||||
if not issuer:
|
||||
raise ValueError('"issuer" is required')
|
||||
|
||||
parsed = urlparse.urlparse(issuer)
|
||||
|
||||
#: 2. uses the "https" scheme
|
||||
if not is_secure_transport(issuer):
|
||||
raise ValueError('"issuer" MUST use "https" scheme')
|
||||
|
||||
#: 3. has no query or fragment
|
||||
if parsed.query or parsed.fragment:
|
||||
raise ValueError('"issuer" has no query or fragment')
|
||||
|
||||
def validate_authorization_endpoint(self):
|
||||
"""URL of the authorization server's authorization endpoint
|
||||
[RFC6749]. This is REQUIRED unless no grant types are supported
|
||||
that use the authorization endpoint.
|
||||
"""
|
||||
url = self.get('authorization_endpoint')
|
||||
if url:
|
||||
if not is_secure_transport(url):
|
||||
raise ValueError(
|
||||
'"authorization_endpoint" MUST use "https" scheme')
|
||||
return
|
||||
|
||||
grant_types_supported = set(self.grant_types_supported)
|
||||
authorization_grant_types = {'authorization_code', 'implicit'}
|
||||
if grant_types_supported & authorization_grant_types:
|
||||
raise ValueError('"authorization_endpoint" is required')
|
||||
|
||||
def validate_token_endpoint(self):
|
||||
"""URL of the authorization server's token endpoint [RFC6749]. This
|
||||
is REQUIRED unless only the implicit grant type is supported.
|
||||
"""
|
||||
grant_types_supported = self.get('grant_types_supported')
|
||||
if grant_types_supported and len(grant_types_supported) == 1 and \
|
||||
grant_types_supported[0] == 'implicit':
|
||||
return
|
||||
|
||||
url = self.get('token_endpoint')
|
||||
if not url:
|
||||
raise ValueError('"token_endpoint" is required')
|
||||
|
||||
if not is_secure_transport(url):
|
||||
raise ValueError('"token_endpoint" MUST use "https" scheme')
|
||||
|
||||
def validate_jwks_uri(self):
|
||||
"""OPTIONAL. URL of the authorization server's JWK Set [JWK]
|
||||
document. The referenced document contains the signing key(s) the
|
||||
client uses to validate signatures from the authorization server.
|
||||
This URL MUST use the "https" scheme. The JWK Set MAY also
|
||||
contain the server's encryption key or keys, which are used by
|
||||
clients to encrypt requests to the server. When both signing and
|
||||
encryption keys are made available, a "use" (public key use)
|
||||
parameter value is REQUIRED for all keys in the referenced JWK Set
|
||||
to indicate each key's intended usage.
|
||||
"""
|
||||
url = self.get('jwks_uri')
|
||||
if url and not is_secure_transport(url):
|
||||
raise ValueError('"jwks_uri" MUST use "https" scheme')
|
||||
|
||||
def validate_registration_endpoint(self):
|
||||
"""OPTIONAL. URL of the authorization server's OAuth 2.0 Dynamic
|
||||
Client Registration endpoint [RFC7591].
|
||||
"""
|
||||
url = self.get('registration_endpoint')
|
||||
if url and not is_secure_transport(url):
|
||||
raise ValueError(
|
||||
'"registration_endpoint" MUST use "https" scheme')
|
||||
|
||||
def validate_scopes_supported(self):
|
||||
"""RECOMMENDED. JSON array containing a list of the OAuth 2.0
|
||||
[RFC6749] "scope" values that this authorization server supports.
|
||||
Servers MAY choose not to advertise some supported scope values
|
||||
even when this parameter is used.
|
||||
"""
|
||||
validate_array_value(self, 'scopes_supported')
|
||||
|
||||
def validate_response_types_supported(self):
|
||||
"""REQUIRED. JSON array containing a list of the OAuth 2.0
|
||||
"response_type" values that this authorization server supports.
|
||||
The array values used are the same as those used with the
|
||||
"response_types" parameter defined by "OAuth 2.0 Dynamic Client
|
||||
Registration Protocol" [RFC7591].
|
||||
"""
|
||||
response_types_supported = self.get('response_types_supported')
|
||||
if not response_types_supported:
|
||||
raise ValueError('"response_types_supported" is required')
|
||||
if not isinstance(response_types_supported, list):
|
||||
raise ValueError('"response_types_supported" MUST be JSON array')
|
||||
|
||||
def validate_response_modes_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of the OAuth 2.0
|
||||
"response_mode" values that this authorization server supports, as
|
||||
specified in "OAuth 2.0 Multiple Response Type Encoding Practices"
|
||||
[OAuth.Responses]. If omitted, the default is "["query",
|
||||
"fragment"]". The response mode value "form_post" is also defined
|
||||
in "OAuth 2.0 Form Post Response Mode" [OAuth.Post].
|
||||
"""
|
||||
validate_array_value(self, 'response_modes_supported')
|
||||
|
||||
def validate_grant_types_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of the OAuth 2.0 grant
|
||||
type values that this authorization server supports. The array
|
||||
values used are the same as those used with the "grant_types"
|
||||
parameter defined by "OAuth 2.0 Dynamic Client Registration
|
||||
Protocol" [RFC7591]. If omitted, the default value is
|
||||
"["authorization_code", "implicit"]".
|
||||
"""
|
||||
validate_array_value(self, 'grant_types_supported')
|
||||
|
||||
def validate_token_endpoint_auth_methods_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of client authentication
|
||||
methods supported by this token endpoint. Client authentication
|
||||
method values are used in the "token_endpoint_auth_method"
|
||||
parameter defined in Section 2 of [RFC7591]. If omitted, the
|
||||
default is "client_secret_basic" -- the HTTP Basic Authentication
|
||||
Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
|
||||
"""
|
||||
validate_array_value(self, 'token_endpoint_auth_methods_supported')
|
||||
|
||||
def validate_token_endpoint_auth_signing_alg_values_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of the JWS signing
|
||||
algorithms ("alg" values) supported by the token endpoint for the
|
||||
signature on the JWT [JWT] used to authenticate the client at the
|
||||
token endpoint for the "private_key_jwt" and "client_secret_jwt"
|
||||
authentication methods. This metadata entry MUST be present if
|
||||
either of these authentication methods are specified in the
|
||||
"token_endpoint_auth_methods_supported" entry. No default
|
||||
algorithms are implied if this entry is omitted. Servers SHOULD
|
||||
support "RS256". The value "none" MUST NOT be used.
|
||||
"""
|
||||
_validate_alg_values(
|
||||
self,
|
||||
'token_endpoint_auth_signing_alg_values_supported',
|
||||
self.token_endpoint_auth_methods_supported
|
||||
)
|
||||
|
||||
def validate_service_documentation(self):
|
||||
"""OPTIONAL. URL of a page containing human-readable information
|
||||
that developers might want or need to know when using the
|
||||
authorization server. In particular, if the authorization server
|
||||
does not support Dynamic Client Registration, then information on
|
||||
how to register clients needs to be provided in this
|
||||
documentation.
|
||||
"""
|
||||
value = self.get('service_documentation')
|
||||
if value and not is_valid_url(value):
|
||||
raise ValueError('"service_documentation" MUST be a URL')
|
||||
|
||||
def validate_ui_locales_supported(self):
|
||||
"""OPTIONAL. Languages and scripts supported for the user interface,
|
||||
represented as a JSON array of language tag values from BCP 47
|
||||
[RFC5646]. If omitted, the set of supported languages and scripts
|
||||
is unspecified.
|
||||
"""
|
||||
validate_array_value(self, 'ui_locales_supported')
|
||||
|
||||
def validate_op_policy_uri(self):
|
||||
"""OPTIONAL. URL that the authorization server provides to the
|
||||
person registering the client to read about the authorization
|
||||
server's requirements on how the client can use the data provided
|
||||
by the authorization server. The registration process SHOULD
|
||||
display this URL to the person registering the client if it is
|
||||
given. As described in Section 5, despite the identifier
|
||||
"op_policy_uri" appearing to be OpenID-specific, its usage in this
|
||||
specification is actually referring to a general OAuth 2.0 feature
|
||||
that is not specific to OpenID Connect.
|
||||
"""
|
||||
value = self.get('op_policy_uri')
|
||||
if value and not is_valid_url(value):
|
||||
raise ValueError('"op_policy_uri" MUST be a URL')
|
||||
|
||||
def validate_op_tos_uri(self):
|
||||
"""OPTIONAL. URL that the authorization server provides to the
|
||||
person registering the client to read about the authorization
|
||||
server's terms of service. The registration process SHOULD
|
||||
display this URL to the person registering the client if it is
|
||||
given. As described in Section 5, despite the identifier
|
||||
"op_tos_uri", appearing to be OpenID-specific, its usage in this
|
||||
specification is actually referring to a general OAuth 2.0 feature
|
||||
that is not specific to OpenID Connect.
|
||||
"""
|
||||
value = self.get('op_tos_uri')
|
||||
if value and not is_valid_url(value):
|
||||
raise ValueError('"op_tos_uri" MUST be a URL')
|
||||
|
||||
def validate_revocation_endpoint(self):
|
||||
"""OPTIONAL. URL of the authorization server's OAuth 2.0 revocation
|
||||
endpoint [RFC7009]."""
|
||||
url = self.get('revocation_endpoint')
|
||||
if url and not is_secure_transport(url):
|
||||
raise ValueError('"revocation_endpoint" MUST use "https" scheme')
|
||||
|
||||
def validate_revocation_endpoint_auth_methods_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of client authentication
|
||||
methods supported by this revocation endpoint. The valid client
|
||||
authentication method values are those registered in the IANA
|
||||
"OAuth Token Endpoint Authentication Methods" registry
|
||||
[IANA.OAuth.Parameters]. If omitted, the default is
|
||||
"client_secret_basic" -- the HTTP Basic Authentication Scheme
|
||||
specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
|
||||
"""
|
||||
validate_array_value(self, 'revocation_endpoint_auth_methods_supported')
|
||||
|
||||
def validate_revocation_endpoint_auth_signing_alg_values_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of the JWS signing
|
||||
algorithms ("alg" values) supported by the revocation endpoint for
|
||||
the signature on the JWT [JWT] used to authenticate the client at
|
||||
the revocation endpoint for the "private_key_jwt" and
|
||||
"client_secret_jwt" authentication methods. This metadata entry
|
||||
MUST be present if either of these authentication methods are
|
||||
specified in the "revocation_endpoint_auth_methods_supported"
|
||||
entry. No default algorithms are implied if this entry is
|
||||
omitted. The value "none" MUST NOT be used.
|
||||
"""
|
||||
_validate_alg_values(
|
||||
self,
|
||||
'revocation_endpoint_auth_signing_alg_values_supported',
|
||||
self.revocation_endpoint_auth_methods_supported
|
||||
)
|
||||
|
||||
def validate_introspection_endpoint(self):
|
||||
"""OPTIONAL. URL of the authorization server's OAuth 2.0
|
||||
introspection endpoint [RFC7662].
|
||||
"""
|
||||
url = self.get('introspection_endpoint')
|
||||
if url and not is_secure_transport(url):
|
||||
raise ValueError(
|
||||
'"introspection_endpoint" MUST use "https" scheme')
|
||||
|
||||
def validate_introspection_endpoint_auth_methods_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of client authentication
|
||||
methods supported by this introspection endpoint. The valid
|
||||
client authentication method values are those registered in the
|
||||
IANA "OAuth Token Endpoint Authentication Methods" registry
|
||||
[IANA.OAuth.Parameters] or those registered in the IANA "OAuth
|
||||
Access Token Types" registry [IANA.OAuth.Parameters]. (These
|
||||
values are and will remain distinct, due to Section 7.2.) If
|
||||
omitted, the set of supported authentication methods MUST be
|
||||
determined by other means.
|
||||
"""
|
||||
validate_array_value(self, 'introspection_endpoint_auth_methods_supported')
|
||||
|
||||
def validate_introspection_endpoint_auth_signing_alg_values_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of the JWS signing
|
||||
algorithms ("alg" values) supported by the introspection endpoint
|
||||
for the signature on the JWT [JWT] used to authenticate the client
|
||||
at the introspection endpoint for the "private_key_jwt" and
|
||||
"client_secret_jwt" authentication methods. This metadata entry
|
||||
MUST be present if either of these authentication methods are
|
||||
specified in the "introspection_endpoint_auth_methods_supported"
|
||||
entry. No default algorithms are implied if this entry is
|
||||
omitted. The value "none" MUST NOT be used.
|
||||
"""
|
||||
_validate_alg_values(
|
||||
self,
|
||||
'introspection_endpoint_auth_signing_alg_values_supported',
|
||||
self.introspection_endpoint_auth_methods_supported
|
||||
)
|
||||
|
||||
def validate_code_challenge_methods_supported(self):
|
||||
"""OPTIONAL. JSON array containing a list of Proof Key for Code
|
||||
Exchange (PKCE) [RFC7636] code challenge methods supported by this
|
||||
authorization server. Code challenge method values are used in
|
||||
the "code_challenge_method" parameter defined in Section 4.3 of
|
||||
[RFC7636]. The valid code challenge method values are those
|
||||
registered in the IANA "PKCE Code Challenge Methods" registry
|
||||
[IANA.OAuth.Parameters]. If omitted, the authorization server
|
||||
does not support PKCE.
|
||||
"""
|
||||
validate_array_value(self, 'code_challenge_methods_supported')
|
||||
|
||||
@property
|
||||
def response_modes_supported(self):
|
||||
#: If omitted, the default is ["query", "fragment"]
|
||||
return self.get('response_modes_supported', ["query", "fragment"])
|
||||
|
||||
@property
|
||||
def grant_types_supported(self):
|
||||
#: If omitted, the default value is ["authorization_code", "implicit"]
|
||||
return self.get('grant_types_supported', ["authorization_code", "implicit"])
|
||||
|
||||
@property
|
||||
def token_endpoint_auth_methods_supported(self):
|
||||
#: If omitted, the default is "client_secret_basic"
|
||||
return self.get('token_endpoint_auth_methods_supported', ["client_secret_basic"])
|
||||
|
||||
@property
|
||||
def revocation_endpoint_auth_methods_supported(self):
|
||||
#: If omitted, the default is "client_secret_basic"
|
||||
return self.get('revocation_endpoint_auth_methods_supported', ["client_secret_basic"])
|
||||
|
||||
@property
|
||||
def introspection_endpoint_auth_methods_supported(self):
|
||||
#: If omitted, the set of supported authentication methods MUST be
|
||||
#: determined by other means
|
||||
#: here, we use "client_secret_basic"
|
||||
return self.get('introspection_endpoint_auth_methods_supported', ["client_secret_basic"])
|
||||
|
||||
def validate(self):
|
||||
"""Validate all server metadata value."""
|
||||
for key in self.REGISTRY_KEYS:
|
||||
object.__getattribute__(self, f'validate_{key}')()
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return object.__getattribute__(self, key)
|
||||
except AttributeError as error:
|
||||
if key in self.REGISTRY_KEYS:
|
||||
return self.get(key)
|
||||
raise error
|
||||
|
||||
|
||||
def _validate_alg_values(data, key, auth_methods_supported):
|
||||
value = data.get(key)
|
||||
if value and not isinstance(value, list):
|
||||
raise ValueError(f'"{key}" MUST be JSON array')
|
||||
|
||||
auth_methods = set(auth_methods_supported)
|
||||
jwt_auth_methods = {'private_key_jwt', 'client_secret_jwt'}
|
||||
if auth_methods & jwt_auth_methods:
|
||||
if not value:
|
||||
raise ValueError(f'"{key}" is required')
|
||||
|
||||
if value and 'none' in value:
|
||||
raise ValueError(
|
||||
f'the value "none" MUST NOT be used in "{key}"')
|
||||
|
||||
|
||||
def validate_array_value(metadata, key):
|
||||
values = metadata.get(key)
|
||||
if values is not None and not isinstance(values, list):
|
||||
raise ValueError(f'"{key}" MUST be JSON array')
|
||||
Reference in New Issue
Block a user