From e3e992ea5d429d9f1afd7ce2ce2fbedfaa361c07 Mon Sep 17 00:00:00 2001 From: peppelinux Date: Mon, 15 Nov 2021 19:36:28 +0100 Subject: [PATCH 01/11] latest rohe's OIDC certification improvements and FAPI OP example project --- .gitignore | 1 + ...ication-mark-l-rgb-150dpi-90mm-300x157.png | Bin 0 -> 17881 bytes docs/source/conf.py | 2 +- docs/source/contents/clients.rst | 75 ++++ docs/source/contents/conf.rst | 4 +- docs/source/contents/session_management.rst | 2 +- docs/source/index.rst | 10 + example/fastapi.tgz | Bin 0 -> 2901 bytes example/fastapi/__init__.py | 0 example/fastapi/config.json | 330 ++++++++++++++ example/fastapi/main.py | 64 +++ example/fastapi/models.py | 31 ++ .../chpy => example/fastapi}/passwd.json | 0 .../chpy => example/fastapi}/users.json | 0 example/fastapi/utils.py | 136 ++++++ example/flask_op/config.json | 15 + example/flask_op/config_cdb.json | 332 ++++++++++++++ example/flask_op/config_martin.yaml | 309 +++++++++++++ example/flask_op/sram_passwd.json | 5 + example/flask_op/sram_users.json | 42 ++ example/flask_op/templates.tgz | Bin 0 -> 15872 bytes example/flask_op/views.py | 4 +- example/flask_op/yaml_to_json.py | 11 + pyproject.toml | 2 +- setup.py | 2 +- src/oidcop/__init__.py | 2 +- src/oidcop/authn_event.py | 8 +- src/oidcop/endpoint.py | 14 +- src/oidcop/endpoint_context.py | 33 +- src/oidcop/oauth2/authorization.py | 95 ++-- src/oidcop/oauth2/token.py | 4 +- src/oidcop/oidc/add_on/custom_scopes.py | 2 +- src/oidcop/oidc/registration.py | 72 +-- src/oidcop/oidc/session.py | 72 ++- src/oidcop/scopes.py | 3 + src/oidcop/session/claims.py | 108 +++-- src/oidcop/session/grant.py | 47 +- src/oidcop/session/manager.py | 11 + src/oidcop/session/token.py | 54 ++- src/oidcop/token/__init__.py | 6 +- src/oidcop/token/id_token.py | 4 +- src/oidcop/user_authn/user.py | 27 +- tests/donot_test_49_session_persistence.py | 227 ++++++++++ tests/logging_config.json | 31 ++ tests/test_00_server.py | 3 +- tests/test_01_claims.py | 150 ++++--- tests/test_01_session_token.py | 4 +- tests/test_04_token_handler.py | 5 +- tests/test_05_id_token.py | 19 +- tests/test_05_jwt_token.py | 6 +- tests/test_06_authn_context.py | 6 +- tests/test_06_session_manager.py | 92 +++- tests/test_08_session_life.py | 18 +- tests/test_100_temp.py | 422 ++++++++++++++++++ tests/test_12_user_authn.py | 6 +- tests/test_23_oidc_registration_endpoint.py | 16 +- .../test_24_oauth2_authorization_endpoint.py | 9 +- tests/test_24_oidc_authorization_endpoint.py | 160 ++++--- tests/test_26_oidc_userinfo_endpoint.py | 12 +- tests/test_30_oidc_end_session.py | 65 ++- tests/test_31_oauth2_introspection.py | 3 +- tests/test_32_oidc_read_registration.py | 5 +- tests/test_33_oauth2_pkce.py | 3 +- tests/test_34_oidc_sso.py | 15 +- unsupported/chpy/certs/cert.pem | 31 -- unsupported/chpy/certs/key.pem | 52 --- unsupported/chpy/conf.py | 143 ------ unsupported/chpy/provider.py | 182 -------- unsupported/chpy/seed.txt | 1 - unsupported/chpy/server.py | 117 ----- 70 files changed, 2808 insertions(+), 934 deletions(-) create mode 100644 docs/source/_images/oid-l-certification-mark-l-rgb-150dpi-90mm-300x157.png create mode 100644 docs/source/contents/clients.rst create mode 100644 example/fastapi.tgz create mode 100644 example/fastapi/__init__.py create mode 100644 example/fastapi/config.json create mode 100644 example/fastapi/main.py create mode 100644 example/fastapi/models.py rename {unsupported/chpy => example/fastapi}/passwd.json (100%) rename {unsupported/chpy => example/fastapi}/users.json (100%) create mode 100644 example/fastapi/utils.py create mode 100644 example/flask_op/config_cdb.json create mode 100644 example/flask_op/config_martin.yaml create mode 100644 example/flask_op/sram_passwd.json create mode 100644 example/flask_op/sram_users.json create mode 100644 example/flask_op/templates.tgz create mode 100755 example/flask_op/yaml_to_json.py create mode 100644 tests/donot_test_49_session_persistence.py create mode 100644 tests/logging_config.json create mode 100644 tests/test_100_temp.py delete mode 100644 unsupported/chpy/certs/cert.pem delete mode 100644 unsupported/chpy/certs/key.pem delete mode 100644 unsupported/chpy/conf.py delete mode 100644 unsupported/chpy/provider.py delete mode 100644 unsupported/chpy/seed.txt delete mode 100644 unsupported/chpy/server.py diff --git a/.gitignore b/.gitignore index 831dbd0f..d2508374 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ conf.yaml flask_op/debug.log flask_op/static/ debug.log +.pytest_cache/ # Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files diff --git a/docs/source/_images/oid-l-certification-mark-l-rgb-150dpi-90mm-300x157.png b/docs/source/_images/oid-l-certification-mark-l-rgb-150dpi-90mm-300x157.png new file mode 100644 index 0000000000000000000000000000000000000000..f226aa8c069b0978c5bf9f7d73bc081d0d4278b1 GIT binary patch literal 17881 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 that shows you how to 'build' an OP using the classes and functions provided by oidc-op. @@ -66,6 +70,12 @@ under the `Apache 2.0 `_. contents/developers.md +.. toctree:: + :maxdepth: 2 + :caption: Client database + + contents/clients.rst + .. toctree:: :maxdepth: 2 :caption: FAQ diff --git a/example/fastapi.tgz b/example/fastapi.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d9ff83002720ee47101a4028e17d7643d43ccda0 GIT binary patch literal 2901 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 L0#6e7WF+uE?UA=5 literal 0 HcmV?d00001 diff --git a/example/flask_op/views.py b/example/flask_op/views.py index d17b7347..648c3de6 100644 --- a/example/flask_op/views.py +++ b/example/flask_op/views.py @@ -304,9 +304,9 @@ def check_session_iframe(): return 'error' return 'OK' - current_app.logger.debug( - 'check_session_iframe: {}'.format(req_args)) + current_app.logger.debug('check_session_iframe: {}'.format(req_args)) doc = open('templates/check_session_iframe.html').read() + current_app.logger.debug(f"check_session_iframe response: {doc}") return doc diff --git a/example/flask_op/yaml_to_json.py b/example/flask_op/yaml_to_json.py new file mode 100755 index 00000000..4f7052d4 --- /dev/null +++ b/example/flask_op/yaml_to_json.py @@ -0,0 +1,11 @@ +#! /usr/bin/env python3 +import json +import sys + +import yaml + +"""Load a YAML configuration file.""" +with open(sys.argv[1], "rt", encoding='utf-8') as file: + config_dict = yaml.safe_load(file) + +print(json.dumps(config_dict)) diff --git a/pyproject.toml b/pyproject.toml index 7564b0ee..f63cfeb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [metadata] name = "oidcop" -version = "2.1.0" +version = "2.3.0" author = "Roland Hedberg" author_email = "roland@catalogix.se" description = "Python implementation of an OAuth2 AS and an OIDC Provider" diff --git a/setup.py b/setup.py index d707fd8a..8ab8a660 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ def run_tests(self): "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules"], install_requires=[ - "oidcmsg==1.4.0", + "oidcmsg==1.5.0", "cryptojwt==1.5.2", "pyyaml", "jinja2>=2.11.3", diff --git a/src/oidcop/__init__.py b/src/oidcop/__init__.py index 29c1f2a8..de2c3aa2 100644 --- a/src/oidcop/__init__.py +++ b/src/oidcop/__init__.py @@ -1,6 +1,6 @@ import secrets -__version__ = "2.2.1" +__version__ = "2.3.0" DEF_SIGN_ALG = { "id_token": "RS256", diff --git a/src/oidcop/authn_event.py b/src/oidcop/authn_event.py index 0b5bf0e8..6b7bae0a 100644 --- a/src/oidcop/authn_event.py +++ b/src/oidcop/authn_event.py @@ -2,7 +2,7 @@ from oidcmsg.message import SINGLE_OPTIONAL_STRING from oidcmsg.message import SINGLE_REQUIRED_STRING from oidcmsg.message import Message -from oidcmsg.time_util import time_sans_frac +from oidcmsg.time_util import utc_time_sans_frac DEFAULT_AUTHN_EXPIRES_IN = 3600 @@ -20,10 +20,10 @@ def is_valid(self, now=0): if now: return self["valid_until"] > now else: - return self["valid_until"] > time_sans_frac() + return self["valid_until"] > utc_time_sans_frac() def expires_in(self): - return self["valid_until"] - time_sans_frac() + return self["valid_until"] - utc_time_sans_frac() def create_authn_event( @@ -59,7 +59,7 @@ def create_authn_event( if _ts: args["authn_time"] = _ts else: - args["authn_time"] = time_sans_frac() + args["authn_time"] = utc_time_sans_frac() if valid_until: args["valid_until"] = valid_until diff --git a/src/oidcop/endpoint.py b/src/oidcop/endpoint.py index f5ea2bf8..3a9a1994 100755 --- a/src/oidcop/endpoint.py +++ b/src/oidcop/endpoint.py @@ -9,6 +9,7 @@ from oidcmsg.exception import MissingRequiredValue from oidcmsg.message import Message from oidcmsg.oauth2 import ResponseMessage +from oidcmsg.oidc import RegistrationRequest from oidcop import sanitize from oidcop.client_authn import client_auth_setup @@ -128,6 +129,10 @@ def __init__(self, server_get: Callable, **kwargs): self.allowed_targets = [self.name] self.client_verification_method = [] + def process_verify_error(self, exception): + _error = "invalid_request" + return self.error_cls(error=_error, error_description="%s" % exception) + def parse_request( self, request: Union[Message, dict, str], http_info: Optional[dict] = None, **kwargs ): @@ -185,7 +190,14 @@ def parse_request( try: req.verify(keyjar=keyjar, opponent_id=_client_id) except (MissingRequiredAttribute, ValueError, MissingRequiredValue) as err: - return self.error_cls(error="invalid_request", error_description="%s" % err) + return self.process_verify_error(err) + _error = "invalid_request" + if isinstance(err, ValueError) and self.request_cls == RegistrationRequest: + if len(err.args) > 1: + if err.args[1] == 'initiate_login_uri': + _error = "invalid_client_metadata" + + return self.error_cls(error=_error, error_description="%s" % err) LOGGER.info("Parsed and verified request: %s" % sanitize(req)) diff --git a/src/oidcop/endpoint_context.py b/src/oidcop/endpoint_context.py index 51629b67..9bdc4035 100755 --- a/src/oidcop/endpoint_context.py +++ b/src/oidcop/endpoint_context.py @@ -15,7 +15,6 @@ from oidcop.configure import OPConfiguration from oidcop.scopes import SCOPE2CLAIMS from oidcop.scopes import Scopes -from oidcop.session.claims import STANDARD_CLAIMS from oidcop.session.manager import SessionManager from oidcop.template_handler import Jinja2TemplateHandler from oidcop.util import get_http_params @@ -75,13 +74,13 @@ def get_token_handler_args(conf: dict) -> dict: {"type": "oct", "bytes": "24", "use": ["enc"], "kid": "code"}, {"type": "oct", "bytes": "24", "use": ["enc"], "kid": "token"}, {"type": "oct", "bytes": "24", "use": ["enc"], "kid": "refresh"}, - ] + ] jwks_def = { "private_path": "private/token_jwks.json", "key_defs": keydef, "read_only": False, - } + } th_args = {"jwks_def": jwks_def} for typ, tid in [("code", 600), ("token", 3600), ("refresh", 86400)]: th_args[typ] = {"lifetime": tid} @@ -117,17 +116,17 @@ class EndpointContext(OidcContext): "symkey": "", "token_args_methods": [], # "userinfo": UserInfo, - } + } def __init__( - self, - conf: Union[dict, OPConfiguration], - server_get: Callable, - keyjar: Optional[KeyJar] = None, - cwd: Optional[str] = "", - cookie_handler: Optional[Any] = None, - httpc: Optional[Any] = None, - ): + self, + conf: Union[dict, OPConfiguration], + server_get: Callable, + keyjar: Optional[KeyJar] = None, + cwd: Optional[str] = "", + cookie_handler: Optional[Any] = None, + httpc: Optional[Any] = None, + ): OidcContext.__init__(self, conf, keyjar, entity_id=conf.get("issuer", "")) self.conf = conf self.server_get = server_get @@ -176,7 +175,7 @@ def __init__( "symkey", "client_authn", # "id_token_schema", - ]: + ]: try: setattr(self, param, conf[param]) except KeyError: @@ -217,7 +216,7 @@ def __init__( "authentication", "id_token", "scope2claims", - ]: + ]: _func = getattr(self, "do_{}".format(item), None) if _func: _func() @@ -244,7 +243,7 @@ def __init__( def new_cookie(self, name: str, max_age: Optional[int] = 0, **kwargs): cookie_cont = self.cookie_handler.make_cookie_content( name=name, value=json.dumps(kwargs), max_age=max_age - ) + ) return cookie_cont def set_scopes_handler(self): @@ -258,7 +257,7 @@ def set_scopes_handler(self): self.server_get, allowed_scopes=self.conf.get("allowed_scopes"), scopes_to_claims=self.conf.get("scopes_to_claims"), - ) + ) def do_add_on(self, endpoints): _add_on_conf = self.conf.get("add_on") @@ -336,6 +335,6 @@ def create_providerinfo(self, capabilities): if "claims_supported" not in _provider_info: _provider_info["claims_supported"] = list( self.scopes_handler.scopes_to_claims(_provider_info["scopes_supported"]).keys() - ) + ) return _provider_info diff --git a/src/oidcop/oauth2/authorization.py b/src/oidcop/oauth2/authorization.py index 2d13ad50..d84e36ef 100755 --- a/src/oidcop/oauth2/authorization.py +++ b/src/oidcop/oauth2/authorization.py @@ -37,6 +37,7 @@ from oidcop.exception import UnAuthorizedClientScope from oidcop.exception import UnknownClient from oidcop.session import Revoked +from oidcop.session.grant import Grant from oidcop.token.exception import UnknownToken from oidcop.user_authn.authn_context import pick_auth from oidcop.util import split_uri @@ -116,20 +117,25 @@ def verify_uri( raise URIError("Contains fragment") (_base, _query) = split_uri(_redirect_uri) - # if _query: - # _query = parse_qs(_query) # Get the clients registered redirect uris client_info = endpoint_context.cdb.get(_cid) if client_info is None: raise KeyError("No such client") - redirect_uris = client_info.get("{}s".format(uri_type)) + redirect_uris = client_info.get(f"{uri_type}s") + if redirect_uris is None: - raise ValueError(f"No registered {uri_type} for {_cid}") + raise RedirectURIError(f"No registered {uri_type} for {_cid}") else: match = False - for regbase, rquery in redirect_uris: + for _item in redirect_uris: + if isinstance(_item, str): + regbase = _item + rquery = {} + else: + regbase, rquery = _item + # The URI MUST exactly match one of the Redirection URI if _base == regbase: # every registered query component must exist in the uri @@ -494,6 +500,17 @@ def _login_required_error(self, redirect_uri, request): logger.debug("Login required error: {}".format(_res)) return _res + def _unwrap_identity(self, identity): + if isinstance(identity, dict): + try: + _id = b64d(as_bytes(identity["uid"])) + except BadSyntax: + return identity + else: + _id = b64d(as_bytes(identity)) + + return json.loads(as_unicode(_id)) + def setup_auth( self, request: Optional[Union[Message, dict]], @@ -502,7 +519,7 @@ def setup_auth( cookie: List[dict] = None, acr: str = None, **kwargs, - ): + ) -> dict: """ :param request: The authorization/authentication request @@ -544,18 +561,16 @@ def setup_auth( _ts = 0 else: if identity: - try: # If identity['uid'] is in fact a base64 encoded JSON string - _id = b64d(as_bytes(identity["uid"])) - except BadSyntax: - pass - else: - identity = json.loads(as_unicode(_id)) - + identity = self._unwrap_identity(identity) + _sid = identity.get("sid") + if _sid: try: - _csi = _context.session_manager[identity.get("sid")] + _csi = _context.session_manager[_sid] except Revoked: + logger.debug("Authentication session revoked!!") identity = None else: + logger.debug(f"Session info type: {_csi.__class__.__name__}") if _csi.is_active() is False: identity = None @@ -574,7 +589,7 @@ def setup_auth( else: return {"function": authn, "args": authn_args} else: - logger.info("Active authentication") + logger.info(f"Active authentication: {identity}") if re_authenticate(request, authn): # demand re-authentication return {"function": authn, "args": authn_args} @@ -628,7 +643,7 @@ def aresp_check(self, aresp, request): def response_mode( self, request: Union[dict, AuthorizationRequest], - response_args: Optional[AuthorizationResponse] = None, + response_args: Optional[Union[dict, AuthorizationResponse]] = None, return_uri: Optional[str] = "", fragment_enc: Optional[bool] = None, **kwargs, @@ -639,7 +654,15 @@ def response_mode( _args = response_args.to_dict() else: _args = response_args - msg = FORM_POST.format(inputs=inputs(_args), action=return_uri, ) + + if "error" in _args: + if not return_uri: + return_uri = _args["return_uri"] + del _args["return_uri"] + if "return_type" in _args: + del _args["return_type"] + + msg = FORM_POST.format(inputs=inputs(_args), action=return_uri) kwargs.update( {"response_msg": msg, "content_type": "text/html", "response_placement": "body", } ) @@ -668,6 +691,13 @@ def error_response(self, response_info, request, error, error_description): response_info["response_args"] = resp return response_info + def error_by_response_mode(self, response_info, request, error, error_description): + response_info = self.error_response(response_info, request, error, error_description) + if 'return_uri' not in response_info: + response_info["return_uri"] = request["redirect_uri"] + response_info = self.response_mode(request, **response_info) + return response_info + def create_authn_response(self, request: Union[dict, Message], sid: str) -> dict: """ :param request: @@ -730,11 +760,15 @@ def create_authn_response(self, request: Union[dict, Message], sid: str) -> dict elif {"id_token", "token"}.issubset(rtype): kwargs = {"access_token": _access_token.value} + if request["response_type"] == ["id_token"]: + kwargs["as_if"] = "userinfo" + try: id_token = self.mint_token( token_class="id_token", grant=grant, session_id=_sinfo["session_id"], + scope=request["scope"], **kwargs, ) # id_token = _context.idtoken.make(sid, **kwargs) @@ -837,20 +871,22 @@ def authz_part2(self, request, session_id, **kwargs): try: resp_info = self.post_authentication(request, session_id, **kwargs) except Exception as err: - return self.error_response({}, request, "server_error", err) + return self.error_by_response_mode({}, request, "server_error", err) _context = self.server_get("endpoint_context") + logger.debug(f"resp_info: {resp_info}") + if "check_session_iframe" in _context.provider_info: salt = rndstr() try: authn_event = _context.session_manager.get_authentication_event(session_id) except KeyError: - return self.error_response({}, request, "server_error", "No such session") + return self.error_by_response_mode({}, request, "server_error", "No such session") else: if authn_event.is_valid() is False: - return self.error_response({}, request, "server_error", - "Authentication has timed out") + return self.error_by_response_mode({}, request, "server_error", + "Authentication has timed out") _state = b64e(as_bytes(json.dumps({"authn_time": authn_event["authn_time"]}))) @@ -860,16 +896,21 @@ def authz_part2(self, request, session_id, **kwargs): opbs_value = _session_cookie_content["value"] + if "return_uri" in resp_info: + re_uri = resp_info["return_uri"] + else: + re_uri = request["redirect_uri"] + logger.debug( "compute_session_state: client_id=%s, origin=%s, opbs=%s, salt=%s", request["client_id"], - resp_info["return_uri"], + re_uri, opbs_value, salt, ) _session_state = compute_session_state( - opbs_value, salt, request["client_id"], resp_info["return_uri"] + opbs_value, salt, request["client_id"], re_uri ) if _session_cookie_content: @@ -878,7 +919,8 @@ def authz_part2(self, request, session_id, **kwargs): else: resp_info["cookie"] = [_session_cookie_content] - resp_info["response_args"]["session_state"] = _session_state + if "response_args" in resp_info: + resp_info["response_args"]["session_state"] = _session_state # Mix-Up mitigation if "response_args" in resp_info: @@ -930,7 +972,10 @@ def process_request( info = self.setup_auth(request, request["redirect_uri"], cinfo, _my_cookies, **kwargs) if "error" in info: - return info + if "response_mode" in request: + return self.response_mode(request, info) + else: + return info _function = info.get("function") if not _function: diff --git a/src/oidcop/oauth2/token.py b/src/oidcop/oauth2/token.py index 768c1734..3bc3850a 100755 --- a/src/oidcop/oauth2/token.py +++ b/src/oidcop/oauth2/token.py @@ -9,7 +9,7 @@ from oidcmsg.oauth2 import ResponseMessage from oidcmsg.oidc import RefreshAccessTokenRequest from oidcmsg.oidc import TokenErrorResponse -from oidcmsg.time_util import time_sans_frac +from oidcmsg.time_util import utc_time_sans_frac from oidcop import sanitize from oidcop.constant import DEFAULT_TOKEN_LIFETIME @@ -90,7 +90,7 @@ def _mint_token( _exp_in = int(_exp_in) if _exp_in: - token.expires_at = time_sans_frac() + _exp_in + token.expires_at = utc_time_sans_frac() + _exp_in _context.session_manager.set(_context.session_manager.unpack_session_key(session_id), grant) diff --git a/src/oidcop/oidc/add_on/custom_scopes.py b/src/oidcop/oidc/add_on/custom_scopes.py index e3981e18..5d3d655a 100644 --- a/src/oidcop/oidc/add_on/custom_scopes.py +++ b/src/oidcop/oidc/add_on/custom_scopes.py @@ -19,7 +19,7 @@ def add_custom_scopes(endpoint, **kwargs): _scopes2claims = SCOPE2CLAIMS.copy() _scopes2claims.update(kwargs) _context = _endpoint.server_get("endpoint_context") - _context.scopes_handler.scopes_to_claims = _scopes2claims + _context.scopes_handler.set_scopes_mapping(_scopes2claims) pi = _context.provider_info _scopes = set(pi.get("scopes_supported", [])) diff --git a/src/oidcop/oidc/registration.py b/src/oidcop/oidc/registration.py index cc67dcd1..dba6f355 100755 --- a/src/oidcop/oidc/registration.py +++ b/src/oidcop/oidc/registration.py @@ -86,31 +86,37 @@ def verify_url(url: str, urlset: List[list]) -> bool: def secret(seed: str, sid: str): - msg = "{}{}{}".format(time.time(), secrets.token_urlsafe(16), sid).encode("utf-8") + msg = "{}{}{}".format(utc_time_sans_frac(), secrets.token_urlsafe(16), sid).encode("utf-8") csum = hmac.new(as_bytes(seed), msg, hashlib.sha224) return csum.hexdigest() def comb_uri(args): - for param in ["redirect_uris", "post_logout_redirect_uris"]: - if param not in args: - continue - + redirect_uris = args.get("redirect_uris") + if redirect_uris: val = [] - for base, query_dict in args[param]: + for base, query_dict in redirect_uris: if query_dict: query_string = urlencode( - [ - (key, v) - for key in query_dict - for v in query_dict[key] - ] + [(key, v) for key in query_dict for v in query_dict[key]] ) - val.append("{base}?{query_string}") + val.append(f"{base}?{query_string}") else: val.append(base) - args[param] = val + args["redirect_uris"] = val + + post_logout_redirect_uri = args.get("post_logout_redirect_uri") + if post_logout_redirect_uri: + base, query_dict = post_logout_redirect_uri + if query_dict: + query_string = urlencode( + [(key, v) for key in query_dict for v in query_dict[key]] + ) + val = f"{base}?{query_string}" + else: + val = base + args["post_logout_redirect_uri"] = val request_uris = args.get("request_uris") if request_uris: @@ -179,17 +185,15 @@ def do_client_registration(self, request, client_id, ignore=None): if key not in ignore: _cinfo[key] = val - if "post_logout_redirect_uris" in request: - plruri = [] - for uri in request["post_logout_redirect_uris"]: - if urlparse(uri).fragment: - err = self.error_cls( - error="invalid_configuration_parameter", - error_description="post_logout_redirect_uris contains fragment", - ) - return err - plruri.append(split_uri(uri)) - _cinfo["post_logout_redirect_uris"] = plruri + _uri = request.get("post_logout_redirect_uri") + if _uri: + if urlparse(_uri).fragment: + err = self.error_cls( + error="invalid_configuration_parameter", + error_description="post_logout_redirect_uri contains fragment", + ) + return err + _cinfo["post_logout_redirect_uri"] = split_uri(_uri) if "redirect_uris" in request: try: @@ -384,10 +388,13 @@ def client_registration_setup(self, request, new_id=True, set_secret=True): try: request.verify() except (MessageException, ValueError) as err: - logger.error("request.verify() on %s", request) - return ResponseMessage( - error="invalid_configuration_request", error_description="%s" % err - ) + logger.error("request.verify() error on %s", request) + _error = "invalid_configuration_request" + if len(err.args) > 1: + if err.args[1] == 'initiate_login_uri': + _error = "invalid_client_metadata" + + return ResponseMessage(error=_error, error_description="%s" % err) request.rm_blanks() try: @@ -474,3 +481,12 @@ def process_request(self, request=None, new_id=True, set_secret=True, **kwargs): ) return {"response_args": reg_resp, "cookie": _cookie, "response_code": 201} + + def process_verify_error(self, exception): + _error = "invalid_request" + if isinstance(exception, ValueError): + if len(exception.args) > 1: + if exception.args[1] == 'initiate_login_uri': + _error = "invalid_client_metadata" + + return self.error_cls(error=_error, error_description=f"{exception}") diff --git a/src/oidcop/oidc/session.py b/src/oidcop/oidc/session.py index 4e35141e..61ce226e 100644 --- a/src/oidcop/oidc/session.py +++ b/src/oidcop/oidc/session.py @@ -51,6 +51,8 @@ def do_front_channel_logout_iframe(cinfo, iss, sid): except KeyError: flsr = False + logger.debug(f"frontchannel_logout_uri: {frontchannel_logout_uri}") + logger.debug(f"frontchannel_logout_session_required: {flsr}") if flsr: _query = {"iss": iss, "sid": sid} if "?" in frontchannel_logout_uri: @@ -61,6 +63,7 @@ def do_front_channel_logout_iframe(cinfo, iss, sid): _np = p._replace(query="") frontchannel_logout_uri = _np.geturl() + logger.debug(f"IFrame query: {_query}") _iframe = '