diff --git a/src/idpyoidc/__init__.py b/src/idpyoidc/__init__.py index cf0971e9..0b77ba88 100644 --- a/src/idpyoidc/__init__.py +++ b/src/idpyoidc/__init__.py @@ -1,5 +1,5 @@ __author__ = "Roland Hedberg" -__version__ = "4.0.0" +__version__ = "4.1.0" VERIFIED_CLAIM_PREFIX = "__verified" diff --git a/src/idpyoidc/client/claims/transform.py b/src/idpyoidc/client/claims/transform.py index ac63b2c8..1ca40c6c 100644 --- a/src/idpyoidc/client/claims/transform.py +++ b/src/idpyoidc/client/claims/transform.py @@ -62,11 +62,16 @@ def supported_to_preferred( _pref_val = preference.get(key) # defined in configuration _info_val = info.get(key) if _info_val: - # Only use provider setting if less or equal to what I support - if key.endswith("supported"): # list - preference[key] = [x for x in _pref_val if x in _info_val] + if isinstance(_info_val, bool): + if _info_val is False and _pref_val is True: + # Turn off support if server doesn't support + preference[key] = _info_val else: - pass + # Only use provider setting if less or equal to what I support + if key.endswith("supported"): # list + preference[key] = [x for x in _pref_val if x in _info_val] + else: + pass elif val is None: # No default, means the RP does not have a preference # if key not in ['jwks_uri', 'jwks']: pass diff --git a/src/idpyoidc/client/client_auth.py b/src/idpyoidc/client/client_auth.py index 41177d02..a8830cd4 100755 --- a/src/idpyoidc/client/client_auth.py +++ b/src/idpyoidc/client/client_auth.py @@ -520,9 +520,7 @@ def _get_audience_and_algorithm(self, context, keyjar, **kwargs): def _construct_client_assertion(self, service, **kwargs): _context = service.upstream_get("context") - _entity = service.upstream_get("entity") - if _entity is None: - _entity = service.upstream_get("unit") + _entity = service.upstream_get("unit") _keyjar = service.upstream_get("attribute", "keyjar") audience, algorithm = self._get_audience_and_algorithm(_context, _keyjar, **kwargs) diff --git a/src/idpyoidc/client/entity.py b/src/idpyoidc/client/entity.py index 2ada9ebe..197d5d73 100644 --- a/src/idpyoidc/client/entity.py +++ b/src/idpyoidc/client/entity.py @@ -145,7 +145,6 @@ def __init__( ) self.setup_client_authn_methods(config) - self.upstream_get = upstream_get def get_services(self, *arg): @@ -170,8 +169,8 @@ def get_service_by_endpoint_name(self, endpoint_name, *arg): return None - def get_entity(self): - return self + # def get_entity(self): + # return self def get_client_id(self): _val = self.context.claims.get_usage("client_id") diff --git a/src/idpyoidc/client/oauth2/add_on/dpop.py b/src/idpyoidc/client/oauth2/add_on/dpop.py index a4ad740d..f92574ff 100644 --- a/src/idpyoidc/client/oauth2/add_on/dpop.py +++ b/src/idpyoidc/client/oauth2/add_on/dpop.py @@ -4,16 +4,18 @@ from typing import Optional 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 JWS from cryptojwt.key_bundle import key_by_alg +from idpyoidc.client.client_auth import BearerHeader +from idpyoidc.client.client_auth import find_token_info from idpyoidc.client.service_context import ServiceContext +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.time_util import utc_time_sans_frac @@ -91,13 +93,13 @@ def verify_header(self, dpop_header) -> Optional["DPoPProof"]: def dpop_header( - service_context: ServiceContext, - service_endpoint: str, - http_method: str, - headers: Optional[dict] = None, - token: Optional[str] = "", - nonce: Optional[str] = "", - **kwargs + service_context: ServiceContext, + service_endpoint: str, + http_method: str, + headers: Optional[dict] = None, + token: Optional[str] = "", + nonce: Optional[str] = "", + **kwargs ) -> dict: """ @@ -159,7 +161,7 @@ def dpop_header( return headers -def add_support(services, dpop_signing_alg_values_supported): +def add_support(services, dpop_signing_alg_values_supported, with_dpop_header=None): """ Add the necessary pieces to make pushed authorization happen. @@ -185,3 +187,53 @@ def add_support(services, dpop_signing_alg_values_supported): _userinfo_service = services.get("userinfo") if _userinfo_service: _userinfo_service.construct_extra_headers.append(dpop_header) + # To be backward compatible + if with_dpop_header is None: + with_dpop_header = ["userinfo"] + + # Add dpop HTTP header to these + for _srv in with_dpop_header: + if _srv == "accesstoken": + continue + _service = services.get(_srv) + if _service: + _service.construct_extra_headers.append(dpop_header) + + +class DPoPClientAuth(BearerHeader): + tag = "dpop_client_auth" + + def construct(self, request=None, service=None, http_args=None, **kwargs): + """ + Constructing the Authorization header. The value of + the Authorization header is "Bearer ". + + :param request: Request class instance + :param service: The service this authentication method applies to. + :param http_args: HTTP header arguments + :param kwargs: extra keyword arguments + :return: + """ + + _token_type = "access_token" + + _token_info = find_token_info(request, _token_type, service, **kwargs) + + if not _token_info: + raise KeyError("No bearer token available") + + # The authorization value starts with the token_type + # if _token_info["token_type"].to_lower() != "bearer": + _bearer = f"DPoP {_token_info[_token_type]}" + + # Add 'Authorization' to the headers + if http_args is None: + http_args = {"headers": {}} + http_args["headers"]["Authorization"] = _bearer + else: + try: + http_args["headers"]["Authorization"] = _bearer + except KeyError: + http_args["headers"] = {"Authorization": _bearer} + + return http_args diff --git a/src/idpyoidc/client/oauth2/add_on/par.py b/src/idpyoidc/client/oauth2/add_on/par.py index db358e8f..705a2223 100644 --- a/src/idpyoidc/client/oauth2/add_on/par.py +++ b/src/idpyoidc/client/oauth2/add_on/par.py @@ -45,13 +45,9 @@ def push_authorization(request_args, service, **kwargs): _context.client_authn_methods[_name] = execute(spec) authn_method = _name - _args = {} + _args = kwargs.copy() if _context.issuer: _args["iss"] = _context.issuer - if _name == "client_attestation": - _wia = kwargs.get("client_attestation") - if _wia: - _args["client_attestation"] = _wia _headers = service.get_headers( request_args, http_method=_http_method, authn_method=authn_method, **_args diff --git a/src/idpyoidc/client/oauth2/server_metadata.py b/src/idpyoidc/client/oauth2/server_metadata.py index 370256d5..0799f23a 100644 --- a/src/idpyoidc/client/oauth2/server_metadata.py +++ b/src/idpyoidc/client/oauth2/server_metadata.py @@ -38,7 +38,7 @@ def get_endpoint(self): :return: Service endpoint """ try: - _iss = self.upstream_get("context").issuer + _iss = self.upstream_get("attribute","issuer") except AttributeError: _iss = self.endpoint @@ -72,13 +72,16 @@ def _verify_issuer(self, resp, issuer): # In some cases we can live with the two URLs not being # the same. But this is an excepted that has to be explicit - try: - self.upstream_get("context").allow["issuer_mismatch"] - except KeyError: - if _issuer != _pcr_issuer: - raise OidcServiceError( - "provider info issuer mismatch '%s' != '%s'" % (_issuer, _pcr_issuer) - ) + _allow = self.upstream_get("attribute", "allow") + if _allow: + _allowed = _allow.get("issuer_mismatch", None) + if _allowed: + return _issuer + + if _issuer != _pcr_issuer: + raise OidcServiceError( + "provider info issuer mismatch '%s' != '%s'" % (_issuer, _pcr_issuer) + ) return _issuer def _set_endpoints(self, resp): @@ -131,9 +134,10 @@ def _update_service_context(self, resp): # is loaded not necessarily that any keys are fetched. if "jwks_uri" in resp: LOGGER.debug(f"'jwks_uri' in provider info: {resp['jwks_uri']}") - _hp = self.upstream_get('entity').httpc_params - if "verify" in _hp and "verify" not in _keyjar.httpc_params: - _keyjar.httpc_params["verify"] = _hp["verify"] + _hp = self.upstream_get("attribute","httpc_params") + if _hp: + if "verify" in _hp and "verify" not in _keyjar.httpc_params: + _keyjar.httpc_params["verify"] = _hp["verify"] _keyjar.load_keys(_pcr_issuer, jwks_uri=resp["jwks_uri"]) _loaded = True elif "jwks" in resp: diff --git a/src/idpyoidc/client/oidc/__init__.py b/src/idpyoidc/client/oidc/__init__.py index ab446455..45cefe23 100755 --- a/src/idpyoidc/client/oidc/__init__.py +++ b/src/idpyoidc/client/oidc/__init__.py @@ -94,7 +94,6 @@ def __init__( jwks_uri: Optional[str] = "", **kwargs ): - self.upstream_get = upstream_get if services: _srvs = services else: diff --git a/src/idpyoidc/client/oidc/access_token.py b/src/idpyoidc/client/oidc/access_token.py index 55bdad78..ffbee3a7 100644 --- a/src/idpyoidc/client/oidc/access_token.py +++ b/src/idpyoidc/client/oidc/access_token.py @@ -42,7 +42,7 @@ def gather_verify_arguments( :return: dictionary with arguments to the verify call """ _context = self.upstream_get("context") - _entity = self.upstream_get("entity") + _entity = self.upstream_get("unit") kwargs = { "client_id": _entity.get_client_id(), diff --git a/src/idpyoidc/client/oidc/authorization.py b/src/idpyoidc/client/oidc/authorization.py index 2228a5e6..03cde130 100644 --- a/src/idpyoidc/client/oidc/authorization.py +++ b/src/idpyoidc/client/oidc/authorization.py @@ -90,7 +90,7 @@ def update_service_context(self, resp, key="", **kwargs): _context.cstate.update(key, resp) def get_request_from_response(self, response): - _context = self.upstream_get("service_context") + _context = self.upstream_get("context") return _context.cstate.get_set(response["state"], message=oauth2.AuthorizationRequest) def post_parse_response(self, response, **kwargs): diff --git a/src/idpyoidc/client/oidc/provider_info_discovery.py b/src/idpyoidc/client/oidc/provider_info_discovery.py index 0f8b0a00..d1ac4416 100644 --- a/src/idpyoidc/client/oidc/provider_info_discovery.py +++ b/src/idpyoidc/client/oidc/provider_info_discovery.py @@ -25,7 +25,7 @@ def add_redirect_uris(request_args, service=None, **kwargs): :param kwargs: Possible extra keyword arguments :return: A possibly augmented set of request arguments. """ - _work_environment = service.upstream_get("context").claims + _work_environment = service.upstream_get("attribute", "claims") if "redirect_uris" not in request_args: # Callbacks is a dictionary with callback type 'code', 'implicit', # 'form_post' as keys. diff --git a/src/idpyoidc/client/service_context.py b/src/idpyoidc/client/service_context.py index 829df449..041b0bab 100644 --- a/src/idpyoidc/client/service_context.py +++ b/src/idpyoidc/client/service_context.py @@ -159,6 +159,7 @@ def __init__( self.httpc_params = {} self.client_secret_expires_at = 0 self.registration_response = {} + self.client_authn_methods = {} # _def_value = copy.deepcopy(DEFAULT_VALUE) @@ -189,6 +190,9 @@ def __init__( self.construct_uris(response_types=_response_types) + self.map_supported_to_preferred() + self.map_preferred_to_registered() + def __setitem__(self, key, value): setattr(self, key, value) diff --git a/src/idpyoidc/node.py b/src/idpyoidc/node.py index 25b1f406..0db622a7 100644 --- a/src/idpyoidc/node.py +++ b/src/idpyoidc/node.py @@ -181,9 +181,9 @@ def get_unit(self, *args): def topmost_unit(unit): if hasattr(unit, "upstream_get"): if unit.upstream_get: - next_unit = unit.upstream_get("unit") - if next_unit: - unit = topmost_unit(next_unit) + superior = unit.upstream_get("unit") + if superior: + unit = topmost_unit(superior) return unit diff --git a/src/idpyoidc/server/__init__.py b/src/idpyoidc/server/__init__.py index 6e93aea0..5946113d 100644 --- a/src/idpyoidc/server/__init__.py +++ b/src/idpyoidc/server/__init__.py @@ -67,7 +67,6 @@ def __init__( issuer_id=self.issuer, ) - self.upstream_get = upstream_get if isinstance(conf, OPConfiguration) or isinstance(conf, ASConfiguration): self.conf = conf else: diff --git a/src/idpyoidc/server/authz/__init__.py b/src/idpyoidc/server/authz/__init__.py index b326e322..7752e531 100755 --- a/src/idpyoidc/server/authz/__init__.py +++ b/src/idpyoidc/server/authz/__init__.py @@ -90,7 +90,7 @@ def __call__( grant.scope = scopes # After this is where user consent should be handled - grant.claims = self.upstream_get("context").claims_interface.get_claims_all_usage( + grant.claims = _context.claims_interface.get_claims_all_usage( session_id=session_id, scopes=scopes ) diff --git a/src/idpyoidc/server/endpoint_context.py b/src/idpyoidc/server/endpoint_context.py index 3317a5fc..e43e74a2 100755 --- a/src/idpyoidc/server/endpoint_context.py +++ b/src/idpyoidc/server/endpoint_context.py @@ -233,7 +233,7 @@ def __init__( self.dev_auth_db = None _interface = conf.get("claims_interface") if _interface: - self.claims_interface = init_service(_interface, self.upstream_get) + self.claims_interface = init_service(_interface, self.unit_get) if isinstance(conf, OPConfiguration): conf = conf.conf @@ -276,9 +276,7 @@ def setup_authz(self): return authz.Implicit(self.unit_get) def setup_client_authn_methods(self): - self.client_authn_methods = client_auth_setup( - self.upstream_get, self.conf.get("client_authn_methods") - ) + self.client_authn_methods = client_auth_setup(self.unit_get, self.conf.get("client_authn_methods")) def setup_login_hint_lookup(self): _conf = self.conf.get("login_hint_lookup") @@ -307,10 +305,10 @@ def set_scopes_handler(self): if _spec: _kwargs = _spec.get("kwargs", {}) _cls = importer(_spec["class"]) - self.scopes_handler = _cls(self.upstream_get, **_kwargs) + self.scopes_handler = _cls(self.unit_get, **_kwargs) else: self.scopes_handler = Scopes( - self.upstream_get, + self.unit_get, allowed_scopes=self.conf.get("allowed_scopes"), scopes_to_claims=self.conf.get("scopes_to_claims"), ) @@ -440,7 +438,7 @@ def setup_authentication(self): _conf = self.conf.get("authentication") if _conf: self.authn_broker = populate_authn_broker( - _conf, self.upstream_get, self.template_handler + _conf, self.unit_get, self.template_handler ) else: self.authn_broker = {} diff --git a/src/idpyoidc/server/oauth2/authorization.py b/src/idpyoidc/server/oauth2/authorization.py index eaf573e5..9f7b2723 100755 --- a/src/idpyoidc/server/oauth2/authorization.py +++ b/src/idpyoidc/server/oauth2/authorization.py @@ -14,7 +14,6 @@ from cryptojwt.utils import as_bytes from cryptojwt.utils import b64e -from idpyoidc import claims from idpyoidc import metadata from idpyoidc.exception import ImproperlyConfigured from idpyoidc.exception import ParameterError @@ -93,11 +92,11 @@ def max_age(request): def verify_uri( - context: EndpointContext, - request: Union[dict, Message], - uri_type: str, - client_id: Optional[str] = None, - endpoint_type: Optional[str] = 'oidc' + context: EndpointContext, + request: Union[dict, Message], + uri_type: str, + client_id: Optional[str] = None, + endpoint_type: Optional[str] = 'oidc' ): """ A redirect URI @@ -210,7 +209,7 @@ def get_uri(context, uri = "" if uri_type in request: - verify_uri(context, request, uri_type,endpoint_type=endpoint_type) + verify_uri(context, request, uri_type, endpoint_type=endpoint_type) uri = request[uri_type] else: uris = f"{uri_type}s" @@ -231,10 +230,10 @@ def get_uri(context, def authn_args_gather( - request: Union[AuthorizationRequest, dict], - authn_class_ref: str, - cinfo: dict, - **kwargs, + request: Union[AuthorizationRequest, dict], + authn_class_ref: str, + cinfo: dict, + **kwargs, ): """ Gather information to be used by the authentication method @@ -298,8 +297,8 @@ def validate_resource_indicators_policy(request, context, **kwargs): client_id = request["client_id"] if ( - isinstance(resource_servers_per_client, dict) - and client_id not in resource_servers_per_client + isinstance(resource_servers_per_client, dict) + and client_id not in resource_servers_per_client ): return oauth2.AuthorizationErrorResponse( error="invalid_target", @@ -375,7 +374,7 @@ def __init__(self, upstream_get, **kwargs): def filter_request(self, context, req): return req - def extra_response_args(self, aresp): + def extra_response_args(self, aresp, **kwargs): return aresp def authentication_error_response(self, request, error, error_description, **kwargs): @@ -526,8 +525,8 @@ def _post_parse_request(self, request, client_id, context, **kwargs): request["redirect_uri"] = redirect_uri if ( - "resource_indicators" in _cinfo - and "authorization_code" in _cinfo["resource_indicators"] + "resource_indicators" in _cinfo + and "authorization_code" in _cinfo["resource_indicators"] ): resource_indicators_config = _cinfo["resource_indicators"]["authorization_code"] else: @@ -642,13 +641,13 @@ def _unwrap_identity(self, identity): return identity def setup_auth( - self, - request: Optional[Union[Message, dict]], - redirect_uri: str, - cinfo: dict, - cookie: List[dict] = None, - acr: str = None, - **kwargs, + self, + request: Optional[Union[Message, dict]], + redirect_uri: str, + cinfo: dict, + cookie: List[dict] = None, + acr: str = None, + **kwargs, ) -> dict: """ @@ -772,12 +771,12 @@ def aresp_check(self, aresp, request): return "" def response_mode( - self, - request: Union[dict, AuthorizationRequest], - response_args: Optional[Union[dict, AuthorizationResponse]] = None, - return_uri: Optional[str] = "", - fragment_enc: Optional[bool] = None, - **kwargs, + self, + request: Union[dict, AuthorizationRequest], + response_args: Optional[Union[dict, AuthorizationResponse]] = None, + return_uri: Optional[str] = "", + fragment_enc: Optional[bool] = None, + **kwargs, ) -> dict: resp_mode = request["response_mode"] if resp_mode == "form_post": @@ -954,7 +953,7 @@ def create_authn_response(self, request: Union[dict, Message], sid: str) -> dict ) return {"response_args": resp, "fragment_enc": fragment_enc} - aresp = self.extra_response_args(aresp) + aresp = self.extra_response_args(aresp, client_id=request["client_id"]) return {"response_args": aresp, "fragment_enc": fragment_enc} @@ -1098,10 +1097,10 @@ def do_request_user(self, request_info, **kwargs): return kwargs def process_request( - self, - request: Optional[Union[Message, dict]] = None, - http_info: Optional[dict] = None, - **kwargs, + self, + request: Optional[Union[Message, dict]] = None, + http_info: Optional[dict] = None, + **kwargs, ): """The AuthorizationRequest endpoint @@ -1164,6 +1163,7 @@ def process_request( class AllowedAlgorithms: + def __init__(self, algorithm_parameters): self.algorithm_parameters = algorithm_parameters diff --git a/src/idpyoidc/server/oidc/userinfo.py b/src/idpyoidc/server/oidc/userinfo.py index 8585790d..d497de0a 100755 --- a/src/idpyoidc/server/oidc/userinfo.py +++ b/src/idpyoidc/server/oidc/userinfo.py @@ -151,8 +151,8 @@ def process_request(self, request=None, **kwargs): # if "offline_access" in session["authn_req"]["scope"]: # pass + _cntxt = self.upstream_get("context") if allowed: - _cntxt = self.upstream_get("context") _claims_restriction = _cntxt.claims_interface.get_claims( _session_info["branch_id"], scopes=token.scope, claims_release_point="userinfo" ) @@ -163,6 +163,10 @@ def process_request(self, request=None, **kwargs): if _grant.add_acr_value("userinfo"): info["acr"] = _grant.authentication_event["authn_info"] + extra_claims = kwargs.get("extra_claims") + if extra_claims: + info.update(extra_claims) + if "userinfo" in _cntxt.cdb[request["client_id"]]: self.config["policy"] = _cntxt.cdb[request["client_id"]]["userinfo"]["policy"] diff --git a/src/idpyoidc/server/scopes.py b/src/idpyoidc/server/scopes.py index 2f5bf76e..f8b9625c 100644 --- a/src/idpyoidc/server/scopes.py +++ b/src/idpyoidc/server/scopes.py @@ -47,6 +47,7 @@ def convert_scopes2claims(scopes, allowed_claims=None, scope2claim_map=None): class Scopes: + def __init__(self, upstream_get, allowed_scopes=None, scopes_to_claims=None): self.upstream_get = upstream_get if not scopes_to_claims: @@ -63,21 +64,21 @@ def get_allowed_scopes(self, client_id=None): """ allowed_scopes = self.allowed_scopes if client_id: - client = self.upstream_get("context").cdb.get(client_id) + client = self.upstream_get("attribute", "cdb").get(client_id) if client is not None: allowed_scopes = client.get("allowed_scopes", allowed_scopes) return allowed_scopes def get_scopes_mapping(self, client_id=None): """ - Returns the mapping of scopes to claims fora specific client. + Returns the mapping of scopes to claims for a specific client. :param client_id: The client identifier :returns: Dict of scopes to claims. Can be empty. """ scopes_to_claims = self._scopes_to_claims if client_id: - client = self.upstream_get("context").cdb.get(client_id) + client = self.upstream_get("attribute", "cdb").get(client_id) if client is not None: scopes_to_claims = client.get("scopes_to_claims", scopes_to_claims) return scopes_to_claims diff --git a/src/idpyoidc/server/session/claims.py b/src/idpyoidc/server/session/claims.py index 04062cfd..63396188 100755 --- a/src/idpyoidc/server/session/claims.py +++ b/src/idpyoidc/server/session/claims.py @@ -45,14 +45,14 @@ def authorization_request_claims( def _get_module(self, usage, context): module = None if usage == "userinfo": - module = self.upstream_get("endpoint", "userinfo") + module = context.upstream_get("endpoint","userinfo") elif usage == "id_token": try: module = context.session_manager.token_handler["id_token"] except KeyError: raise ServiceError("No support for ID Tokens") elif usage == "introspection": - module = self.upstream_get("endpoint", "introspection") + module = context.upstream_get("endpoint","introspection") elif usage == "access_token": try: module = context.session_manager.token_handler["access_token"] @@ -68,8 +68,8 @@ def _client_claims( claims_release_point: str, secondary_identifier: Optional[str] = "", ): - _context = self.upstream_get("context") - add_claims_by_scope = _context.cdb[client_id].get("add_claims", {}).get("by_scope", {}) + _cdb = self.upstream_get("attribute", "cdb") + add_claims_by_scope = _cdb[client_id].get("add_claims", {}).get("by_scope", {}) if add_claims_by_scope: _claims_by_scope = add_claims_by_scope.get(claims_release_point) if _claims_by_scope is None and secondary_identifier: @@ -80,7 +80,7 @@ def _client_claims( else: _claims_by_scope = module.kwargs.get("add_claims_by_scope", {}) - add_claims_always = _context.cdb[client_id].get("add_claims", {}).get("always", {}) + add_claims_always = _cdb[client_id].get("add_claims", {}).get("always", {}) _always_add = add_claims_always.get(claims_release_point, []) if secondary_identifier: _always_2 = add_claims_always.get(secondary_identifier, []) diff --git a/src/idpyoidc/server/session/database.py b/src/idpyoidc/server/session/database.py index a886143f..c9cdaf55 100644 --- a/src/idpyoidc/server/session/database.py +++ b/src/idpyoidc/server/session/database.py @@ -30,8 +30,8 @@ def __init__(self, crypt_config: Optional[dict] = None, **kwargs): ImpExp.__init__(self) self.db = DLDict() - for k, v in kwargs.items(): - setattr(self, k, v) + # for k, v in kwargs.items(): + # setattr(self, k, v) if crypt_config is None: crypt_config = default_crypt_config() diff --git a/tests/private/token_jwks.json b/tests/private/token_jwks.json index 3ef68090..e587b2a1 100644 --- a/tests/private/token_jwks.json +++ b/tests/private/token_jwks.json @@ -1 +1 @@ -{"keys": [{"kty": "oct", "use": "enc", "kid": "code", "k": "vSHDkLBHhDStkR0NWu8519rmV5zmnm5_"}]} \ No newline at end of file +{"keys": [{"kty": "oct", "use": "enc", "kid": "code", "k": "vSHDkLBHhDStkR0NWu8519rmV5zmnm5_"}, {"kty": "oct", "use": "enc", "kid": "refresh", "k": "Ps_KwX5Kw9lB89TaXnArVOZGClLgW2CX"}]} \ No newline at end of file diff --git a/tests/request123456.jwt b/tests/request123456.jwt index 391614ae..fed3886b 100644 --- a/tests/request123456.jwt +++ b/tests/request123456.jwt @@ -1 +1 @@ -eyJhbGciOiJSUzI1NiIsImtpZCI6IlNIRXlZV2N3TlZrMExUZFJPVFp6WjJGVVduZElWWGRhY2sweFdVTTVTRXB3Y1MwM2RWVXhXVTR6UlEifQ.eyJyZXNwb25zZV90eXBlIjogImNvZGUiLCAic3RhdGUiOiAic3RhdGUiLCAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vZXhhbXBsZS5jb20vY2xpL2F1dGh6X2NiIiwgInNjb3BlIjogIm9wZW5pZCIsICJub25jZSI6ICI1ams1WkdMLTE3NXpHT0FPQW5yTlZra2paZXltS0JwOVFoek81QV90eDQwIiwgImNsaWVudF9pZCI6ICJjbGllbnRfaWQiLCAiaXNzIjogImNsaWVudF9pZCIsICJpYXQiOiAxNjg3MTgwNzk0LCAiYXVkIjogWyJodHRwczovL2V4YW1wbGUuY29tIl19.HUbiyiC0pypd8hamG9JJ-xQaJ7FEAVjDoy4jH00hJ5FtLqm87PAKIvD5aptYv8VzdpA5X8hCDUW4g0noNBbEsmvXeJpoXHSeVz_A4Ue8Ziz7z6dnrYf7BNFt3NyTibKVlkcWNGPBhEjyw0k4r6O86lQ2mSQjINJuqpR7VeEQyK7CBhDl5bicPctB4yGm4VksvC39695hhyGtUrUyrGW539g54VkG-x0kKv2HMc_ZGsnsEgFrT0fHKWuc1hPRkGi2XuSyhhD20zhnZhMGyTovwoZxmbx2seiIinjd0_wZVMZS277yUvMQTCvjOHJyu80XLLZqI71GguonCWdxIIrblQ \ No newline at end of file +eyJhbGciOiJSUzI1NiIsImtpZCI6IlNIRXlZV2N3TlZrMExUZFJPVFp6WjJGVVduZElWWGRhY2sweFdVTTVTRXB3Y1MwM2RWVXhXVTR6UlEifQ.eyJyZXNwb25zZV90eXBlIjogImNvZGUiLCAic3RhdGUiOiAic3RhdGUiLCAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vZXhhbXBsZS5jb20vY2xpL2F1dGh6X2NiIiwgInNjb3BlIjogIm9wZW5pZCIsICJub25jZSI6ICJpcno4SG5ELXFsOVhNYVJYUll1S3BpcEpHM2hiRWZ5akxBYXQwMjNLZEdvIiwgImNsaWVudF9pZCI6ICJjbGllbnRfaWQiLCAiaXNzIjogImNsaWVudF9pZCIsICJpYXQiOiAxNzEwODM2MDQwLCAiYXVkIjogWyJodHRwczovL2V4YW1wbGUuY29tIl19.EDvgPn7QJFm6O4d9QFU9gVZEmAREDIfl1RTiMtec7_ZJ4vGag3dxCyXgz15GbDrQgo6mqCydCe-Mal_4HBlRwMctqhy9NMIGM5PxIKzrqMjsk88jxAoz-WWw3I-pKrJUS4m23mEgLZkGQpB1N3YgO_RhG-7vGCkiJd_8VuomRMd2dX5_Jax3j12T7vhM_TUI9S6XJ5zsLn2ZOPQVXfoprr7HHY6UJjJ65Fp_hoGA3gmfJiHwbxYss8D2X1BNoLmEMze_e6cS-DGe648t2U47E77BvHdzsKi791Y1L3eizkm364gJ371KWbi3avvbSkTi4hEd3OikkyeMQZk6vDiJww \ No newline at end of file diff --git a/tests/test_09_work_condition.py b/tests/test_09_work_condition.py index 72fdf386..4cfa075f 100644 --- a/tests/test_09_work_condition.py +++ b/tests/test_09_work_condition.py @@ -252,3 +252,141 @@ def test_registration_response(self): # Not what I asked for but something I can handle assert to_use["subject_type"] == "pairwise" + + def test_registration_response_consistence(self): + client_conf = { + "application_type": "web", + "base_url": "https://client.example.org/", + "redirect_uris": [ + "https://client.example.org/callback", + "https://client.example.org/callback2", + ], + "client_name": "My Example", + "client_id": "client_id", + "keys": {"key_defs": KEYSPEC, "read_only": True}, + "client_secret": "a longesh password", + "logo_uri": "https://client.example.org/logo.png", + "contacts": ["ve7jtb@example.org", "mary@example.org"], + } + + self.claims.load_conf(client_conf, self.supported) + + self.claims.prefer = supported_to_preferred( + supported=self.supported, + preference=self.claims.prefer, + base_url="https://example.com", + ) + to_use_1 = preferred_to_registered( + prefers=self.claims.prefer, + supported=self.supported, + ) + + OP_BASEURL = "https://example.com" + provider_info_response = { + "version": "3.0", + "token_endpoint_auth_methods_supported": [ + "client_secret_post", + "client_secret_basic", + "client_secret_jwt", + "private_key_jwt", + ], + "issuer": OP_BASEURL, + "jwks_uri": f"{OP_BASEURL}/static/jwks_tE2iLbOAqXhe8bqh.json", + "authorization_endpoint": f"{OP_BASEURL}/authorization", + "token_endpoint": f"{OP_BASEURL}/token", + "userinfo_endpoint": f"{OP_BASEURL}/userinfo", + "registration_endpoint": f"{OP_BASEURL}/registration", + "end_session_endpoint": f"{OP_BASEURL}/end_session", + # below are a set which the RP has default values but the OP overwrites + "scopes_supported": ["openid", "fee", "faa", "foo", "fum"], + "response_types_supported": ["code", "id_token", "code id_token"], + "response_modes_supported": ["query", "form_post", "new_fangled"], + # this does not have a default value + "acr_values_supported": ["mfa"], + } + + pref = self.claims.prefer = supported_to_preferred( + supported=self.supported, + preference=self.claims.prefer, + base_url="https://example.com", + info=provider_info_response, + ) + + registration_request = create_registration_request(self.claims.prefer, self.supported) + + assert set(registration_request.keys()) == { + "application_type", + "client_name", + "contacts", + "default_max_age", + "id_token_signed_response_alg", + "jwks", + "logo_uri", + "redirect_uris", + "request_object_signing_alg", + "response_modes", # non-standard + "response_types", + "subject_type", + "token_endpoint_auth_method", + "token_endpoint_auth_signing_alg", + "userinfo_signed_response_alg", + } + + assert registration_request["subject_type"] == "public" + + registration_response = { + "application_type": "web", + "redirect_uris": [ + "https://client.example.org/callback", + "https://client.example.org/callback2", + ], + "client_name": "My Example", + "logo_uri": "https://client.example.org/logo.png", + "subject_type": "pairwise", + "sector_identifier_uri": "https://other.example.net/file_of_redirect_uris.json", + "token_endpoint_auth_method": "client_secret_basic", + "jwks_uri": "https://client.example.org/my_public_keys.jwks", + "userinfo_encrypted_response_alg": "RSA1_5", + "userinfo_encrypted_response_enc": "A128CBC-HS256", + "contacts": ["ve7jtb@example.org", "mary@example.org"], + "request_uris": [ + "https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA" + ], + } + + to_use_2 = preferred_to_registered( + prefers=self.claims.prefer, + supported=self.supported, + registration_response=registration_response, + ) + + assert set(to_use_2.keys()) == { + "application_type", + "client_id", + "client_name", + "client_secret", + "contacts", + "default_max_age", + "encrypt_request_object_supported", + "encrypt_userinfo_supported", + "id_token_signed_response_alg", + "jwks", + "jwks_uri", + "logo_uri", + "redirect_uris", + "request_object_signing_alg", + "request_uris", + "response_modes", + "response_types", + "scope", + "sector_identifier_uri", + "subject_type", + "token_endpoint_auth_method", + "token_endpoint_auth_signing_alg", + "userinfo_encrypted_response_alg", + "userinfo_encrypted_response_enc", + "userinfo_signed_response_alg", + } + + # Not what I asked for but something I can handle + assert to_use_2["subject_type"] == "pairwise" diff --git a/tests/test_client_21_oidc_service.py b/tests/test_client_21_oidc_service.py index 18eb38c5..028561f9 100644 --- a/tests/test_client_21_oidc_service.py +++ b/tests/test_client_21_oidc_service.py @@ -1,13 +1,13 @@ import os -import pytest -import responses from cryptojwt.exception import UnsupportedAlgorithm from cryptojwt.jws import jws from cryptojwt.jws.utils import left_hash from cryptojwt.jwt import JWT from cryptojwt.key_jar import build_keyjar from cryptojwt.key_jar import init_key_jar +import pytest +import responses from idpyoidc.client.defaults import DEFAULT_OIDC_SERVICES from idpyoidc.client.entity import Entity @@ -725,7 +725,7 @@ def test_post_parse(self): "end_session_endpoint": "{}/end_session".format(OP_BASEURL), } _context = self.service.upstream_get("context") - assert _context.claims.use == {} + # assert _context.claims.use == {} resp = self.service.post_parse_response(provider_info_response) iss_jwks = ISS_KEY.export_jwks_as_json(issuer_id=ISS) @@ -788,7 +788,31 @@ def test_post_parse_2(self): "end_session_endpoint": "{}/end_session".format(OP_BASEURL), } _context = self.service.upstream_get("context") - assert _context.claims.use == {} + assert set(_context.claims.use.keys()) == { + 'application_type', + 'backchannel_logout_session_required', + 'backchannel_logout_uri', + 'callback_uris', + 'client_id', + 'client_secret', + 'contacts', + 'default_max_age', + 'encrypt_id_token_supported', + 'encrypt_request_object_supported', + 'encrypt_userinfo_supported', + 'grant_types', + 'id_token_signed_response_alg', + 'jwks', + 'post_logout_redirect_uris', + 'redirect_uris', + 'request_object_signing_alg', + 'response_modes', + 'response_types', + 'scope', + 'subject_type', + 'token_endpoint_auth_method', + 'token_endpoint_auth_signing_alg', + 'userinfo_signed_response_alg'} resp = self.service.post_parse_response(provider_info_response) iss_jwks = ISS_KEY.export_jwks_as_json(issuer_id=ISS) diff --git a/tests/test_client_22_oidc.py b/tests/test_client_22_oidc.py index 94b06dbf..07764ff2 100755 --- a/tests/test_client_22_oidc.py +++ b/tests/test_client_22_oidc.py @@ -121,6 +121,7 @@ def test_construct_refresh_token_request(self): "client_secret": "abcdefghijklmnop", "grant_type": "refresh_token", "refresh_token": "refresh_with_me", + 'scope': 'openid' } def test_do_userinfo_request_init(self): diff --git a/tests/test_client_30_rp_handler_oidc.py b/tests/test_client_30_rp_handler_oidc.py index 6f97b879..855916b9 100644 --- a/tests/test_client_30_rp_handler_oidc.py +++ b/tests/test_client_30_rp_handler_oidc.py @@ -4,16 +4,16 @@ from urllib.parse import urlparse from urllib.parse import urlsplit +from cryptojwt.key_jar import init_key_jar import pytest import responses -from cryptojwt.key_jar import init_key_jar 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 @@ -260,14 +260,21 @@ def test_init_client(self): } _pref = [k for k, v in _context.prefers().items() if v] - assert set(_pref) == { - "client_id", - "client_secret", - "redirect_uris", - "response_types_supported", - "callback_uris", - "scopes_supported", - } + assert set(_pref) == {'application_type', + 'callback_uris', + 'client_id', + 'client_secret', + 'default_max_age', + 'grant_types_supported', + 'id_token_signing_alg_values_supported', + 'redirect_uris', + 'request_object_signing_alg_values_supported', + 'response_modes_supported', + 'response_types_supported', + 'scopes_supported', + 'subject_types_supported', + 'token_endpoint_auth_signing_alg_values_supported', + 'userinfo_signing_alg_values_supported'} _github_id = iss_id("github") _keyjar = _context.upstream_get("attribute", "keyjar") @@ -304,8 +311,9 @@ 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): diff --git a/tests/test_client_30_rph_defaults.py b/tests/test_client_30_rph_defaults.py index 7d380462..177b7721 100644 --- a/tests/test_client_30_rph_defaults.py +++ b/tests/test_client_30_rph_defaults.py @@ -1,9 +1,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.client.defaults import DEFAULT_KEY_DEFS from idpyoidc.client.rp_handler import RPHandler @@ -36,18 +36,29 @@ 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', + 'default_max_age', + 'encrypt_request_object_supported', + 'encrypt_userinfo_supported', + 'grant_types_supported', + 'id_token_encryption_alg_values_supported', + 'id_token_encryption_enc_values_supported', + 'id_token_signing_alg_values_supported', + 'jwks_uri', + 'redirect_uris', + 'request_object_encryption_alg_values_supported', + 'request_object_encryption_enc_values_supported', + 'request_object_signing_alg_values_supported', + 'response_modes_supported', + 'response_types_supported', + 'scopes_supported', + 'subject_types_supported', + 'token_endpoint_auth_methods_supported', + 'token_endpoint_auth_signing_alg_values_supported', + 'userinfo_encryption_alg_values_supported', + 'userinfo_encryption_enc_values_supported', + 'userinfo_signing_alg_values_supported'} _keyjar = client.get_attribute("keyjar") assert list(_keyjar.owners()) == ["", BASE_URL] diff --git a/tests/test_client_32_oidc_persistent.py b/tests/test_client_32_oidc_persistent.py index 3744fc60..e05e903f 100755 --- a/tests/test_client_32_oidc_persistent.py +++ b/tests/test_client_32_oidc_persistent.py @@ -112,6 +112,7 @@ def test_construct_refresh_token_request(self): "client_secret": "abcdefghijklmnop", "grant_type": "refresh_token", "refresh_token": "refresh_with_me", + 'scope': 'openid' } def test_do_userinfo_request_init(self): diff --git a/tests/test_server_40_oauth2_pushed_authorization.py b/tests/test_server_40_oauth2_pushed_authorization.py index 323dd6d6..4d7ea6da 100644 --- a/tests/test_server_40_oauth2_pushed_authorization.py +++ b/tests/test_server_40_oauth2_pushed_authorization.py @@ -139,8 +139,8 @@ def create_endpoint(self): "client_secret_jwt", "private_key_jwt", ] - }, - }, + } + } }, "authentication": { "anon": { diff --git a/tests/test_server_50_persistence.py b/tests/test_server_50_persistence.py index 3ef3dcc4..adc31d94 100644 --- a/tests/test_server_50_persistence.py +++ b/tests/test_server_50_persistence.py @@ -281,6 +281,7 @@ def create_endpoint(self): "handler": server2.context.session_manager.token_handler, }, ) + server2.context.upstream_get = server2.unit_get self.endpoint = { 1: server1.get_endpoint("userinfo"), @@ -291,6 +292,10 @@ def create_endpoint(self): 1: server1.context.session_manager, 2: server2.context.session_manager, } + self.context_unit_get = { + 1: server1.context.unit_get, + 2: server2.context.unit_get + } self.user_id = "diana" def _create_session(self, auth_req, sub_type="public", sector_identifier="", index=1): @@ -339,8 +344,9 @@ def _mint_access_token(self, grant, session_id, token_ref=None, index=1): def _dump_restore(self, fro, to): _store = self.session_manager[fro].dump() + context_unit_get = self.endpoint[to].upstream_get("unit").context.unit_get self.session_manager[to].load( - _store, init_args={"upstream_get": self.endpoint[to].upstream_get} + _store, init_args={"upstream_get": context_unit_get} ) def test_init(self):