diff --git a/demo/oauth2_add_on_dpop.py b/demo/oauth2_add_on_dpop.py index bfb37bc2..c4f935d1 100755 --- a/demo/oauth2_add_on_dpop.py +++ b/demo/oauth2_add_on_dpop.py @@ -5,7 +5,7 @@ from common import KEYDEFS from common import full_path from flow import Flow -from idpyoidc.claims import get_signing_algs +from idpyoidc.metadata import get_signing_algs from idpyoidc.client.oauth2 import Client from idpyoidc.server import Server from idpyoidc.server.configure import ASConfiguration diff --git a/demo/oidc_add_on_dpop.py b/demo/oidc_add_on_dpop.py index bfb37bc2..c4f935d1 100755 --- a/demo/oidc_add_on_dpop.py +++ b/demo/oidc_add_on_dpop.py @@ -5,7 +5,7 @@ from common import KEYDEFS from common import full_path from flow import Flow -from idpyoidc.claims import get_signing_algs +from idpyoidc.metadata import get_signing_algs from idpyoidc.client.oauth2 import Client from idpyoidc.server import Server from idpyoidc.server.configure import ASConfiguration diff --git a/example/flask_op/config.json b/example/flask_op/config.json index 19432ffb..3b75e18f 100644 --- a/example/flask_op/config.json +++ b/example/flask_op/config.json @@ -39,7 +39,7 @@ "server_info": { "add_on": { "pkce": { - "function": "idpyoidc.server.oidc.add_on.pkce.add_pkce_support", + "function": "idpyoidc.server.oauth2.add_on.pkce.add_support", "kwargs": { "essential": false, "code_challenge_method": "S256 S384 S512" diff --git a/src/idpyoidc/__init__.py b/src/idpyoidc/__init__.py index 4fb4fa96..51dca059 100644 --- a/src/idpyoidc/__init__.py +++ b/src/idpyoidc/__init__.py @@ -1,5 +1,5 @@ __author__ = "Roland Hedberg" -__version__ = "2.1.0" +__version__ = "2.2.0" VERIFIED_CLAIM_PREFIX = "__verified" diff --git a/src/idpyoidc/claims.py b/src/idpyoidc/claims.py index 6328fc17..7a474acf 100644 --- a/src/idpyoidc/claims.py +++ b/src/idpyoidc/claims.py @@ -1,10 +1,7 @@ -from functools import cmp_to_key from typing import Callable from typing import Optional from cryptojwt import KeyJar -from cryptojwt.jwe import SUPPORTED -from cryptojwt.jws.jws import SIGNER_ALGS from cryptojwt.key_jar import init_key_jar from cryptojwt.utils import importer @@ -215,42 +212,3 @@ def get_claim(self, key, default=None): return default else: return _val - - -SIGNING_ALGORITHM_SORT_ORDER = ["RS", "ES", "PS", "HS"] - - -def cmp(a, b): - return (a > b) - (a < b) - - -def alg_cmp(a, b): - if a == "none": - return 1 - elif b == "none": - return -1 - - _pos1 = SIGNING_ALGORITHM_SORT_ORDER.index(a[0:2]) - _pos2 = SIGNING_ALGORITHM_SORT_ORDER.index(b[0:2]) - if _pos1 == _pos2: - return (a > b) - (a < b) - elif _pos1 > _pos2: - return 1 - else: - return -1 - - -def get_signing_algs(): - # Assumes Cryptojwt - _list = list(SIGNER_ALGS.keys()) - # know how to do none but should not - _list.remove("none") - return sorted(_list, key=cmp_to_key(alg_cmp)) - - -def get_encryption_algs(): - return SUPPORTED["alg"] - - -def get_encryption_encs(): - return SUPPORTED["enc"] diff --git a/src/idpyoidc/client/claims/oidc.py b/src/idpyoidc/client/claims/oidc.py index 787e3443..0c140246 100644 --- a/src/idpyoidc/client/claims/oidc.py +++ b/src/idpyoidc/client/claims/oidc.py @@ -2,7 +2,7 @@ import os from typing import Optional -from idpyoidc import claims +from idpyoidc import metadata from idpyoidc.client import claims as client_claims from idpyoidc.client.claims.transform import create_registration_request from idpyoidc.message.oidc import RegistrationRequest @@ -75,9 +75,9 @@ class Claims(client_claims.Claims): "encrypt_id_token_supported": None, # "grant_types_supported": ["authorization_code", "refresh_token"], "logo_uri": None, - "id_token_signing_alg_values_supported": claims.get_signing_algs, - "id_token_encryption_alg_values_supported": claims.get_encryption_algs, - "id_token_encryption_enc_values_supported": claims.get_encryption_encs, + "id_token_signing_alg_values_supported": metadata.get_signing_algs, + "id_token_encryption_alg_values_supported": metadata.get_encryption_algs, + "id_token_encryption_enc_values_supported": metadata.get_encryption_encs, "initiate_login_uri": None, "jwks": None, "jwks_uri": None, diff --git a/src/idpyoidc/client/client_auth.py b/src/idpyoidc/client/client_auth.py index 7e49969a..fbf422a1 100755 --- a/src/idpyoidc/client/client_auth.py +++ b/src/idpyoidc/client/client_auth.py @@ -12,14 +12,15 @@ from idpyoidc.defaults import DEF_SIGN_ALG from idpyoidc.defaults import JWT_BEARER -from idpyoidc.message.oauth2 import AccessTokenRequest from idpyoidc.message.oauth2 import SINGLE_OPTIONAL_STRING +from idpyoidc.message.oauth2 import AccessTokenRequest from idpyoidc.message.oidc import AuthnToken from idpyoidc.time_util import utc_time_sans_frac from idpyoidc.util import rndstr -from .util import sanitize + from ..message import VREQUIRED from ..util import instantiate +from .util import sanitize # from idpyoidc.oidc.backchannel_authentication import ClientNotificationAuthn diff --git a/src/idpyoidc/client/configure.py b/src/idpyoidc/client/configure.py index 50740986..ad52d9a4 100755 --- a/src/idpyoidc/client/configure.py +++ b/src/idpyoidc/client/configure.py @@ -7,6 +7,7 @@ from idpyoidc.configure import Base from idpyoidc.logging import configure_logging + from .util import lower_or_upper try: diff --git a/src/idpyoidc/client/entity.py b/src/idpyoidc/client/entity.py index d9f29ca7..61ca959f 100644 --- a/src/idpyoidc/client/entity.py +++ b/src/idpyoidc/client/entity.py @@ -141,6 +141,7 @@ def __init__( keyjar=self.keyjar, upstream_get=self.unit_get, client_type=client_type, + entity_id=self.entity_id, ) self.setup_client_authn_methods(config) diff --git a/src/idpyoidc/client/http.py b/src/idpyoidc/client/http.py index 7a7f58b3..0f58c6b9 100644 --- a/src/idpyoidc/client/http.py +++ b/src/idpyoidc/client/http.py @@ -4,10 +4,10 @@ from http.cookies import CookieError from http.cookies import SimpleCookie -from requests import request from idpyoidc.client.exception import NonFatalException from idpyoidc.client.util import sanitize from idpyoidc.client.util import set_cookie +from requests import request __author__ = "roland" diff --git a/src/idpyoidc/client/oauth2/__init__.py b/src/idpyoidc/client/oauth2/__init__.py index 312b0cfa..9911697e 100755 --- a/src/idpyoidc/client/oauth2/__init__.py +++ b/src/idpyoidc/client/oauth2/__init__.py @@ -5,15 +5,14 @@ from typing import Union from cryptojwt.key_jar import KeyJar -from requests import request from idpyoidc.client.entity import Entity from idpyoidc.client.exception import ConfigurationError from idpyoidc.client.exception import OidcServiceError from idpyoidc.client.exception import ParseError from idpyoidc.client.service import REQUEST_INFO -from idpyoidc.client.service import Service from idpyoidc.client.service import SUCCESSFUL +from idpyoidc.client.service import Service from idpyoidc.client.util import do_add_ons from idpyoidc.client.util import get_deserialization_method from idpyoidc.configure import Configuration @@ -21,6 +20,7 @@ from idpyoidc.exception import FormatError from idpyoidc.message import Message from idpyoidc.message.oauth2 import is_error_message +from requests import request __author__ = "Roland Hedberg" @@ -38,20 +38,20 @@ class Client(Entity): client_type = "oauth2" def __init__( - self, - keyjar: Optional[KeyJar] = None, - config: Optional[Union[dict, Configuration]] = None, - services: Optional[dict] = None, - httpc: Optional[Callable] = None, - httpc_params: Optional[dict] = None, - context: Optional[OidcContext] = None, - upstream_get: Optional[Callable] = None, - key_conf: Optional[dict] = None, - entity_id: Optional[str] = "", - verify_ssl: Optional[bool] = True, - jwks_uri: Optional[str] = "", - client_type: Optional[str] = "", - **kwargs + self, + keyjar: Optional[KeyJar] = None, + config: Optional[Union[dict, Configuration]] = None, + services: Optional[dict] = None, + httpc: Optional[Callable] = None, + httpc_params: Optional[dict] = None, + context: Optional[OidcContext] = None, + upstream_get: Optional[Callable] = None, + key_conf: Optional[dict] = None, + entity_id: Optional[str] = "", + verify_ssl: Optional[bool] = True, + jwks_uri: Optional[str] = "", + client_type: Optional[str] = "", + **kwargs ): """ @@ -68,9 +68,12 @@ def __init__( :return: Client instance """ + if config is None: + config = {} + if client_type: self.client_type = client_type - elif config and 'client_type' in config: + elif config and "client_type" in config: client_type = self.client_type = config["client_type"] else: client_type = self.client_type @@ -82,7 +85,7 @@ def __init__( else: httpc_params = {"verify": False} - jwks_uri = jwks_uri or config.get('jwks_uri', '') + jwks_uri = jwks_uri or config.get("jwks_uri", "") Entity.__init__( self, @@ -110,12 +113,12 @@ def __init__( do_add_ons(_add_ons, self._service) def do_request( - self, - request_type: str, - response_body_type: Optional[str] = "", - request_args: Optional[dict] = None, - behaviour_args: Optional[dict] = None, - **kwargs + self, + request_type: str, + response_body_type: Optional[str] = "", + request_args: Optional[dict] = None, + behaviour_args: Optional[dict] = None, + **kwargs ): _srv = self._service[request_type] @@ -138,14 +141,14 @@ def set_client_id(self, client_id): self.get_context().set("client_id", client_id) def get_response( - self, - service: Service, - url: str, - method: Optional[str] = "GET", - body: Optional[dict] = None, - response_body_type: Optional[str] = "", - headers: Optional[dict] = None, - **kwargs + self, + service: Service, + url: str, + method: Optional[str] = "GET", + body: Optional[dict] = None, + response_body_type: Optional[str] = "", + headers: Optional[dict] = None, + **kwargs ): """ @@ -181,14 +184,14 @@ def get_response( return self.parse_request_response(service, resp, response_body_type, **kwargs) def service_request( - self, - service: Service, - url: str, - method: Optional[str] = "GET", - body: Optional[dict] = None, - response_body_type: Optional[str] = "", - headers: Optional[dict] = None, - **kwargs + self, + service: Service, + url: str, + method: Optional[str] = "GET", + body: Optional[dict] = None, + response_body_type: Optional[str] = "", + headers: Optional[dict] = None, + **kwargs ) -> Message: """ The method that sends the request and handles the response returned. @@ -317,10 +320,10 @@ def dynamic_provider_info_discovery(client: Client, behaviour_args: Optional[dic :param client: A :py:class:`idpyoidc.client.oidc.Client` instance """ - if client.client_type == 'oidc' and client.get_service("provider_info"): - service = 'provider_info' - elif client.client_type == 'oauth2' and client.get_service('server_metadata'): - service = 'server_metadata' + if client.client_type == "oidc" and client.get_service("provider_info"): + service = "provider_info" + elif client.client_type == "oauth2" and client.get_service("server_metadata"): + service = "server_metadata" else: raise ConfigurationError("Can not do dynamic provider info discovery") diff --git a/src/idpyoidc/client/oauth2/access_token.py b/src/idpyoidc/client/oauth2/access_token.py index 83f1ef96..7030a531 100644 --- a/src/idpyoidc/client/oauth2/access_token.py +++ b/src/idpyoidc/client/oauth2/access_token.py @@ -7,8 +7,8 @@ from idpyoidc.client.service import Service from idpyoidc.message import oauth2 from idpyoidc.message.oauth2 import ResponseMessage +from idpyoidc.metadata import get_signing_algs from idpyoidc.time_util import time_sans_frac -from idpyoidc.claims import get_signing_algs LOGGER = logging.getLogger(__name__) diff --git a/src/idpyoidc/client/oauth2/add_on/dpop.py b/src/idpyoidc/client/oauth2/add_on/dpop.py index 9c8e7f23..a4ad740d 100644 --- a/src/idpyoidc/client/oauth2/add_on/dpop.py +++ b/src/idpyoidc/client/oauth2/add_on/dpop.py @@ -3,20 +3,18 @@ from hashlib import sha256 from typing import Optional -from cryptography.hazmat.primitives import hashes from cryptojwt.jwk.jwk import key_from_jwk_dict from cryptojwt.jws.jws import JWS from cryptojwt.jws.jws import factory -from cryptojwt.jws.jws import SIGNER_ALGS from cryptojwt.key_bundle import key_by_alg -from idpyoidc.claims import get_signing_algs from idpyoidc.client.service_context import ServiceContext from idpyoidc.message import SINGLE_OPTIONAL_STRING from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_JSON from idpyoidc.message import SINGLE_REQUIRED_STRING from idpyoidc.message import Message +from idpyoidc.metadata import get_signing_algs from idpyoidc.time_util import utc_time_sans_frac logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/client/oauth2/add_on/jar.py b/src/idpyoidc/client/oauth2/add_on/jar.py index 349050ce..a775532b 100644 --- a/src/idpyoidc/client/oauth2/add_on/jar.py +++ b/src/idpyoidc/client/oauth2/add_on/jar.py @@ -2,6 +2,7 @@ from typing import Optional from idpyoidc import claims +from idpyoidc import metadata from idpyoidc.client.oidc.utils import construct_request_uri from idpyoidc.client.oidc.utils import request_object_encryption from idpyoidc.message.oidc import make_openid_request @@ -207,8 +208,8 @@ def add_support( args["request_dir"] = request_dir if request_object_encryption_enc and request_object_encryption_alg: - if request_object_encryption_enc in claims.get_encryption_encs(): - if request_object_encryption_alg in claims.get_encryption_algs(): + if request_object_encryption_enc in metadata.get_encryption_encs(): + if request_object_encryption_alg in metadata.get_encryption_algs(): args["request_object_encryption_enc"] = request_object_encryption_enc args["request_object_encryption_alg"] = request_object_encryption_alg else: diff --git a/src/idpyoidc/client/oauth2/add_on/par.py b/src/idpyoidc/client/oauth2/add_on/par.py index 03416c5d..fb5fa9b7 100644 --- a/src/idpyoidc/client/oauth2/add_on/par.py +++ b/src/idpyoidc/client/oauth2/add_on/par.py @@ -2,12 +2,12 @@ from cryptojwt import JWT from cryptojwt.utils import importer -from requests import request from idpyoidc.client.client_auth import CLIENT_AUTHN_METHOD from idpyoidc.message import Message from idpyoidc.message.oauth2 import JWTSecuredAuthorizationRequest from idpyoidc.util import instantiate +from requests import request logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/client/oauth2/authorization.py b/src/idpyoidc/client/oauth2/authorization.py index 1ce76728..9d85f1fd 100644 --- a/src/idpyoidc/client/oauth2/authorization.py +++ b/src/idpyoidc/client/oauth2/authorization.py @@ -32,11 +32,6 @@ class Authorization(Service): _supports = { "response_types_supported": ["code"], "response_modes_supported": ["query", "fragment"], - # Below not OAuth2 functionality - # "request_object_signing_alg_values_supported": claims.get_signing_algs, - # "request_object_encryption_alg_values_supported": claims.get_encryption_algs, - # "request_object_encryption_enc_values_supported": claims.get_encryption_encs, - # "encrypt_request_object_supported": False, } _callback_path = { @@ -107,14 +102,14 @@ def _do_flow(self, flow_type, response_types, context) -> str: elif flow_type == "fragment": if implicit_response_types(response_types): return "fragment" - elif flow_type == 'form_post': - rm = context.get_preference('response_modes_supported') - if rm and 'form_post' in rm: + elif flow_type == "form_post": + rm = context.get_preference("response_modes_supported") + if rm and "form_post" in rm: if context.config.conf.get("separate_form_post_cb", True): return "form_post" else: return "query" - return '' + return "" def _do_redirect_uris(self, base_url, hex, context, callback_uris, response_types): _redirect_uris = context.get_preference("redirect_uris", []) diff --git a/src/idpyoidc/client/oauth2/resource.py b/src/idpyoidc/client/oauth2/resource.py index efec4db6..18a616b6 100644 --- a/src/idpyoidc/client/oauth2/resource.py +++ b/src/idpyoidc/client/oauth2/resource.py @@ -1,17 +1,8 @@ import logging -from typing import Optional -from typing import Union -from idpyoidc import verified_claim_name -from idpyoidc.client.oauth2.utils import get_state_parameter from idpyoidc.client.service import Service -from idpyoidc.claims import get_encryption_algs -from idpyoidc.claims import get_encryption_encs -from idpyoidc.claims import get_signing_algs -from idpyoidc.exception import MissingSigningKey from idpyoidc.message import Message from idpyoidc.message import oauth2 -from idpyoidc.message import oidc logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/client/oauth2/stand_alone_client.py b/src/idpyoidc/client/oauth2/stand_alone_client.py index db14bf1f..c4d219c8 100644 --- a/src/idpyoidc/client/oauth2/stand_alone_client.py +++ b/src/idpyoidc/client/oauth2/stand_alone_client.py @@ -19,8 +19,8 @@ from idpyoidc.exception import MissingRequiredAttribute from idpyoidc.exception import NotForMe from idpyoidc.message import Message -from idpyoidc.message.oauth2 import is_error_message from idpyoidc.message.oauth2 import ResponseMessage +from idpyoidc.message.oauth2 import is_error_message from idpyoidc.message.oidc import AuthorizationRequest from idpyoidc.message.oidc import AuthorizationResponse from idpyoidc.message.oidc import Claims @@ -34,7 +34,6 @@ class StandAloneClient(Client): - def get_session_information(self, key): """ This is the second of the methods users of this class should know about. @@ -48,8 +47,8 @@ def get_session_information(self, key): return self.get_context().cstate.get(key) def do_provider_info( - self, - behaviour_args: Optional[dict] = None, + self, + behaviour_args: Optional[dict] = None, ) -> str: """ Either get the provider info from configuration or through dynamic @@ -65,8 +64,8 @@ def do_provider_info( if _pi is None or _pi == {}: dynamic_provider_info_discovery(self, behaviour_args=behaviour_args) _pi = _context.provider_info - elif len(_pi) == 1 and 'issuer' in _pi: - _context.issuer = _pi['issuer'] + elif len(_pi) == 1 and "issuer" in _pi: + _context.issuer = _pi["issuer"] dynamic_provider_info_discovery(self, behaviour_args=behaviour_args) _pi = _context.provider_info else: @@ -101,14 +100,14 @@ def do_provider_info( _context.map_supported_to_preferred(info=_pi) try: - return _context.provider_info['issuer'] + return _context.provider_info["issuer"] except: return _context.issuer def do_client_registration( - self, - request_args: Optional[dict] = None, - behaviour_args: Optional[dict] = None, + self, + request_args: Optional[dict] = None, + behaviour_args: Optional[dict] = None, ): """ Prepare for and do client registration if configured to do so @@ -148,21 +147,22 @@ def _get_response_type(self, context, req_args: Optional[dict] = None): def _get_response_mode(self, context, response_type, request_args): if request_args: - _requested = request_args.get('response_mode') + _requested = request_args.get("response_mode") else: _requested = None - _supported = context.claims.get_usage('response_modes') + _supported = context.claims.get_usage("response_modes") if _requested: if _supported and _requested not in _supported: raise ValueError( - "You can not use a response_mode you have not stated should be supported") + "You can not use a response_mode you have not stated should be supported" + ) if DEFAULT_RESPONSE_MODE[response_type] == _requested: return None else: return _requested elif _supported: - _type = response_type.split(' ') + _type = response_type.split(" ") _type.sort() response_type = " ".join(_type) # Is it the default response mode @@ -174,9 +174,9 @@ def _get_response_mode(self, context, response_type, request_args): return None def init_authorization( - self, - req_args: Optional[dict] = None, - behaviour_args: Optional[dict] = None, + self, + req_args: Optional[dict] = None, + behaviour_args: Optional[dict] = None, ) -> str: """ Constructs the URL that will redirect the user to the authorization @@ -196,12 +196,15 @@ def init_authorization( _response_mode = self._get_response_mode(_context, _response_type, req_args) try: _redirect_uri = pick_redirect_uri( - _context, request_args=req_args, response_type=_response_type, - response_mode=_response_mode + _context, + request_args=req_args, + response_type=_response_type, + response_mode=_response_mode, ) except KeyError: raise Unsupported( - 'Could not pick a redirect_uri based on the given response_type and response_mode') + "Could not pick a redirect_uri based on the given response_type and response_mode" + ) except [MissingRequiredAttribute, ValueError]: raise @@ -211,12 +214,12 @@ def init_authorization( } if _response_mode: - request_args['response_mode'] = _response_mode + request_args["response_mode"] = _response_mode - _nonce = '' - if self.client_type == 'oidc': + _nonce = "" + if self.client_type == "oidc": _nonce = rndstr(24) - request_args['nonce'] = _nonce + request_args["nonce"] = _nonce _scope = _context.claims.get_usage("scope") if _scope: @@ -387,9 +390,7 @@ def userinfo_in_id_token(id_token: Message, user_info_claims: Optional[List] = N res.update(id_token.extra()) return res - def finalize_auth( - self, response: dict, behaviour_args: Optional[dict] = None - ): + def finalize_auth(self, response: dict, behaviour_args: Optional[dict] = None): """ Given the response returned to the redirect_uri, parse and verify it. @@ -426,7 +427,7 @@ def finalize_auth( raise KeyError("Unknown state value") try: - issuer = _context.provider_info['issuer'] + issuer = _context.provider_info["issuer"] except KeyError: issuer = _context.issuer @@ -439,10 +440,10 @@ def finalize_auth( return authorization_response def get_access_and_id_token( - self, - authorization_response: Optional[Message] = None, - state: Optional[str] = "", - behaviour_args: Optional[dict] = None, + self, + authorization_response: Optional[Message] = None, + state: Optional[str] = "", + behaviour_args: Optional[dict] = None, ): """ There are a number of services where access tokens and ID tokens can @@ -591,7 +592,7 @@ def finalize(self, response, behaviour_args: Optional[dict] = None): "token": token["access_token"], "id_token": _id_token, "session_state": authorization_response.get("session_state", ""), - "issuer": _context.issuer + "issuer": _context.issuer, } def has_active_authentication(self, state): @@ -645,9 +646,9 @@ def get_valid_access_token(self, state: str) -> tuple: raise OidcServiceError("No valid access token") def logout( - self, - state: str, - post_logout_redirect_uri: Optional[str] = "", + self, + state: str, + post_logout_redirect_uri: Optional[str] = "", ) -> dict: """ Does an RP initiated logout from an OP. After logout the user will be @@ -677,15 +678,11 @@ def logout( logger.debug(f"EndSession Request: {_info['request'].to_dict()}") return _info - def close( - self, state: str, post_logout_redirect_uri: Optional[str] = "" - ) -> dict: + def close(self, state: str, post_logout_redirect_uri: Optional[str] = "") -> dict: logger.debug(20 * "*" + " close " + 20 * "*") - return self.logout( - state=state, post_logout_redirect_uri=post_logout_redirect_uri - ) + return self.logout(state=state, post_logout_redirect_uri=post_logout_redirect_uri) def clear_session(self, state): self.get_context().cstate.remove_state(state) diff --git a/src/idpyoidc/client/oauth2/utils.py b/src/idpyoidc/client/oauth2/utils.py index 1933a2d0..5b8a365d 100644 --- a/src/idpyoidc/client/oauth2/utils.py +++ b/src/idpyoidc/client/oauth2/utils.py @@ -28,7 +28,7 @@ def pick_redirect_uri( context, request_args: Optional[Union[Message, dict]] = None, response_type: Optional[str] = "", - response_mode: Optional[str] = "" + response_mode: Optional[str] = "", ): if request_args is None: request_args = {} diff --git a/src/idpyoidc/client/oidc/access_token.py b/src/idpyoidc/client/oidc/access_token.py index 39f778fb..1c5e6740 100644 --- a/src/idpyoidc/client/oidc/access_token.py +++ b/src/idpyoidc/client/oidc/access_token.py @@ -2,7 +2,6 @@ from typing import Optional from typing import Union -from idpyoidc.claims import get_signing_algs from idpyoidc.client.client_auth import get_client_authn_methods from idpyoidc.client.exception import ParameterError from idpyoidc.client.oauth2 import access_token @@ -10,6 +9,7 @@ from idpyoidc.message import Message from idpyoidc.message import oidc from idpyoidc.message.oidc import verified_claim_name +from idpyoidc.metadata import get_signing_algs from idpyoidc.time_util import time_sans_frac __author__ = "Roland Hedberg" diff --git a/src/idpyoidc/client/oidc/authorization.py b/src/idpyoidc/client/oidc/authorization.py index dfccc14d..2228a5e6 100644 --- a/src/idpyoidc/client/oidc/authorization.py +++ b/src/idpyoidc/client/oidc/authorization.py @@ -3,7 +3,7 @@ from typing import Optional from typing import Union -from idpyoidc import claims +from idpyoidc import metadata from idpyoidc.client.oauth2 import authorization from idpyoidc.client.oauth2.utils import pre_construct_pick_redirect_uri from idpyoidc.client.oidc import IDT2REG @@ -32,9 +32,9 @@ class Authorization(authorization.Authorization): error_msg = oidc.ResponseMessage _supports = { - "request_object_signing_alg_values_supported": claims.get_signing_algs, - "request_object_encryption_alg_values_supported": claims.get_encryption_algs, - "request_object_encryption_enc_values_supported": claims.get_encryption_encs, + "request_object_signing_alg_values_supported": metadata.get_signing_algs(), + "request_object_encryption_alg_values_supported": metadata.get_encryption_algs(), + "request_object_encryption_enc_values_supported": metadata.get_encryption_encs(), "response_types_supported": ["code", "id_token", "code id_token"], "request_parameter_supported": None, "request_uri_parameter_supported": None, @@ -376,7 +376,7 @@ def _do_type(self, context, typ, response_types): elif typ == "form_post": if typ in context.get_preference("response_modes_supported"): return "form_post" - return '' + return "" def construct_uris( self, diff --git a/src/idpyoidc/client/oidc/userinfo.py b/src/idpyoidc/client/oidc/userinfo.py index 602d2ce9..05fce76b 100644 --- a/src/idpyoidc/client/oidc/userinfo.py +++ b/src/idpyoidc/client/oidc/userinfo.py @@ -5,12 +5,12 @@ from idpyoidc import verified_claim_name from idpyoidc.client.oauth2.utils import get_state_parameter from idpyoidc.client.service import Service -from idpyoidc.claims import get_encryption_algs -from idpyoidc.claims import get_encryption_encs -from idpyoidc.claims import get_signing_algs from idpyoidc.exception import MissingSigningKey from idpyoidc.message import Message from idpyoidc.message import oidc +from idpyoidc.metadata import get_encryption_algs +from idpyoidc.metadata import get_encryption_encs +from idpyoidc.metadata import get_signing_algs logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/client/provider/github.py b/src/idpyoidc/client/provider/github.py index 7749bfb4..56c91031 100644 --- a/src/idpyoidc/client/provider/github.py +++ b/src/idpyoidc/client/provider/github.py @@ -1,12 +1,12 @@ from idpyoidc.client.client_auth import get_client_authn_methods from idpyoidc.client.oauth2 import access_token from idpyoidc.client.oidc import userinfo -from idpyoidc.message import Message from idpyoidc.message import SINGLE_OPTIONAL_STRING from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message from idpyoidc.message import oauth2 from idpyoidc.message.oauth2 import ResponseMessage -from idpyoidc.claims import get_signing_algs +from idpyoidc.metadata import get_signing_algs class AccessTokenResponse(Message): diff --git a/src/idpyoidc/client/provider/linkedin.py b/src/idpyoidc/client/provider/linkedin.py index f889dffb..17c7e85b 100644 --- a/src/idpyoidc/client/provider/linkedin.py +++ b/src/idpyoidc/client/provider/linkedin.py @@ -1,13 +1,13 @@ +from idpyoidc.client.client_auth import get_client_authn_methods from idpyoidc.client.oauth2 import access_token from idpyoidc.client.oidc import userinfo -from idpyoidc.client.client_auth import get_client_authn_methods -from idpyoidc.message import Message from idpyoidc.message import SINGLE_OPTIONAL_JSON from idpyoidc.message import SINGLE_OPTIONAL_STRING from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message from idpyoidc.message import oauth2 -from idpyoidc.claims import get_signing_algs +from idpyoidc.metadata import get_signing_algs class AccessTokenResponse(Message): diff --git a/src/idpyoidc/client/rp_handler.py b/src/idpyoidc/client/rp_handler.py index fe44054d..acbad9e5 100644 --- a/src/idpyoidc/client/rp_handler.py +++ b/src/idpyoidc/client/rp_handler.py @@ -4,8 +4,8 @@ from typing import List from typing import Optional -from cryptojwt import as_unicode from cryptojwt import KeyJar +from cryptojwt import as_unicode from cryptojwt.key_jar import init_key_jar from cryptojwt.utils import as_bytes @@ -27,28 +27,28 @@ from idpyoidc.time_util import utc_time_sans_frac from idpyoidc.util import add_path from idpyoidc.util import rndstr -from .oauth2 import Client + from ..message import Message from ..message.oauth2 import ResponseMessage +from .oauth2 import Client logger = logging.getLogger(__name__) class RPHandler(object): - def __init__( - self, - base_url: Optional[str] = "", - client_configs=None, - services=None, - keyjar=None, - hash_seed="", - verify_ssl=True, - state_db=None, - httpc=None, - httpc_params=None, - config=None, - **kwargs, + self, + base_url: Optional[str] = "", + client_configs=None, + services=None, + keyjar=None, + hash_seed="", + verify_ssl=True, + state_db=None, + httpc=None, + httpc_params=None, + config=None, + **kwargs, ): self.base_url = base_url _jwks_path = kwargs.get("jwks_path") @@ -219,10 +219,10 @@ def init_client(self, issuer): return client def do_provider_info( - self, - client: Optional[Client] = None, - state: Optional[str] = "", - behaviour_args: Optional[dict] = None, + self, + client: Optional[Client] = None, + state: Optional[str] = "", + behaviour_args: Optional[dict] = None, ) -> str: """ Either get the provider info from configuration or through dynamic @@ -243,12 +243,12 @@ def do_provider_info( return client.do_provider_info(behaviour_args=behaviour_args) def do_client_registration( - self, - client=None, - iss_id: Optional[str] = "", - state: Optional[str] = "", - request_args: Optional[dict] = None, - behaviour_args: Optional[dict] = None, + self, + client=None, + iss_id: Optional[str] = "", + state: Optional[str] = "", + request_args: Optional[dict] = None, + behaviour_args: Optional[dict] = None, ): """ Prepare for and do client registration if configured to do so @@ -270,8 +270,9 @@ def do_client_registration( _iss = _context.get("issuer") self.hash2issuer[iss_id] = _iss - return client.do_client_registration(request_args=request_args, - behaviour_args=behaviour_args) + return client.do_client_registration( + request_args=request_args, behaviour_args=behaviour_args + ) def do_webfinger(self, user: str) -> Client: """ @@ -289,10 +290,10 @@ def do_webfinger(self, user: str) -> Client: return temporary_client def client_setup( - self, - iss_id: Optional[str] = "", - user: Optional[str] = "", - behaviour_args: Optional[dict] = None, + self, + iss_id: Optional[str] = "", + user: Optional[str] = "", + behaviour_args: Optional[dict] = None, ) -> StandAloneClient: """ First if no issuer ID is given then the identifier for the user is @@ -348,11 +349,11 @@ def _get_response_type(self, context, req_args: Optional[dict] = None): return context.claims.get_usage("response_types")[0] def init_authorization( - self, - client: Optional[Client] = None, - state: Optional[str] = "", - req_args: Optional[dict] = None, - behaviour_args: Optional[dict] = None, + self, + client: Optional[Client] = None, + state: Optional[str] = "", + req_args: Optional[dict] = None, + behaviour_args: Optional[dict] = None, ) -> str: """ Constructs the URL that will redirect the user to the authorization @@ -502,7 +503,7 @@ def userinfo_in_id_token(id_token: Message, user_info_claims: Optional[List] = N return StandAloneClient.userinfo_in_id_token(id_token, user_info_claims) def finalize_auth( - self, client, issuer: str, response: dict, behaviour_args: Optional[dict] = None + self, client, issuer: str, response: dict, behaviour_args: Optional[dict] = None ): """ Given the response returned to the redirect_uri, parse and verify it. @@ -521,11 +522,11 @@ def finalize_auth( return client.finalize_auth(response, behaviour_args=behaviour_args) def get_access_and_id_token( - self, - authorization_response=None, - state: Optional[str] = "", - client: Optional[object] = None, - behaviour_args: Optional[dict] = None, + self, + authorization_response=None, + state: Optional[str] = "", + client: Optional[object] = None, + behaviour_args: Optional[dict] = None, ): """ There are a number of services where access tokens and ID tokens can @@ -544,8 +545,11 @@ def get_access_and_id_token( if client is None: client = self.get_client_from_session_key(state) - return client.get_access_and_id_token(authorization_response=authorization_response, - state=state, behaviour_args=behaviour_args) + return client.get_access_and_id_token( + authorization_response=authorization_response, + state=state, + behaviour_args=behaviour_args, + ) # noinspection PyUnusedLocal def finalize(self, issuer, response, behaviour_args: Optional[dict] = None): @@ -594,10 +598,10 @@ def get_valid_access_token(self, state): return client.get_valid_access_token(state) def logout( - self, - state: str, - client: Optional[Client] = None, - post_logout_redirect_uri: Optional[str] = "", + self, + state: str, + client: Optional[Client] = None, + post_logout_redirect_uri: Optional[str] = "", ) -> dict: """ Does an RP initiated logout from an OP. After logout the user will be @@ -615,10 +619,8 @@ def logout( return client.logout(state, post_logout_redirect_uri=post_logout_redirect_uri) - def close( - self, state: str, issuer: Optional[str] = "", - post_logout_redirect_uri: Optional[str] = "" + self, state: str, issuer: Optional[str] = "", post_logout_redirect_uri: Optional[str] = "" ) -> dict: if issuer: @@ -626,11 +628,8 @@ def close( else: client = self.get_client_from_session_key(state) - return client.logout( - state=state, post_logout_redirect_uri=post_logout_redirect_uri - ) + return client.logout(state=state, post_logout_redirect_uri=post_logout_redirect_uri) def clear_session(self, state): client = self.get_client_from_session_key(state) client.get_context().cstate.remove_state(state) - diff --git a/src/idpyoidc/client/service.py b/src/idpyoidc/client/service.py index 0b9dd641..f2348c77 100644 --- a/src/idpyoidc/client/service.py +++ b/src/idpyoidc/client/service.py @@ -19,6 +19,10 @@ from idpyoidc.message.oauth2 import ResponseMessage from idpyoidc.message.oauth2 import is_error_message from idpyoidc.util import importer + +from ..constant import JOSE_ENCODED +from ..constant import JSON_ENCODED +from ..constant import URL_ENCODED from .client_auth import client_auth_setup from .client_auth import method_to_item from .client_auth import single_authn_setup @@ -26,9 +30,6 @@ from .exception import ResponseError from .util import get_http_body from .util import get_http_url -from ..constant import JOSE_ENCODED -from ..constant import JSON_ENCODED -from ..constant import URL_ENCODED __author__ = "Roland Hedberg" @@ -591,8 +592,10 @@ def parse_response( LOGGER.debug("response format: %s", sformat) resp = None + _jws = _jwe = None if sformat == "jose": # can be jwe, jws or json # the checks for JWS and JWE will be replaced with functions from cryptojwt + _jws = info try: if jws_factory(info): info = self._do_jwt(info) @@ -601,19 +604,21 @@ def parse_response( if jwe_factory(info): info = self._do_jwt(info) except: - LOGGER.debug('jwe detected') + LOGGER.debug("jwe detected") if info and isinstance(info, str): info = json.loads(info) sformat = "dict" elif sformat == "jwe": _keyjar = self.upstream_get("attribute", "keyjar") _client_id = self.upstream_get("attribute", "client_id") + _jwe = info resp = self.response_cls().from_jwe(info, keys=_keyjar.get_issuer_keys(_client_id)) # If format is urlencoded 'info' may be a URL # in which case I have to get at the query/fragment part elif sformat == "urlencoded": info = self.get_urlinfo(info) elif sformat in ["jwt", "jws"]: + _jws = info info = self._do_jwt(info) sformat = "dict" elif sformat == "json": @@ -648,6 +653,11 @@ def parse_response( LOGGER.error("Got exception while verifying response: %s", err) raise + if _jws: + resp._jws = _jws + elif _jwe: + resp._jwe = _jwe + resp = self.post_parse_response(resp, state=state) if not resp: diff --git a/src/idpyoidc/client/service_context.py b/src/idpyoidc/client/service_context.py index 57dfc7dc..d5491bfa 100644 --- a/src/idpyoidc/client/service_context.py +++ b/src/idpyoidc/client/service_context.py @@ -21,11 +21,12 @@ from idpyoidc.client.claims.oidc import Claims as OIDC_Specs from idpyoidc.client.configure import Configuration from idpyoidc.util import rndstr + +from ..impexp import ImpExp from .claims.transform import preferred_to_registered from .claims.transform import supported_to_preferred from .configure import get_configuration from .current import Current -from ..impexp import ImpExp logger = logging.getLogger(__name__) @@ -135,7 +136,10 @@ def __init__( else: raise ValueError(f"Unknown client type: {client_type}") - self.entity_id = config.conf.get("client_id", "") + if "client_id" in kwargs: + self.entity_id = kwargs["entity_id"] + else: + self.entity_id = config.conf.get("client_id", "") self.cstate = cstate or Current() self.kid = {"sig": {}, "enc": {}} @@ -171,9 +175,9 @@ def __init__( self.keyjar = self.claims.load_conf(config.conf, supports=self.supports(), keyjar=keyjar) - _jwks_uri = self.provider_info.get('jwks_uri') + _jwks_uri = self.provider_info.get("jwks_uri") if _jwks_uri: - self.keyjar.load_keys(self.provider_info.get('issuer'), jwks_uri=_jwks_uri) + self.keyjar.load_keys(self.provider_info.get("issuer"), jwks_uri=_jwks_uri) _response_types = self.get_preference( "response_types_supported", self.supports().get("response_types_supported", []) diff --git a/src/idpyoidc/client/util.py b/src/idpyoidc/client/util.py index 38cb3a09..03084d20 100755 --- a/src/idpyoidc/client/util.py +++ b/src/idpyoidc/client/util.py @@ -14,6 +14,7 @@ from idpyoidc.defaults import BASECHR from idpyoidc.exception import UnSupported from idpyoidc.util import importer + from .exception import TimeFormatError from .exception import WrongContentType diff --git a/src/idpyoidc/message/__init__.py b/src/idpyoidc/message/__init__.py index 010155fb..41a4ddb5 100644 --- a/src/idpyoidc/message/__init__.py +++ b/src/idpyoidc/message/__init__.py @@ -2,6 +2,8 @@ import json import logging from collections.abc import MutableMapping +from typing import Any +from typing import Optional from urllib.parse import parse_qs from urllib.parse import urlencode @@ -363,7 +365,11 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed, sformat="urlenc try: _val = [] for v in val: - _val.append(vtype(**{str(x): y for x, y in v.items()})) + if isinstance(v, vtype): + _val.append(v) + continue + else: + _val.append(vtype(**{str(x): y for x, y in v.items()})) val = _val except Exception as exc: raise DecodeError(ERRTXT % (key, exc)) @@ -816,7 +822,7 @@ def required_parameters(self): """ return [p for p, s in self.c_param.items() if s[1] is True] - def value_type(self, parameter): + def value_type(self, parameter: object) -> Optional[Any]: """ Return the type of value that a parameter can have. @@ -988,7 +994,6 @@ def msg_list_ser(val, sformat="urlencoded"): False, ) SINGLE_OPTIONAL_JSON = (dict, False, json_serializer, json_deserializer, False) - SINGLE_REQUIRED_JSON = (dict, True, json_serializer, json_deserializer, False) REQUIRED = [SINGLE_REQUIRED_STRING, REQUIRED_LIST_OF_STRINGS, REQUIRED_LIST_OF_SP_SEP_STRINGS] @@ -1012,6 +1017,10 @@ def any_ser(val, sformat="urlencoded"): raise ValueError("Can't serialize this type of data") +def ser_any_list(val, sformat): + return [any_ser(v, sformat) for v in val] + + def any_deser(val, sformat="urlencoded"): if isinstance(val, dict): return Message(**val) @@ -1021,6 +1030,12 @@ def any_deser(val, sformat="urlencoded"): raise ValueError("Can't deserialize this type of data") -SINGLE_OPTIONAL_ANY = (any, False, any_ser, any_deser, False) +def deser_any_list(val, sformat): + return [any_deser(v, sformat) for v in val] + + +SINGLE_OPTIONAL_ANY = (Any, False, any_ser, any_deser, False) +OPTIONAL_ANY_LIST = ([Any], False, ser_any_list, deser_any_list, False) + REQUIRED_LIST_OF_DICTS = ([dict], True, list_serializer, list_deserializer, False) OPTIONAL_LIST_OF_DICTS = ([dict], False, list_serializer, list_deserializer, False) diff --git a/src/idpyoidc/message/oauth2/__init__.py b/src/idpyoidc/message/oauth2/__init__.py index 9b411790..5349a7f1 100644 --- a/src/idpyoidc/message/oauth2/__init__.py +++ b/src/idpyoidc/message/oauth2/__init__.py @@ -9,8 +9,6 @@ from idpyoidc.exception import MissingAttribute from idpyoidc.exception import MissingRequiredAttribute from idpyoidc.exception import VerificationError -from idpyoidc.message import Message -from idpyoidc.message import msg_ser from idpyoidc.message import OPTIONAL_LIST_OF_SP_SEP_STRINGS from idpyoidc.message import OPTIONAL_LIST_OF_STRINGS from idpyoidc.message import REQUIRED_LIST_OF_SP_SEP_STRINGS @@ -21,6 +19,8 @@ from idpyoidc.message import SINGLE_REQUIRED_BOOLEAN from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message +from idpyoidc.message import msg_ser logger = logging.getLogger(__name__) @@ -362,18 +362,17 @@ class ASConfigurationResponse(Message): def deserialize_from_one_of(val, msgtype, sformat): - if sformat in ["dict", "json"]: - flist = ["json", "urlencoded"] + if sformat == "dict": + return msgtype(**val) + + if sformat == "json": if not isinstance(val, str): val = json.dumps(val) - else: - flist = ["urlencoded", "json"] + return msgtype().deserialize(val, "json") + + if sformat == "urlencoded": + return msgtype().deserialize(val, "urlencoded") - for _format in flist: - try: - return msgtype().deserialize(val, _format) - except FormatError: - pass raise FormatError("Unexpected format") diff --git a/src/idpyoidc/message/oidc/identity_assurance.py b/src/idpyoidc/message/oidc/identity_assurance.py index 9dd66887..bf9bc5ae 100644 --- a/src/idpyoidc/message/oidc/identity_assurance.py +++ b/src/idpyoidc/message/oidc/identity_assurance.py @@ -4,18 +4,23 @@ from cryptojwt.utils import importer -from idpyoidc.message import Message +from idpyoidc.message import OPTIONAL_ANY_LIST +from idpyoidc.message import OPTIONAL_LIST_OF_MESSAGES from idpyoidc.message import OPTIONAL_LIST_OF_STRINGS from idpyoidc.message import OPTIONAL_MESSAGE +from idpyoidc.message import SINGLE_OPTIONAL_ANY +from idpyoidc.message import SINGLE_OPTIONAL_INT from idpyoidc.message import SINGLE_OPTIONAL_STRING from idpyoidc.message import SINGLE_REQUIRED_STRING -from idpyoidc.message import msg_deser +from idpyoidc.message import Message from idpyoidc.message import msg_list_ser from idpyoidc.message import msg_ser from idpyoidc.message.oauth2 import error_chars +from idpyoidc.message.oidc import SINGLE_OPTIONAL_BOOLEAN from idpyoidc.message.oidc import AddressClaim from idpyoidc.message.oidc import ClaimsRequest from idpyoidc.message.oidc import OpenIDSchema +from idpyoidc.message.oidc import OPTIONAL_MULTIPLE_Claims from idpyoidc.message.oidc import claims_request_deser from idpyoidc.message.oidc import deserialize_from_one_of from idpyoidc.message.oidc import msg_ser_json @@ -149,6 +154,9 @@ def date_deser(val, sformat="", lev=0): OPTIONAL_DATE = (str, False, date_ser, date_deser, False) +# ------------------------------------------------------------------------------ + + class IdentityAssuranceClaims(OpenIDSchema): c_param = OpenIDSchema.c_param.copy() c_param.update( @@ -161,42 +169,73 @@ class IdentityAssuranceClaims(OpenIDSchema): "salutation": SINGLE_OPTIONAL_STRING, "title": SINGLE_OPTIONAL_STRING, "msisdn": SINGLE_OPTIONAL_STRING, - "also_known_as": SINGLE_OPTIONAL_STRING + "also_known_as": SINGLE_OPTIONAL_STRING, } ) +def identity_assurance_claims_deser(val, sformat="json"): + return deserialize_from_one_of(val, IdentityAssuranceClaims, sformat) + + +OPTIONAL_IDA_CLAIMS = ( + IdentityAssuranceClaims, + False, + msg_ser, + identity_assurance_claims_deser, + False, +) +REQUIRED_IDA_CLAIMS = ( + IdentityAssuranceClaims, + True, + msg_ser, + identity_assurance_claims_deser, + False, +) + + +# ------------------------------------------------------------------------------ + + class Address(AddressClaim): c_param = AddressClaim.c_param.copy() c_param.update({"country_code": SINGLE_OPTIONAL_STRING}) -OPTIONAL_IDA_CLAIMS = (IdentityAssuranceClaims, False, msg_ser, msg_deser, False) -REQUIRED_IDA_CLAIMS = (IdentityAssuranceClaims, True, msg_ser, msg_deser, False) +def address_deser(val, sformat="urlencoded"): + return deserialize_from_one_of(val, Address, sformat) + + +OPTIONAL_ADDRESS = (Address, False, msg_ser, address_deser, False) +# ------------------------------------------------------------------------------ class Verifier(Message): c_param = {"organization": SINGLE_REQUIRED_STRING, "txn": SINGLE_REQUIRED_STRING} -def verifier_deser(val, sformat="urlencoded"): - if isinstance(val, Message): - return val - elif sformat in ["dict", "json"]: - if not isinstance(val, str): - val = json.dumps(val) - sformat = "json" - return Verifier().deserialize(val, sformat) +def verifier_deser(val, sformat="json"): + return deserialize_from_one_of(val, Verifier, sformat) REQUIRED_VERIFIER = (Verifier, True, msg_ser, verifier_deser, False) +def verifier_list_deser(val, sformat="json"): + return [verifier_deser(v, sformat) for v in val] + + +OPTIONAL_VERIFIERS = ([Verifier], False, msg_ser, verifier_list_deser, False) + + +# ------------------------------------------------------------------------------ class Issuer(Message): c_param = { "name": SINGLE_REQUIRED_STRING, - "country": SINGLE_REQUIRED_STRING + "country_code": SINGLE_REQUIRED_STRING, + "jurisdiction": SINGLE_OPTIONAL_STRING, } + c_param.update(AddressClaim.c_param) def issuer_deser(val, sformat="urlencoded"): @@ -210,57 +249,77 @@ def issuer_deser(val, sformat="urlencoded"): REQUIRED_ISSUER = (Issuer, True, msg_ser, issuer_deser, False) +OPTIONAL_ISSUER = (Issuer, False, msg_ser, issuer_deser, False) -class Document(Message): +def json_list_deserializer(insts, sformat): + if sformat == "dict": + return insts + elif sformat == "json": + return json.loads(insts) + + +# MULTIPLE_OPTIONAL_JSON = ([dict], False, msg_ser, json_list_deserializer, False) + + +class EmbeddedAttachments(Message): c_param = { - "type": SINGLE_REQUIRED_STRING, - "number": SINGLE_REQUIRED_STRING, - "issuer": REQUIRED_ISSUER, - "date_of_issuance": REQURIED_TIME_STAMP, - "date_of_expiry": REQURIED_TIME_STAMP, + "desc": SINGLE_OPTIONAL_STRING, + "content_type": SINGLE_REQUIRED_STRING, + "content": SINGLE_REQUIRED_STRING, + "txn": SINGLE_OPTIONAL_STRING, } -def document_deser(val, sformat="urlencoded"): - return deserialize_from_one_of(val, Document, sformat) +class Digest(Message): + c_param = {"alg": SINGLE_REQUIRED_STRING, "value": SINGLE_REQUIRED_STRING} -OPTIONAL_DOCUMENT = (Document, False, msg_ser, document_deser, False) +def digest_deser(val, sformat="json"): + return deserialize_from_one_of(val, Digest, sformat) + + +OPTIONAL_DIGEST = (Digest, False, msg_ser, digest_deser, False) + + +class ExternalAttachments(Message): + c_param = { + "desc": SINGLE_OPTIONAL_STRING, + "url": SINGLE_REQUIRED_STRING, + "access_token": SINGLE_OPTIONAL_STRING, + "expires_in": SINGLE_OPTIONAL_INT, + "digest": OPTIONAL_DIGEST, + "txn": SINGLE_OPTIONAL_STRING, + } + + +# ----------------------------------------------------------------- class Evidence(Message): - c_param = {"type": SINGLE_OPTIONAL_STRING} + c_param = {"type": SINGLE_REQUIRED_STRING, "attachments": OPTIONAL_LIST_OF_MESSAGES} def verify(self, **kwargs): + super(Evidence, self).verify(**kwargs) + if "attachments" in self: + _items = [] + for attch in self["attachments"]: + if "url" in attch: + _items.append(ExternalAttachments(**attch)) + else: + _items.append(EmbeddedAttachments(**attch)) + self._dict["attachments"] = _items + _type = self.get("type") - if _type: - if _type == "id_document": - _doc = IdDocument(**self.to_dict()) - _doc.verify(**kwargs) - elif _type == "utility_bill": - _bill = UtilityBill(**self.to_dict()) - _bill.verify(**kwargs) - elif _type == "qes": - _qes = QES(**self.to_dict()) - _qes.verify(**kwargs) - else: - raise ValueError("Unknown type") - else: # let the guessing begin - if all(x in self.keys() for x in IdDocument.c_param.keys()): - _doc = IdDocument(**self.to_dict()) - _doc.verify(**kwargs) - self["type"] = "id_document" - elif all(x in self.keys() for x in UtilityBill.c_param.keys()): - _bill = UtilityBill(**self.to_dict()) - _bill.verify(**kwargs) - self["type"] = "utility_bill" - elif all(x in self.keys() for x in QES.c_param.keys()): - _qes = QES(**self.to_dict()) - _qes.verify(**kwargs) - self["type"] = "qes" - else: - raise ValueError("Unknown object") + if _type not in EVIDENCE_TYPES: + raise ValueError("Unknown event type") + + _evidence_cls = EVIDENCE_TYPES.get(_type) + if _evidence_cls: + _evidence_instance = _evidence_cls(**self.to_dict()) + _evidence_instance.verify(**kwargs) + else: + raise ValueError("Unknown type") def evidence_deser(val, sformat="urlencoded"): @@ -278,39 +337,81 @@ def evidence_list_deser(val, sformat="urlencoded", lev=0): OPTIONAL_EVIDENCE_LIST = ([Evidence], False, msg_list_ser, evidence_list_deser, True) -class IdDocument(Evidence): +# ------------------------------------------------------------------------------ +class CheckDetails(Message): + c_param = { + "check_method": SINGLE_REQUIRED_STRING, + "organization": SINGLE_OPTIONAL_STRING, + "txn": SINGLE_OPTIONAL_STRING, + "time": OPTIONAL_TIME_STAMP, + } + + +def check_details_deser(val, sformat="urlencoded"): + return deserialize_from_one_of(val, CheckDetails, sformat) + + +def check_details_list_deser(val, sformat="json"): + return [check_details_deser(v, sformat) for v in val] + + +OPTIONAL_CHECK_DETAILS_LIST = ([CheckDetails], False, msg_list_ser, check_details_list_deser, True) + + +# ------------------------------------------------------------------------------ +class DocumentDetails(Message): + c_param = { + "type": SINGLE_REQUIRED_STRING, + "document_number": SINGLE_REQUIRED_STRING, + "personal_number": SINGLE_OPTIONAL_STRING, + "serial_number": SINGLE_OPTIONAL_STRING, + "date_of_issuance": REQURIED_TIME_STAMP, + "date_of_expiry": REQURIED_TIME_STAMP, + "issuer": REQUIRED_ISSUER, + } + + +def document_details_deser(val, sformat="urlencoded"): + return deserialize_from_one_of(val, Document, sformat) + + +REQUIRED_DOCUMENT_DETAILS = (DocumentDetails, True, msg_ser, document_details_deser, False) +OPTIONAL_DOCUMENT_DETAILS = (DocumentDetails, False, msg_ser, document_details_deser, False) + + +# ------------------------------------------------------------------------------ + + +class Document(Message): c_param = Evidence.c_param.copy() c_param.update( { - "method": SINGLE_REQUIRED_STRING, - "verifier": REQUIRED_VERIFIER, + "check_details": OPTIONAL_CHECK_DETAILS_LIST, + "method": SINGLE_OPTIONAL_STRING, + "verifier": OPTIONAL_VERIFIERS, "time": OPTIONAL_TIME_STAMP, - "document": OPTIONAL_DOCUMENT, + "document_details": OPTIONAL_DOCUMENT_DETAILS, } ) -def id_document_deser(val, sformat="urlencoded"): - if isinstance(val, Message): - return val - elif sformat in ["dict", "json"]: - if not isinstance(val, str): - val = json.dumps(val) - sformat = "json" - return IdDocument().deserialize(val, sformat) +# ------------------------------------------------------------------------------ + + +def document_deser(val, sformat="urlencoded"): + return deserialize_from_one_of(val, Document, sformat) -REQUIRED_ID_DOCUMENT = (IdDocument, True, msg_ser, id_document_deser, False) -OPTIONAL_ID_DOCUMENT = (IdDocument, False, msg_ser, id_document_deser, False) +REQUIRED_DOCUMENT = (Document, True, msg_ser, document_deser, False) +OPTIONAL_DOCUMENT = (Document, False, msg_ser, document_deser, False) + + +# ------------------------------------------------------------------------------ class Provider(AddressClaim): c_param = AddressClaim.c_param.copy() - c_param.update( - { - "name": SINGLE_OPTIONAL_STRING, - } - ) + c_param.update({"name": SINGLE_OPTIONAL_STRING, "country_code": SINGLE_OPTIONAL_STRING}) def provider_deser(val, sformat="urlencoded"): @@ -326,61 +427,153 @@ def provider_deser(val, sformat="urlencoded"): REQUIRED_PROVIDER = (Provider, True, msg_ser, provider_deser, False) -class UtilityBill(Evidence): +# ------------------------------------------------------------------------------ + + +class UtilityBill(Message): c_param = Evidence.c_param.copy() - c_param.update({"provider": REQUIRED_PROVIDER, "date": OPTIONAL_TIME_STAMP}) + c_param.update( + { + "provider": REQUIRED_PROVIDER, + "date": OPTIONAL_TIME_STAMP, + "method": SINGLE_OPTIONAL_STRING, + "time": SINGLE_OPTIONAL_INT, + } + ) -def utility_bill_deser(val, sformat="urlencoded"): - if isinstance(val, Message): - return val - elif sformat in ["dict", "json"]: - if not isinstance(val, str): - val = json.dumps(val) - sformat = "json" - return UtilityBill().deserialize(val, sformat) +# ------------------------------------------------------------------------------ + + +class Record(Message): + c_param = { + "type": SINGLE_REQUIRED_STRING, + "personal_number": SINGLE_OPTIONAL_STRING, + "created_at": OPTIONAL_DATE, + "date_of_expiry": OPTIONAL_DATE, + "source": OPTIONAL_ISSUER, + } + + +def record_deser(val, sformat="json"): + return deserialize_from_one_of(val, Record, sformat) + +OPTIONAL_RECORD = (Record, False, msg_list_ser, record_deser, True) -REQUIRED_UTILITY_BILL = (UtilityBill, True, msg_ser, utility_bill_deser, False) -OPTIONAL_UTILITY_BILL = (UtilityBill, False, msg_ser, utility_bill_deser, False) +# ------------------------------------------------------------------------------ -class QES(Evidence): + +class ElectronicRecord(Message): c_param = Evidence.c_param.copy() c_param.update( { + "check_details": OPTIONAL_CHECK_DETAILS_LIST, + "time": SINGLE_OPTIONAL_INT, + "record": OPTIONAL_RECORD, + } + ) + + +# ------------------------------------------------------------------------------ + + +class ElectronicSignature(Message): + c_param = Evidence.c_param.copy() + c_param.update( + { + "signature_type": SINGLE_REQUIRED_STRING, "issuer": SINGLE_REQUIRED_STRING, "serial_number": SINGLE_REQUIRED_STRING, - "created_at": REQURIED_TIME_STAMP, + "created_at": OPTIONAL_DATE, } ) -def qes_deser(val, sformat="urlencoded"): +# ------------------------------------------------------------------------------ + + +class Voucher(Message): + c_param = { + "name": SINGLE_OPTIONAL_STRING, + "birthdate": SINGLE_OPTIONAL_STRING, + "country_code": SINGLE_OPTIONAL_STRING, + "occupation": SINGLE_OPTIONAL_STRING, + "organization": SINGLE_OPTIONAL_STRING, + } + + +def voucher_deser(val, sformat="json"): + return deserialize_from_one_of(val, Voucher, sformat) + + +OPTIONAL_VOUCHER = (Voucher, False, msg_ser, voucher_deser, False) + +# ------------------------------------------------------------------------------ + +EVIDENCE_TYPES = { + "document": Document, + "utility_bill": UtilityBill, + "electronic_record": ElectronicRecord, + "voucher": Voucher, + "electronic_signature": ElectronicSignature, +} + + +# ------------------------------------------------------------------------------ + + +class Attestation(Message): + c_param = { + "type": SINGLE_REQUIRED_STRING, + "reference_number": SINGLE_OPTIONAL_STRING, + "date_of_issuance": OPTIONAL_DATE, + "date_of_expiry": OPTIONAL_DATE, + "voucher": OPTIONAL_VOUCHER, + } + + +def attestation_deser(val, sformat="json"): + return deserialize_from_one_of(val, Attestation, sformat) + + +OPTIONAL_ATTESTATION = (Attestation, False, msg_ser, attestation_deser, False) + + +# ------------------------------------------------------------------------------ + + +class Vouch(Message): + c_param = Evidence.c_param.copy() + c_param.update( + { + "check_details": OPTIONAL_CHECK_DETAILS_LIST, + "time": SINGLE_OPTIONAL_INT, + "attestation": OPTIONAL_ATTESTATION, + } + ) + + +def vouch_deser(val, sformat="urlencoded"): if isinstance(val, Message): return val elif sformat in ["dict", "json"]: if not isinstance(val, str): val = json.dumps(val) sformat = "json" - return QES().deserialize(val, sformat) + return Vouch().deserialize(val, sformat) -REQUIRED_QES = (QES, True, msg_ser, qes_deser, False) -OPTIONAL_QES = (QES, False, msg_ser, qes_deser, False) +REQUIRED_VOUCH = (Vouch, True, msg_ser, vouch_deser, False) +OPTIONAL_VOUCH = (Vouch, False, msg_ser, vouch_deser, False) -def address_deser(val, sformat="urlencoded"): - return deserialize_from_one_of(val, Address, sformat) - - -OPTIONAL_ADDRESS = (Address, False, msg_ser, address_deser, False) +# ------------------------------------------------------------------------------ class EvidenceMetadata(Message): - c_param = { - "evidence_classification": SINGLE_OPTIONAL_STRING - } + c_param = {"evidence_classification": SINGLE_OPTIONAL_STRING} def evidence_metadata_deser(val, sformat="urlencoded"): @@ -391,10 +584,7 @@ def evidence_metadata_deser(val, sformat="urlencoded"): class EvidenceRef(Message): - c_param = { - "txn": SINGLE_REQUIRED_STRING, - "evidence_metadata": OPTIONAL_EVIDENCE_METADATA - } + c_param = {"txn": SINGLE_REQUIRED_STRING, "evidence_metadata": OPTIONAL_EVIDENCE_METADATA} def evidence_ref_deser(val, sformat="urlencoded"): @@ -408,7 +598,7 @@ class AssuranceDetails(Message): c_param = { "assurance_type": SINGLE_OPTIONAL_STRING, "assurance_classification": SINGLE_OPTIONAL_STRING, - "evidence_ref": OPTIONAL_EVIDENCE_REFS + "evidence_ref": OPTIONAL_EVIDENCE_REFS, } @@ -423,7 +613,7 @@ class AssuranceProcess(Message): c_param = { "policy": SINGLE_OPTIONAL_STRING, "procedure": SINGLE_OPTIONAL_STRING, - "assurance_details": OPTIONAL_ASSURANCE_DETAILS + "assurance_details": OPTIONAL_ASSURANCE_DETAILS, } @@ -441,18 +631,29 @@ class VerificationElement(Message): "verification_process": SINGLE_OPTIONAL_STRING, "evidence": OPTIONAL_EVIDENCE_LIST, "assurance_level": SINGLE_OPTIONAL_STRING, - "assurance_process": OPTIONAL_ASSURANCE_PROFILE + "assurance_process": OPTIONAL_ASSURANCE_PROFILE, } + def verify(self, **kwargs): + if "evidence" in self and self["evidence"]: + for evid in self["evidence"]: + evid.verify(**kwargs) + if "assurance_process" in self and self["assurance_process"]: + self["assurance_process"].verify(**kwargs) + def verification_element_deser(val, sformat="urlencoded"): if isinstance(val, Message): return val elif sformat in ["dict", "json"]: + if isinstance(val, dict): + return VerificationElement(**val) + if not isinstance(val, str): val = json.dumps(val) sformat = "json" - return VerificationElement().deserialize(val, sformat) + + return VerificationElement().deserialize(val, sformat) REQUIRED_VERIFICATION_ELEMENT = ( @@ -463,6 +664,45 @@ def verification_element_deser(val, sformat="urlencoded"): False, ) +OPTIONAL_VERIFICATION_ELEMENT = ( + VerificationElement, + False, + msg_ser, + verification_element_deser, + False, +) + + +class VerifiedClaims(Message): + c_param = {"verification": OPTIONAL_VERIFICATION_ELEMENT, "claims": OPTIONAL_MULTIPLE_Claims} + + def verify(self, **kwargs): + super(VerifiedClaims, self).verify(**kwargs) + if "verification" in self: + self["verification"].verify() + if "claims" in self and self["claims"]: + self["claims"].verify() + + +def verified_claims_deser(val, sformat="json"): + if isinstance(val, Message): + val = VerifiedClaims() + val.from_dict(val.to_dict()) + return val + elif sformat in ["dict", "json"]: + if isinstance(val, dict): + return VerificationElement(**val) + + if not isinstance(val, str): + val = json.dumps(val) + sformat = "json" + + return VerifiedClaims().deserialize(val, sformat) + + +REQUIRED_VERIFIED_CLAIMS = (VerifiedClaims, False, msg_ser, verified_claims_deser, False) + +# ============================================================================================ SINGLE_OPTIONAL_CLAIMSREQ = (ClaimsRequest, False, msg_ser_json, claims_request_deser, False) @@ -478,62 +718,45 @@ def _correct_value_type(val, value_type): return True -def _verify_claims_request_value(value, value_type=str): - if value is None: - return True - elif isinstance(value, dict): - # know about keys: essential, value and values, purpose - if not value.get("essential") in (None, True, False): - return False - - _v = value.get("value") - if _v: - if not _correct_value_type(_v, value_type): - return False - - _vs = value.get("values", []) - for _v in _vs: - if not _correct_value_type(_v, value_type): - return False +class ClaimsSpec(Message): + c_param = { + "essential": SINGLE_OPTIONAL_BOOLEAN, + "value": SINGLE_OPTIONAL_ANY, + "values": OPTIONAL_ANY_LIST, + "purpose": SINGLE_OPTIONAL_STRING, + "max_age": SINGLE_OPTIONAL_INT, + } - _p = value.get("purpose") - if _p: - if len(_p) < 3 or len(_p) > 300: - return False - if not all(x in error_chars for x in _p): + def _verify_claims_request_value(self, value, value_type=str): + if value is None: + return True + elif isinstance(value, dict): + # know about keys: essential, value and values, purpose + if not value.get("essential") in (None, True, False): return False - return True - + _v = value.get("value") + if _v: + if not _correct_value_type(_v, value_type): + return False + + _vs = value.get("values", []) + for _v in _vs: + if not _correct_value_type(_v, value_type): + return False + + _p = value.get("purpose") + if _p: + if len(_p) < 3 or len(_p) > 300: + return False + if not all(x in error_chars for x in _p): + return False + _p = value.get("max_age") + if _p: + if not isinstance(_p, int): + return False -def verify_claims_request(instance, base_cls_instance): - for key, spec in base_cls_instance.c_param.items(): - try: - _val = instance[key] - except KeyError: - continue - - _value_type = spec[0] - - if _value_type in (str, int, bool): - if not _verify_claims_request_value(_val, _value_type): - raise ValueError("{}: '{}'".format(key, _val)) - elif type(_value_type) == abc.ABCMeta: - if _val is None: - continue - verify_claims_request(_val, _value_type()) - elif isinstance(_value_type, list): - if _val is None: - continue - _item_val_type = _value_type[0] - for _v in _val: - if _item_val_type in (str, int, bool): - if not _verify_claims_request_value(_v, _item_val_type): - raise ValueError("{}: '{}'".format(key, _v)) - elif type(_item_val_type) == abc.ABCMeta: - if _v is None: - continue - verify_claims_request(_v, _item_val_type()) + return True class VerificationElementRequest(Message): @@ -546,7 +769,6 @@ class VerificationElementRequest(Message): def verify(self, **kwargs): super(VerificationElementRequest, self).verify(**kwargs) - verify_claims_request(self, VerificationElement()) def verification_element_request_deser(val, sformat="urlencoded"): @@ -562,17 +784,6 @@ def verification_element_request_deser(val, sformat="urlencoded"): ) -class VerifiedClaims(Message): - c_param = { - "verification": OPTIONAL_MESSAGE, - "claims": OPTIONAL_IDA_CLAIMS - } - - def verify(self, **kwargs): - super(VerifiedClaims, self).verify(**kwargs) - verify_claims_request(self, VerifiedClaims()) - - class IDAClaimsRequest(ClaimsRequest): def verify(self, **kwargs): super(IDAClaimsRequest, self).verify(**kwargs) @@ -601,7 +812,7 @@ def __setitem__(self, key, value): :param key: :param value: one of None or a dictionary with keys: "essential", - "value" or "values. + "value", "values" or "max_age". :return: """ if value is not None: @@ -628,3 +839,58 @@ def to_dict(self): def to_json(self): return json.dumps(self.to_dict()) + + +class ClaimsDeconstructor: + def __init__(self, base_class=Message, **kwargs): + if isinstance(base_class, str): + self.base_class = importer(base_class)() + elif isinstance(base_class, Message): + self.base_class = base_class + elif type(base_class) == abc.ABCMeta: + self.base_class = base_class() + + self.info = {} + if kwargs: + self.from_dict(**kwargs) + + def _is_simple_type(self, typ): + if isinstance(typ, str) or isinstance(typ, int): + return True + else: + return False + + def _list_of_simple_type(self, typ): + return self._is_simple_type(typ[0]) + + def from_dict(self, **kwargs): + for key, spec in self.base_class.items(): + val = kwargs.get(key) + if val: + _value_type = self.base_class.value_type(key) + if self._is_simple_type(_value_type): + if isinstance(val, dict): # should be a claims request then + val = ClaimsSpec(**val) + elif self._list_of_simple_type(_value_type): + pass + elif isinstance(_value_type, Message): + _val = ClaimsDeconstructor(_value_type, **val) + else: + raise ValueError(f"Other value_type: {_value_type}") + + self.info[key] = val + + for key, val in kwargs.items(): + if key not in self.base_class: + if val is None: + pass + elif isinstance(val, dict): + _val = ClaimsSpec(**val) + try: + _val.verify() + except Exception: + raise + else: + val = _val + + self.info[key] = val diff --git a/src/idpyoidc/message/oidc/session.py b/src/idpyoidc/message/oidc/session.py index 8136b052..c1533f6a 100644 --- a/src/idpyoidc/message/oidc/session.py +++ b/src/idpyoidc/message/oidc/session.py @@ -4,20 +4,21 @@ from idpyoidc.exception import MessageException from idpyoidc.exception import NotForMe -from idpyoidc.message import Message from idpyoidc.message import OPTIONAL_LIST_OF_SP_SEP_STRINGS from idpyoidc.message import REQUIRED_LIST_OF_STRINGS from idpyoidc.message import SINGLE_OPTIONAL_STRING from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_JSON from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message from idpyoidc.time_util import utc_time_sans_frac + from ..oauth2 import ResponseMessage -from ..oidc import clear_verified_claims from ..oidc import ID_TOKEN_VERIFY_ARGS +from ..oidc import SINGLE_OPTIONAL_IDTOKEN from ..oidc import IdToken from ..oidc import MessageWithIdToken -from ..oidc import SINGLE_OPTIONAL_IDTOKEN +from ..oidc import clear_verified_claims from ..oidc import verified_claim_name from ..oidc import verify_id_token diff --git a/src/idpyoidc/metadata.py b/src/idpyoidc/metadata.py index c879a17d..8b332322 100644 --- a/src/idpyoidc/metadata.py +++ b/src/idpyoidc/metadata.py @@ -211,7 +211,7 @@ def prefers(self): return self.prefer -SIGNING_ALGORITHM_SORT_ORDER = ["RS", "ES", "PS", "HS"] +SIGNING_ALGORITHM_SORT_ORDER = ["RS", "ES", "PS", "HS", "Ed"] def cmp(a, b): diff --git a/src/idpyoidc/node.py b/src/idpyoidc/node.py index f1148612..4256a5f1 100644 --- a/src/idpyoidc/node.py +++ b/src/idpyoidc/node.py @@ -13,10 +13,10 @@ def create_keyjar( - keyjar: Optional[KeyJar] = None, - conf: Optional[Union[dict, Configuration]] = None, - key_conf: Optional[dict] = None, - id: Optional[str] = "", + keyjar: Optional[KeyJar] = None, + conf: Optional[Union[dict, Configuration]] = None, + key_conf: Optional[dict] = None, + id: Optional[str] = "", ): if keyjar is None: if key_conf: @@ -46,12 +46,12 @@ def create_keyjar( def make_keyjar( - keyjar: Optional[Union[KeyJar, bool]] = None, - config: Optional[Union[Configuration, dict]] = None, - key_conf: Optional[dict] = None, - issuer_id: Optional[str] = "", - client_id: Optional[str] = "", - ): + keyjar: Optional[Union[KeyJar, bool]] = None, + config: Optional[Union[Configuration, dict]] = None, + key_conf: Optional[dict] = None, + issuer_id: Optional[str] = "", + client_id: Optional[str] = "", +): if keyjar is False: return None @@ -125,15 +125,15 @@ class Unit(ImpExp): init_args = ["upstream_get"] def __init__( - self, - upstream_get: Callable = None, - keyjar: Optional[Union[KeyJar, bool]] = None, - httpc: Optional[object] = None, - httpc_params: Optional[dict] = None, - config: Optional[Union[Configuration, dict]] = None, - key_conf: Optional[dict] = None, - issuer_id: Optional[str] = "", - client_id: Optional[str] = "", + self, + upstream_get: Callable = None, + keyjar: Optional[Union[KeyJar, bool]] = None, + httpc: Optional[object] = None, + httpc_params: Optional[dict] = None, + config: Optional[Union[Configuration, dict]] = None, + key_conf: Optional[dict] = None, + issuer_id: Optional[str] = "", + client_id: Optional[str] = "", ): ImpExp.__init__(self) self.upstream_get = upstream_get @@ -157,19 +157,22 @@ def unit_get(self, what, *arg): return None def get_attribute(self, attr, *args): - try: - val = getattr(self, attr) - except AttributeError: - if self.upstream_get: - return self.upstream_get("attribute", attr) - else: - return None - else: - if val is None and self.upstream_get: - return self.upstream_get("attribute", attr) - else: + val = getattr(self, attr, None) + if val: + return val + + cntx = getattr(self, "context", None) + if cntx: + val = getattr(cntx, attr, None) + if val: return val + # Go upstairs if possible + if self.upstream_get: + return self.upstream_get("attribute", attr) + else: + return val + def set_attribute(self, attr, val): setattr(self, attr, val) @@ -191,16 +194,16 @@ class ClientUnit(Unit): name = "" def __init__( - self, - upstream_get: Callable = None, - httpc: Optional[object] = None, - httpc_params: Optional[dict] = None, - keyjar: Optional[KeyJar] = None, - context: Optional[ImpExp] = None, - config: Optional[Union[Configuration, dict]] = None, - # jwks_uri: Optional[str] = "", - entity_id: Optional[str] = "", - key_conf: Optional[dict] = None, + self, + upstream_get: Callable = None, + httpc: Optional[object] = None, + httpc_params: Optional[dict] = None, + keyjar: Optional[KeyJar] = None, + context: Optional[ImpExp] = None, + config: Optional[Union[Configuration, dict]] = None, + # jwks_uri: Optional[str] = "", + entity_id: Optional[str] = "", + key_conf: Optional[dict] = None, ): if config is None: config = {} @@ -232,16 +235,16 @@ def get_context_attribute(self, attr, *args): # Neither client nor Server class Collection(Unit): def __init__( - self, - upstream_get: Callable = None, - keyjar: Optional[KeyJar] = None, - httpc: Optional[object] = None, - httpc_params: Optional[dict] = None, - config: Optional[Union[Configuration, dict]] = None, - entity_id: Optional[str] = "", - key_conf: Optional[dict] = None, - functions: Optional[dict] = None, - claims: Optional[dict] = None, + self, + upstream_get: Callable = None, + keyjar: Optional[KeyJar] = None, + httpc: Optional[object] = None, + httpc_params: Optional[dict] = None, + config: Optional[Union[Configuration, dict]] = None, + entity_id: Optional[str] = "", + key_conf: Optional[dict] = None, + functions: Optional[dict] = None, + claims: Optional[dict] = None, ): if config is None: config = {} diff --git a/src/idpyoidc/server/claims/oidc.py b/src/idpyoidc/server/claims/oidc.py index a6620a05..9fedc3a9 100644 --- a/src/idpyoidc/server/claims/oidc.py +++ b/src/idpyoidc/server/claims/oidc.py @@ -1,6 +1,7 @@ from typing import Optional from idpyoidc import claims +from idpyoidc import metadata from idpyoidc.message.oidc import ProviderConfigurationResponse from idpyoidc.message.oidc import RegistrationRequest from idpyoidc.message.oidc import RegistrationResponse @@ -48,9 +49,9 @@ class Claims(server_claims.Claims): "display_values_supported": None, "encrypt_id_token_supported": None, # "grant_types_supported": ["authorization_code", "implicit", "refresh_token"], - "id_token_signing_alg_values_supported": claims.get_signing_algs, - "id_token_encryption_alg_values_supported": claims.get_encryption_algs, - "id_token_encryption_enc_values_supported": claims.get_encryption_encs, + "id_token_signing_alg_values_supported": metadata.get_signing_algs(), + "id_token_encryption_alg_values_supported": metadata.get_encryption_algs(), + "id_token_encryption_enc_values_supported": metadata.get_encryption_encs(), "initiate_login_uri": None, "jwks": None, "jwks_uri": None, diff --git a/src/idpyoidc/server/client_authn.py b/src/idpyoidc/server/client_authn.py index 06321c0a..205a5854 100755 --- a/src/idpyoidc/server/client_authn.py +++ b/src/idpyoidc/server/client_authn.py @@ -471,8 +471,12 @@ def verify_client( authorization_token = None auth_info = {} + + # 'methods' refer to all client authentication methods that can be used by a server. + # 'allowed_methods' are the ones that can be used by a specific endpoint. _context = endpoint.upstream_get("context") methods = _context.client_authn_methods + client_id = None allowed_methods = getattr(endpoint, "client_authn_method") if not allowed_methods: diff --git a/src/idpyoidc/server/configure.py b/src/idpyoidc/server/configure.py index 952d3929..544a98da 100755 --- a/src/idpyoidc/server/configure.py +++ b/src/idpyoidc/server/configure.py @@ -37,13 +37,10 @@ }, }, }, - "claims_interface": { - "class": "idpyoidc.server.session.claims.ClaimsInterface", - "kwargs": {} - }, + "claims_interface": {"class": "idpyoidc.server.session.claims.ClaimsInterface", "kwargs": {}}, "httpc_params": {"verify": False, "timeout": 4}, "issuer": "https://{domain}:{port}", - "template_dir": "templates" + "template_dir": "templates", } AS_DEFAULT_CONFIG = copy.deepcopy(_DEFAULT_CONFIG) @@ -56,7 +53,7 @@ "authorization_code": { "supports_minting": ["access_token", "refresh_token"], "max_usage": 1, - "expires_in": 120 # 2 minutes + "expires_in": 120, # 2 minutes }, "access_token": {"expires_in": 3600}, # An hour "refresh_token": { @@ -66,13 +63,11 @@ }, "expires_in": 2592000, # a month, 30 days } - } + }, }, "claims_interface": { "class": "idpyoidc.server.session.claims.ClaimsInterface", - "kwargs": { - "claims_release_points": ["introspection", "access_token"] - } + "kwargs": {"claims_release_points": ["introspection", "access_token"]}, }, "endpoint": { "provider_info": { @@ -102,107 +97,108 @@ "client_secret_jwt", "private_key_jwt", ] - } - } - } + }, + }, + }, } AS_DEFAULT_CONFIG.update(_C) OP_DEFAULT_CONFIG = copy.deepcopy(_DEFAULT_CONFIG) -OP_DEFAULT_CONFIG.update({ - "preference": { - "subject_types_supported": ["public", "pairwise"], - }, - "authz": { - "class": "idpyoidc.server.authz.AuthzHandling", - "kwargs": { - "grant_config": { - "usage_rules": { - "authorization_code": { - "supports_minting": [ - "access_token", - "refresh_token", - "id_token", - ], - "max_usage": 1, - 'expires_in': 120 # 2 minutes - }, - "access_token": {'expires_in': 3600}, # An hour - "refresh_token": { - "supports_minting": ["access_token", "refresh_token", "id_token"], - "expires_in": 86400, # One day - }, - }, - "expires_in": 2592000, # a month, 30 days - } - }, - }, - "claims_interface": { - "class": "idpyoidc.server.session.claims.ClaimsInterface", - "kwargs": {} - }, - "endpoint": { - "provider_info": { - "path": ".well-known/openid-configuration", - "class": "idpyoidc.server.oidc.provider_config.ProviderConfiguration", - "kwargs": {"client_authn_method": None}, - }, - "authorization": { - "path": "authorization", - "class": "idpyoidc.server.oidc.authorization.Authorization", - "kwargs": { - "client_authn_method": None, - "claims_parameter_supported": True, - "request_parameter_supported": True, - "request_uri_parameter_supported": True, - "response_types_supported": [ - "code", - # "token", - "id_token", - # "code token", - "code id_token", - # "id_token token", - # "code id_token token", - # "none" - ], - "response_modes_supported": ["query", "fragment", "form_post"], - }, +OP_DEFAULT_CONFIG.update( + { + "preference": { + "subject_types_supported": ["public", "pairwise"], }, - "token": { - "path": "token", - "class": "idpyoidc.server.oidc.token.Token", + "authz": { + "class": "idpyoidc.server.authz.AuthzHandling", "kwargs": { - "client_authn_method": [ - "client_secret_post", - "client_secret_basic", - "client_secret_jwt", - "private_key_jwt", - ] + "grant_config": { + "usage_rules": { + "authorization_code": { + "supports_minting": [ + "access_token", + "refresh_token", + "id_token", + ], + "max_usage": 1, + "expires_in": 120, # 2 minutes + }, + "access_token": {"expires_in": 3600}, # An hour + "refresh_token": { + "supports_minting": ["access_token", "refresh_token", "id_token"], + "expires_in": 86400, # One day + }, + }, + "expires_in": 2592000, # a month, 30 days + } }, }, - "userinfo": { - "path": "userinfo", - "class": "idpyoidc.server.oidc.userinfo.UserInfo", - "kwargs": {"claim_types_supported": ["normal", "aggregated", "distributed"]}, + "claims_interface": { + "class": "idpyoidc.server.session.claims.ClaimsInterface", + "kwargs": {}, }, - }, - "token_handler_args": { - "jwks_file": "private/token_jwks.json", - "code": {"kwargs": {"lifetime": 600}}, - "token": { - "class": "idpyoidc.server.token.jwt_token.JWTToken", - "kwargs": {"lifetime": 3600}, + "endpoint": { + "provider_info": { + "path": ".well-known/openid-configuration", + "class": "idpyoidc.server.oidc.provider_config.ProviderConfiguration", + "kwargs": {"client_authn_method": None}, + }, + "authorization": { + "path": "authorization", + "class": "idpyoidc.server.oidc.authorization.Authorization", + "kwargs": { + "client_authn_method": None, + "claims_parameter_supported": True, + "request_parameter_supported": True, + "request_uri_parameter_supported": True, + "response_types_supported": [ + "code", + # "token", + "id_token", + # "code token", + "code id_token", + # "id_token token", + # "code id_token token", + # "none" + ], + "response_modes_supported": ["query", "fragment", "form_post"], + }, + }, + "token": { + "path": "token", + "class": "idpyoidc.server.oidc.token.Token", + "kwargs": { + "client_authn_method": [ + "client_secret_post", + "client_secret_basic", + "client_secret_jwt", + "private_key_jwt", + ] + }, + }, + "userinfo": { + "path": "userinfo", + "class": "idpyoidc.server.oidc.userinfo.UserInfo", + "kwargs": {"claim_types_supported": ["normal", "aggregated", "distributed"]}, + }, }, - "refresh": { - "class": "idpyoidc.server.token.jwt_token.JWTToken", - "kwargs": {"lifetime": 86400}, + "token_handler_args": { + "jwks_file": "private/token_jwks.json", + "code": {"kwargs": {"lifetime": 600}}, + "token": { + "class": "idpyoidc.server.token.jwt_token.JWTToken", + "kwargs": {"lifetime": 3600}, + }, + "refresh": { + "class": "idpyoidc.server.token.jwt_token.JWTToken", + "kwargs": {"lifetime": 86400}, + }, + "id_token": {"class": "idpyoidc.server.token.id_token.IDToken", "kwargs": {}}, }, - "id_token": {"class": "idpyoidc.server.token.id_token.IDToken", "kwargs": {}}, - }, - "scopes_to_claims": SCOPE2CLAIMS, -}) - + "scopes_to_claims": SCOPE2CLAIMS, + } +) class EntityConfiguration(Base): @@ -231,15 +227,15 @@ class EntityConfiguration(Base): } def __init__( - self, - conf: Dict, - base_path: Optional[str] = "", - entity_conf: Optional[List[dict]] = None, - domain: Optional[str] = "", - port: Optional[int] = 0, - file_attributes: Optional[List[str]] = None, - dir_attributes: Optional[List[str]] = None, - upstream_get: Optional[Callable] = None, + self, + conf: Dict, + base_path: Optional[str] = "", + entity_conf: Optional[List[dict]] = None, + domain: Optional[str] = "", + port: Optional[int] = 0, + file_attributes: Optional[List[str]] = None, + dir_attributes: Optional[List[str]] = None, + upstream_get: Optional[Callable] = None, ): conf = copy.deepcopy(conf) @@ -303,14 +299,14 @@ class OPConfiguration(EntityConfiguration): ) def __init__( - self, - conf: Dict, - base_path: Optional[str] = "", - entity_conf: Optional[List[dict]] = None, - domain: Optional[str] = "", - port: Optional[int] = 0, - file_attributes: Optional[List[str]] = None, - dir_attributes: Optional[List[str]] = None, + self, + conf: Dict, + base_path: Optional[str] = "", + entity_conf: Optional[List[dict]] = None, + domain: Optional[str] = "", + port: Optional[int] = 0, + file_attributes: Optional[List[str]] = None, + dir_attributes: Optional[List[str]] = None, ): super().__init__( conf=conf, @@ -327,14 +323,14 @@ class ASConfiguration(EntityConfiguration): "Authorization server configuration" def __init__( - self, - conf: Dict, - base_path: Optional[str] = "", - entity_conf: Optional[List[dict]] = None, - domain: Optional[str] = "", - port: Optional[int] = 0, - file_attributes: Optional[List[str]] = None, - dir_attributes: Optional[List[str]] = None, + self, + conf: Dict, + base_path: Optional[str] = "", + entity_conf: Optional[List[dict]] = None, + domain: Optional[str] = "", + port: Optional[int] = 0, + file_attributes: Optional[List[str]] = None, + dir_attributes: Optional[List[str]] = None, ): EntityConfiguration.__init__( self, @@ -610,3 +606,31 @@ def __init__( }, }, } + +DEFAULT_OIDC_ENDPOINTS = { + "provider_info": { + "path": ".well-known/openid-configuration", + "class": "idpyoidc.server.oidc.provider_config.ProviderConfiguration", + "kwargs": {}, + }, + "register": { + "path": "registration", + "class": "idpyoidc.server.oidc.registration.Registration", + "kwargs": {}, + }, + "authorization": { + "path": "authorization", + "class": "idpyoidc.server.oidc.authorization.Authorization", + "kwargs": {}, + }, + "token": { + "path": "token", + "class": "idpyoidc.server.oidc.token.Token", + "kwargs": {}, + }, + "userinfo": { + "path": "user", + "class": "idpyoidc.server.oidc.userinfo.UserInfo", + "kwargs": {}, + }, +} diff --git a/src/idpyoidc/server/construct.py b/src/idpyoidc/server/construct.py index 22e0be67..9b985200 100644 --- a/src/idpyoidc/server/construct.py +++ b/src/idpyoidc/server/construct.py @@ -5,7 +5,7 @@ from cryptojwt import jwe from cryptojwt.jws.jws import SIGNER_ALGS -ALG_SORT_ORDER = {"RS": 0, "ES": 1, "HS": 2, "PS": 3, "no": 4} +ALG_SORT_ORDER = {"RS": 0, "ES": 1, "HS": 2, "PS": 3, "Ed": 4, "no": 5} WEAK_ALGS = ["RSA1_5", "none"] logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/endpoint.py b/src/idpyoidc/server/endpoint.py index 849bb318..3abc5343 100755 --- a/src/idpyoidc/server/endpoint.py +++ b/src/idpyoidc/server/endpoint.py @@ -165,11 +165,11 @@ def verify_request(self, request, keyjar, client_id, verify_args, lap=0): except IssuerNotFound as err: if lap: return self.error_cls(error=err) + # Find a client ID I believe will work client_id = self.find_client_keys(err.args[0]) if not client_id: return self.error_cls(error=err) else: - # Fund a client ID I believe will work self.verify_request( request=request, keyjar=keyjar, diff --git a/src/idpyoidc/server/endpoint_context.py b/src/idpyoidc/server/endpoint_context.py index ad5acad6..3317a5fc 100755 --- a/src/idpyoidc/server/endpoint_context.py +++ b/src/idpyoidc/server/endpoint_context.py @@ -8,7 +8,6 @@ from cryptojwt import KeyJar from jinja2 import Environment from jinja2 import FileSystemLoader -from requests import request from idpyoidc.context import OidcContext from idpyoidc.server import authz @@ -19,13 +18,14 @@ from idpyoidc.server.configure import OPConfiguration from idpyoidc.server.scopes import SCOPE2CLAIMS from idpyoidc.server.scopes import Scopes -from idpyoidc.server.session.manager import create_session_manager from idpyoidc.server.session.manager import SessionManager +from idpyoidc.server.session.manager import create_session_manager from idpyoidc.server.template_handler import Jinja2TemplateHandler from idpyoidc.server.user_authn.authn_context import populate_authn_broker from idpyoidc.server.util import get_http_params from idpyoidc.util import importer from idpyoidc.util import rndstr +from requests import request logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/add_on/dpop.py b/src/idpyoidc/server/oauth2/add_on/dpop.py index 849d151a..1c07648a 100644 --- a/src/idpyoidc/server/oauth2/add_on/dpop.py +++ b/src/idpyoidc/server/oauth2/add_on/dpop.py @@ -4,17 +4,17 @@ from typing import Optional from typing import Union -from cryptojwt import as_unicode from cryptojwt import JWS +from cryptojwt import as_unicode from cryptojwt.jwk.jwk import key_from_jwk_dict from cryptojwt.jws.jws import factory -from idpyoidc.claims import get_signing_algs -from idpyoidc.message import Message from idpyoidc.message import SINGLE_OPTIONAL_STRING from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_JSON from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message +from idpyoidc.metadata import get_signing_algs from idpyoidc.server.client_authn import BearerHeader logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/add_on/pkce.py b/src/idpyoidc/server/oauth2/add_on/pkce.py index 0b1e697a..9cd6c879 100644 --- a/src/idpyoidc/server/oauth2/add_on/pkce.py +++ b/src/idpyoidc/server/oauth2/add_on/pkce.py @@ -6,9 +6,9 @@ from cryptojwt.utils import b64e from idpyoidc.message.oauth2 import AuthorizationErrorResponse +from idpyoidc.message.oauth2 import CCAccessTokenRequest from idpyoidc.message.oauth2 import RefreshAccessTokenRequest from idpyoidc.message.oauth2 import TokenExchangeRequest -from idpyoidc.message.oauth2 import CCAccessTokenRequest from idpyoidc.message.oidc import TokenErrorResponse from idpyoidc.server.endpoint import Endpoint @@ -94,7 +94,12 @@ def post_token_parse(request, client_id, context, **kwargs): """ if isinstance( request, - (AuthorizationErrorResponse, RefreshAccessTokenRequest, TokenExchangeRequest, CCAccessTokenRequest), + ( + AuthorizationErrorResponse, + RefreshAccessTokenRequest, + TokenExchangeRequest, + CCAccessTokenRequest, + ), ): return request diff --git a/src/idpyoidc/server/oauth2/authorization.py b/src/idpyoidc/server/oauth2/authorization.py index 9468d426..60c0f983 100755 --- a/src/idpyoidc/server/oauth2/authorization.py +++ b/src/idpyoidc/server/oauth2/authorization.py @@ -15,6 +15,7 @@ from cryptojwt.utils import b64e from idpyoidc import claims +from idpyoidc import metadata from idpyoidc.exception import ImproperlyConfigured from idpyoidc.exception import ParameterError from idpyoidc.exception import URIError @@ -39,10 +40,9 @@ from idpyoidc.server.token.exception import UnknownToken from idpyoidc.server.user_authn.authn_context import pick_auth from idpyoidc.time_util import utc_time_sans_frac +from idpyoidc.util import importer from idpyoidc.util import rndstr from idpyoidc.util import split_uri -from idpyoidc.util import importer - logger = logging.getLogger(__name__) @@ -269,7 +269,9 @@ def authn_args_gather( def check_unknown_scopes_policy(request_info, client_id, context): cinfo = context.cdb.get(client_id, {}) - deny_unknown_scopes = cinfo.get("deny_unknown_scopes", context.get_preference("deny_unknown_scopes")) + deny_unknown_scopes = cinfo.get( + "deny_unknown_scopes", context.get_preference("deny_unknown_scopes") + ) if not deny_unknown_scopes: return @@ -349,9 +351,9 @@ class Authorization(Endpoint): "request_uri_parameter_supported": True, "response_types_supported": ["code"], "response_modes_supported": ["query", "fragment", "form_post"], - "request_object_signing_alg_values_supported": claims.get_signing_algs, - "request_object_encryption_alg_values_supported": claims.get_encryption_algs, - "request_object_encryption_enc_values_supported": claims.get_encryption_encs, + "request_object_signing_alg_values_supported": metadata.get_signing_algs, + "request_object_encryption_alg_values_supported": metadata.get_encryption_algs, + "request_object_encryption_enc_values_supported": metadata.get_encryption_encs, # "grant_types_supported": ["authorization_code", "implicit"], "code_challenge_methods_supported": ["S256"], "scopes_supported": [], diff --git a/src/idpyoidc/server/oauth2/introspection.py b/src/idpyoidc/server/oauth2/introspection.py index 5937d0d5..9f48714b 100644 --- a/src/idpyoidc/server/oauth2/introspection.py +++ b/src/idpyoidc/server/oauth2/introspection.py @@ -4,9 +4,9 @@ from idpyoidc.message import oauth2 from idpyoidc.server.endpoint import Endpoint +from idpyoidc.server.exception import ToOld from idpyoidc.server.token.exception import UnknownToken from idpyoidc.server.token.exception import WrongTokenClass -from idpyoidc.server.exception import ToOld LOGGER = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/pushed_authorization.py b/src/idpyoidc/server/oauth2/pushed_authorization.py index d71c6b34..065bda04 100644 --- a/src/idpyoidc/server/oauth2/pushed_authorization.py +++ b/src/idpyoidc/server/oauth2/pushed_authorization.py @@ -1,6 +1,6 @@ +import uuid from typing import Optional from typing import Union -import uuid from idpyoidc.message import Message from idpyoidc.message import oauth2 diff --git a/src/idpyoidc/server/oauth2/token.py b/src/idpyoidc/server/oauth2/token.py index cf59e173..aea552a5 100755 --- a/src/idpyoidc/server/oauth2/token.py +++ b/src/idpyoidc/server/oauth2/token.py @@ -13,6 +13,7 @@ from idpyoidc.server.oauth2.token_helper import TokenEndpointHelper from idpyoidc.server.session import MintingNotAllowed from idpyoidc.util import importer + from .token_helper.access_token import AccessTokenHelper from .token_helper.client_credentials import ClientCredentials from .token_helper.refresh_token import RefreshTokenHelper diff --git a/src/idpyoidc/server/oauth2/token_helper/access_token.py b/src/idpyoidc/server/oauth2/token_helper/access_token.py index 46dad9c7..3262cb71 100755 --- a/src/idpyoidc/server/oauth2/token_helper/access_token.py +++ b/src/idpyoidc/server/oauth2/token_helper/access_token.py @@ -9,11 +9,12 @@ from idpyoidc.message import Message from idpyoidc.message.oauth2 import TokenErrorResponse from idpyoidc.util import sanitize -from . import TokenEndpointHelper -from . import validate_resource_indicators_policy + from ...session import MintingNotAllowed from ...session.token import AuthorizationCode from ...token import UnknownToken +from . import TokenEndpointHelper +from . import validate_resource_indicators_policy logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/token_helper/client_credentials.py b/src/idpyoidc/server/oauth2/token_helper/client_credentials.py index 622efd3a..fa3db7cd 100755 --- a/src/idpyoidc/server/oauth2/token_helper/client_credentials.py +++ b/src/idpyoidc/server/oauth2/token_helper/client_credentials.py @@ -6,6 +6,7 @@ from idpyoidc.message.oauth2 import CCAccessTokenRequest from idpyoidc.time_util import utc_time_sans_frac from idpyoidc.util import sanitize + from . import TokenEndpointHelper logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/token_helper/refresh_token.py b/src/idpyoidc/server/oauth2/token_helper/refresh_token.py index bb6150af..10d9f906 100755 --- a/src/idpyoidc/server/oauth2/token_helper/refresh_token.py +++ b/src/idpyoidc/server/oauth2/token_helper/refresh_token.py @@ -7,6 +7,7 @@ from idpyoidc.server.session.token import RefreshToken from idpyoidc.server.token.exception import UnknownToken from idpyoidc.time_util import utc_time_sans_frac + from . import TokenEndpointHelper logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/token_helper/resource_owner_password_credentials.py b/src/idpyoidc/server/oauth2/token_helper/resource_owner_password_credentials.py index b6003f38..f18e7f28 100755 --- a/src/idpyoidc/server/oauth2/token_helper/resource_owner_password_credentials.py +++ b/src/idpyoidc/server/oauth2/token_helper/resource_owner_password_credentials.py @@ -6,8 +6,9 @@ from idpyoidc.message import Message from idpyoidc.time_util import utc_time_sans_frac from idpyoidc.util import instantiate -from . import TokenEndpointHelper + from ...user_authn.authn_context import pick_auth +from . import TokenEndpointHelper logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/oauth2/token_helper/token_exchange.py b/src/idpyoidc/server/oauth2/token_helper/token_exchange.py index 9fdcb8e7..cc81fb0d 100755 --- a/src/idpyoidc/server/oauth2/token_helper/token_exchange.py +++ b/src/idpyoidc/server/oauth2/token_helper/token_exchange.py @@ -13,11 +13,12 @@ from idpyoidc.server.exception import ToOld from idpyoidc.server.exception import UnAuthorizedClientScope from idpyoidc.server.oauth2.authorization import check_unknown_scopes_policy -from idpyoidc.server.session.token import MintingNotAllowed from idpyoidc.server.session.token import TOKEN_TYPES_MAPPING +from idpyoidc.server.session.token import MintingNotAllowed from idpyoidc.server.token.exception import UnknownToken from idpyoidc.time_util import utc_time_sans_frac from idpyoidc.util import importer + from . import TokenEndpointHelper from . import validate_token_exchange_policy diff --git a/src/idpyoidc/server/oidc/authorization.py b/src/idpyoidc/server/oidc/authorization.py index ca441f69..44602206 100644 --- a/src/idpyoidc/server/oidc/authorization.py +++ b/src/idpyoidc/server/oidc/authorization.py @@ -2,7 +2,7 @@ from typing import Callable from urllib.parse import urlsplit -from idpyoidc import claims +from idpyoidc import metadata from idpyoidc.message import oidc from idpyoidc.message.oidc import Claims from idpyoidc.message.oidc import verified_claim_name @@ -81,9 +81,9 @@ class Authorization(authorization.Authorization): **{ "claims_parameter_supported": True, "encrypt_request_object_supported": False, - "request_object_signing_alg_values_supported": claims.get_signing_algs, - "request_object_encryption_alg_values_supported": claims.get_encryption_algs, - "request_object_encryption_enc_values_supported": claims.get_encryption_encs, + "request_object_signing_alg_values_supported": metadata.get_signing_algs(), + "request_object_encryption_alg_values_supported": metadata.get_encryption_algs(), + "request_object_encryption_enc_values_supported": metadata.get_encryption_encs(), "request_parameter_supported": True, "request_uri_parameter_supported": True, "require_request_uri_registration": False, diff --git a/src/idpyoidc/server/oidc/backchannel_authentication.py b/src/idpyoidc/server/oidc/backchannel_authentication.py index b94dbdf0..d498a63c 100644 --- a/src/idpyoidc/server/oidc/backchannel_authentication.py +++ b/src/idpyoidc/server/oidc/backchannel_authentication.py @@ -1,8 +1,8 @@ import logging +import uuid from typing import Callable from typing import Optional from typing import Union -import uuid from cryptojwt.jwe.exception import JWEException from cryptojwt.jws.exception import NoSuitableSigningKeys diff --git a/src/idpyoidc/server/oidc/token.py b/src/idpyoidc/server/oidc/token.py index 5c602c6b..b927e597 100755 --- a/src/idpyoidc/server/oidc/token.py +++ b/src/idpyoidc/server/oidc/token.py @@ -1,7 +1,6 @@ import logging -from idpyoidc import claims - +from idpyoidc import metadata from idpyoidc.message import Message from idpyoidc.message import oidc from idpyoidc.message.oidc import TokenErrorResponse @@ -29,6 +28,7 @@ class Token(token.Token): helper_by_grant_type = { "authorization_code": AccessTokenHelper, "refresh_token": RefreshTokenHelper, + "urn:openid:params:grant-type:ciba": CIBATokenHelper, "urn:ietf:params:oauth:grant-type:token-exchange": TokenExchangeHelper, } @@ -39,15 +39,8 @@ class Token(token.Token): "client_secret_jwt", "private_key_jwt", ], - "token_endpoint_auth_signing_alg_values_supported": claims.get_signing_algs(), - "grant_types_supported": list(helper_by_grant_type.keys()) - } - - helper_by_grant_type = { - "authorization_code": AccessTokenHelper, - "refresh_token": RefreshTokenHelper, - "urn:openid:params:grant-type:ciba": CIBATokenHelper, - "urn:ietf:params:oauth:grant-type:token-exchange": TokenExchangeHelper, + "token_endpoint_auth_signing_alg_values_supported": metadata.get_signing_algs(), + "grant_types_supported": list(helper_by_grant_type.keys()), } token_exchange_helper = TokenExchangeHelper diff --git a/src/idpyoidc/server/oidc/userinfo.py b/src/idpyoidc/server/oidc/userinfo.py index 1ececaf1..8b376e91 100755 --- a/src/idpyoidc/server/oidc/userinfo.py +++ b/src/idpyoidc/server/oidc/userinfo.py @@ -9,15 +9,15 @@ from cryptojwt.jwt import JWT from cryptojwt.jwt import utc_time_sans_frac -from idpyoidc import claims -from idpyoidc.util import importer +from idpyoidc import metadata +from idpyoidc.exception import ImproperlyConfigured from idpyoidc.message import Message from idpyoidc.message import oidc from idpyoidc.message.oauth2 import ResponseMessage from idpyoidc.server.endpoint import Endpoint from idpyoidc.server.exception import ClientAuthenticationError -from idpyoidc.exception import ImproperlyConfigured from idpyoidc.server.util import OAUTH2_NOCACHE_HEADERS +from idpyoidc.util import importer logger = logging.getLogger(__name__) @@ -33,9 +33,9 @@ class UserInfo(Endpoint): _supports = { "claim_types_supported": ["normal", "aggregated", "distributed"], "encrypt_userinfo_supported": True, - "userinfo_signing_alg_values_supported": claims.get_signing_algs, - "userinfo_encryption_alg_values_supported": claims.get_encryption_algs, - "userinfo_encryption_enc_values_supported": claims.get_encryption_encs, + "userinfo_signing_alg_values_supported": metadata.get_signing_algs, + "userinfo_encryption_alg_values_supported": metadata.get_encryption_algs, + "userinfo_encryption_enc_values_supported": metadata.get_encryption_encs, } def __init__( diff --git a/src/idpyoidc/server/session/claims.py b/src/idpyoidc/server/session/claims.py index fef2c953..04062cfd 100755 --- a/src/idpyoidc/server/session/claims.py +++ b/src/idpyoidc/server/session/claims.py @@ -27,7 +27,7 @@ class ClaimsInterface: init_args = {"add_claims_by_scope": False, "enable_claims_per_client": False} claims_release_points = ["userinfo", "introspection", "id_token", "access_token"] - def __init__(self, upstream_get, claims_release_points:List[str] = None): + def __init__(self, upstream_get, claims_release_points: List[str] = None): self.upstream_get = upstream_get if claims_release_points: self.claims_release_points = claims_release_points diff --git a/src/idpyoidc/server/session/database.py b/src/idpyoidc/server/session/database.py index 1a8191ff..7073c839 100644 --- a/src/idpyoidc/server/session/database.py +++ b/src/idpyoidc/server/session/database.py @@ -15,6 +15,7 @@ from idpyoidc.server.util import lv_pack from idpyoidc.server.util import lv_unpack from idpyoidc.util import rndstr + from .grant import Grant from .info import NodeInfo diff --git a/src/idpyoidc/server/session/grant.py b/src/idpyoidc/server/session/grant.py index e59c54d2..0d1c85a2 100644 --- a/src/idpyoidc/server/session/grant.py +++ b/src/idpyoidc/server/session/grant.py @@ -12,12 +12,13 @@ from idpyoidc.server.session.token import TOKEN_MAP from idpyoidc.server.token import Token as TokenHandler from idpyoidc.util import importer + +from ...message.oauth2 import TokenExchangeRequest +from ...util import qualified_name from . import MintingNotAllowed from .claims import claims_match from .token import Item from .token import SessionToken -from ...message.oauth2 import TokenExchangeRequest -from ...util import qualified_name logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/session/grant_manager.py b/src/idpyoidc/server/session/grant_manager.py index 3d357aaa..e29d1d1a 100644 --- a/src/idpyoidc/server/session/grant_manager.py +++ b/src/idpyoidc/server/session/grant_manager.py @@ -13,12 +13,13 @@ from idpyoidc.message.oauth2 import TokenExchangeRequest from idpyoidc.server.session.info import ClientSessionInfo from idpyoidc.server.token import handler + +from ..exception import InvalidBranchID +from ..token.handler import TokenHandler from .database import Database from .grant import ExchangeGrant from .grant import Grant from .info import NodeInfo -from ..exception import InvalidBranchID -from ..token.handler import TokenHandler logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/session/manager.py b/src/idpyoidc/server/session/manager.py index 8c017f27..81b06c1f 100644 --- a/src/idpyoidc/server/session/manager.py +++ b/src/idpyoidc/server/session/manager.py @@ -1,10 +1,10 @@ import hashlib import logging import os +import uuid from typing import Callable from typing import List from typing import Optional -import uuid from idpyoidc.encrypter import default_crypt_config from idpyoidc.message.oauth2 import AuthorizationRequest @@ -13,15 +13,16 @@ from idpyoidc.server.exception import ConfigurationError from idpyoidc.server.session.grant_manager import GrantManager from idpyoidc.util import rndstr + +from ..token import UnknownToken +from ..token import WrongTokenClass +from ..token import handler +from ..token.handler import TokenHandler from .database import Database from .grant import Grant from .grant import SessionToken from .info import ClientSessionInfo from .info import UserSessionInfo -from ..token import UnknownToken -from ..token import WrongTokenClass -from ..token import handler -from ..token.handler import TokenHandler logger = logging.getLogger(__name__) diff --git a/src/idpyoidc/server/token/handler.py b/src/idpyoidc/server/token/handler.py index ad2ae4e9..92752ccb 100755 --- a/src/idpyoidc/server/token/handler.py +++ b/src/idpyoidc/server/token/handler.py @@ -10,6 +10,7 @@ from idpyoidc.impexp import ImpExp from idpyoidc.item import DLDict from idpyoidc.util import importer + from . import DefaultToken from . import Token from . import UnknownToken diff --git a/src/idpyoidc/server/token/jwt_token.py b/src/idpyoidc/server/token/jwt_token.py index 698d3ea1..abbfa97f 100644 --- a/src/idpyoidc/server/token/jwt_token.py +++ b/src/idpyoidc/server/token/jwt_token.py @@ -7,13 +7,14 @@ from cryptojwt.utils import importer from idpyoidc.server.exception import ToOld + +from ...message import Message +from ...message.oauth2 import JWTAccessToken +from ..constant import DEFAULT_TOKEN_LIFETIME from . import Token from . import is_expired from .exception import UnknownToken from .exception import WrongTokenClass -from ..constant import DEFAULT_TOKEN_LIFETIME -from ...message import Message -from ...message.oauth2 import JWTAccessToken class JWTToken(Token): diff --git a/src/idpyoidc/server/util.py b/src/idpyoidc/server/util.py index 1105da88..c18513e7 100755 --- a/src/idpyoidc/server/util.py +++ b/src/idpyoidc/server/util.py @@ -2,6 +2,7 @@ import logging from idpyoidc.util import importer + from .exception import OidcEndpointError logger = logging.getLogger(__name__) diff --git a/tests/Xtest_10_identity_assurance_represent.py b/tests/Xtest_10_identity_assurance_represent.py new file mode 100644 index 00000000..fec10464 --- /dev/null +++ b/tests/Xtest_10_identity_assurance_represent.py @@ -0,0 +1,187 @@ +import time +from urllib.parse import quote_plus + +from idpyoidc.message.oidc.identity_assurance import VerificationElement +from idpyoidc.message.oidc.identity_assurance import VerifiedClaims +from idpyoidc.message.oidc.identity_assurance import from_iso8601_2004_time +from idpyoidc.message.oidc.identity_assurance import to_iso8601_2004_time +from idpyoidc.time_util import time_sans_frac + + +def test_time_stamp(): + now = time_sans_frac() + iso = to_iso8601_2004_time() + + d = from_iso8601_2004_time(iso) + + assert now == d + + +def test_verification_element(): + ve = VerificationElement(trust_framework="TrustAreUs", time=time.time()) + ve_dict1 = ve.to_dict() + + ve = VerificationElement(trust_framework="TrustAreUs") + ve["time"] = time.time() + ve_dict2 = ve.to_dict() + + assert ve_dict1 == ve_dict2 + + ve = VerificationElement().from_dict(ve_dict1) + + assert ve + + s = "2020-01-11T11:00:00+0100" + ve_2 = VerificationElement(trust_framework="TrustAreUs") + ve_2["time"] = s + + assert quote_plus("2020-01-11T11:00:00+0100") in ve_2.to_urlencoded() + + +def test_userinfo_response(): + resp = { + "sub": "248289761001", + "email": "janedoe@example.com", + "email_verified": True, + "verified_claims": { + "verification": { + "trust_framework": "de_aml", + "time": "2012-04-23T18:25:43.511+01", + "verification_process": "676q3636461467647q8498785747q487", + "evidence": [ + { + "type": "document", + "method": "pipp", + "document": { + "type": "idcard", + "issuer": {"name": "Stadt Augsburg", "country": "DE"}, + "number": "53554554", + "date_of_issuance": "2012-04-23", + "date_of_expiry": "2022-04-22", + }, + } + ], + }, + "claims": {"given_name": "Max", "family_name": "Meier", "birthdate": "1956-01-28"}, + }, + } + + v = VerifiedClaims(**resp["verified_claims"]) + assert v + assert set(v.keys()) == {"verification", "claims"} + + _ver = v["verification"] + assert isinstance(_ver, VerificationElement) + + assert set(_ver.keys()) == {"trust_framework", "time", "verification_process", "evidence"} + _evidence = _ver["evidence"] + assert len(_evidence) == 1 + _evidence_1 = _evidence[0] + assert _evidence_1["type"] == "document" + + +def test_embedded_attachments(): + document = { + "verified_claims": { + "verification": { + "trust_framework": "eidas", + "assurance_level": "substantial", + "evidence": [ + { + "type": "document", + "method": "pipp", + "time": "2012-04-22T11:30Z", + "document_details": { + "type": "idcard", + "issuer": {"name": "Stadt Augsburg", "country": "DE"}, + "document_number": "53554554", + "date_of_issuance": "2010-03-23", + "date_of_expiry": "2020-03-22", + }, + "attachments": [ + { + "desc": "Front of id document", + "content_type": "image/png", + "content": "Wkd0bWFtVnlhWFI2Wlc0Mk16VER2RFUyY0RRMWFUbDBNelJ1TlRjd31dzdaM1pTQXJaWGRsTXpNZ2RETmxDZwo=", + }, + { + "desc": "Back of id document", + "content_type": "image/png", + "content": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAADSFsjdkhjwhAABJRU5ErkJggg==", + }, + ], + } + ], + }, + "claims": {"given_name": "Max", "family_name": "Mustermann", "birthdate": "1956-01-28"}, + } + } + + vc = VerifiedClaims(**document["verified_claims"]) + vc.verify() + assert len(vc["verification"]["evidence"][0]["attachments"]) == 2 + assert {a.__class__.__name__ for a in vc["verification"]["evidence"][0]["attachments"]} == { + "EmbeddedAttachments" + } + + +def test_external_attachments(): + doc = { + "verified_claims": { + "verification": { + "trust_framework": "eidas", + "assurance_level": "substantial", + "evidence": [ + { + "type": "document", + "method": "pipp", + "time": "2012-04-22T11:30Z", + "document_details": { + "type": "idcard", + "issuer": {"name": "Stadt Augsburg", "country": "DE"}, + "document_number": "53554554", + "date_of_issuance": "2010-03-23", + "date_of_expiry": "2020-03-22", + }, + "attachments": [ + { + "desc": "Front of id document", + "digest": { + "alg": "sha-256", + "value": "qC1zE5AfxylOFLrCnOIURXJUvnZwSFe5uUj8t6hdQVM=", + }, + "url": "https://example.com/attachments/pGL9yz4hZQ", + "access_token": "ksj3n283dke", + "expires_in": 30, + }, + { + "desc": "Back of id document", + "digest": { + "alg": "sha-256", + "value": "2QcDeLJ/qeXJn4nP+v3nijMgxOBCT9WJaV0LjRS4aT8=", + }, + "url": "https://example.com/attachments/4Ag8IpOf95", + }, + { + "desc": "Signed document", + "digest": { + "alg": "sha-256", + "value": "i3O7U79LiyKmmesIgULKT2Q8LAxNO0CpwJVcbepaYf8=", + }, + "url": "https://example.com/attachments/4Ag8IpOf95", + "expires_in": 30, + }, + ], + } + ], + }, + "claims": {"given_name": "Max", "family_name": "Mustermann", "birthdate": "1956-01-28"}, + } + } + + vc = VerifiedClaims(**doc["verified_claims"]) + vc.verify() + assert len(vc["verification"]["evidence"][0]["attachments"]) == 3 + assert {a.__class__.__name__ for a in vc["verification"]["evidence"][0]["attachments"]} == { + "ExternalAttachments" + } diff --git a/tests/test_10_identity_assurance.py b/tests/Xtest_10_identity_assurance_request.py similarity index 65% rename from tests/test_10_identity_assurance.py rename to tests/Xtest_10_identity_assurance_request.py index a07f0e2d..725a8322 100644 --- a/tests/test_10_identity_assurance.py +++ b/tests/Xtest_10_identity_assurance_request.py @@ -3,6 +3,7 @@ from idpyoidc.message.oidc import Claims from idpyoidc.message.oidc.identity_assurance import ClaimsConstructor +from idpyoidc.message.oidc.identity_assurance import ClaimsDeconstructor from idpyoidc.message.oidc.identity_assurance import IDAClaimsRequest from idpyoidc.message.oidc.identity_assurance import VerificationElement from idpyoidc.message.oidc.identity_assurance import VerifiedClaims @@ -11,36 +12,6 @@ from idpyoidc.time_util import time_sans_frac -def test_time_stamp(): - now = time_sans_frac() - iso = to_iso8601_2004_time() - - d = from_iso8601_2004_time(iso) - - assert now == d - - -def test_verification_element(): - ve = VerificationElement(trust_framework="TrustAreUs", time=time.time()) - ve_dict1 = ve.to_dict() - - ve = VerificationElement(trust_framework="TrustAreUs") - ve["time"] = time.time() - ve_dict2 = ve.to_dict() - - assert ve_dict1 == ve_dict2 - - ve = VerificationElement().from_dict(ve_dict1) - - assert ve - - s = "2020-01-11T11:00:00+0100" - ve_2 = VerificationElement(trust_framework="TrustAreUs") - ve_2["time"] = s - - assert quote_plus("2020-01-11T11:00:00+0100") in ve_2.to_urlencoded() - - def test_verified_claims(): s = { "userinfo": { @@ -54,85 +25,17 @@ def test_verified_claims(): assert "userinfo" in c -def test_verified_claims_2(): - s = { - "verified_claims": { - "verification": { - "trust_framework": "trust_framework_example" - }, - "claims": { - "given_name": "Max", - "family_name": "Meier" - } - } - } - - vc = VerifiedClaims(**s['verified_claims']) - vc.verify() - assert set(vc.keys()) == {'verification', 'claims'} - assert isinstance(vc["claims"], Claims) - -def test_verfication_element_from_dict(): - d = { - "verification": {"trust_framework": "eidas_ial_substantial"}, - "claims": { - "given_name": "Max", - "family_name": "Meier", - "birthdate": "1956-01-28", - "place_of_birth": {"country": "DE", "locality": "Musterstadt"}, - "nationality": "DE", - "address": { - "locality": "Maxstadt", - "postal_code": "12344", - "country": "DE", - "street_address": "An der Sanddüne 22", - }, - }, - } - v = VerifiedClaims(**d) - assert v - - -def test_userinfo_response(): - resp = { - "sub": "248289761001", - "email": "janedoe@example.com", - "email_verified": True, - "verified_claims": { - "verification": { - "trust_framework": "de_aml", - "time": "2012-04-23T18:25:43.511+01", - "verification_process": "676q3636461467647q8498785747q487", - "evidence": [ - { - "type": "id_document", - "method": "pipp", - "document": { - "type": "idcard", - "issuer": {"name": "Stadt Augsburg", "country": "DE"}, - "number": "53554554", - "date_of_issuance": "2012-04-23", - "date_of_expiry": "2022-04-22", - }, - } - ], - }, - "claims": {"given_name": "Max", "family_name": "Meier", "birthdate": "1956-01-28"}, - }, - } - - v = VerifiedClaims(**resp["verified_claims"]) - assert v - assert set(v.keys()) == {"verification", "claims"} +def test_construct_5_2_1(): + _verification = ClaimsConstructor(VerificationElement) + _verification["time"] = None + _verification["evidence"] = None - _ver = v["verification"] - assert isinstance(_ver, VerificationElement) + verified_claims = ClaimsConstructor("idpyoidc.message.oidc.identity_assurance.VerifiedClaims") + verified_claims["verification"] = _verification + verified_claims["claims"] = None - assert set(_ver.keys()) == {"trust_framework", "time", "verification_process", "evidence"} - _evidence = _ver["evidence"] - assert len(_evidence) == 1 - _evidence_1 = _evidence[0] - assert _evidence_1["type"] == "id_document" + _val = verified_claims.to_json() + assert _val == '{"verification": {"time": null, "evidence": null}, "claims": null}' def test_userinfo_claims_request_5_1_1(): @@ -144,9 +47,9 @@ def test_userinfo_claims_request_5_1_1(): } } - icr = IDAClaimsRequest(**userinfo_claims["userinfo"]) - icr.verify() - assert isinstance(icr["verified_claims"], VerifiedClaims) + _cd = ClaimsDeconstructor() + _cd.from_dict(**userinfo_claims["userinfo"]) + assert isinstance(_cd["verified_claims"], VerifiedClaims) def test_userinfo_claims_request_5_1_2(): @@ -206,55 +109,20 @@ def test_userinfo_claims_request_5_2_1(): assert icr -def test_userinfo_claims_request_5_2_2(): - verified_claims = { - "verified_claims": { - "verification": {"time": None, "evidence": [{"method": None, "document": None}]}, - "claims": None, - } - } - - icr = IDAClaimsRequest(**verified_claims) - icr.verify() - assert icr - - -def test_userinfo_claims_request_5_2_3(): - verified_claims = { - "verified_claims": { - "verification": { - "time": None, - "evidence": [ - { - "method": None, - "document": {"issuer": None, "number": None, "date_of_issuance": None}, - } - ], - }, - "claims": None, - } - } - - icr = IDAClaimsRequest(**verified_claims) - icr.verify() - assert icr - - def test_userinfo_claims_request_5_3_1(): userinfo_claims = { "userinfo": { "verified_claims": { "verification": { - "trust_framework": {"value": "de_aml"}, + "trust_framework": "de_aml", "evidence": [ { - "type": {"value": "id_document"}, - "method": {"value": "pipp"}, - "document": {"type": {"values": ["idcard", "passport"]}}, + "type": "document", + "method": "pipp", + "document_details": {"type": {"values": ["idcard", "passport"]}}, } ], }, - "claims": None, } } } @@ -266,9 +134,7 @@ def test_userinfo_claims_request_5_3_1(): def test_userinfo_claims_request_5_3_2(): userinfo_claims = { - "userinfo": { - "verified_claims": {"verification": {"date": {"max_age": 63113852}}, "claims": None} - } + "userinfo": {"verified_claims": {"verification": {"date": {"max_age": 63113852}}}} } icr = IDAClaimsRequest(**userinfo_claims["userinfo"]) @@ -285,7 +151,7 @@ def test_example_6_1(): "verification_process": "676q3636461467647q8498785747q487", "evidence": [ { - "type": "id_document", + "type": "document", "method": "pipp", "document": { "type": "idcard", @@ -316,7 +182,7 @@ def test_example_6_1(): vc = VerifiedClaims(**verified_claims["verified_claims"]) vc.verify() assert vc["verification"]["trust_framework"] == "de_aml" - assert vc["verification"]["evidence"][0]["type"] == "id_document" + assert vc["verification"]["evidence"][0]["type"] == "document" def test_example_6_2(): @@ -328,7 +194,7 @@ def test_example_6_2(): "verification_process": "676q3636461467647q8498785747q487", "evidence": [ { - "type": "id_document", + "type": "document", "method": "pipp", "document": { "document_type": "de_erp_replacement_idcard", @@ -372,7 +238,7 @@ def test_example_6_2(): assert len(vc["verification"]["evidence"]) == 2 evidence_types = [e["type"] for e in vc["verification"]["evidence"]] - assert set(evidence_types) == {"id_document", "utility_bill"} + assert set(evidence_types) == {"document", "utility_bill"} def test_example_6_3(): @@ -412,7 +278,7 @@ def test_example_6_4_2(): "verification_process": "676q3636461467647q8498785747q487", "evidence": [ { - "type": "id_document", + "type": "document", "method": "pipp", "document": { "type": "idcard", @@ -444,3 +310,102 @@ def test_construct_5_2_1(): _val = verified_claims.to_json() assert _val == '{"verification": {"time": null, "evidence": null}, "claims": null}' + + +def test_embedded_attachments(): + document = { + "verified_claims": { + "verification": { + "trust_framework": "eidas", + "assurance_level": "substantial", + "evidence": [ + { + "type": "document", + "method": "pipp", + "time": "2012-04-22T11:30Z", + "document_details": { + "type": "idcard", + "issuer": {"name": "Stadt Augsburg", "country": "DE"}, + "document_number": "53554554", + "date_of_issuance": "2010-03-23", + "date_of_expiry": "2020-03-22", + }, + "attachments": [ + { + "desc": "Front of id document", + "content_type": "image/png", + "content": "Wkd0bWFtVnlhWFI2Wlc0Mk16VER2RFUyY0RRMWFUbDBNelJ1TlRjd31dzdaM1pTQXJaWGRsTXpNZ2RETmxDZwo=", + }, + { + "desc": "Back of id document", + "content_type": "image/png", + "content": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAADSFsjdkhjwhAABJRU5ErkJggg==", + }, + ], + } + ], + }, + "claims": {"given_name": "Max", "family_name": "Mustermann", "birthdate": "1956-01-28"}, + } + } + + vc = VerifiedClaims(**document["verified_claims"]) + vc.verify() + assert len(vc["verification"]["evidence"][0]["attachments"]) == 2 + assert {type(a) for a in vc["verification"]["evidence"][0]["attachments"]} == {} + + +def test_external_attachments(): + doc = { + "verified_claims": { + "verification": { + "trust_framework": "eidas", + "assurance_level": "substantial", + "evidence": [ + { + "type": "document", + "method": "pipp", + "time": "2012-04-22T11:30Z", + "document_details": { + "type": "idcard", + "issuer": {"name": "Stadt Augsburg", "country": "DE"}, + "document_number": "53554554", + "date_of_issuance": "2010-03-23", + "date_of_expiry": "2020-03-22", + }, + "attachments": [ + { + "desc": "Front of id document", + "digest": { + "alg": "sha-256", + "value": "qC1zE5AfxylOFLrCnOIURXJUvnZwSFe5uUj8t6hdQVM=", + }, + "url": "https://example.com/attachments/pGL9yz4hZQ", + "access_token": "ksj3n283dke", + "expires_in": 30, + }, + { + "desc": "Back of id document", + "digest": { + "alg": "sha-256", + "value": "2QcDeLJ/qeXJn4nP+v3nijMgxOBCT9WJaV0LjRS4aT8=", + }, + "url": "https://example.com/attachments/4Ag8IpOf95", + }, + { + "desc": "Signed document", + "digest": { + "alg": "sha-256", + "value": "i3O7U79LiyKmmesIgULKT2Q8LAxNO0CpwJVcbepaYf8=", + }, + "url": "https://example.com/attachments/4Ag8IpOf95", + "access_token": null, + "expires_in": 30, + }, + ], + } + ], + }, + "claims": {"given_name": "Max", "family_name": "Mustermann", "birthdate": "1956-01-28"}, + } + } diff --git a/tests/private/token_jwks.json b/tests/private/token_jwks.json index c8197bd2..105acf1b 100644 --- a/tests/private/token_jwks.json +++ b/tests/private/token_jwks.json @@ -1 +1 @@ -{"keys": [{"kty": "oct", "use": "enc", "kid": "code", "k": "vSHDkLBHhDStkR0NWu8519rmV5zmnm5_"}, {"kty": "oct", "use": "enc", "kid": "refresh", "k": "ASSv_faXjb13FYcRMP7ht8f641jIWw3W"}]} \ No newline at end of file +{"keys": [{"kty": "oct", "use": "enc", "kid": "code", "k": "vSHDkLBHhDStkR0NWu8519rmV5zmnm5_"}, {"kty": "oct", "use": "enc", "kid": "refresh", "k": "GupTpe3QRVG7tC6UFJShLi3i9L8gznvU"}]} \ No newline at end of file diff --git a/tests/test_08_transform.py b/tests/test_08_transform.py index 27eed5ca..8afe9095 100644 --- a/tests/test_08_transform.py +++ b/tests/test_08_transform.py @@ -3,7 +3,6 @@ import pytest from cryptojwt.utils import importer -from idpyoidc.claims import Claims from idpyoidc.client.claims.oidc import Claims as OIDC_Claims from idpyoidc.client.claims.transform import create_registration_request from idpyoidc.client.claims.transform import preferred_to_registered @@ -194,7 +193,7 @@ def test_oidc_setup(self): assert set(RegistrationRequest.c_param.keys()).difference(set(reg_claim)) == { "post_logout_redirect_uri", "grant_types", - "response_modes" # Extra item + "response_modes", # Extra item } # Which ones are list -> singletons @@ -367,7 +366,7 @@ def test_registration_response(self): "redirect_uris", "request_object_signing_alg", "response_types", - "response_modes", # non-standard + "response_modes", # non-standard "subject_type", "token_endpoint_auth_method", "token_endpoint_auth_signing_alg", @@ -416,7 +415,7 @@ def test_registration_response(self): "request_object_signing_alg", "request_uris", "response_types", - "response_modes", # non-standard + "response_modes", # non-standard "scope", "sector_identifier_uri", "subject_type", diff --git a/tests/test_09_work_condition.py b/tests/test_09_work_condition.py index 44f918a8..72fdf386 100644 --- a/tests/test_09_work_condition.py +++ b/tests/test_09_work_condition.py @@ -186,7 +186,7 @@ def test_registration_response(self): "logo_uri", "redirect_uris", "request_object_signing_alg", - "response_modes", # non-standard + "response_modes", # non-standard "response_types", "subject_type", "token_endpoint_auth_method", diff --git a/tests/test_12_context.py b/tests/test_12_context.py index 7c0c30a4..21c9cce8 100644 --- a/tests/test_12_context.py +++ b/tests/test_12_context.py @@ -1,6 +1,5 @@ from idpyoidc.context import OidcContext - ENTITY_ID = "https://example.com" diff --git a/tests/test_client_06_client_authn.py b/tests/test_client_06_client_authn.py index 04d098c0..52fb95c2 100644 --- a/tests/test_client_06_client_authn.py +++ b/tests/test_client_06_client_authn.py @@ -1,6 +1,7 @@ import base64 import os +import pytest from cryptojwt.exception import MissingKey from cryptojwt.jws.jws import JWS from cryptojwt.jws.jws import factory @@ -8,8 +9,8 @@ from cryptojwt.key_bundle import KeyBundle from cryptojwt.key_jar import KeyJar from cryptojwt.key_jar import init_key_jar -import pytest +from idpyoidc.claims import Claims from idpyoidc.client.client_auth import AuthnFailure from idpyoidc.client.client_auth import BearerBody from idpyoidc.client.client_auth import BearerHeader @@ -21,7 +22,6 @@ from idpyoidc.client.client_auth import bearer_auth from idpyoidc.client.client_auth import valid_service_context from idpyoidc.client.entity import Entity -from idpyoidc.claims import Claims from idpyoidc.defaults import JWT_BEARER from idpyoidc.message import Message from idpyoidc.message.oauth2 import AccessTokenRequest diff --git a/tests/test_client_12_client_auth.py b/tests/test_client_12_client_auth.py index bb81133e..4b3949a8 100755 --- a/tests/test_client_12_client_auth.py +++ b/tests/test_client_12_client_auth.py @@ -4,21 +4,21 @@ import pytest from cryptojwt.exception import MissingKey from cryptojwt.jwk.rsa import new_rsa_key -from cryptojwt.jws.jws import factory from cryptojwt.jws.jws import JWS +from cryptojwt.jws.jws import factory from cryptojwt.jwt import JWT from cryptojwt.key_bundle import KeyBundle from cryptojwt.key_jar import KeyJar -from idpyoidc.client.client_auth import assertion_jwt from idpyoidc.client.client_auth import AuthnFailure -from idpyoidc.client.client_auth import bearer_auth from idpyoidc.client.client_auth import BearerBody from idpyoidc.client.client_auth import BearerHeader from idpyoidc.client.client_auth import ClientSecretBasic from idpyoidc.client.client_auth import ClientSecretJWT from idpyoidc.client.client_auth import ClientSecretPost from idpyoidc.client.client_auth import PrivateKeyJWT +from idpyoidc.client.client_auth import assertion_jwt +from idpyoidc.client.client_auth import bearer_auth from idpyoidc.client.client_auth import valid_service_context from idpyoidc.client.entity import Entity from idpyoidc.defaults import JWT_BEARER @@ -71,7 +71,7 @@ def test_quote(): class TestClientSecretBasic(object): def test_construct(self, entity): - entity.context.cstate.update("ABCDE", {'code': 'abcdefghijklmnopqrst'}) + entity.context.cstate.update("ABCDE", {"code": "abcdefghijklmnopqrst"}) _token_service = entity.get_service("accesstoken") request = _token_service.construct( @@ -236,7 +236,7 @@ def test_construct_with_request(self, entity): class TestClientSecretPost(object): def test_construct(self, entity): - entity.context.cstate.update("ABCDE", {'code': 'abcdefghijklmnopqrst'}) + entity.context.cstate.update("ABCDE", {"code": "abcdefghijklmnopqrst"}) _token_service = entity.get_service("accesstoken") request = _token_service.construct(redirect_uri="http://example.com", state="ABCDE") @@ -254,7 +254,7 @@ def test_construct(self, entity): assert http_args is None def test_modify_1(self, entity): - entity.context.cstate.update("ABCDE", {'code': 'abcdefghijklmnopqrst'}) + entity.context.cstate.update("ABCDE", {"code": "abcdefghijklmnopqrst"}) token_service = entity.get_service("accesstoken") request = token_service.construct(redirect_uri="http://example.com", state="ABCDE") @@ -265,7 +265,7 @@ def test_modify_1(self, entity): assert "client_secret" in request def test_modify_2(self, entity): - entity.context.cstate.update("ABCDE", {'code': 'abcdefghijklmnopqrst'}) + entity.context.cstate.update("ABCDE", {"code": "abcdefghijklmnopqrst"}) token_service = entity.get_service("accesstoken") request = token_service.construct(redirect_uri="http://example.com", state="ABCDE") diff --git a/tests/test_client_20_oauth2.py b/tests/test_client_20_oauth2.py index cb227a68..5e5df3f8 100644 --- a/tests/test_client_20_oauth2.py +++ b/tests/test_client_20_oauth2.py @@ -2,9 +2,9 @@ import sys import time +import pytest from cryptojwt.jwk.rsa import import_private_rsa_key_from_file from cryptojwt.key_bundle import KeyBundle -import pytest from idpyoidc.client.configure import RPHConfiguration from idpyoidc.client.exception import OidcServiceError diff --git a/tests/test_client_26_read_registration.py b/tests/test_client_26_read_registration.py index 32eacbd6..20cb8e06 100644 --- a/tests/test_client_26_read_registration.py +++ b/tests/test_client_26_read_registration.py @@ -1,11 +1,11 @@ import json import time -from cryptojwt.utils import as_bytes import pytest -import requests import responses +from cryptojwt.utils import as_bytes +import requests from idpyoidc.client.entity import Entity from idpyoidc.message.oidc import RegistrationResponse diff --git a/tests/test_client_27_conversation.py b/tests/test_client_27_conversation.py index fa0d24cc..2f36cca9 100644 --- a/tests/test_client_27_conversation.py +++ b/tests/test_client_27_conversation.py @@ -9,9 +9,9 @@ from idpyoidc.client.entity import Entity from idpyoidc.client.oidc.webfinger import WebFinger +from idpyoidc.message.oidc import JRD from idpyoidc.message.oidc import AccessTokenResponse from idpyoidc.message.oidc import AuthorizationResponse -from idpyoidc.message.oidc import JRD from idpyoidc.message.oidc import Link from idpyoidc.message.oidc import OpenIDSchema from idpyoidc.message.oidc import ProviderConfigurationResponse diff --git a/tests/test_client_28_stand_alone.py b/tests/test_client_28_stand_alone.py index 05cce795..d3c3a2a9 100644 --- a/tests/test_client_28_stand_alone.py +++ b/tests/test_client_28_stand_alone.py @@ -30,36 +30,40 @@ "authorization_endpoint": "https://op.example.com/authn", "token_endpoint": "https://op.example.com/token", "userinfo_endpoint": "https://op.example.com/user", - } + }, } def get_state_from_url(url): p = urlsplit(url) qs = parse_qs(p.query) - return qs['state'][0] + return qs["state"][0] class TestStandAloneClientOIDCStatic(object): - @pytest.fixture(autouse=True) def client_setup(self): self.client = StandAloneClient(config=STATIC_CONFIG) def test_get_services(self): - assert set(self.client.get_services().keys()) == {'provider_info', 'registration', - 'authorization', 'accesstoken', - 'refresh_token', 'userinfo'} + assert set(self.client.get_services().keys()) == { + "provider_info", + "registration", + "authorization", + "accesstoken", + "refresh_token", + "userinfo", + } def test_do_provider_info(self): issuer = self.client.do_provider_info() - assert issuer == STATIC_CONFIG['provider_info']['issuer'] - assert self.client.context.get('issuer') == issuer + assert issuer == STATIC_CONFIG["provider_info"]["issuer"] + assert self.client.context.get("issuer") == issuer def test_client_registration(self): self.client.do_provider_info() self.client.do_client_registration() - assert self.client.context.get_usage('client_id') == STATIC_CONFIG['client_id'] + assert self.client.context.get_usage("client_id") == STATIC_CONFIG["client_id"] def test_init_authorization(self): self.client.do_provider_info() @@ -68,66 +72,62 @@ def test_init_authorization(self): assert url p = urlsplit(url) qs = parse_qs(p.query) - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'code' + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "code" def test_response_type_id_token(self): self.client.do_provider_info() self.client.do_client_registration() # Explicitly set - url = self.client.init_authorization(req_args={'response_type': 'id_token'}) + url = self.client.init_authorization(req_args={"response_type": "id_token"}) assert url p = urlsplit(url) qs = parse_qs(p.query) - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'id_token' + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "id_token" def test_response_mode(): conf = STATIC_CONFIG.copy() - conf.update({ - "response_modes_supported": ['query', 'form_post'], - 'separate_form_post_cb': True - }) + conf.update({"response_modes_supported": ["query", "form_post"], "separate_form_post_cb": True}) client = StandAloneClient(config=conf) client.do_provider_info() client.do_client_registration() # Explicitly set - url = client.init_authorization(req_args={'response_mode': 'form_post'}) + url = client.init_authorization(req_args={"response_mode": "form_post"}) assert url p = urlsplit(url) qs = parse_qs(p.query) - assert 'authz_cb_form' in qs['redirect_uri'][0] - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'code' - assert qs['response_mode'][0] == 'form_post' + assert "authz_cb_form" in qs["redirect_uri"][0] + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "code" + assert qs["response_mode"][0] == "form_post" def test_response_mode_not_separate_endpoint(): conf = STATIC_CONFIG.copy() - conf.update({ - "response_modes_supported": ['query', 'form_post'], - 'separate_form_post_cb': False - }) + conf.update( + {"response_modes_supported": ["query", "form_post"], "separate_form_post_cb": False} + ) client = StandAloneClient(config=conf) client.do_provider_info() client.do_client_registration() # Explicitly set - url = client.init_authorization(req_args={'response_mode': 'form_post'}) + url = client.init_authorization(req_args={"response_mode": "form_post"}) assert url p = urlsplit(url) qs = parse_qs(p.query) - assert 'authz_cb_form' not in qs['redirect_uri'][0] - assert 'authz_cb' in qs['redirect_uri'][0] - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'code' - assert qs['response_mode'][0] == 'form_post' + assert "authz_cb_form" not in qs["redirect_uri"][0] + assert "authz_cb" in qs["redirect_uri"][0] + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "code" + assert qs["response_mode"][0] == "form_post" SEMI_DYN_CONFIG = { @@ -135,9 +135,7 @@ def test_response_mode_not_separate_endpoint(): "client_id": "Number5", "client_secret": "asdflkjh0987654321", "client_type": "oidc", - "provider_info": { - "issuer": "https://op.example.com" - } + "provider_info": {"issuer": "https://op.example.com"}, } PROVIDER_INFO = ProviderConfigurationResponse( @@ -148,15 +146,14 @@ def test_response_mode_not_separate_endpoint(): registration_endpoint="https://op.example.com/register", jwks_uri="https://op.example.com/keys/jwks.json", response_types_supported=["code"], - subject_types_supported=['public'], - id_token_signing_alg_values_supported=['RS256'] + subject_types_supported=["public"], + id_token_signing_alg_values_supported=["RS256"], ) OP_KEYS = build_keyjar(DEFAULT_KEY_DEFS) class TestStandAloneClientOIDCDynProviderInfo(object): - @pytest.fixture(autouse=True) def client_setup(self): self.client = StandAloneClient(config=SEMI_DYN_CONFIG) @@ -165,22 +162,22 @@ def test_do_provider_info(self): with responses.RequestsMock() as rsps: rsps.add( "GET", - OIDCONF_PATTERN.format(SEMI_DYN_CONFIG['provider_info']['issuer']), + OIDCONF_PATTERN.format(SEMI_DYN_CONFIG["provider_info"]["issuer"]), body=PROVIDER_INFO.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, ) rsps.add( "GET", - PROVIDER_INFO['jwks_uri'], + PROVIDER_INFO["jwks_uri"], body=OP_KEYS.export_jwks_as_json(), adding_headers={"Content-Type": "application/json"}, status=200, ) issuer = self.client.do_provider_info() - assert issuer == SEMI_DYN_CONFIG['provider_info']['issuer'] - assert self.client.context.get('issuer') == issuer + assert issuer == SEMI_DYN_CONFIG["provider_info"]["issuer"] + assert self.client.context.get("issuer") == issuer DYN_CONFIG = { @@ -188,14 +185,11 @@ def test_do_provider_info(self): "redirect_uris": ["https://rp.example.com/cb"], "key_conf": {"key_defs": DEFAULT_KEY_DEFS}, "client_type": "oidc", - "provider_info": { - "issuer": "https://op.example.com" - } + "provider_info": {"issuer": "https://op.example.com"}, } class TestStandAloneClientOIDCDyn(object): - @pytest.fixture(autouse=True) def client_setup(self): self.client = StandAloneClient(config=DYN_CONFIG) @@ -204,33 +198,33 @@ def test_do_provider_info(self): with responses.RequestsMock() as rsps: rsps.add( "GET", - OIDCONF_PATTERN.format(SEMI_DYN_CONFIG['provider_info']['issuer']), + OIDCONF_PATTERN.format(SEMI_DYN_CONFIG["provider_info"]["issuer"]), body=PROVIDER_INFO.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, ) rsps.add( "GET", - PROVIDER_INFO['jwks_uri'], + PROVIDER_INFO["jwks_uri"], body=OP_KEYS.export_jwks_as_json(), adding_headers={"Content-Type": "application/json"}, status=200, ) issuer = self.client.do_provider_info() - assert issuer == DYN_CONFIG['provider_info']['issuer'] - assert self.client.context.get('issuer') == issuer + assert issuer == DYN_CONFIG["provider_info"]["issuer"] + assert self.client.context.get("issuer") == issuer registration_response = RegistrationResponse( client_id="client_1", client_secret="a0b1c2d3e4f5g6h7i8j9", - redirect_uris=["https://rp.example.com/cb"] + redirect_uris=["https://rp.example.com/cb"], ) with responses.RequestsMock() as rsps: # registration response rsps.add( "POST", - PROVIDER_INFO['registration_endpoint'], + PROVIDER_INFO["registration_endpoint"], body=registration_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -238,15 +232,17 @@ def test_do_provider_info(self): self.client.do_client_registration() - assert self.client.context.get_usage('client_id') == 'client_1' + assert self.client.context.get_usage("client_id") == "client_1" def test_request_type_mode_1(): config = STATIC_CONFIG.copy() - config.update({ - "response_modes_supported": ['query', 'form_post'], - "response_types_supported": ['code', 'code idtoken'] - }) + config.update( + { + "response_modes_supported": ["query", "form_post"], + "response_types_supported": ["code", "code idtoken"], + } + ) client = StandAloneClient(config=config) client.do_provider_info() client.do_client_registration() @@ -257,19 +253,21 @@ def test_request_type_mode_1(): assert url p = urlsplit(url) qs = parse_qs(p.query) - assert 'authz_cb' in qs['redirect_uri'][0] - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'code' + assert "authz_cb" in qs["redirect_uri"][0] + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "code" - assert 'response_mode' not in qs + assert "response_mode" not in qs def test_request_type_mode_2(): config = STATIC_CONFIG.copy() - config.update({ - "response_modes_supported": ['form_post'], - "response_types_supported": ['code', 'code id_token'] - }) + config.update( + { + "response_modes_supported": ["form_post"], + "response_types_supported": ["code", "code id_token"], + } + ) client = StandAloneClient(config=config) client.do_provider_info() client.do_client_registration() @@ -280,18 +278,17 @@ def test_request_type_mode_2(): assert url p = urlsplit(url) qs = parse_qs(p.query) - assert 'authz_cb' in qs['redirect_uri'][0] - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'code' - assert qs['response_mode'][0] == 'form_post' + assert "authz_cb" in qs["redirect_uri"][0] + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "code" + assert qs["response_mode"][0] == "form_post" def test_request_type_mode_3(): config = STATIC_CONFIG.copy() - config.update({ - "response_modes_supported": ['form_post'], - "response_types_supported": ['id_token code'] - }) + config.update( + {"response_modes_supported": ["form_post"], "response_types_supported": ["id_token code"]} + ) client = StandAloneClient(config=config) client.do_provider_info() client.do_client_registration() @@ -302,18 +299,17 @@ def test_request_type_mode_3(): assert url p = urlsplit(url) qs = parse_qs(p.query) - assert 'authz_cb' in qs['redirect_uri'][0] - assert qs['client_id'][0] == STATIC_CONFIG['client_id'] - assert qs['response_type'][0] == 'id_token code' - assert qs['response_mode'][0] == 'form_post' + assert "authz_cb" in qs["redirect_uri"][0] + assert qs["client_id"][0] == STATIC_CONFIG["client_id"] + assert qs["response_type"][0] == "id_token code" + assert qs["response_mode"][0] == "form_post" def test_request_type_mode_4(): config = STATIC_CONFIG.copy() - config.update({ - "response_modes_supported": ['query'], - "response_types_supported": ['id_token code'] - }) + config.update( + {"response_modes_supported": ["query"], "response_types_supported": ["id_token code"]} + ) client = StandAloneClient(config=config) client.do_provider_info() client.do_client_registration() @@ -324,7 +320,6 @@ def test_request_type_mode_4(): class TestFinalizeAuth(object): - @pytest.fixture(autouse=True) def client_setup(self): self.client = StandAloneClient(config=STATIC_CONFIG) @@ -336,10 +331,10 @@ def test_one(self): _state = get_state_from_url(url) _response = AuthorizationResponse( - code=24 * 'x', + code=24 * "x", state=_state, iss=self.client.context.issuer, - client_id=self.client.context.get_client_id() + client_id=self.client.context.get_client_id(), ) _auth_response = self.client.finalize_auth(_response.to_dict()) assert _auth_response @@ -349,10 +344,10 @@ def test_imposter(self): _state = get_state_from_url(url) _response = AuthorizationResponse( - code=24 * 'x', + code=24 * "x", state=_state, iss="https://fake.example.com", - client_id=self.client.context.get_client_id() + client_id=self.client.context.get_client_id(), ) with pytest.raises(VerificationError): @@ -363,10 +358,10 @@ def test_wrong_state(self): _state = get_state_from_url(url) _response = AuthorizationResponse( - code=24 * 'x', + code=24 * "x", state="_state", iss=self.client.context.issuer, - client_id=self.client.context.get_client_id() + client_id=self.client.context.get_client_id(), ) with pytest.raises(KeyError): @@ -376,7 +371,7 @@ def test_wrong_state(self): ISSUER_KEYS = build_keyjar(DEFAULT_KEY_DEFS, issuer_id=ISSUER) SUBJECT_NAME = "Subject" _services = DEFAULT_OIDC_SERVICES.copy() -_services["end_session"] = {'class': "idpyoidc.client.oidc.end_session.EndSession"} +_services["end_session"] = {"class": "idpyoidc.client.oidc.end_session.EndSession"} EXTENDED_STATIC_CONFIG = { "base_url": "https://example.com/cli/", @@ -390,13 +385,12 @@ def test_wrong_state(self): "authorization_endpoint": "https://op.example.com/authn", "token_endpoint": "https://op.example.com/token", "userinfo_endpoint": "https://op.example.com/user", - "end_session_endpoint": "https://op.example.com/end_session" - } + "end_session_endpoint": "https://op.example.com/end_session", + }, } class TestPostAuthn(object): - @pytest.fixture(autouse=True) def client_setup(self): self.client = StandAloneClient(config=EXTENDED_STATIC_CONFIG) @@ -406,10 +400,10 @@ def client_setup(self): self.state = get_state_from_url(url) _response = AuthorizationResponse( - code=24 * 'x', + code=24 * "x", state=self.state, iss=self.client.context.issuer, - client_id=self.client.context.get_client_id() + client_id=self.client.context.get_client_id(), ) self.client.finalize_auth(_response.to_dict()) @@ -433,13 +427,10 @@ def _create_id_token(self, subject): def test_get_access_token(self): with responses.RequestsMock() as rsps: - token_response = AccessTokenResponse( - access_token='access_token', - token_type='Bearer' - ) + token_response = AccessTokenResponse(access_token="access_token", token_type="Bearer") rsps.add( "POST", - STATIC_CONFIG['provider_info']['token_endpoint'], + STATIC_CONFIG["provider_info"]["token_endpoint"], body=token_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -447,18 +438,18 @@ def test_get_access_token(self): response = self.client.get_tokens(self.state) assert isinstance(response, AccessTokenResponse) - assert 'access_token' in response + assert "access_token" in response def test_get_access_and_id_token(self): with responses.RequestsMock() as rsps: token_response = AccessTokenResponse( - access_token='access_token', - token_type='Bearer', - id_token=self._create_id_token('Subject') + access_token="access_token", + token_type="Bearer", + id_token=self._create_id_token("Subject"), ) rsps.add( "POST", - STATIC_CONFIG['provider_info']['token_endpoint'], + STATIC_CONFIG["provider_info"]["token_endpoint"], body=token_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -467,19 +458,16 @@ def test_get_access_and_id_token(self): response = self.client.get_access_and_id_token(state=self.state) assert response - assert set(response.keys()) == {'access_token', 'id_token'} - assert response['access_token'] == "access_token" - assert response['id_token']['iss'] == ISSUER + assert set(response.keys()) == {"access_token", "id_token"} + assert response["access_token"] == "access_token" + assert response["id_token"]["iss"] == ISSUER def test_userinfo(self): with responses.RequestsMock() as rsps: - token_response = AccessTokenResponse( - access_token='access_token', - token_type='Bearer' - ) + token_response = AccessTokenResponse(access_token="access_token", token_type="Bearer") rsps.add( "POST", - STATIC_CONFIG['provider_info']['token_endpoint'], + STATIC_CONFIG["provider_info"]["token_endpoint"], body=token_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -488,13 +476,10 @@ def test_userinfo(self): self.client.get_tokens(self.state) with responses.RequestsMock() as rsps: - _response = OpenIDSchema( - sub=SUBJECT_NAME, - email='subject@example.com' - ) + _response = OpenIDSchema(sub=SUBJECT_NAME, email="subject@example.com") rsps.add( "GET", - STATIC_CONFIG['provider_info']['userinfo_endpoint'], + STATIC_CONFIG["provider_info"]["userinfo_endpoint"], body=_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -505,31 +490,25 @@ def test_userinfo(self): def test_finalize_1(self): _auth_response = AuthorizationResponse( - code=24 * 'x', + code=24 * "x", state=self.state, iss=self.client.context.issuer, - client_id=self.client.context.get_client_id() + client_id=self.client.context.get_client_id(), ) with responses.RequestsMock() as rsps: - token_response = AccessTokenResponse( - access_token='access_token', - token_type='Bearer' - ) + token_response = AccessTokenResponse(access_token="access_token", token_type="Bearer") rsps.add( "POST", - STATIC_CONFIG['provider_info']['token_endpoint'], + STATIC_CONFIG["provider_info"]["token_endpoint"], body=token_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, ) - _response = OpenIDSchema( - sub=SUBJECT_NAME, - email='subject@example.com' - ) + _response = OpenIDSchema(sub=SUBJECT_NAME, email="subject@example.com") rsps.add( "GET", - STATIC_CONFIG['provider_info']['userinfo_endpoint'], + STATIC_CONFIG["provider_info"]["userinfo_endpoint"], body=_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -537,42 +516,45 @@ def test_finalize_1(self): response = self.client.finalize(_auth_response.to_dict()) assert response - assert set(response.keys()) == {'userinfo', 'state', 'token', 'id_token', - 'session_state', 'issuer'} - assert response['token'] == 'access_token' - assert response['id_token'] is None - assert response['userinfo']['sub'] == SUBJECT_NAME - assert response['issuer'] == ISSUER + assert set(response.keys()) == { + "userinfo", + "state", + "token", + "id_token", + "session_state", + "issuer", + } + assert response["token"] == "access_token" + assert response["id_token"] is None + assert response["userinfo"]["sub"] == SUBJECT_NAME + assert response["issuer"] == ISSUER def test_finalize_2(self): _auth_response = AuthorizationResponse( - code=24 * 'x', + code=24 * "x", state=self.state, iss=self.client.context.issuer, - client_id=self.client.context.get_client_id() + client_id=self.client.context.get_client_id(), ) with responses.RequestsMock() as rsps: token_response = AccessTokenResponse( - access_token='access_token', + access_token="access_token", expires_in=300, - token_type='Bearer', - id_token=self._create_id_token(SUBJECT_NAME) + token_type="Bearer", + id_token=self._create_id_token(SUBJECT_NAME), ) rsps.add( "POST", - STATIC_CONFIG['provider_info']['token_endpoint'], + STATIC_CONFIG["provider_info"]["token_endpoint"], body=token_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, ) - _response = OpenIDSchema( - sub=SUBJECT_NAME, - email='subject@example.com' - ) + _response = OpenIDSchema(sub=SUBJECT_NAME, email="subject@example.com") rsps.add( "GET", - STATIC_CONFIG['provider_info']['userinfo_endpoint'], + STATIC_CONFIG["provider_info"]["userinfo_endpoint"], body=_response.to_json(), adding_headers={"Content-Type": "application/json"}, status=200, @@ -580,12 +562,18 @@ def test_finalize_2(self): response = self.client.finalize(_auth_response.to_dict()) assert response - assert set(response.keys()) == {'userinfo', 'state', 'token', 'id_token', - 'session_state', 'issuer'} - assert response['token'] == 'access_token' - assert response['id_token'] is not None - assert response['userinfo']['sub'] == SUBJECT_NAME - assert response['issuer'] == ISSUER + assert set(response.keys()) == { + "userinfo", + "state", + "token", + "id_token", + "session_state", + "issuer", + } + assert response["token"] == "access_token" + assert response["id_token"] is not None + assert response["userinfo"]["sub"] == SUBJECT_NAME + assert response["issuer"] == ISSUER assert self.client.has_active_authentication(self.state) @@ -593,6 +581,9 @@ def test_finalize_2(self): assert token == "access_token" assert eat > 0 logout_info = self.client.logout(self.state, "https://example.com/cli/logout") - assert set(logout_info.keys()) == {'method', 'request', 'url'} - assert set(logout_info['request'].keys()) == {'post_logout_redirect_uri', 'id_token_hint', - 'state'} + assert set(logout_info.keys()) == {"method", "request", "url"} + assert set(logout_info["request"].keys()) == { + "post_logout_redirect_uri", + "id_token_hint", + "state", + } diff --git a/tests/test_client_30_rp_handler_oidc.py b/tests/test_client_30_rp_handler_oidc.py index b992a55d..6f97b879 100644 --- a/tests/test_client_30_rp_handler_oidc.py +++ b/tests/test_client_30_rp_handler_oidc.py @@ -10,10 +10,10 @@ from idpyoidc.client.entity import Entity from idpyoidc.client.rp_handler import RPHandler +from idpyoidc.message.oidc import JRD from idpyoidc.message.oidc import AccessTokenResponse from idpyoidc.message.oidc import AuthorizationResponse from idpyoidc.message.oidc import IdToken -from idpyoidc.message.oidc import JRD from idpyoidc.message.oidc import Link from idpyoidc.message.oidc import OpenIDSchema from idpyoidc.message.oidc import ProviderConfigurationResponse @@ -216,7 +216,6 @@ def iss_id(iss): class TestRPHandler(object): - @pytest.fixture(autouse=True) def rphandler_setup(self): self.rph = RPHandler( @@ -305,9 +304,8 @@ def test_do_client_registration(self): assert self.rph.hash2issuer["github"] == issuer assert ( - client.get_context().get_preference("callback_uris").get( - "post_logout_redirect_uris") - is None + client.get_context().get_preference("callback_uris").get("post_logout_redirect_uris") + is None ) def test_do_client_setup(self): @@ -690,7 +688,6 @@ def test_get_provider_specific_service(): class TestRPHandlerTier2(object): - @pytest.fixture(autouse=True) def rphandler_setup(self): self.rph = RPHandler(BASE_URL, CLIENT_CONFIG, keyjar=CLI_KEY) @@ -812,7 +809,6 @@ def test_get_valid_access_token(self): class MockResponse: - def __init__(self, status_code, text, headers=None): self.status_code = status_code self.text = text @@ -820,7 +816,6 @@ def __init__(self, status_code, text, headers=None): class MockOP(object): - def __init__(self, issuer, keyjar=None): self.keyjar = keyjar self.issuer = issuer @@ -909,7 +904,6 @@ def test_rphandler_request(): class TestRPHandlerWithMockOP(object): - @pytest.fixture(autouse=True) def rphandler_setup(self): self.issuer = "https://github.com/login/oauth/authorize" @@ -974,8 +968,14 @@ def test_finalize(self): # assume code flow resp = self.rph.finalize(_session["iss"], auth_response.to_dict()) - assert set(resp.keys()) == {'token', 'session_state', 'userinfo', 'state', 'issuer', - 'id_token'} + assert set(resp.keys()) == { + "token", + "session_state", + "userinfo", + "state", + "issuer", + "id_token", + } def test_dynamic_setup(self): user_id = "acct:foobar@example.com" diff --git a/tests/test_client_30_rph_defaults.py b/tests/test_client_30_rph_defaults.py index 30dadd6e..0b6af004 100644 --- a/tests/test_client_30_rph_defaults.py +++ b/tests/test_client_30_rph_defaults.py @@ -14,7 +14,6 @@ class TestRPHandler(object): - @pytest.fixture(autouse=True) def rphandler_setup(self): self.rph = RPHandler(BASE_URL) @@ -37,17 +36,18 @@ def test_init_client(self): _context = client.get_context() assert set(_context.claims.prefer.keys()) == { - 'application_type', - 'callback_uris', - 'id_token_encryption_alg_values_supported', - 'id_token_encryption_enc_values_supported', - 'jwks_uri', - 'redirect_uris', - 'request_object_encryption_alg_values_supported', - 'request_object_encryption_enc_values_supported', - 'scopes_supported', - 'userinfo_encryption_alg_values_supported', - 'userinfo_encryption_enc_values_supported'} + "application_type", + "callback_uris", + "id_token_encryption_alg_values_supported", + "id_token_encryption_enc_values_supported", + "jwks_uri", + "redirect_uris", + "request_object_encryption_alg_values_supported", + "request_object_encryption_enc_values_supported", + "scopes_supported", + "userinfo_encryption_alg_values_supported", + "userinfo_encryption_enc_values_supported", + } _keyjar = client.get_attribute("keyjar") assert list(_keyjar.owners()) == ["", BASE_URL] diff --git a/tests/test_client_41_rp_handler_persistent.py b/tests/test_client_41_rp_handler_persistent.py index be07ad4a..113a3dea 100644 --- a/tests/test_client_41_rp_handler_persistent.py +++ b/tests/test_client_41_rp_handler_persistent.py @@ -175,7 +175,6 @@ def get_state_from_url(url): class TestRPHandler(object): - def test_pick_config(self): rph_1 = RPHandler( BASE_URL, client_configs=CLIENT_CONFIG, keyjar=CLI_KEY, module_dirs=["oidc"] @@ -421,9 +420,7 @@ def test_get_tokens(self): } atresp = ( - client.get_service("accesstoken") - .upstream_get("service_context") - .cstate.get(_state) + client.get_service("accesstoken").upstream_get("service_context").cstate.get(_state) ) assert set(atresp.keys()) == { "__expires_at", diff --git a/tests/test_client_55_token_exchange.py b/tests/test_client_55_token_exchange.py index d2951070..108e867e 100644 --- a/tests/test_client_55_token_exchange.py +++ b/tests/test_client_55_token_exchange.py @@ -1,7 +1,7 @@ import os -from cryptojwt.key_jar import init_key_jar import pytest +from cryptojwt.key_jar import init_key_jar from idpyoidc.client.entity import Entity from idpyoidc.message import Message diff --git a/tests/test_server_16_endpoint_context.py b/tests/test_server_16_endpoint_context.py index 39c1113b..6a946402 100644 --- a/tests/test_server_16_endpoint_context.py +++ b/tests/test_server_16_endpoint_context.py @@ -11,9 +11,10 @@ from idpyoidc.server.exception import OidcEndpointError from idpyoidc.server.user_authn.authn_context import INTERNETPROTOCOLPASSWORD from idpyoidc.server.util import allow_refresh_token + from . import CRYPT_CONFIG -from . import full_path from . import SESSION_PARAMS +from . import full_path KEYDEFS = [ {"type": "RSA", "key": "", "use": ["sig"]}, diff --git a/tests/test_server_20a_server.py b/tests/test_server_20a_server.py index 11e6d6fb..8413b019 100755 --- a/tests/test_server_20a_server.py +++ b/tests/test_server_20a_server.py @@ -1,11 +1,11 @@ -from copy import copy -from copy import deepcopy import io import json import os +from copy import copy +from copy import deepcopy -from cryptojwt.key_jar import build_keyjar import yaml +from cryptojwt.key_jar import build_keyjar from idpyoidc.server import Server from idpyoidc.server.configure import OPConfiguration diff --git a/tests/test_server_20d_client_authn.py b/tests/test_server_20d_client_authn.py index 21beb359..187414ac 100755 --- a/tests/test_server_20d_client_authn.py +++ b/tests/test_server_20d_client_authn.py @@ -1,13 +1,13 @@ import base64 from unittest.mock import MagicMock +import pytest from cryptojwt.jws.exception import NoSuitableSigningKeys from cryptojwt.jwt import JWT from cryptojwt.key_jar import KeyJar from cryptojwt.key_jar import build_keyjar from cryptojwt.utils import as_bytes from cryptojwt.utils import as_unicode -import pytest from idpyoidc.defaults import JWT_BEARER from idpyoidc.server import Server diff --git a/tests/test_server_23_oidc_registration_endpoint.py b/tests/test_server_23_oidc_registration_endpoint.py index 44f1f2ce..0b33c570 100755 --- a/tests/test_server_23_oidc_registration_endpoint.py +++ b/tests/test_server_23_oidc_registration_endpoint.py @@ -77,7 +77,7 @@ def create_endpoint(self): conf = { "issuer": "https://example.com/", "httpc_params": {"verify": False, "timeout": 1}, - "capabilities": { + "preference": { "subject_types_supported": ["public", "pairwise", "ephemeral"], "grant_types_supported": [ "authorization_code", diff --git a/tests/test_server_24_oauth2_authorization_endpoint.py b/tests/test_server_24_oauth2_authorization_endpoint.py index 4d97d188..627c4ebd 100755 --- a/tests/test_server_24_oauth2_authorization_endpoint.py +++ b/tests/test_server_24_oauth2_authorization_endpoint.py @@ -602,7 +602,7 @@ def test_setup_auth_invalid_scope_2(self): "redirect_uris": [("https://rp.example.com/cb", {})], "id_token_signed_response_alg": "RS256", "allowed_scopes": ["openid", "profile", "email", "address", "phone", "offline_access"], - "deny_unknown_scopes": True + "deny_unknown_scopes": True, } _context = self.endpoint.upstream_get("context") diff --git a/tests/test_server_24_oauth2_resource_indicators.py b/tests/test_server_24_oauth2_resource_indicators.py index a98638ed..4df18ea3 100644 --- a/tests/test_server_24_oauth2_resource_indicators.py +++ b/tests/test_server_24_oauth2_resource_indicators.py @@ -7,19 +7,20 @@ import pytest import yaml -from cryptojwt.jwt import JWT -from cryptojwt.key_jar import init_key_jar from cryptojwt import KeyJar +from cryptojwt.jwt import JWT from cryptojwt.jwt import utc_time_sans_frac +from cryptojwt.key_jar import init_key_jar from cryptojwt.utils import as_bytes from cryptojwt.utils import b64e from idpyoidc.exception import ParameterError from idpyoidc.exception import URIError -from idpyoidc.message.oauth2 import AuthorizationErrorResponse, TokenErrorResponse -from idpyoidc.message.oidc import AccessTokenRequest +from idpyoidc.message.oauth2 import AuthorizationErrorResponse from idpyoidc.message.oauth2 import AuthorizationRequest from idpyoidc.message.oauth2 import AuthorizationResponse +from idpyoidc.message.oauth2 import TokenErrorResponse +from idpyoidc.message.oidc import AccessTokenRequest from idpyoidc.server import Server from idpyoidc.server.authn_event import create_authn_event from idpyoidc.server.authz import AuthzHandling @@ -33,14 +34,14 @@ from idpyoidc.server.exception import UnknownClient from idpyoidc.server.oauth2.authorization import FORM_POST from idpyoidc.server.oauth2.authorization import Authorization -from idpyoidc.server.oauth2.token import Token from idpyoidc.server.oauth2.authorization import get_uri from idpyoidc.server.oauth2.authorization import inputs from idpyoidc.server.oauth2.authorization import join_query -from idpyoidc.server.oauth2.authorization import verify_uri from idpyoidc.server.oauth2.authorization import ( validate_resource_indicators_policy as validate_authorization_resource_indicators_policy, ) +from idpyoidc.server.oauth2.authorization import verify_uri +from idpyoidc.server.oauth2.token import Token from idpyoidc.server.oauth2.token_helper import ( validate_resource_indicators_policy as validate_token_resource_indicators_policy, ) diff --git a/tests/test_server_24_oauth2_token_endpoint.py b/tests/test_server_24_oauth2_token_endpoint.py index 55dcebf3..d4789959 100644 --- a/tests/test_server_24_oauth2_token_endpoint.py +++ b/tests/test_server_24_oauth2_token_endpoint.py @@ -9,10 +9,10 @@ from idpyoidc.context import OidcContext from idpyoidc.defaults import JWT_BEARER -from idpyoidc.message import Message from idpyoidc.message import REQUIRED_LIST_OF_STRINGS from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message from idpyoidc.message.oauth2 import CCAccessTokenRequest from idpyoidc.message.oauth2 import JWTAccessToken from idpyoidc.message.oauth2 import ROPCAccessTokenRequest diff --git a/tests/test_server_24_oauth2_token_endpoint_def_conf.py b/tests/test_server_24_oauth2_token_endpoint_def_conf.py index a367d4dc..9508002f 100644 --- a/tests/test_server_24_oauth2_token_endpoint_def_conf.py +++ b/tests/test_server_24_oauth2_token_endpoint_def_conf.py @@ -8,10 +8,10 @@ from idpyoidc.context import OidcContext from idpyoidc.defaults import JWT_BEARER -from idpyoidc.message import Message from idpyoidc.message import REQUIRED_LIST_OF_STRINGS from idpyoidc.message import SINGLE_REQUIRED_INT from idpyoidc.message import SINGLE_REQUIRED_STRING +from idpyoidc.message import Message from idpyoidc.message.oauth2 import AccessTokenRequest from idpyoidc.message.oauth2 import AuthorizationRequest from idpyoidc.message.oauth2 import CCAccessTokenRequest @@ -64,15 +64,14 @@ def full_path(local_file): class TestEndpoint(object): - @pytest.fixture(autouse=True) def create_endpoint(self): conf = { "issuer": "https://example.com/", - 'userinfo': { + "userinfo": { "class": "idpyoidc.server.user_info.UserInfo", "kwargs": {"db_file": full_path("users.json")}, - } + }, } server = Server(ASConfiguration(conf=conf, base_path=BASEDIR), cwd=BASEDIR) context = server.context @@ -801,15 +800,14 @@ def test_jwttoken_2(): class TestClientCredentialsFlow(object): - @pytest.fixture(autouse=True) def create_endpoint(self): conf = { "issuer": "https://example.com/", - 'userinfo': { + "userinfo": { "class": "idpyoidc.server.user_info.UserInfo", "kwargs": {"db_file": full_path("users.json")}, - } + }, } server = Server(ASConfiguration(conf=conf, base_path=BASEDIR), cwd=BASEDIR) @@ -847,12 +845,11 @@ def test_client_credentials(self): class TestResourceOwnerPasswordCredentialsFlow(object): - @pytest.fixture(autouse=True) def create_endpoint(self): conf = { "issuer": "https://example.com/", - 'userinfo': { + "userinfo": { "class": "idpyoidc.server.user_info.UserInfo", "kwargs": {"db_file": full_path("users.json")}, }, @@ -867,7 +864,8 @@ def create_endpoint(self): } }, } - }} + }, + } server = Server(ASConfiguration(conf=conf, base_path=BASEDIR), cwd=BASEDIR) context = server.context diff --git a/tests/test_server_24_oidc_authorization_endpoint.py b/tests/test_server_24_oidc_authorization_endpoint.py index dd53ac8a..836b9a81 100755 --- a/tests/test_server_24_oidc_authorization_endpoint.py +++ b/tests/test_server_24_oidc_authorization_endpoint.py @@ -4,14 +4,14 @@ from urllib.parse import parse_qs from urllib.parse import urlparse +import pytest +import responses +import yaml from cryptojwt import JWT from cryptojwt import KeyJar from cryptojwt.jws.jws import factory from cryptojwt.utils import as_bytes from cryptojwt.utils import b64e -import pytest -import responses -import yaml from idpyoidc.exception import ParameterError from idpyoidc.exception import URIError diff --git a/tests/test_server_30_oidc_end_session.py b/tests/test_server_30_oidc_end_session.py index 59487b74..95255a70 100644 --- a/tests/test_server_30_oidc_end_session.py +++ b/tests/test_server_30_oidc_end_session.py @@ -4,9 +4,9 @@ from urllib.parse import parse_qs from urllib.parse import urlparse -from cryptojwt.key_jar import build_keyjar import pytest import responses +from cryptojwt.key_jar import build_keyjar from idpyoidc.exception import InvalidRequest from idpyoidc.message import Message diff --git a/tests/test_server_31_oauth2_introspection.py b/tests/test_server_31_oauth2_introspection.py index 5e28c632..bf283476 100644 --- a/tests/test_server_31_oauth2_introspection.py +++ b/tests/test_server_31_oauth2_introspection.py @@ -3,8 +3,8 @@ import os import pytest -from cryptojwt import as_unicode from cryptojwt import JWT +from cryptojwt import as_unicode from cryptojwt.key_jar import build_keyjar from cryptojwt.utils import as_bytes diff --git a/tests/test_server_33_oauth2_pkce.py b/tests/test_server_33_oauth2_pkce.py index 78fcbe3a..60cf7539 100644 --- a/tests/test_server_33_oauth2_pkce.py +++ b/tests/test_server_33_oauth2_pkce.py @@ -21,6 +21,7 @@ from idpyoidc.server.oauth2.add_on.pkce import add_support from idpyoidc.server.oidc.authorization import Authorization from idpyoidc.server.oidc.token import Token + from . import CRYPT_CONFIG from . import SESSION_PARAMS from . import full_path diff --git a/tests/test_server_35_oidc_token_endpoint.py b/tests/test_server_35_oidc_token_endpoint.py index 0f18fea7..d6af31e8 100755 --- a/tests/test_server_35_oidc_token_endpoint.py +++ b/tests/test_server_35_oidc_token_endpoint.py @@ -28,6 +28,7 @@ from idpyoidc.server.user_info import UserInfo from idpyoidc.server.util import lv_pack from idpyoidc.time_util import utc_time_sans_frac + from . import CRYPT_CONFIG from . import SESSION_PARAMS from .test_server_24_oauth2_token_endpoint import TestEndpoint as _TestEndpoint diff --git a/tests/test_server_35_oidc_token_endpoint_def_conf.py b/tests/test_server_35_oidc_token_endpoint_def_conf.py index 62a4bef9..f08fefa8 100755 --- a/tests/test_server_35_oidc_token_endpoint_def_conf.py +++ b/tests/test_server_35_oidc_token_endpoint_def_conf.py @@ -48,16 +48,15 @@ def full_path(local_file): return os.path.join(BASEDIR, local_file) -class TestEndpoint(): - +class TestEndpoint: @pytest.fixture(autouse=True) def create_endpoint(self): conf = { "issuer": "https://example.com/", - 'userinfo': { + "userinfo": { "class": "idpyoidc.server.user_info.UserInfo", "kwargs": {"db_file": full_path("users.json")}, - } + }, } self.server = Server(OPConfiguration(conf=conf, base_path=BASEDIR), cwd=BASEDIR) @@ -200,8 +199,7 @@ def test_process_request_using_private_key_jwt(self): _jwt = JWT(CLIENT_KEYJAR, iss=AUTH_REQ["client_id"], sign_alg="RS256") _jwt.with_jti = True _assertion = _jwt.pack({"aud": [self.token_endpoint.full_path]}) - _token_request.update({"client_assertion": _assertion, - "client_assertion_type": JWT_BEARER}) + _token_request.update({"client_assertion": _assertion, "client_assertion_type": JWT_BEARER}) _token_request["code"] = code.value _req = self.token_endpoint.parse_request(_token_request) diff --git a/tests/test_server_50_persistence.py b/tests/test_server_50_persistence.py index 7cea5afc..d28d112d 100644 --- a/tests/test_server_50_persistence.py +++ b/tests/test_server_50_persistence.py @@ -2,9 +2,9 @@ import os import shutil +import pytest from cryptojwt.jwt import utc_time_sans_frac from cryptojwt.key_jar import init_key_jar -import pytest from idpyoidc.message.oidc import AccessTokenRequest from idpyoidc.message.oidc import AuthorizationRequest diff --git a/tests/test_tandem_oauth2_add_on.py b/tests/test_tandem_oauth2_add_on.py index 77b9391f..a3776fc8 100644 --- a/tests/test_tandem_oauth2_add_on.py +++ b/tests/test_tandem_oauth2_add_on.py @@ -169,7 +169,6 @@ def full_path(local_file): class Flow(object): - def __init__(self, client, server): self.client = client self.server = server diff --git a/tests/test_tandem_oauth2_cc_ropc.py b/tests/test_tandem_oauth2_cc_ropc.py index 4dd718aa..e65776fb 100644 --- a/tests/test_tandem_oauth2_cc_ropc.py +++ b/tests/test_tandem_oauth2_cc_ropc.py @@ -1,7 +1,6 @@ import os from idpyoidc.client.oauth2 import Client - from idpyoidc.server import Server from idpyoidc.server.authz import AuthzHandling from idpyoidc.server.client_authn import verify_client diff --git a/tests/test_tandem_oauth2_code.py b/tests/test_tandem_oauth2_code.py index 5adec34a..091a0046 100644 --- a/tests/test_tandem_oauth2_code.py +++ b/tests/test_tandem_oauth2_code.py @@ -1,8 +1,8 @@ import json import os -from cryptojwt.key_jar import build_keyjar import pytest +from cryptojwt.key_jar import build_keyjar from idpyoidc.client.oauth2 import Client from idpyoidc.message.oauth2 import is_error_message diff --git a/tests/test_tandem_oauth2_token_exchange.py b/tests/test_tandem_oauth2_token_exchange.py index 141b1333..6b722d3b 100644 --- a/tests/test_tandem_oauth2_token_exchange.py +++ b/tests/test_tandem_oauth2_token_exchange.py @@ -1,8 +1,8 @@ import json import os -from cryptojwt.key_jar import build_keyjar import pytest +from cryptojwt.key_jar import build_keyjar from idpyoidc.client.oauth2 import Client from idpyoidc.message.oauth2 import is_error_message diff --git a/tests/test_tandem_oidc_code.py b/tests/test_tandem_oidc_code.py index ad40a46e..5f11f4ab 100644 --- a/tests/test_tandem_oidc_code.py +++ b/tests/test_tandem_oidc_code.py @@ -1,9 +1,8 @@ import json import os -from cryptojwt.key_jar import build_keyjar - import pytest +from cryptojwt.key_jar import build_keyjar from idpyoidc.client.oidc import RP from idpyoidc.message.oauth2 import is_error_message