Initial commit: Email alerts application
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
oauthlib.openid
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
from .connect.core.endpoints import Server, UserInfoEndpoint
|
||||
from .connect.core.request_validator import RequestValidator
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
oauthlib.oopenid.core
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OpenID Connect
|
||||
"""
|
||||
from .pre_configured import Server
|
||||
from .userinfo import UserInfoEndpoint
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+139
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.endpoints.pre_configured
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various endpoints needed
|
||||
for providing OpenID Connect servers.
|
||||
"""
|
||||
|
||||
from oauthlib.oauth2.rfc6749.endpoints import (
|
||||
AuthorizationEndpoint,
|
||||
IntrospectEndpoint,
|
||||
ResourceEndpoint,
|
||||
RevocationEndpoint,
|
||||
TokenEndpoint,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.grant_types import (
|
||||
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||
ClientCredentialsGrant,
|
||||
ImplicitGrant as OAuth2ImplicitGrant,
|
||||
ResourceOwnerPasswordCredentialsGrant,
|
||||
)
|
||||
from oauthlib.oauth2.rfc8628.grant_types import DeviceCodeGrant
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
|
||||
from ..grant_types import (
|
||||
AuthorizationCodeGrant,
|
||||
HybridGrant,
|
||||
ImplicitGrant,
|
||||
RefreshTokenGrant,
|
||||
)
|
||||
from ..grant_types.dispatchers import (
|
||||
AuthorizationCodeGrantDispatcher,
|
||||
AuthorizationTokenGrantDispatcher,
|
||||
ImplicitTokenGrantDispatcher,
|
||||
)
|
||||
from ..tokens import JWTToken
|
||||
from .userinfo import UserInfoEndpoint
|
||||
|
||||
|
||||
class Server(
|
||||
AuthorizationEndpoint,
|
||||
IntrospectEndpoint,
|
||||
TokenEndpoint,
|
||||
ResourceEndpoint,
|
||||
RevocationEndpoint,
|
||||
UserInfoEndpoint,
|
||||
):
|
||||
"""
|
||||
An all-in-one endpoint featuring all four major grant types
|
||||
and extension grants.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request_validator,
|
||||
token_expires_in=None,
|
||||
token_generator=None,
|
||||
refresh_token_generator=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""Construct a new all-grants-in-one server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
self.auth_grant = OAuth2AuthorizationCodeGrant(request_validator)
|
||||
self.implicit_grant = OAuth2ImplicitGrant(request_validator)
|
||||
self.password_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
|
||||
self.credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
self.refresh_grant = RefreshTokenGrant(request_validator)
|
||||
self.openid_connect_auth = AuthorizationCodeGrant(request_validator)
|
||||
self.openid_connect_implicit = ImplicitGrant(request_validator)
|
||||
self.openid_connect_hybrid = HybridGrant(request_validator)
|
||||
self.device_code_grant = DeviceCodeGrant(request_validator, **kwargs)
|
||||
|
||||
self.bearer = BearerToken(
|
||||
request_validator, token_generator, token_expires_in, refresh_token_generator
|
||||
)
|
||||
|
||||
self.jwt = JWTToken(
|
||||
request_validator, token_generator, token_expires_in, refresh_token_generator
|
||||
)
|
||||
|
||||
self.auth_grant_choice = AuthorizationCodeGrantDispatcher(
|
||||
default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth
|
||||
)
|
||||
self.implicit_grant_choice = ImplicitTokenGrantDispatcher(
|
||||
default_grant=self.implicit_grant, oidc_grant=self.openid_connect_implicit
|
||||
)
|
||||
|
||||
# See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations
|
||||
# internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination
|
||||
AuthorizationEndpoint.__init__(
|
||||
self,
|
||||
default_response_type="code",
|
||||
response_types={
|
||||
"code": self.auth_grant_choice,
|
||||
"token": self.implicit_grant_choice,
|
||||
"id_token": self.openid_connect_implicit,
|
||||
"id_token token": self.openid_connect_implicit,
|
||||
"code token": self.openid_connect_hybrid,
|
||||
"code id_token": self.openid_connect_hybrid,
|
||||
"code id_token token": self.openid_connect_hybrid,
|
||||
"none": self.auth_grant,
|
||||
},
|
||||
default_token_type=self.bearer,
|
||||
)
|
||||
|
||||
self.token_grant_choice = AuthorizationTokenGrantDispatcher(
|
||||
request_validator, default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth
|
||||
)
|
||||
|
||||
TokenEndpoint.__init__(
|
||||
self,
|
||||
default_grant_type="authorization_code",
|
||||
grant_types={
|
||||
"authorization_code": self.token_grant_choice,
|
||||
"password": self.password_grant,
|
||||
"client_credentials": self.credentials_grant,
|
||||
"refresh_token": self.refresh_grant,
|
||||
"urn:ietf:params:oauth:grant-type:device_code": self.device_code_grant,
|
||||
},
|
||||
default_token_type=self.bearer,
|
||||
)
|
||||
ResourceEndpoint.__init__(
|
||||
self, default_token="Bearer", token_types={"Bearer": self.bearer, "JWT": self.jwt}
|
||||
)
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
IntrospectEndpoint.__init__(self, request_validator)
|
||||
UserInfoEndpoint.__init__(self, request_validator)
|
||||
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.endpoints.userinfo
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of userinfo endpoint.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
from oauthlib.oauth2.rfc6749 import errors
|
||||
from oauthlib.oauth2.rfc6749.endpoints.base import (
|
||||
BaseEndpoint, catch_errors_and_unavailability,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserInfoEndpoint(BaseEndpoint):
|
||||
"""Authorizes access to userinfo resource.
|
||||
"""
|
||||
def __init__(self, request_validator):
|
||||
self.bearer = BearerToken(request_validator, None, None, None)
|
||||
self.request_validator = request_validator
|
||||
BaseEndpoint.__init__(self)
|
||||
|
||||
@catch_errors_and_unavailability
|
||||
def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None):
|
||||
"""Validate BearerToken and return userinfo from RequestValidator
|
||||
|
||||
The UserInfo Endpoint MUST return a
|
||||
content-type header to indicate which format is being returned. The
|
||||
content-type of the HTTP response MUST be application/json if the
|
||||
response body is a text JSON object; the response body SHOULD be encoded
|
||||
using UTF-8.
|
||||
"""
|
||||
request = Request(uri, http_method, body, headers)
|
||||
request.scopes = ["openid"]
|
||||
self.validate_userinfo_request(request)
|
||||
|
||||
claims = self.request_validator.get_userinfo_claims(request)
|
||||
if claims is None:
|
||||
log.error('Userinfo MUST have claims for %r.', request)
|
||||
raise errors.ServerError(status_code=500)
|
||||
|
||||
if isinstance(claims, dict):
|
||||
resp_headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if "sub" not in claims:
|
||||
log.error('Userinfo MUST have "sub" for %r.', request)
|
||||
raise errors.ServerError(status_code=500)
|
||||
body = json.dumps(claims)
|
||||
elif isinstance(claims, str):
|
||||
resp_headers = {
|
||||
'Content-Type': 'application/jwt'
|
||||
}
|
||||
body = claims
|
||||
else:
|
||||
log.error('Userinfo return unknown response for %r.', request)
|
||||
raise errors.ServerError(status_code=500)
|
||||
log.debug('Userinfo access valid for %r.', request)
|
||||
return resp_headers, body, 200
|
||||
|
||||
def validate_userinfo_request(self, request):
|
||||
"""Ensure the request is valid.
|
||||
|
||||
5.3.1. UserInfo Request
|
||||
The Client sends the UserInfo Request using either HTTP GET or HTTP
|
||||
POST. The Access Token obtained from an OpenID Connect Authentication
|
||||
Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0
|
||||
Bearer Token Usage [RFC6750].
|
||||
|
||||
It is RECOMMENDED that the request use the HTTP GET method and the
|
||||
Access Token be sent using the Authorization header field.
|
||||
|
||||
The following is a non-normative example of a UserInfo Request:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
GET /userinfo HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Bearer SlAV32hkKG
|
||||
|
||||
5.3.3. UserInfo Error Response
|
||||
When an error condition occurs, the UserInfo Endpoint returns an Error
|
||||
Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage
|
||||
[RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
|
||||
Agent using the appropriate HTTP status code.)
|
||||
|
||||
The following is a non-normative example of a UserInfo Error Response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer error="invalid_token",
|
||||
error_description="The Access Token expired"
|
||||
|
||||
.. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2
|
||||
.. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
||||
"""
|
||||
if not self.bearer.validate_request(request):
|
||||
raise errors.InvalidTokenError()
|
||||
if "openid" not in request.scopes:
|
||||
raise errors.InsufficientScopeError()
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.errors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Error used both by OAuth 2 clients and providers to represent the spec
|
||||
defined error responses for all four core grant types.
|
||||
"""
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error
|
||||
|
||||
|
||||
class FatalOpenIDClientError(FatalClientError):
|
||||
pass
|
||||
|
||||
|
||||
class OpenIDClientError(OAuth2Error):
|
||||
pass
|
||||
|
||||
|
||||
class InteractionRequired(OpenIDClientError):
|
||||
"""
|
||||
The Authorization Server requires End-User interaction to proceed.
|
||||
|
||||
This error MAY be returned when the prompt parameter value in the
|
||||
Authentication Request is none, but the Authentication Request cannot be
|
||||
completed without displaying a user interface for End-User interaction.
|
||||
"""
|
||||
error = 'interaction_required'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class LoginRequired(OpenIDClientError):
|
||||
"""
|
||||
The Authorization Server requires End-User authentication.
|
||||
|
||||
This error MAY be returned when the prompt parameter value in the
|
||||
Authentication Request is none, but the Authentication Request cannot be
|
||||
completed without displaying a user interface for End-User authentication.
|
||||
"""
|
||||
error = 'login_required'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class AccountSelectionRequired(OpenIDClientError):
|
||||
"""
|
||||
The End-User is REQUIRED to select a session at the Authorization Server.
|
||||
|
||||
The End-User MAY be authenticated at the Authorization Server with
|
||||
different associated accounts, but the End-User did not select a session.
|
||||
This error MAY be returned when the prompt parameter value in the
|
||||
Authentication Request is none, but the Authentication Request cannot be
|
||||
completed without displaying a user interface to prompt for a session to
|
||||
use.
|
||||
"""
|
||||
error = 'account_selection_required'
|
||||
|
||||
|
||||
class ConsentRequired(OpenIDClientError):
|
||||
"""
|
||||
The Authorization Server requires End-User consent.
|
||||
|
||||
This error MAY be returned when the prompt parameter value in the
|
||||
Authentication Request is none, but the Authentication Request cannot be
|
||||
completed without displaying a user interface for End-User consent.
|
||||
"""
|
||||
error = 'consent_required'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class InvalidRequestURI(OpenIDClientError):
|
||||
"""
|
||||
The request_uri in the Authorization Request returns an error or
|
||||
contains invalid data.
|
||||
"""
|
||||
error = 'invalid_request_uri'
|
||||
description = ('The request_uri in the Authorization Request returns an '
|
||||
'error or contains invalid data.')
|
||||
|
||||
|
||||
class InvalidRequestObject(OpenIDClientError):
|
||||
"""
|
||||
The request parameter contains an invalid Request Object.
|
||||
"""
|
||||
error = 'invalid_request_object'
|
||||
description = 'The request parameter contains an invalid Request Object.'
|
||||
|
||||
|
||||
class RequestNotSupported(OpenIDClientError):
|
||||
"""
|
||||
The OP does not support use of the request parameter.
|
||||
"""
|
||||
error = 'request_not_supported'
|
||||
description = 'The request parameter is not supported.'
|
||||
|
||||
|
||||
class RequestURINotSupported(OpenIDClientError):
|
||||
"""
|
||||
The OP does not support use of the request_uri parameter.
|
||||
"""
|
||||
error = 'request_uri_not_supported'
|
||||
description = 'The request_uri parameter is not supported.'
|
||||
|
||||
|
||||
class RegistrationNotSupported(OpenIDClientError):
|
||||
"""
|
||||
The OP does not support use of the registration parameter.
|
||||
"""
|
||||
error = 'registration_not_supported'
|
||||
description = 'The registration parameter is not supported.'
|
||||
|
||||
|
||||
class InvalidTokenError(OAuth2Error):
|
||||
"""
|
||||
The access token provided is expired, revoked, malformed, or
|
||||
invalid for other reasons. The resource SHOULD respond with
|
||||
the HTTP 401 (Unauthorized) status code. The client MAY
|
||||
request a new access token and retry the protected resource
|
||||
request.
|
||||
"""
|
||||
error = 'invalid_token'
|
||||
status_code = 401
|
||||
description = ("The access token provided is expired, revoked, malformed, "
|
||||
"or invalid for other reasons.")
|
||||
|
||||
|
||||
class InsufficientScopeError(OAuth2Error):
|
||||
"""
|
||||
The request requires higher privileges than provided by the
|
||||
access token. The resource server SHOULD respond with the HTTP
|
||||
403 (Forbidden) status code and MAY include the "scope"
|
||||
attribute with the scope necessary to access the protected
|
||||
resource.
|
||||
"""
|
||||
error = 'insufficient_scope'
|
||||
status_code = 403
|
||||
description = ("The request requires higher privileges than provided by "
|
||||
"the access token.")
|
||||
|
||||
|
||||
def raise_from_error(error, params=None):
|
||||
kwargs = {
|
||||
'description': params.get('error_description'),
|
||||
'uri': params.get('error_uri'),
|
||||
'state': params.get('state')
|
||||
}
|
||||
for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
|
||||
if cls.error == error:
|
||||
raise cls(**kwargs)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from .authorization_code import AuthorizationCodeGrant
|
||||
from .base import GrantTypeBase
|
||||
from .dispatchers import (
|
||||
AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
|
||||
ImplicitTokenGrantDispatcher,
|
||||
)
|
||||
from .hybrid import HybridGrant
|
||||
from .implicit import ImplicitGrant
|
||||
from .refresh_token import RefreshTokenGrant
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+43
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
|
||||
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthorizationCodeGrant(GrantTypeBase):
|
||||
|
||||
def __init__(self, request_validator=None, **kwargs):
|
||||
self.proxy_target = OAuth2AuthorizationCodeGrant(
|
||||
request_validator=request_validator, **kwargs)
|
||||
self.custom_validators.post_auth.append(
|
||||
self.openid_authorization_validator)
|
||||
self.register_token_modifier(self.add_id_token)
|
||||
|
||||
def add_id_token(self, token, token_handler, request):
|
||||
"""
|
||||
Construct an initial version of id_token, and let the
|
||||
request_validator sign or encrypt it.
|
||||
|
||||
The authorization_code version of this method is used to
|
||||
retrieve the nonce accordingly to the code storage.
|
||||
"""
|
||||
# Treat it as normal OAuth 2 auth code request if openid is not present
|
||||
if not request.scopes or 'openid' not in request.scopes:
|
||||
return token
|
||||
|
||||
nonce = self.request_validator.get_authorization_code_nonce(
|
||||
request.client_id,
|
||||
request.code,
|
||||
request.redirect_uri,
|
||||
request
|
||||
)
|
||||
return super().add_id_token(token, token_handler, request, nonce=nonce)
|
||||
@@ -0,0 +1,330 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
from json import loads
|
||||
|
||||
from oauthlib.oauth2.rfc6749.errors import (
|
||||
ConsentRequired, InvalidRequestError, LoginRequired,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GrantTypeBase:
|
||||
|
||||
# Just proxy the majority of method calls through to the
|
||||
# proxy_target grant type handler, which will usually be either
|
||||
# the standard OAuth2 AuthCode or Implicit grant types.
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.proxy_target, attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
proxied_attrs = {'refresh_token', 'response_types'}
|
||||
if attr in proxied_attrs:
|
||||
setattr(self.proxy_target, attr, value)
|
||||
else:
|
||||
super(OpenIDConnectBase, self).__setattr__(attr, value)
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Validates the OpenID Connect authorization request parameters.
|
||||
|
||||
:returns: (list of scopes, dict of request info)
|
||||
"""
|
||||
return self.proxy_target.validate_authorization_request(request)
|
||||
|
||||
def _inflate_claims(self, request):
|
||||
# this may be called multiple times in a single request so make sure we only de-serialize the claims once
|
||||
if request.claims and not isinstance(request.claims, dict):
|
||||
# specific claims are requested during the Authorization Request and may be requested for inclusion
|
||||
# in either the id_token or the UserInfo endpoint response
|
||||
# see http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
|
||||
try:
|
||||
request.claims = loads(request.claims)
|
||||
except Exception as ex:
|
||||
raise InvalidRequestError(description="Malformed claims parameter",
|
||||
uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter")
|
||||
|
||||
def id_token_hash(self, value, hashfunc=hashlib.sha256):
|
||||
"""
|
||||
Its value is the base64url encoding of the left-most half of the
|
||||
hash of the octets of the ASCII representation of the access_token
|
||||
value, where the hash algorithm used is the hash algorithm used in
|
||||
the alg Header Parameter of the ID Token's JOSE Header.
|
||||
|
||||
For instance, if the alg is RS256, hash the access_token value
|
||||
with SHA-256, then take the left-most 128 bits and
|
||||
base64url-encode them.
|
||||
For instance, if the alg is HS512, hash the code value with
|
||||
SHA-512, then take the left-most 256 bits and base64url-encode
|
||||
them. The c_hash value is a case-sensitive string.
|
||||
|
||||
Example of hash from OIDC specification (bound to a JWS using RS256):
|
||||
|
||||
code:
|
||||
Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
|
||||
|
||||
c_hash:
|
||||
LDktKdoQak3Pk0cnXxCltA
|
||||
"""
|
||||
digest = hashfunc(value.encode()).digest()
|
||||
left_most = len(digest) // 2
|
||||
return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=")
|
||||
|
||||
def add_id_token(self, token, token_handler, request, nonce=None):
|
||||
"""
|
||||
Construct an initial version of id_token, and let the
|
||||
request_validator sign or encrypt it.
|
||||
|
||||
The initial version can contain the fields below, accordingly
|
||||
to the spec:
|
||||
- aud
|
||||
- iat
|
||||
- nonce
|
||||
- at_hash
|
||||
- c_hash
|
||||
"""
|
||||
# Treat it as normal OAuth 2 auth code request if openid is not present
|
||||
if not request.scopes or 'openid' not in request.scopes:
|
||||
return token
|
||||
|
||||
# Only add an id token on auth/token step if asked for.
|
||||
if request.response_type and 'id_token' not in request.response_type:
|
||||
return token
|
||||
|
||||
# Implementation mint its own id_token without help.
|
||||
id_token = self.request_validator.get_id_token(token, token_handler, request)
|
||||
if id_token:
|
||||
token['id_token'] = id_token
|
||||
return token
|
||||
|
||||
# Fallback for asking some help from oauthlib framework.
|
||||
# Start with technicals fields bound to the specification.
|
||||
id_token = {}
|
||||
id_token['aud'] = request.client_id
|
||||
id_token['iat'] = int(time.time())
|
||||
|
||||
# nonce is REQUIRED when response_type value is:
|
||||
# - id_token token (Implicit)
|
||||
# - id_token (Implicit)
|
||||
# - code id_token (Hybrid)
|
||||
# - code id_token token (Hybrid)
|
||||
#
|
||||
# nonce is OPTIONAL when response_type value is:
|
||||
# - code (Authorization Code)
|
||||
# - code token (Hybrid)
|
||||
if nonce is not None:
|
||||
id_token["nonce"] = nonce
|
||||
|
||||
# at_hash is REQUIRED when response_type value is:
|
||||
# - id_token token (Implicit)
|
||||
# - code id_token token (Hybrid)
|
||||
#
|
||||
# at_hash is OPTIONAL when:
|
||||
# - code (Authorization code)
|
||||
# - code id_token (Hybrid)
|
||||
# - code token (Hybrid)
|
||||
#
|
||||
# at_hash MAY NOT be used when:
|
||||
# - id_token (Implicit)
|
||||
if "access_token" in token:
|
||||
id_token["at_hash"] = self.id_token_hash(token["access_token"])
|
||||
|
||||
# c_hash is REQUIRED when response_type value is:
|
||||
# - code id_token (Hybrid)
|
||||
# - code id_token token (Hybrid)
|
||||
#
|
||||
# c_hash is OPTIONAL for others.
|
||||
if "code" in token:
|
||||
id_token["c_hash"] = self.id_token_hash(token["code"])
|
||||
|
||||
# Call request_validator to complete/sign/encrypt id_token
|
||||
token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request)
|
||||
|
||||
return token
|
||||
|
||||
def openid_authorization_validator(self, request):
|
||||
"""Perform OpenID Connect specific authorization request validation.
|
||||
|
||||
nonce
|
||||
OPTIONAL. String value used to associate a Client session with
|
||||
an ID Token, and to mitigate replay attacks. The value is
|
||||
passed through unmodified from the Authentication Request to
|
||||
the ID Token. Sufficient entropy MUST be present in the nonce
|
||||
values used to prevent attackers from guessing values
|
||||
|
||||
display
|
||||
OPTIONAL. ASCII string value that specifies how the
|
||||
Authorization Server displays the authentication and consent
|
||||
user interface pages to the End-User. The defined values are:
|
||||
|
||||
page - The Authorization Server SHOULD display the
|
||||
authentication and consent UI consistent with a full User
|
||||
Agent page view. If the display parameter is not specified,
|
||||
this is the default display mode.
|
||||
|
||||
popup - The Authorization Server SHOULD display the
|
||||
authentication and consent UI consistent with a popup User
|
||||
Agent window. The popup User Agent window should be of an
|
||||
appropriate size for a login-focused dialog and should not
|
||||
obscure the entire window that it is popping up over.
|
||||
|
||||
touch - The Authorization Server SHOULD display the
|
||||
authentication and consent UI consistent with a device that
|
||||
leverages a touch interface.
|
||||
|
||||
wap - The Authorization Server SHOULD display the
|
||||
authentication and consent UI consistent with a "feature
|
||||
phone" type display.
|
||||
|
||||
The Authorization Server MAY also attempt to detect the
|
||||
capabilities of the User Agent and present an appropriate
|
||||
display.
|
||||
|
||||
prompt
|
||||
OPTIONAL. Space delimited, case sensitive list of ASCII string
|
||||
values that specifies whether the Authorization Server prompts
|
||||
the End-User for reauthentication and consent. The defined
|
||||
values are:
|
||||
|
||||
none - The Authorization Server MUST NOT display any
|
||||
authentication or consent user interface pages. An error is
|
||||
returned if an End-User is not already authenticated or the
|
||||
Client does not have pre-configured consent for the
|
||||
requested Claims or does not fulfill other conditions for
|
||||
processing the request. The error code will typically be
|
||||
login_required, interaction_required, or another code
|
||||
defined in Section 3.1.2.6. This can be used as a method to
|
||||
check for existing authentication and/or consent.
|
||||
|
||||
login - The Authorization Server SHOULD prompt the End-User
|
||||
for reauthentication. If it cannot reauthenticate the
|
||||
End-User, it MUST return an error, typically
|
||||
login_required.
|
||||
|
||||
consent - The Authorization Server SHOULD prompt the
|
||||
End-User for consent before returning information to the
|
||||
Client. If it cannot obtain consent, it MUST return an
|
||||
error, typically consent_required.
|
||||
|
||||
select_account - The Authorization Server SHOULD prompt the
|
||||
End-User to select a user account. This enables an End-User
|
||||
who has multiple accounts at the Authorization Server to
|
||||
select amongst the multiple accounts that they might have
|
||||
current sessions for. If it cannot obtain an account
|
||||
selection choice made by the End-User, it MUST return an
|
||||
error, typically account_selection_required.
|
||||
|
||||
The prompt parameter can be used by the Client to make sure
|
||||
that the End-User is still present for the current session or
|
||||
to bring attention to the request. If this parameter contains
|
||||
none with any other value, an error is returned.
|
||||
|
||||
max_age
|
||||
OPTIONAL. Maximum Authentication Age. Specifies the allowable
|
||||
elapsed time in seconds since the last time the End-User was
|
||||
actively authenticated by the OP. If the elapsed time is
|
||||
greater than this value, the OP MUST attempt to actively
|
||||
re-authenticate the End-User. (The max_age request parameter
|
||||
corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age
|
||||
request parameter.) When max_age is used, the ID Token returned
|
||||
MUST include an auth_time Claim Value.
|
||||
|
||||
ui_locales
|
||||
OPTIONAL. End-User's preferred languages and scripts for the
|
||||
user interface, represented as a space-separated list of BCP47
|
||||
[RFC5646] language tag values, ordered by preference. For
|
||||
instance, the value "fr-CA fr en" represents a preference for
|
||||
French as spoken in Canada, then French (without a region
|
||||
designation), followed by English (without a region
|
||||
designation). An error SHOULD NOT result if some or all of the
|
||||
requested locales are not supported by the OpenID Provider.
|
||||
|
||||
id_token_hint
|
||||
OPTIONAL. ID Token previously issued by the Authorization
|
||||
Server being passed as a hint about the End-User's current or
|
||||
past authenticated session with the Client. If the End-User
|
||||
identified by the ID Token is logged in or is logged in by the
|
||||
request, then the Authorization Server returns a positive
|
||||
response; otherwise, it SHOULD return an error, such as
|
||||
login_required. When possible, an id_token_hint SHOULD be
|
||||
present when prompt=none is used and an invalid_request error
|
||||
MAY be returned if it is not; however, the server SHOULD
|
||||
respond successfully when possible, even if it is not present.
|
||||
The Authorization Server need not be listed as an audience of
|
||||
the ID Token when it is used as an id_token_hint value. If the
|
||||
ID Token received by the RP from the OP is encrypted, to use it
|
||||
as an id_token_hint, the Client MUST decrypt the signed ID
|
||||
Token contained within the encrypted ID Token. The Client MAY
|
||||
re-encrypt the signed ID token to the Authentication Server
|
||||
using a key that enables the server to decrypt the ID Token,
|
||||
and use the re-encrypted ID token as the id_token_hint value.
|
||||
|
||||
login_hint
|
||||
OPTIONAL. Hint to the Authorization Server about the login
|
||||
identifier the End-User might use to log in (if necessary).
|
||||
This hint can be used by an RP if it first asks the End-User
|
||||
for their e-mail address (or other identifier) and then wants
|
||||
to pass that value as a hint to the discovered authorization
|
||||
service. It is RECOMMENDED that the hint value match the value
|
||||
used for discovery. This value MAY also be a phone number in
|
||||
the format specified for the phone_number Claim. The use of
|
||||
this parameter is left to the OP's discretion.
|
||||
|
||||
acr_values
|
||||
OPTIONAL. Requested Authentication Context Class Reference
|
||||
values. Space-separated string that specifies the acr values
|
||||
that the Authorization Server is being requested to use for
|
||||
processing this Authentication Request, with the values
|
||||
appearing in order of preference. The Authentication Context
|
||||
Class satisfied by the authentication performed is returned as
|
||||
the acr Claim Value, as specified in Section 2. The acr Claim
|
||||
is requested as a Voluntary Claim by this parameter.
|
||||
"""
|
||||
|
||||
# Treat it as normal OAuth 2 auth code request if openid is not present
|
||||
if not request.scopes or 'openid' not in request.scopes:
|
||||
return {}
|
||||
|
||||
prompt = request.prompt if request.prompt else []
|
||||
if hasattr(prompt, 'split'):
|
||||
prompt = prompt.strip().split()
|
||||
prompt = set(prompt)
|
||||
|
||||
if 'none' in prompt:
|
||||
|
||||
if len(prompt) > 1:
|
||||
msg = "Prompt none is mutually exclusive with other values."
|
||||
raise InvalidRequestError(request=request, description=msg)
|
||||
|
||||
if not self.request_validator.validate_silent_login(request):
|
||||
raise LoginRequired(request=request)
|
||||
|
||||
if not self.request_validator.validate_silent_authorization(request):
|
||||
raise ConsentRequired(request=request)
|
||||
|
||||
self._inflate_claims(request)
|
||||
|
||||
if not self.request_validator.validate_user_match(
|
||||
request.id_token_hint, request.scopes, request.claims, request):
|
||||
msg = "Session user does not match client supplied user."
|
||||
raise LoginRequired(request=request, description=msg)
|
||||
|
||||
ui_locales = request.ui_locales if request.ui_locales else []
|
||||
if hasattr(ui_locales, 'split'):
|
||||
ui_locales = ui_locales.strip().split()
|
||||
|
||||
request_info = {
|
||||
'display': request.display,
|
||||
'nonce': request.nonce,
|
||||
'prompt': prompt,
|
||||
'ui_locales': ui_locales,
|
||||
'id_token_hint': request.id_token_hint,
|
||||
'login_hint': request.login_hint,
|
||||
'claims': request.claims
|
||||
}
|
||||
|
||||
return request_info
|
||||
|
||||
|
||||
OpenIDConnectBase = GrantTypeBase
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
default_grant = None
|
||||
oidc_grant = None
|
||||
|
||||
|
||||
class AuthorizationCodeGrantDispatcher(Dispatcher):
|
||||
"""
|
||||
This is an adapter class that will route simple Authorization Code
|
||||
requests, those that have `response_type=code` and a scope including
|
||||
`openid` to either the `default_grant` or the `oidc_grant` based on
|
||||
the scopes requested.
|
||||
"""
|
||||
def __init__(self, default_grant=None, oidc_grant=None):
|
||||
self.default_grant = default_grant
|
||||
self.oidc_grant = oidc_grant
|
||||
|
||||
def _handler_for_request(self, request):
|
||||
handler = self.default_grant
|
||||
|
||||
if request.scopes and "openid" in request.scopes:
|
||||
handler = self.oidc_grant
|
||||
|
||||
log.debug('Selecting handler for request %r.', handler)
|
||||
return handler
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).create_authorization_response(request, token_handler)
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).validate_authorization_request(request)
|
||||
|
||||
|
||||
class ImplicitTokenGrantDispatcher(Dispatcher):
|
||||
"""
|
||||
This is an adapter class that will route simple Authorization
|
||||
requests, those that have `id_token` in `response_type` and a scope
|
||||
including `openid` to either the `default_grant` or the `oidc_grant`
|
||||
based on the scopes requested.
|
||||
"""
|
||||
def __init__(self, default_grant=None, oidc_grant=None):
|
||||
self.default_grant = default_grant
|
||||
self.oidc_grant = oidc_grant
|
||||
|
||||
def _handler_for_request(self, request):
|
||||
handler = self.default_grant
|
||||
|
||||
if request.scopes and "openid" in request.scopes and 'id_token' in request.response_type:
|
||||
handler = self.oidc_grant
|
||||
|
||||
log.debug('Selecting handler for request %r.', handler)
|
||||
return handler
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).create_authorization_response(request, token_handler)
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).validate_authorization_request(request)
|
||||
|
||||
|
||||
class AuthorizationTokenGrantDispatcher(Dispatcher):
|
||||
"""
|
||||
This is an adapter class that will route simple Token requests, those that authorization_code have a scope
|
||||
including 'openid' to either the default_grant or the oidc_grant based on the scopes requested.
|
||||
"""
|
||||
def __init__(self, request_validator, default_grant=None, oidc_grant=None):
|
||||
self.default_grant = default_grant
|
||||
self.oidc_grant = oidc_grant
|
||||
self.request_validator = request_validator
|
||||
|
||||
def _handler_for_request(self, request):
|
||||
handler = self.default_grant
|
||||
scopes = ()
|
||||
parameters = dict(request.decoded_body)
|
||||
client_id = parameters.get('client_id')
|
||||
code = parameters.get('code')
|
||||
redirect_uri = parameters.get('redirect_uri')
|
||||
|
||||
# If code is not present fallback to `default_grant` which will
|
||||
# raise an error for the missing `code` in `create_token_response` step.
|
||||
if code:
|
||||
scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request)
|
||||
|
||||
if 'openid' in scopes:
|
||||
handler = self.oidc_grant
|
||||
|
||||
log.debug('Selecting handler for request %r.', handler)
|
||||
return handler
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Read scope and route to the designated handler."""
|
||||
handler = self._handler_for_request(request)
|
||||
return handler.create_token_response(request, token_handler)
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
|
||||
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
|
||||
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||
)
|
||||
|
||||
from ..request_validator import RequestValidator
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HybridGrant(GrantTypeBase):
|
||||
|
||||
def __init__(self, request_validator=None, **kwargs):
|
||||
self.request_validator = request_validator or RequestValidator()
|
||||
|
||||
self.proxy_target = OAuth2AuthorizationCodeGrant(
|
||||
request_validator=request_validator, **kwargs)
|
||||
# All hybrid response types should be fragment-encoded.
|
||||
self.proxy_target.default_response_mode = "fragment"
|
||||
self.register_response_type('code id_token')
|
||||
self.register_response_type('code token')
|
||||
self.register_response_type('code id_token token')
|
||||
self.custom_validators.post_auth.append(
|
||||
self.openid_authorization_validator)
|
||||
# Hybrid flows can return the id_token from the authorization
|
||||
# endpoint as part of the 'code' response
|
||||
self.register_code_modifier(self.add_token)
|
||||
self.register_code_modifier(self.add_id_token)
|
||||
self.register_token_modifier(self.add_id_token)
|
||||
|
||||
def add_id_token(self, token, token_handler, request):
|
||||
return super().add_id_token(token, token_handler, request, nonce=request.nonce)
|
||||
|
||||
def openid_authorization_validator(self, request):
|
||||
"""Additional validation when following the Authorization Code flow.
|
||||
"""
|
||||
request_info = super().openid_authorization_validator(request)
|
||||
if not request_info: # returns immediately if OAuth2.0
|
||||
return request_info
|
||||
|
||||
# REQUIRED if the Response Type of the request is `code
|
||||
# id_token` or `code id_token token` and OPTIONAL when the
|
||||
# Response Type of the request is `code token`. It is a string
|
||||
# value used to associate a Client session with an ID Token,
|
||||
# and to mitigate replay attacks. The value is passed through
|
||||
# unmodified from the Authentication Request to the ID
|
||||
# Token. Sufficient entropy MUST be present in the `nonce`
|
||||
# values used to prevent attackers from guessing values. For
|
||||
# implementation notes, see Section 15.5.2.
|
||||
if request.response_type in ["code id_token", "code id_token token"] and not request.nonce:
|
||||
raise InvalidRequestError(
|
||||
request=request,
|
||||
description='Request is missing mandatory nonce parameter.'
|
||||
)
|
||||
return request_info
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
|
||||
from oauthlib.oauth2.rfc6749.grant_types.implicit import (
|
||||
ImplicitGrant as OAuth2ImplicitGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImplicitGrant(GrantTypeBase):
|
||||
|
||||
def __init__(self, request_validator=None, **kwargs):
|
||||
self.proxy_target = OAuth2ImplicitGrant(
|
||||
request_validator=request_validator, **kwargs)
|
||||
self.register_response_type('id_token')
|
||||
self.register_response_type('id_token token')
|
||||
self.custom_validators.post_auth.append(
|
||||
self.openid_authorization_validator)
|
||||
self.register_token_modifier(self.add_id_token)
|
||||
|
||||
def add_id_token(self, token, token_handler, request):
|
||||
if 'state' not in token and request.state:
|
||||
token['state'] = request.state
|
||||
return super().add_id_token(token, token_handler, request, nonce=request.nonce)
|
||||
|
||||
def openid_authorization_validator(self, request):
|
||||
"""Additional validation when following the implicit flow.
|
||||
"""
|
||||
request_info = super().openid_authorization_validator(request)
|
||||
if not request_info: # returns immediately if OAuth2.0
|
||||
return request_info
|
||||
|
||||
# REQUIRED. String value used to associate a Client session with an ID
|
||||
# Token, and to mitigate replay attacks. The value is passed through
|
||||
# unmodified from the Authentication Request to the ID Token.
|
||||
# Sufficient entropy MUST be present in the nonce values used to
|
||||
# prevent attackers from guessing values. For implementation notes, see
|
||||
# Section 15.5.2.
|
||||
if not request.nonce:
|
||||
raise InvalidRequestError(
|
||||
request=request,
|
||||
description='Request is missing mandatory nonce parameter.'
|
||||
)
|
||||
return request_info
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.grant_types.refresh_token import (
|
||||
RefreshTokenGrant as OAuth2RefreshTokenGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RefreshTokenGrant(GrantTypeBase):
|
||||
|
||||
def __init__(self, request_validator=None, **kwargs):
|
||||
self.proxy_target = OAuth2RefreshTokenGrant(
|
||||
request_validator=request_validator, **kwargs)
|
||||
self.register_token_modifier(self.add_id_token)
|
||||
|
||||
def add_id_token(self, token, token_handler, request):
|
||||
"""
|
||||
Construct an initial version of id_token, and let the
|
||||
request_validator sign or encrypt it.
|
||||
|
||||
The authorization_code version of this method is used to
|
||||
retrieve the nonce accordingly to the code storage.
|
||||
"""
|
||||
if not self.request_validator.refresh_id_token(request):
|
||||
return token
|
||||
|
||||
return super().add_id_token(token, token_handler, request)
|
||||
@@ -0,0 +1,320 @@
|
||||
"""
|
||||
oauthlib.openid.connect.core.request_validator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.request_validator import (
|
||||
RequestValidator as OAuth2RequestValidator,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RequestValidator(OAuth2RequestValidator):
|
||||
|
||||
def get_authorization_code_scopes(self, client_id, code, redirect_uri, request):
|
||||
""" Extracts scopes from saved authorization code.
|
||||
|
||||
The scopes returned by this method is used to route token requests
|
||||
based on scopes passed to Authorization Code requests.
|
||||
|
||||
With that the token endpoint knows when to include OpenIDConnect
|
||||
id_token in token response only based on authorization code scopes.
|
||||
|
||||
Only code param should be sufficient to retrieve grant code from
|
||||
any storage you are using, `client_id` and `redirect_uri` can have a
|
||||
blank value `""` don't forget to check it before using those values
|
||||
in a select query if a database is used.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param code: Unicode authorization code grant
|
||||
:param redirect_uri: Unicode absolute URI
|
||||
:return: A list of scope
|
||||
|
||||
Method is used by:
|
||||
- Authorization Token Grant Dispatcher
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_authorization_code_nonce(self, client_id, code, redirect_uri, request):
|
||||
""" Extracts nonce from saved authorization code.
|
||||
|
||||
If present in the Authentication Request, Authorization
|
||||
Servers MUST include a nonce Claim in the ID Token with the
|
||||
Claim Value being the nonce value sent in the Authentication
|
||||
Request. Authorization Servers SHOULD perform no other
|
||||
processing on nonce values used. The nonce value is a
|
||||
case-sensitive string.
|
||||
|
||||
Only code param should be sufficient to retrieve grant code from
|
||||
any storage you are using. However, `client_id` and `redirect_uri`
|
||||
have been validated and can be used also.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param code: Unicode authorization code grant
|
||||
:param redirect_uri: Unicode absolute URI
|
||||
:return: Unicode nonce
|
||||
|
||||
Method is used by:
|
||||
- Authorization Token Grant Dispatcher
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_jwt_bearer_token(self, token, token_handler, request):
|
||||
"""Get JWT Bearer token or OpenID Connect ID token
|
||||
|
||||
If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
|
||||
|
||||
:param token: A Bearer token dict
|
||||
:param token_handler: the token handler (BearerToken class)
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT)
|
||||
|
||||
Method is used by JWT Bearer and OpenID Connect tokens:
|
||||
- JWTToken.create_token
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_id_token(self, token, token_handler, request):
|
||||
"""Get OpenID Connect ID token
|
||||
|
||||
This method is OPTIONAL and is NOT RECOMMENDED.
|
||||
`finalize_id_token` SHOULD be implemented instead. However, if you
|
||||
want a full control over the minting of the `id_token`, you
|
||||
MAY want to override `get_id_token` instead of using
|
||||
`finalize_id_token`.
|
||||
|
||||
In the OpenID Connect workflows when an ID Token is requested this method is called.
|
||||
Subclasses should implement the construction, signing and optional encryption of the
|
||||
ID Token as described in the OpenID Connect spec.
|
||||
|
||||
In addition to the standard OAuth2 request properties, the request may also contain
|
||||
these OIDC specific properties which are useful to this method:
|
||||
|
||||
- nonce, if workflow is implicit or hybrid and it was provided
|
||||
- claims, if provided to the original Authorization Code request
|
||||
|
||||
The token parameter is a dict which may contain an ``access_token`` entry, in which
|
||||
case the resulting ID Token *should* include a calculated ``at_hash`` claim.
|
||||
|
||||
Similarly, when the request parameter has a ``code`` property defined, the ID Token
|
||||
*should* include a calculated ``c_hash`` claim.
|
||||
|
||||
http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_)
|
||||
|
||||
.. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
.. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
|
||||
.. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
||||
|
||||
:param token: A Bearer token dict
|
||||
:param token_handler: the token handler (BearerToken class)
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:return: The ID Token (a JWS signed JWT)
|
||||
"""
|
||||
return None
|
||||
|
||||
def finalize_id_token(self, id_token, token, token_handler, request):
|
||||
"""Finalize OpenID Connect ID token & Sign or Encrypt.
|
||||
|
||||
In the OpenID Connect workflows when an ID Token is requested
|
||||
this method is called. Subclasses should implement the
|
||||
construction, signing and optional encryption of the ID Token
|
||||
as described in the OpenID Connect spec.
|
||||
|
||||
The `id_token` parameter is a dict containing a couple of OIDC
|
||||
technical fields related to the specification. Prepopulated
|
||||
attributes are:
|
||||
|
||||
- `aud`, equals to `request.client_id`.
|
||||
- `iat`, equals to current time.
|
||||
- `nonce`, if present, is equals to the `nonce` from the
|
||||
authorization request.
|
||||
- `at_hash`, hash of `access_token`, if relevant.
|
||||
- `c_hash`, hash of `code`, if relevant.
|
||||
|
||||
This method MUST provide required fields as below:
|
||||
|
||||
- `iss`, REQUIRED. Issuer Identifier for the Issuer of the response.
|
||||
- `sub`, REQUIRED. Subject Identifier
|
||||
- `exp`, REQUIRED. Expiration time on or after which the ID
|
||||
Token MUST NOT be accepted by the RP when performing
|
||||
authentication with the OP.
|
||||
|
||||
Additional claims must be added, note that `request.scope`
|
||||
should be used to determine the list of claims.
|
||||
|
||||
More information can be found at `OpenID Connect Core#Claims`_
|
||||
|
||||
.. _`OpenID Connect Core#Claims`: https://openid.net/specs/openid-connect-core-1_0.html#Claims
|
||||
|
||||
:param id_token: A dict containing technical fields of id_token
|
||||
:param token: A Bearer token dict
|
||||
:param token_handler: the token handler (BearerToken class)
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:return: The ID Token (a JWS signed JWT or JWE encrypted JWT)
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_jwt_bearer_token(self, token, scopes, request):
|
||||
"""Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes.
|
||||
|
||||
If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
|
||||
|
||||
If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response.
|
||||
|
||||
OpenID connect core 1.0 describe how to validate an id_token:
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
|
||||
|
||||
:param token: Unicode Bearer token
|
||||
:param scopes: List of scopes (defined by you)
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is indirectly used by all core OpenID connect JWT token issuing grant types:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Hybrid Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_id_token(self, token, scopes, request):
|
||||
"""Ensure the id token is valid and authorized access to scopes.
|
||||
|
||||
OpenID connect core 1.0 describe how to validate an id_token:
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
|
||||
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
|
||||
|
||||
:param token: Unicode Bearer token
|
||||
:param scopes: List of scopes (defined by you)
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is indirectly used by all core OpenID connect JWT token issuing grant types:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Hybrid Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_silent_authorization(self, request):
|
||||
"""Ensure the logged in user has authorized silent OpenID authorization.
|
||||
|
||||
Silent OpenID authorization allows access tokens and id tokens to be
|
||||
granted to clients without any user prompt or interaction.
|
||||
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_silent_login(self, request):
|
||||
"""Ensure session user has authorized silent OpenID login.
|
||||
|
||||
If no user is logged in or has not authorized silent login, this
|
||||
method should return False.
|
||||
|
||||
If the user is logged in but associated with multiple accounts and
|
||||
not selected which one to link to the token then this method should
|
||||
raise an oauthlib.oauth2.AccountSelectionRequired error.
|
||||
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_user_match(self, id_token_hint, scopes, claims, request):
|
||||
"""Ensure client supplied user id hint matches session user.
|
||||
|
||||
If the sub claim or id_token_hint is supplied then the session
|
||||
user must match the given ID.
|
||||
|
||||
:param id_token_hint: User identifier string.
|
||||
:param scopes: List of OAuth 2 scopes and OpenID claims (strings).
|
||||
:param claims: OpenID Connect claims dict.
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_userinfo_claims(self, request):
|
||||
"""Return the UserInfo claims in JSON or Signed or Encrypted.
|
||||
|
||||
The UserInfo Claims MUST be returned as the members of a JSON object
|
||||
unless a signed or encrypted response was requested during Client
|
||||
Registration. The Claims defined in Section 5.1 can be returned, as can
|
||||
additional Claims not specified there.
|
||||
|
||||
For privacy reasons, OpenID Providers MAY elect to not return values for
|
||||
some requested Claims.
|
||||
|
||||
If a Claim is not returned, that Claim Name SHOULD be omitted from the
|
||||
JSON object representing the Claims; it SHOULD NOT be present with a
|
||||
null or empty string value.
|
||||
|
||||
The sub (subject) Claim MUST always be returned in the UserInfo
|
||||
Response.
|
||||
|
||||
Upon receipt of the UserInfo Request, the UserInfo Endpoint MUST return
|
||||
the JSON Serialization of the UserInfo Response as in Section 13.3 in
|
||||
the HTTP response body unless a different format was specified during
|
||||
Registration [OpenID.Registration].
|
||||
|
||||
If the UserInfo Response is signed and/or encrypted, then the Claims are
|
||||
returned in a JWT and the content-type MUST be application/jwt. The
|
||||
response MAY be encrypted without also being signed. If both signing and
|
||||
encryption are requested, the response MUST be signed then encrypted,
|
||||
with the result being a Nested JWT, as defined in [JWT].
|
||||
|
||||
If signed, the UserInfo Response SHOULD contain the Claims iss (issuer)
|
||||
and aud (audience) as members. The iss value SHOULD be the OP's Issuer
|
||||
Identifier URL. The aud value SHOULD be or include the RP's Client ID
|
||||
value.
|
||||
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: Claims as a dict OR JWT/JWS/JWE as a string
|
||||
|
||||
Method is used by:
|
||||
UserInfoEndpoint
|
||||
"""
|
||||
|
||||
def refresh_id_token(self, request):
|
||||
"""Whether the id token should be refreshed. Default, True
|
||||
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
RefreshTokenGrant
|
||||
"""
|
||||
return True
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
authlib.openid.connect.core.tokens
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains methods for adding JWT tokens to requests.
|
||||
"""
|
||||
from oauthlib.oauth2.rfc6749.tokens import (
|
||||
TokenBase, get_token_from_header, random_token_generator,
|
||||
)
|
||||
|
||||
|
||||
class JWTToken(TokenBase):
|
||||
__slots__ = (
|
||||
'request_validator', 'token_generator',
|
||||
'refresh_token_generator', 'expires_in'
|
||||
)
|
||||
|
||||
def __init__(self, request_validator=None, token_generator=None,
|
||||
expires_in=None, refresh_token_generator=None):
|
||||
self.request_validator = request_validator
|
||||
self.token_generator = token_generator or random_token_generator
|
||||
self.refresh_token_generator = (
|
||||
refresh_token_generator or self.token_generator
|
||||
)
|
||||
self.expires_in = expires_in or 3600
|
||||
|
||||
def create_token(self, request, refresh_token=False):
|
||||
"""Create a JWT Token, using requestvalidator method."""
|
||||
|
||||
expires_in = self.expires_in(request) if callable(self.expires_in) else self.expires_in
|
||||
|
||||
request.expires_in = expires_in
|
||||
|
||||
return self.request_validator.get_jwt_bearer_token(None, None, request)
|
||||
|
||||
def validate_request(self, request):
|
||||
token = get_token_from_header(request)
|
||||
return self.request_validator.validate_jwt_bearer_token(
|
||||
token, request.scopes, request)
|
||||
|
||||
def estimate_type(self, request):
|
||||
token = get_token_from_header(request)
|
||||
if token and token.startswith('ey') and token.count('.') in (2, 4):
|
||||
return 10
|
||||
return 0
|
||||
Reference in New Issue
Block a user