From 3a7787c8b705484e82bd8e9b82a6995ce8459025 Mon Sep 17 00:00:00 2001 From: peppelinux Date: Tue, 18 May 2021 12:39:24 +0200 Subject: [PATCH 1/3] chore: src/oidcop/token/id_token.py coverage from 86% to 90% --- src/oidcop/token/id_token.py | 12 +++++++--- tests/test_05_id_token.py | 43 +++++++++++++++++++++++++++++++++++- tests/test_05_jwt_token.py | 1 + 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/oidcop/token/id_token.py b/src/oidcop/token/id_token.py index 7f23cd66..8e137331 100755 --- a/src/oidcop/token/id_token.py +++ b/src/oidcop/token/id_token.py @@ -298,16 +298,21 @@ def info(self, token): :param token: A token :return: tuple of token type and session id """ + _context = self.server_get("endpoint_context") _jwt = factory(token) _payload = _jwt.jwt.payload() - client_info = _context.cdb[_payload["aud"][0]] + client_id = _payload["aud"][0] + client_info = _context.cdb[client_id] alg_dict = get_sign_and_encrypt_algorithms( _context, client_info, "id_token", sign=True ) - verifier = JWT(key_jar=_context.keyjar, allowed_sign_algs=alg_dict["sign_alg"]) + verifier = JWT( + key_jar=_context.keyjar, + allowed_sign_algs=alg_dict["sign_alg"] + ) try: _payload = verifier.unpack(token) except JWSException: @@ -317,8 +322,9 @@ def info(self, token): raise ToOld("Token has expired") # All the token metadata return { - "sid": _payload["sid"], + "sid": _payload.get("sid", ''), # TODO: would sid be there? # "type": _payload["ttype"], "exp": _payload["exp"], + "aud": client_id, "handler": self, } diff --git a/tests/test_05_id_token.py b/tests/test_05_id_token.py index 7eec527f..76ba827d 100644 --- a/tests/test_05_id_token.py +++ b/tests/test_05_id_token.py @@ -387,11 +387,20 @@ def test_sign_encrypt_id_token(self): def test_get_sign_algorithm(self): client_info = self.endpoint_context.cdb[AREQ["client_id"]] algs = get_sign_and_encrypt_algorithms( - self.endpoint_context, client_info, "id_token", sign=True + self.endpoint_context, client_info, "id_token", sign=True, ) # default signing alg assert algs == {"sign": True, "encrypt": False, "sign_alg": "RS256"} + algs = get_sign_and_encrypt_algorithms( + self.endpoint_context, client_info, "id_token", sign=True, encrypt=True + ) + # default signing alg + assert algs == { + 'sign': True, 'encrypt': True, 'sign_alg': 'RS256', + 'enc_alg': 'RSA-OAEP', 'enc_enc': 'A128CBC-HS256' + } + def test_available_claims(self): session_id = self._create_session(AREQ) grant = self.session_manager[session_id] @@ -542,3 +551,35 @@ def test_client_claims_scopes_and_request_claims_one_match(self): assert "email" not in res # Scope -> claims assert "address" in res + + + def test_id_token_info(self): + session_id = self._create_session(AREQ) + grant = self.session_manager[session_id] + code = self._mint_code(grant, session_id) + access_token = self._mint_access_token(grant, session_id, code) + + id_token = self._mint_id_token( + grant, session_id, token_ref=code, access_token=access_token.value + ) + + endpoint_context = self.endpoint_context + sman = endpoint_context.session_manager + server_get = sman.token_handler.handler['id_token'].server_get + _info = self.session_manager.token_handler.info(id_token.value) + assert 'sid' in _info + assert 'exp' in _info + assert 'aud' in _info + + client_id = AREQ.get('client_id') + _id_token = sman.token_handler.handler['id_token'] + _id_token.sign_encrypt(session_id, client_id) + + # TODO: we need an authentication event for this id_token for a better coverage + _id_token.payload(session_id) + + client_info = endpoint_context.cdb[client_id] + get_sign_and_encrypt_algorithms( + endpoint_context, client_info, payload_type="id_token", + sign=True, encrypt=True + ) diff --git a/tests/test_05_jwt_token.py b/tests/test_05_jwt_token.py index bd35afcc..fab24e71 100644 --- a/tests/test_05_jwt_token.py +++ b/tests/test_05_jwt_token.py @@ -86,6 +86,7 @@ "authorization_code": "code", "access_token": "access_token", "refresh_token": "refresh_token", + "id_token": "id_token" } From 0bbdf9b75fb2ccb33899d170024b48b788dc567e Mon Sep 17 00:00:00 2001 From: peppelinux Date: Tue, 18 May 2021 13:43:43 +0200 Subject: [PATCH 2/3] chore: coverage of user_authn.user increased --- src/oidcop/user_authn/user.py | 17 +++++++++-------- tests/test_12_user_authn.py | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/oidcop/user_authn/user.py b/src/oidcop/user_authn/user.py index 317d2202..8ff48f33 100755 --- a/src/oidcop/user_authn/user.py +++ b/src/oidcop/user_authn/user.py @@ -169,12 +169,14 @@ def __call__(self, **kwargs): ), OnlyForTestingWarning, ) - + if not self.server_get: + raise Exception( + f"{self.__class__.__name__} doesn't have a working server_get" + ) _context = self.server_get("endpoint_context") # Stores information need afterwards in a signed JWT that then # appears as a hidden input in the form jws = create_signed_jwt(_context.issuer, _context.keyjar, **kwargs) - _kwargs = self.kwargs.copy() for attr in ["policy", "tos", "logo"]: _uri = "{}_uri".format(attr) @@ -220,8 +222,8 @@ def authenticated_as(self, client_id, cookie=None, authorization="", **kwargs): authorization = authorization[6:] (user, pwd) = base64.b64decode(authorization).split(b":") - user = unquote(user) - self.verify_password(user, pwd) + user = unquote(user.decode()) + self.verify_password(user, pwd.decode()) res = {"uid": user} if cookie: res.update(self.cookie_info(cookie, client_id)) @@ -237,7 +239,7 @@ def __init__(self, ttl, symkey, server_get=None): if symkey is not None and symkey == "": msg = "SymKeyAuthn.symkey cannot be an empty value" raise ImproperlyConfigured(msg) - self.symkey = symkey + self.symkey = symkey.encode() if isinstance(symkey, str) else symkey self.ttl = ttl def authenticated_as(self, client_id, cookie=None, authorization="", **kwargs): @@ -252,7 +254,7 @@ def authenticated_as(self, client_id, cookie=None, authorization="", **kwargs): try: aesgcm = AESGCM(self.symkey) user = aesgcm.decrypt(iv, encmsg, None) - except (AssertionError, KeyError): + except (AssertionError, KeyError): # pragma: no-cover raise FailedAuthentication("Decryption failed") res = {"uid": user} @@ -278,8 +280,7 @@ def authenticated_as(self, client_id="", cookie=None, authorization="", **kwargs :param kwargs: extra key word arguments :return: """ - - if self.fail: + if self.fail: # pragma: no-cover raise self.fail() res = {"uid": self.user} diff --git a/tests/test_12_user_authn.py b/tests/test_12_user_authn.py index eb0dbf51..e0fe2cf2 100644 --- a/tests/test_12_user_authn.py +++ b/tests/test_12_user_authn.py @@ -1,3 +1,4 @@ +import base64 import os import pytest @@ -7,8 +8,12 @@ from oidcop.user_authn.authn_context import UNSPECIFIED from oidcop.user_authn.user import NoAuthn from oidcop.user_authn.user import UserPassJinja2 +from oidcop.user_authn.user import BasicAuthn +from oidcop.user_authn.user import NoAuthn +from oidcop.user_authn.user import SymKeyAuthn from oidcop.util import JSONDictDB + KEYDEFS = [ {"type": "RSA", "key": "", "use": ["sig"]}, {"type": "EC", "crv": "P-256", "use": ["sig"]}, @@ -66,7 +71,7 @@ def create_endpoint_context(self): }, }, }, - "template_dir": "template", + "template_dir": "tests/templates", } server = Server(conf) self.endpoint_context = server.endpoint_context @@ -96,3 +101,32 @@ def test_authenticated_as_with_cookie(self): _info, _time_stamp = method.authenticated_as("client 12345", [_cookie]) assert set(_info.keys()) == {"sub", "sid", "state", "client_id"} assert _info["sub"] == "diana" + + def test_userpassjinja2(self): + db = { + "class": JSONDictDB, + "kwargs": {"filename": full_path("passwd.json")}, + } + template_handler = self.endpoint_context.template_handler + sg = self.endpoint_context.session_manager.token_handler.handler['access_token'].kwargs['server_get'] + res = UserPassJinja2(db, template_handler, server_get=sg) + res() + assert 'page_header' in res.kwargs + + + def test_basic_auth(self): + sg = self.endpoint_context.session_manager.token_handler.handler['access_token'].kwargs['server_get'] + basic_auth = base64.b64encode(b'diana:krall').decode() + ba = BasicAuthn(pwd={'diana': 'krall'}, + server_get=sg) + ba.authenticated_as( + client_id='', authorization=f"Basic {basic_auth}" + ) + + def test_no_auth(self): + sg = self.endpoint_context.session_manager.token_handler.handler['access_token'].kwargs['server_get'] + basic_auth = base64.b64encode( + b'D\xfd\x8a\x85\xa6\xd1\x16\xe4\\6\x1e\x9ds~\xc3\t\x95\x99\x83\x91\x1f\xfb:iviviviv' + ) + ba = SymKeyAuthn(symkey=b'0'*32, ttl=600, server_get=sg) + ba.authenticated_as(client_id='', authorization=basic_auth) From 02c06a31e6c7e9cd58e79a982927a75912fc6c17 Mon Sep 17 00:00:00 2001 From: peppelinux Date: Tue, 18 May 2021 13:51:26 +0200 Subject: [PATCH 3/3] fix: adding missing templates --- .gitignore | 2 - .../templates/check_session_iframe.html | 122 ++++++++++++++++++ .../templates/frontchannel_logout.html | 31 +++++ .../oidc_provider/templates/logout.html | 87 +++++++++++++ .../oidc_provider/templates/oidc_login.html | 39 ++++++ .../oidc_provider/templates/post_logout.html | 10 ++ .../oidc_provider/templates/user_pass.jinja2 | 39 ++++++ .../templates/check_session_iframe.html | 122 ++++++++++++++++++ example/flask_op/templates/error.html | 12 ++ .../templates/frontchannel_logout.html | 31 +++++ example/flask_op/templates/index.html | 10 ++ example/flask_op/templates/logout.html | 87 +++++++++++++ example/flask_op/templates/post_logout.html | 10 ++ example/flask_op/templates/user_pass.jinja2 | 39 ++++++ tests/templates/user_pass.jinja2 | 39 ++++++ 15 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 example/django_op/oidc_provider/templates/check_session_iframe.html create mode 100644 example/django_op/oidc_provider/templates/frontchannel_logout.html create mode 100644 example/django_op/oidc_provider/templates/logout.html create mode 100644 example/django_op/oidc_provider/templates/oidc_login.html create mode 100644 example/django_op/oidc_provider/templates/post_logout.html create mode 100644 example/django_op/oidc_provider/templates/user_pass.jinja2 create mode 100644 example/flask_op/templates/check_session_iframe.html create mode 100644 example/flask_op/templates/error.html create mode 100644 example/flask_op/templates/frontchannel_logout.html create mode 100644 example/flask_op/templates/index.html create mode 100644 example/flask_op/templates/logout.html create mode 100644 example/flask_op/templates/post_logout.html create mode 100644 example/flask_op/templates/user_pass.jinja2 create mode 100644 tests/templates/user_pass.jinja2 diff --git a/.gitignore b/.gitignore index cc7f589b..fea22345 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -django_op/db.sqlite3 -templates static/ private/ conf.yaml diff --git a/example/django_op/oidc_provider/templates/check_session_iframe.html b/example/django_op/oidc_provider/templates/check_session_iframe.html new file mode 100644 index 00000000..fcd49607 --- /dev/null +++ b/example/django_op/oidc_provider/templates/check_session_iframe.html @@ -0,0 +1,122 @@ + + + + + Session Management - OP iframe + + + + + + + + + diff --git a/example/django_op/oidc_provider/templates/frontchannel_logout.html b/example/django_op/oidc_provider/templates/frontchannel_logout.html new file mode 100644 index 00000000..0cca93c1 --- /dev/null +++ b/example/django_op/oidc_provider/templates/frontchannel_logout.html @@ -0,0 +1,31 @@ + + + + Logout + + + + + + + {{ frames|safe }} + + \ No newline at end of file diff --git a/example/django_op/oidc_provider/templates/logout.html b/example/django_op/oidc_provider/templates/logout.html new file mode 100644 index 00000000..5b8e91ea --- /dev/null +++ b/example/django_op/oidc_provider/templates/logout.html @@ -0,0 +1,87 @@ + + + + Logout Request + + + + + +
+

Do you want to sign-out from {{ op }}?

+ +
+
+ + +
+ + \ No newline at end of file diff --git a/example/django_op/oidc_provider/templates/oidc_login.html b/example/django_op/oidc_provider/templates/oidc_login.html new file mode 100644 index 00000000..9add475c --- /dev/null +++ b/example/django_op/oidc_provider/templates/oidc_login.html @@ -0,0 +1,39 @@ + + + + + + Please login + + + +

{{ page_header }}

+ +
+ + +

+ + +

+ +

+ + +

+ +

+ {{ logo_label }} +

+

+ {{ tos_label }} +

+

+ {{ policy_label }} +

+ + +
+ + diff --git a/example/django_op/oidc_provider/templates/post_logout.html b/example/django_op/oidc_provider/templates/post_logout.html new file mode 100644 index 00000000..509c1f7a --- /dev/null +++ b/example/django_op/oidc_provider/templates/post_logout.html @@ -0,0 +1,10 @@ + + + + + Post Logout + + +

You have now been logged out from this server!

+ + diff --git a/example/django_op/oidc_provider/templates/user_pass.jinja2 b/example/django_op/oidc_provider/templates/user_pass.jinja2 new file mode 100644 index 00000000..9add475c --- /dev/null +++ b/example/django_op/oidc_provider/templates/user_pass.jinja2 @@ -0,0 +1,39 @@ + + + + + + Please login + + + +

{{ page_header }}

+ +
+ + +

+ + +

+ +

+ + +

+ +

+ {{ logo_label }} +

+

+ {{ tos_label }} +

+

+ {{ policy_label }} +

+ + +
+ + diff --git a/example/flask_op/templates/check_session_iframe.html b/example/flask_op/templates/check_session_iframe.html new file mode 100644 index 00000000..08f2f420 --- /dev/null +++ b/example/flask_op/templates/check_session_iframe.html @@ -0,0 +1,122 @@ + + + + + Session Management - OP iframe + + + + + + + + + \ No newline at end of file diff --git a/example/flask_op/templates/error.html b/example/flask_op/templates/error.html new file mode 100644 index 00000000..5933d924 --- /dev/null +++ b/example/flask_op/templates/error.html @@ -0,0 +1,12 @@ + + + +

Error: {{ title }}

+ +{% if redirect_url is defined %} +

Continue

+{% else %} +{% endif %} + + + diff --git a/example/flask_op/templates/frontchannel_logout.html b/example/flask_op/templates/frontchannel_logout.html new file mode 100644 index 00000000..0cca93c1 --- /dev/null +++ b/example/flask_op/templates/frontchannel_logout.html @@ -0,0 +1,31 @@ + + + + Logout + + + + + + + {{ frames|safe }} + + \ No newline at end of file diff --git a/example/flask_op/templates/index.html b/example/flask_op/templates/index.html new file mode 100644 index 00000000..e45bf506 --- /dev/null +++ b/example/flask_op/templates/index.html @@ -0,0 +1,10 @@ + + + + + Title + + +

Hi There!

+ + \ No newline at end of file diff --git a/example/flask_op/templates/logout.html b/example/flask_op/templates/logout.html new file mode 100644 index 00000000..5b8e91ea --- /dev/null +++ b/example/flask_op/templates/logout.html @@ -0,0 +1,87 @@ + + + + Logout Request + + + + + +
+

Do you want to sign-out from {{ op }}?

+ +
+
+ + +
+ + \ No newline at end of file diff --git a/example/flask_op/templates/post_logout.html b/example/flask_op/templates/post_logout.html new file mode 100644 index 00000000..509c1f7a --- /dev/null +++ b/example/flask_op/templates/post_logout.html @@ -0,0 +1,10 @@ + + + + + Post Logout + + +

You have now been logged out from this server!

+ + diff --git a/example/flask_op/templates/user_pass.jinja2 b/example/flask_op/templates/user_pass.jinja2 new file mode 100644 index 00000000..9add475c --- /dev/null +++ b/example/flask_op/templates/user_pass.jinja2 @@ -0,0 +1,39 @@ + + + + + + Please login + + + +

{{ page_header }}

+ +
+ + +

+ + +

+ +

+ + +

+ +

+ {{ logo_label }} +

+

+ {{ tos_label }} +

+

+ {{ policy_label }} +

+ + +
+ + diff --git a/tests/templates/user_pass.jinja2 b/tests/templates/user_pass.jinja2 new file mode 100644 index 00000000..9add475c --- /dev/null +++ b/tests/templates/user_pass.jinja2 @@ -0,0 +1,39 @@ + + + + + + Please login + + + +

{{ page_header }}

+ +
+ + +

+ + +

+ +

+ + +

+ +

+ {{ logo_label }} +

+

+ {{ tos_label }} +

+

+ {{ policy_label }} +

+ + +
+ +