mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-14 01:02:59 -07:00
Update oauthlib-3.1.1
This commit is contained in:
parent
e58aa40099
commit
d76838a607
64 changed files with 4329 additions and 1421 deletions
7
lib/oauthlib/openid/__init__.py
Normal file
7
lib/oauthlib/openid/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
oauthlib.openid
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
from .connect.core.endpoints import Server, UserInfoEndpoint
|
||||
from .connect.core.request_validator import RequestValidator
|
0
lib/oauthlib/openid/connect/__init__.py
Normal file
0
lib/oauthlib/openid/connect/__init__.py
Normal file
0
lib/oauthlib/openid/connect/core/__init__.py
Normal file
0
lib/oauthlib/openid/connect/core/__init__.py
Normal file
9
lib/oauthlib/openid/connect/core/endpoints/__init__.py
Normal file
9
lib/oauthlib/openid/connect/core/endpoints/__init__.py
Normal file
|
@ -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
|
97
lib/oauthlib/openid/connect/core/endpoints/pre_configured.py
Normal file
97
lib/oauthlib/openid/connect/core/endpoints/pre_configured.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
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,
|
||||
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
|
||||
from ..grant_types import AuthorizationCodeGrant, HybridGrant, ImplicitGrant
|
||||
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."""
|
||||
|
||||
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.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,
|
||||
},
|
||||
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)
|
99
lib/oauthlib/openid/connect/core/endpoints/userinfo.py
Normal file
99
lib/oauthlib/openid/connect/core/endpoints/userinfo.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
"""
|
||||
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:
|
||||
|
||||
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:
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer error="invalid_token",
|
||||
error_description="The Access Token expired"
|
||||
"""
|
||||
if not self.bearer.validate_request(request):
|
||||
raise errors.InvalidTokenError()
|
||||
if "openid" not in request.scopes:
|
||||
raise errors.InsufficientScopeError()
|
149
lib/oauthlib/openid/connect/core/exceptions.py
Normal file
149
lib/oauthlib/openid/connect/core/exceptions.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
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):
|
||||
import inspect
|
||||
import sys
|
||||
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)
|
12
lib/oauthlib/openid/connect/core/grant_types/__init__.py
Normal file
12
lib/oauthlib/openid/connect/core/grant_types/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
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
|
|
@ -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)
|
327
lib/oauthlib/openid/connect/core/grant_types/base.py
Normal file
327
lib/oauthlib/openid/connect/core/grant_types/base.py
Normal file
|
@ -0,0 +1,327 @@
|
|||
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)
|
||||
|
||||
request_info = {
|
||||
'display': request.display,
|
||||
'nonce': request.nonce,
|
||||
'prompt': prompt,
|
||||
'ui_locales': request.ui_locales.split() if request.ui_locales else [],
|
||||
'id_token_hint': request.id_token_hint,
|
||||
'login_hint': request.login_hint,
|
||||
'claims': request.claims
|
||||
}
|
||||
|
||||
return request_info
|
||||
|
||||
|
||||
OpenIDConnectBase = GrantTypeBase
|
101
lib/oauthlib/openid/connect/core/grant_types/dispatchers.py
Normal file
101
lib/oauthlib/openid/connect/core/grant_types/dispatchers.py
Normal file
|
@ -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', None)
|
||||
code = parameters.get('code', None)
|
||||
redirect_uri = parameters.get('redirect_uri', None)
|
||||
|
||||
# If code is not pressent 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)
|
63
lib/oauthlib/openid/connect/core/grant_types/hybrid.py
Normal file
63
lib/oauthlib/openid/connect/core/grant_types/hybrid.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
"""
|
||||
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"]:
|
||||
if not request.nonce:
|
||||
raise InvalidRequestError(
|
||||
request=request,
|
||||
description='Request is missing mandatory nonce parameter.'
|
||||
)
|
||||
return request_info
|
51
lib/oauthlib/openid/connect/core/grant_types/implicit.py
Normal file
51
lib/oauthlib/openid/connect/core/grant_types/implicit.py
Normal file
|
@ -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
|
308
lib/oauthlib/openid/connect/core/request_validator.py
Normal file
308
lib/oauthlib/openid/connect/core/request_validator.py
Normal file
|
@ -0,0 +1,308 @@
|
|||
"""
|
||||
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.
|
||||
|
||||
Additionals 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
|
||||
"""
|
46
lib/oauthlib/openid/connect/core/tokens.py
Normal file
46
lib/oauthlib/openid/connect/core/tokens.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
authlib.openid.connect.core.tokens
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains methods for adding JWT tokens to requests.
|
||||
"""
|
||||
from oauthlib.oauth2.rfc6749.tokens import TokenBase, random_token_generator, get_token_from_header
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
if callable(self.expires_in):
|
||||
expires_in = self.expires_in(request)
|
||||
else:
|
||||
expires_in = 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
|
Loading…
Add table
Add a link
Reference in a new issue