From 652a13dcb6d656707947fef9742f9858df21eb4a Mon Sep 17 00:00:00 2001 From: peppelinux Date: Fri, 21 May 2021 14:01:50 +0200 Subject: [PATCH 01/13] fix: Session dump inconsistent schema after sman flush --- src/oidcop/session/database.py | 1 + src/oidcop/session/manager.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/oidcop/session/database.py b/src/oidcop/session/database.py index bf75991b..01391a7c 100644 --- a/src/oidcop/session/database.py +++ b/src/oidcop/session/database.py @@ -1,5 +1,6 @@ import base64 import logging + from typing import List from typing import Optional from typing import Union diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index 23c6a255..f38f7152 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -78,15 +78,12 @@ def __init__( conf: Optional[dict] = None, sub_func: Optional[dict] = None, ): - if conf: - _key = conf.get("password", rndstr(24)) - else: - _key = rndstr(24) + self.conf = conf or {} + self._key = self.conf.get("password", rndstr(24)) - Database.__init__(self, key=_key) + self._init_db() self.token_handler = handler self.salt = rndstr(32) - self.conf = conf or {} # this allows the subject identifier minters to be defined by someone # else then me. @@ -105,6 +102,9 @@ def __init__( if "ephemeral" not in sub_func: self.sub_func["ephemeral"] = ephemeral_id + def _init_db(self): + Database.__init__(self, key=self._key) + def get_user_info(self, uid: str) -> UserSessionInfo: usi = self.get([uid]) if isinstance(usi, UserSessionInfo): @@ -470,6 +470,10 @@ def remove_session(self, session_id: str): def local_load_adjustments(self, **kwargs): self.crypt = Crypt(self.key) + def flush(self): + super().flush() + self._init_db() + def create_session_manager(server_get, token_handler_args, sub_func=None): _token_handler = handler.factory(server_get, **token_handler_args) From 71b324e500fd549eada162eb4b5d4024e4ef5ddf Mon Sep 17 00:00:00 2001 From: peppelinux Date: Fri, 21 May 2021 14:29:09 +0200 Subject: [PATCH 02/13] fix: session manager gets password from general config --- src/oidcop/configure.py | 1 + src/oidcop/server.py | 1 + src/oidcop/session/manager.py | 5 ++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/oidcop/configure.py b/src/oidcop/configure.py index 0e9f67ef..56b54cc8 100755 --- a/src/oidcop/configure.py +++ b/src/oidcop/configure.py @@ -234,6 +234,7 @@ def __init__( self.template_dir = None self.token_handler_args = {} self.userinfo = None + self.password = None if file_attributes is None: file_attributes = DEFAULT_FILE_ATTRIBUTE_NAMES diff --git a/src/oidcop/server.py b/src/oidcop/server.py index eae929bd..dc96a225 100644 --- a/src/oidcop/server.py +++ b/src/oidcop/server.py @@ -91,6 +91,7 @@ def __init__( self.server_get, self.endpoint_context.th_args, sub_func=self.endpoint_context._sub_func, + conf=self.conf ) self.endpoint_context.do_userinfo() # Must be done after userinfo diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index f38f7152..9e7ff04d 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -80,7 +80,6 @@ def __init__( ): self.conf = conf or {} self._key = self.conf.get("password", rndstr(24)) - self._init_db() self.token_handler = handler self.salt = rndstr(32) @@ -475,6 +474,6 @@ def flush(self): self._init_db() -def create_session_manager(server_get, token_handler_args, sub_func=None): +def create_session_manager(server_get, token_handler_args, sub_func=None, conf=None): _token_handler = handler.factory(server_get, **token_handler_args) - return SessionManager(_token_handler, sub_func=sub_func) + return SessionManager(_token_handler, sub_func=sub_func, conf=conf) From 1d72522450a38ee8bf493949f0acfef9c7adb563 Mon Sep 17 00:00:00 2001 From: peppelinux Date: Fri, 21 May 2021 14:44:42 +0200 Subject: [PATCH 03/13] fix: session manager salt optionally from global conf chore: session Database init small generalization --- src/oidcop/configure.py | 1 + src/oidcop/session/database.py | 8 ++++---- src/oidcop/session/manager.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/oidcop/configure.py b/src/oidcop/configure.py index 56b54cc8..224a2f85 100755 --- a/src/oidcop/configure.py +++ b/src/oidcop/configure.py @@ -235,6 +235,7 @@ def __init__( self.token_handler_args = {} self.userinfo = None self.password = None + self.salt = None if file_attributes is None: file_attributes = DEFAULT_FILE_ATTRIBUTE_NAMES diff --git a/src/oidcop/session/database.py b/src/oidcop/session/database.py index 01391a7c..1ea380a2 100644 --- a/src/oidcop/session/database.py +++ b/src/oidcop/session/database.py @@ -37,14 +37,14 @@ class InconsistentDatabase(TypeError): class Database(ImpExp): parameter = {"db": DLDict, "key": ""} - def __init__(self, key: Optional[str] = ""): + def __init__(self, key: Optional[str] = "", **kwargs): ImpExp.__init__(self) self.db = DLDict() - if not key: - key = rndstr(24) + for k,v in kwargs.items(): + setattr(self, k, v) - self.key = key + self.key = key or rndstr(24) self.crypt = Crypt(key) @staticmethod diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index 9e7ff04d..d8237d2c 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -80,9 +80,9 @@ def __init__( ): self.conf = conf or {} self._key = self.conf.get("password", rndstr(24)) + self._salt = self.conf.get("salt", rndstr(32)) self._init_db() self.token_handler = handler - self.salt = rndstr(32) # this allows the subject identifier minters to be defined by someone # else then me. @@ -102,7 +102,7 @@ def __init__( self.sub_func["ephemeral"] = ephemeral_id def _init_db(self): - Database.__init__(self, key=self._key) + Database.__init__(self, key=self._key, salt=self._salt) def get_user_info(self, uid: str) -> UserSessionInfo: usi = self.get([uid]) From 990ada2249e7475e85f29f2e4df88c17666228c9 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Fri, 21 May 2021 19:26:18 +0200 Subject: [PATCH 04/13] some improvements but salt being overwritten runtime with a "" value ... also called bug! --- src/oidcop/client_authn.py | 7 +------ src/oidcop/session/database.py | 4 ++++ src/oidcop/session/manager.py | 13 +++++++------ tests/test_50_persistence.py | 1 - 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/oidcop/client_authn.py b/src/oidcop/client_authn.py index 57086be9..f03b8405 100755 --- a/src/oidcop/client_authn.py +++ b/src/oidcop/client_authn.py @@ -353,12 +353,7 @@ def verify_client( authorization_token = None auth_info = {} - _methods = [] - if endpoint: - try: - _methods = endpoint.client_authn_method - except AttributeError: - pass + _methods = getattr(endpoint, 'client_authn_method', []) for _method in _methods: if _method is None: diff --git a/src/oidcop/session/database.py b/src/oidcop/session/database.py index 1ea380a2..e99368de 100644 --- a/src/oidcop/session/database.py +++ b/src/oidcop/session/database.py @@ -1,4 +1,5 @@ import base64 +import cryptography import logging from typing import List @@ -189,6 +190,9 @@ def encrypted_session_id(self, *args) -> str: def decrypt_session_id(self, key: str) -> List[str]: try: plain = self.crypt.decrypt(base64.b64decode(key)) + except cryptography.fernet.InvalidToken as err: + logger.error(f"cryptography.fernet.InvalidToken: {key}") + raise ValueError(err) except Exception as err: raise ValueError(err) # order: rnd, type, sid diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index d8237d2c..b1532e97 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -79,8 +79,8 @@ def __init__( sub_func: Optional[dict] = None, ): self.conf = conf or {} - self._key = self.conf.get("password", rndstr(24)) - self._salt = self.conf.get("salt", rndstr(32)) + self.key = self.conf.get("password") or rndstr(24) + self.salt = self.conf.get("salt") or rndstr(32) self._init_db() self.token_handler = handler @@ -102,7 +102,7 @@ def __init__( self.sub_func["ephemeral"] = ephemeral_id def _init_db(self): - Database.__init__(self, key=self._key, salt=self._salt) + Database.__init__(self, key=self.key, salt=self.salt) def get_user_info(self, uid: str) -> UserSessionInfo: usi = self.get([uid]) @@ -432,14 +432,16 @@ def get_session_info_by_token( authorization_request: bool = False, ) -> dict: _token_info = self.token_handler.info(token_value) - return self.get_session_info( - _token_info["sid"], + sid = _token_info["sid"] + session_info = self.get_session_info( + sid, user_session_info=user_session_info, client_session_info=client_session_info, grant=grant, authentication_event=authentication_event, authorization_request=authorization_request, ) + return session_info def get_session_id_by_token(self, token_value: str) -> str: _token_info = self.token_handler.info(token_value) @@ -473,7 +475,6 @@ def flush(self): super().flush() self._init_db() - def create_session_manager(server_get, token_handler_args, sub_func=None, conf=None): _token_handler = handler.factory(server_get, **token_handler_args) return SessionManager(_token_handler, sub_func=sub_func, conf=conf) diff --git a/tests/test_50_persistence.py b/tests/test_50_persistence.py index 419258da..2fb0de1c 100644 --- a/tests/test_50_persistence.py +++ b/tests/test_50_persistence.py @@ -484,4 +484,3 @@ def test_custom_scope(self): "email_verified", "eduperson_scoped_affiliation", } - From 31975d818e9cd052ee17c1f59d646502ec3a87fa Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Fri, 21 May 2021 23:51:28 +0200 Subject: [PATCH 05/13] fix: sman has finally made peace with pointers and primary memory is thanksfull --- src/oidcop/session/manager.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index b1532e97..c5ec68b2 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -79,8 +79,8 @@ def __init__( sub_func: Optional[dict] = None, ): self.conf = conf or {} - self.key = self.conf.get("password") or rndstr(24) - self.salt = self.conf.get("salt") or rndstr(32) + self.load_key() + self.load_salt() self._init_db() self.token_handler = handler @@ -101,8 +101,20 @@ def __init__( if "ephemeral" not in sub_func: self.sub_func["ephemeral"] = ephemeral_id + def load_key(self): + self.key = self.conf.get("password") or rndstr(24) + return self.key + + def load_salt(self): + self.salt = self.conf.get("salt") or rndstr(32) + return self.salt + def _init_db(self): - Database.__init__(self, key=self.key, salt=self.salt) + Database.__init__( + self, + key=self.key or self.load_key(), + salt=self.salt or self.load_salt() + ) def get_user_info(self, uid: str) -> UserSessionInfo: usi = self.get([uid]) @@ -475,6 +487,7 @@ def flush(self): super().flush() self._init_db() + def create_session_manager(server_get, token_handler_args, sub_func=None, conf=None): _token_handler = handler.factory(server_get, **token_handler_args) return SessionManager(_token_handler, sub_func=sub_func, conf=conf) From cb28f0e9b4be69272f4dd01bb1f546bcb173cfe4 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 22 May 2021 15:13:44 +0200 Subject: [PATCH 06/13] chore: sman db unit tests, private _key and _salt are readonly values --- src/oidcop/session/manager.py | 32 +++++++++++++++----- tests/test_50_persistence.py | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index c5ec68b2..4a1d1a63 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -79,8 +79,14 @@ def __init__( sub_func: Optional[dict] = None, ): self.conf = conf or {} - self.load_key() - self.load_salt() + + # these won't change runtime + self._key = self.conf.get("password") or rndstr(24) + self._salt = self.conf.get("salt") or rndstr(32) + + self.key = self.load_key() + self.salt = self.load_key() + self._init_db() self.token_handler = handler @@ -102,18 +108,28 @@ def __init__( self.sub_func["ephemeral"] = ephemeral_id def load_key(self): - self.key = self.conf.get("password") or rndstr(24) - return self.key + """returns the original key assigned in init""" + return self._key def load_salt(self): - self.salt = self.conf.get("salt") or rndstr(32) - return self.salt + """returns the original salt assigned in init""" + return self._salt + + def __setattr__(self, key, value): + if key in ('_key', '_salt'): + if hasattr(self, key): + # not first time we configure it! + raise AttributeError( + f"{key} is a ReadOnly attribute " + "that can't be overwritten!" + ) + super().__setattr__(key, value) def _init_db(self): Database.__init__( self, - key=self.key or self.load_key(), - salt=self.salt or self.load_salt() + key=self.load_key(), + salt=self.load_salt() ) def get_user_info(self, uid: str) -> UserSessionInfo: diff --git a/tests/test_50_persistence.py b/tests/test_50_persistence.py index 2fb0de1c..490ddae3 100644 --- a/tests/test_50_persistence.py +++ b/tests/test_50_persistence.py @@ -484,3 +484,59 @@ def test_custom_scope(self): "email_verified", "eduperson_scoped_affiliation", } + + def test_sman_db_integrity(self): + """ + this test assures that session database remains consistent after + - many consecutives flush + - deletion of key or salt + - some mess with values overwritten runtime + it show that flush and loads method will keep order, anyway. + """ + session_id = self._create_session(AUTH_REQ, index=1) + grant = ( + self.endpoint[1].server_get("endpoint_context").authz( + session_id, AUTH_REQ + ) + ) + sman = self.session_manager[1] + session_dump = sman.dump() + + # there after an exception a database could be inconsistent + # it would be better to always flush database when a new http request come + # and load session from previously loaded sessions + sman.flush() + # yes, two times to simulate those things that happens in real world + sman.flush() + + # check that a sman db schema is consistent after a flush + tdump = sman.dump() + for i in 'db', 'key', 'salt': + if i not in tdump: + raise ValueError( + f"{i} not found in session dump after a flush!" + ) + + # test that key and salt have not be touched after the flush + # they wouldn't change runtime (even if they are randomic). + for i in 'key', 'salt': + if session_dump[i] != tdump[i]: + raise ValueError( + f"Inconsistent Session schema dump after a flush. " + f"{i} has changed compared to which was configured." + ) + + # tests readonlyness of private attributes _key and _salt + for i in '_key', '_salt': + with pytest.raises(AttributeError): + setattr(sman, i, 'that thing') + + # ok, load the session and assert that everything is in the right place + # some mess before doing that + sman.key = 'ingoalla' + sman.salt = 'fantozzi' + + # ok, end of the games, session have been loaded and all the things be finally there! + sman.load(session_dump) + for i in 'db', 'key', 'salt': + assert session_dump[i] == sman.dump()[i] From ba808dc033e3a5136a03a7b45691551c9b50fbe4 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 22 May 2021 15:29:18 +0200 Subject: [PATCH 07/13] fix: .gitignore improved - now salts created by unittests are ignored --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index cc7f589b..831dbd0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -django_op/db.sqlite3 -templates static/ private/ conf.yaml @@ -117,3 +115,5 @@ venv.bak/ src/oidcendpoint.egg-info/ .iframes/ +tests/pairwise.salt +tests/public.salt From 302d17cfd8975f6bca6f81738325df856f0b70ab Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 22 May 2021 22:41:01 +0200 Subject: [PATCH 08/13] chore: Documentation --- doc/source/contents/conf.rst | 95 +++++++++++++++++++--- doc/source/contents/session_management.rst | 52 ++++++++---- 2 files changed, 117 insertions(+), 30 deletions(-) diff --git a/doc/source/contents/conf.rst b/doc/source/contents/conf.rst index 6eafffee..b49c1bd2 100644 --- a/doc/source/contents/conf.rst +++ b/doc/source/contents/conf.rst @@ -8,6 +8,38 @@ issuer The issuer ID of the OP, a unique value in URI format. +---- +seed +---- + +Used in dynamic client registration endpoint when creating a new client_secret. +If unset it will be random. + +-------- +password +-------- + +Encryption key used to encrypt the SessionID (sid) in access_token. +If unset it will be random. + +---- +salt +---- + +Salt, value or filename, used in sub_funcs (pairwise, public) for creating the opaque hash of *sub* claim. + +----------- +session_key +----------- + +An example:: + + "session_key": { + "filename": "private/session_jwk.json", + "type": "OCT", + "use": "sig" + }, + ------ add_on ------ @@ -266,6 +298,15 @@ An example:: } } +You can specify which algoritms are supported, for example in userinfo_endpoint:: + + "userinfo_signing_alg_values_supported": OIDC_SIGN_ALGS, + "userinfo_encryption_alg_values_supported": OIDC_ENC_ALGS, + +Or in authorization endpoint:: + + "request_object_encryption_alg_values_supported": OIDC_ENC_ALGS, + ------------ httpc_params ------------ @@ -307,6 +348,9 @@ An example:: "uri_path": "static/jwks.json" }, +*read_only* means that on each restart the keys will created and overwritten with new ones. +This can be useful during the first time the project have been executed, then to keep them as they are *read_only* would be configured to *True*. + --------------- login_hint2acrs --------------- @@ -358,19 +402,6 @@ An example:: } }, - ------------ -session_key ------------ - -An example:: - - "session_key": { - "filename": "private/session_jwk.json", - "type": "OCT", - "use": "sig" - }, - ------------ template_dir ------------ @@ -442,6 +473,44 @@ An example:: } } +jwks_defs can be replaced eventually by `jwks_file`:: + + "jwks_file": f"{OIDC_JWKS_PRIVATE_PATH}/token_jwks.json", + +You can even select wich algorithms to support in id_token, eg:: + + "id_token": { + "class": "oidcop.token.id_token.IDToken", + "kwargs": { + "id_token_signing_alg_values_supported": [ + "RS256", + "RS512", + "ES256", + "ES512", + "PS256", + "PS512", + ], + "id_token_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "A192KW", + "A256KW", + "ECDH-ES", + "ECDH-ES+A128KW", + "ECDH-ES+A192KW", + "ECDH-ES+A256KW", + ], + "id_token_encryption_enc_values_supported": [ + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + 'A128GCM', + 'A192GCM', + 'A256GCM' + ], + } + } + -------- userinfo -------- diff --git a/doc/source/contents/session_management.rst b/doc/source/contents/session_management.rst index 22227c4b..26e99371 100644 --- a/doc/source/contents/session_management.rst +++ b/doc/source/contents/session_management.rst @@ -372,16 +372,16 @@ max_usage used :::: -How many times the token has been used + How many times the token has been used based_on :::::::: -Reference to the token that was used to mint this token. Might be empty if the -token was minted based on the grant it belongs to. + Reference to the token that was used to mint this token. Might be empty if the + token was minted based on the grant it belongs to. id :: -Token identifier + Token identifier Session Info API ---------------- @@ -391,38 +391,56 @@ add_subordinate +++++++++++++++ .. _`add_subordinate`: + ... + remove_subordinate ++++++++++++++++++ .. _`removed_subordinate`: + ... + revoke ++++++ .. _`revoke`: + ... + is_revoked ++++++++++ .. _`is_revoked`: + ... + to_json +++++++ .. _`to_json`: + ... + from_json +++++++++ .. _`from_json`: + ... + Grant API --------- .. _`Grant API`: + ... + Token API --------- .. _`Token API`: + ... + Session Manager API ------------------- .. _`Session Manager API`: + ... + create_session ++++++++++++++ .. _create_session: @@ -463,77 +481,77 @@ add_grant +++++++++ .. _add_grant: -add_grant(self, user_id, client_id, **kwargs) + add_grant(self, user_id, client_id, **kwargs) find_token ++++++++++ .. _find_token: -find_token(self, session_id, token_value) + find_token(self, session_id, token_value) get_authentication_event ++++++++++++++++++++++++ .. _get_authentication_event: -get_authentication_event(self, session_id) + get_authentication_event(self, session_id) get_client_session_info +++++++++++++++++++++++ .. _get_client_session_info: -get_client_session_info(self, session_id) + get_client_session_info(self, session_id) get_grant_by_response_type ++++++++++++++++++++++++++ .. _get_grant_by_response_type: -get_grant_by_response_type(self, user_id, client_id) + get_grant_by_response_type(self, user_id, client_id) get_session_info ++++++++++++++++ .. _get_session_info: -get_session_info(self, session_id) + get_session_info(self, session_id) get_session_info_by_token +++++++++++++++++++++++++ .. _get_session_info_by_token: -get_session_info_by_token(self, token_value) + get_session_info_by_token(self, token_value) get_sids_by_user_id +++++++++++++++++++ .. _get_sids_by_user_id: -get_sids_by_user_id(self, user_id) + get_sids_by_user_id(self, user_id) get_user_info +++++++++++++ .. _get_user_info: -get_user_info(self, uid) + get_user_info(self, uid) grants ++++++ .. _grants: -grants(self, session_id) + grants(self, session_id) revoke_client_session +++++++++++++++++++++ .. _revoke_client_session: -revoke_client_session(self, session_id) + revoke_client_session(self, session_id) revoke_grant ++++++++++++ .. _revoke_grant: -revoke_grant(self, session_id) + revoke_grant(self, session_id) revoke_token ++++++++++++ .. _revoke_token: -revoke_token(self, session_id, token_value, recursive=False) + revoke_token(self, session_id, token_value, recursive=False) From b91eb9b8e7ee12bcfdbb48f4f093fd68a66aa590 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 22 May 2021 22:58:07 +0200 Subject: [PATCH 09/13] chore: django_op example definitively moved to django-oidc-op github repository --- example/django_op/.gitignore | 7 - example/django_op/AUTHORS | 1 - example/django_op/LICENSE | 190 ------ example/django_op/README.md | 149 +---- example/django_op/example/.coveragerc | 9 - .../django_op/example/accounts/__init__.py | 2 - example/django_op/example/accounts/admin.py | 53 -- .../example/accounts/admin_inlines.py | 8 - example/django_op/example/accounts/apps.py | 6 - example/django_op/example/accounts/forms.py | 2 - .../accounts/migrations/0001_initial.py | 64 -- .../migrations/0002_auto_20191202_1526.py | 18 - .../example/accounts/migrations/__init__.py | 0 example/django_op/example/accounts/models.py | 82 --- .../example/accounts/templatetags/__init__.py | 0 .../accounts/templatetags/has_group.py | 9 - example/django_op/example/accounts/tests.py | 3 - example/django_op/example/accounts/urls.py | 26 - example/django_op/example/accounts/views.py | 27 - .../example/data/oidc_op/certs/cert.pem | 31 - .../example/data/oidc_op/certs/key.pem | 52 -- .../example/data/oidc_rp/conf.django.yaml | 159 ----- .../django_op/example/data/oidc_rp/conf.json | 322 ---------- example/django_op/example/example/__init__.py | 0 .../example/example/oidc_op.conf.yaml | 329 ---------- .../example/example/oidc_provider_settings.py | 354 ----------- example/django_op/example/example/settings.py | 210 ------- example/django_op/example/example/urls.py | 30 - example/django_op/example/example/wsgi.py | 16 - example/django_op/example/manage.py | 21 - example/django_op/example/oidc_provider | 1 - example/django_op/example/requirements.txt | 1 - example/django_op/example/run.bash | 7 - example/django_op/oidc-op.dev.notes.md | 260 -------- example/django_op/oidc_provider/__init__.py | 1 - example/django_op/oidc_provider/admin.py | 214 ------- .../django_op/oidc_provider/application.py | 49 -- example/django_op/oidc_provider/apps.py | 6 - .../oidc_provider/migrations/0001_initial.py | 155 ----- .../oidc_provider/migrations/__init__.py | 0 example/django_op/oidc_provider/models.py | 582 ------------------ .../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 -- example/django_op/oidc_provider/tests.py | 2 - .../oidc_provider/tests/01_client_db.py | 102 --- .../django_op/oidc_provider/tests/__init__.py | 0 example/django_op/oidc_provider/urls.py | 33 - example/django_op/oidc_provider/users.py | 139 ----- example/django_op/oidc_provider/utils.py | 31 - example/django_op/oidc_provider/views.py | 351 ----------- example/django_op/requirements.txt | 10 - example/django_op/snippets/db_interfaces.py | 287 --------- example/django_op/snippets/msg_test.py | 39 -- example/django_op/snippets/rp_handler.py | 178 ------ 58 files changed, 1 insertion(+), 4955 deletions(-) delete mode 100644 example/django_op/.gitignore delete mode 100644 example/django_op/AUTHORS delete mode 100644 example/django_op/LICENSE delete mode 100644 example/django_op/example/.coveragerc delete mode 100644 example/django_op/example/accounts/__init__.py delete mode 100644 example/django_op/example/accounts/admin.py delete mode 100644 example/django_op/example/accounts/admin_inlines.py delete mode 100644 example/django_op/example/accounts/apps.py delete mode 100644 example/django_op/example/accounts/forms.py delete mode 100644 example/django_op/example/accounts/migrations/0001_initial.py delete mode 100644 example/django_op/example/accounts/migrations/0002_auto_20191202_1526.py delete mode 100644 example/django_op/example/accounts/migrations/__init__.py delete mode 100644 example/django_op/example/accounts/models.py delete mode 100644 example/django_op/example/accounts/templatetags/__init__.py delete mode 100644 example/django_op/example/accounts/templatetags/has_group.py delete mode 100644 example/django_op/example/accounts/tests.py delete mode 100644 example/django_op/example/accounts/urls.py delete mode 100644 example/django_op/example/accounts/views.py delete mode 100644 example/django_op/example/data/oidc_op/certs/cert.pem delete mode 100644 example/django_op/example/data/oidc_op/certs/key.pem delete mode 100644 example/django_op/example/data/oidc_rp/conf.django.yaml delete mode 100644 example/django_op/example/data/oidc_rp/conf.json delete mode 100644 example/django_op/example/example/__init__.py delete mode 100644 example/django_op/example/example/oidc_op.conf.yaml delete mode 100644 example/django_op/example/example/oidc_provider_settings.py delete mode 100644 example/django_op/example/example/settings.py delete mode 100644 example/django_op/example/example/urls.py delete mode 100644 example/django_op/example/example/wsgi.py delete mode 100755 example/django_op/example/manage.py delete mode 120000 example/django_op/example/oidc_provider delete mode 100644 example/django_op/example/requirements.txt delete mode 100644 example/django_op/example/run.bash delete mode 100644 example/django_op/oidc-op.dev.notes.md delete mode 100644 example/django_op/oidc_provider/__init__.py delete mode 100644 example/django_op/oidc_provider/admin.py delete mode 100644 example/django_op/oidc_provider/application.py delete mode 100644 example/django_op/oidc_provider/apps.py delete mode 100644 example/django_op/oidc_provider/migrations/0001_initial.py delete mode 100644 example/django_op/oidc_provider/migrations/__init__.py delete mode 100644 example/django_op/oidc_provider/models.py delete mode 100644 example/django_op/oidc_provider/templates/check_session_iframe.html delete mode 100644 example/django_op/oidc_provider/templates/frontchannel_logout.html delete mode 100644 example/django_op/oidc_provider/templates/logout.html delete mode 100644 example/django_op/oidc_provider/templates/oidc_login.html delete mode 100644 example/django_op/oidc_provider/templates/post_logout.html delete mode 100644 example/django_op/oidc_provider/templates/user_pass.jinja2 delete mode 100644 example/django_op/oidc_provider/tests.py delete mode 100644 example/django_op/oidc_provider/tests/01_client_db.py delete mode 100644 example/django_op/oidc_provider/tests/__init__.py delete mode 100644 example/django_op/oidc_provider/urls.py delete mode 100644 example/django_op/oidc_provider/users.py delete mode 100644 example/django_op/oidc_provider/utils.py delete mode 100644 example/django_op/oidc_provider/views.py delete mode 100644 example/django_op/requirements.txt delete mode 100644 example/django_op/snippets/db_interfaces.py delete mode 100644 example/django_op/snippets/msg_test.py delete mode 100644 example/django_op/snippets/rp_handler.py diff --git a/example/django_op/.gitignore b/example/django_op/.gitignore deleted file mode 100644 index 302d5602..00000000 --- a/example/django_op/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -*/db.sqlite3 -example/db.sqlite3 -example/data/static/* -!example/data/static/README.md -__pycache__/* -*.pyc - diff --git a/example/django_op/AUTHORS b/example/django_op/AUTHORS deleted file mode 100644 index b995f227..00000000 --- a/example/django_op/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Giuseppe De Marco diff --git a/example/django_op/LICENSE b/example/django_op/LICENSE deleted file mode 100644 index fe2e6d0d..00000000 --- a/example/django_op/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2019 Giuseppe De Marco - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/example/django_op/README.md b/example/django_op/README.md index a03cea12..108e9bd2 100644 --- a/example/django_op/README.md +++ b/example/django_op/README.md @@ -1,150 +1,3 @@ # django-oidc-op -A Django implementation of an **OIDC Provider** on top of [jwtconnect.io](https://jwtconnect.io/). -To configure a standard OIDC Provider you have to edit the oidcop configuration file. -See `example/example/oidc_op.conf.yaml` to get in. -This project is based on [Roland Hedberg's oidc-op](https://github.com/rohe/oidc-op). -Oidcendpoint supports the following standards and drafts: - -- [OpenID Connect Core 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-core-1_0.html) -- [OpenID Connect Discovery 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-discovery-1_0.html) -- [OpenID Connect Dynamic Client Registration 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-registration-1_0.html) -- [OpenID Connect Session Management 1.0](https://openid.net/specs/openid-connect-session-1_0.html) -- [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html) -- [OAuth2 Token introspection](https://tools.ietf.org/html/rfc7662) - -It also supports the followings `add_ons` modules. - -- Custom scopes, that extends [OIDC standard ScopeClaims](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) -- PKCE, [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) - -## Status - -The development status of this project is *experimental*, something goes wrong following latest oidcendpoint releases. -A roadmap for a stable release is still in progress. - - -Works: - -- Relying-Parties Admin UI completed, unit tests included (works v1.0.1) -- Session and SSO management completed (works v1.0.1) -- Logout session handler - -Work in progress: -- KeyJAR and default storage (issuer, keybundles) (TODO with a full storage handler integration) -- Cookie handling, at this time we do not use cookies (disabled in configuration) -- Custom scopes - -## Run the example demo - -```` -git clone https://github.com/peppelinux/django-oidc-op.git -cd django-oidc-op - -pip install -r requirements.txt - -cd example -pip install -r requirements.txt -./manage.py migrate -./manage.py createsuperuser -./manage.py collectstatic - -## debug server -gunicorn example.wsgi -b0.0.0.0:8000 --keyfile=./data/oidc_op/certs/key.pem --certfile=./data/oidc_op/certs/cert.pem --reload --timeout 3600 --capture-output --enable-stdio-inheritance -```` - -You can use [JWTConnect-Python-OidcRP](https://github.com/openid/JWTConnect-Python-OidcRP) as follow: -``` -cd JWTConnect-Python-OidcRP -RP_LOGFILE_NAME="./flrp.django.log" python3 -m flask_rp.wsgi ../django-oidc-op/example/data/oidc_rp/conf.django.yaml -``` - -You can also use a scripted RP handler on top of oidc-rp -```` -python3 snippets/rp_handler.py -c example/data/oidc_rp/conf.django.yaml -u myuser -p mypass -iss django_oidc_op -```` - - -## Configure OIDC endpoint - -#### Django settings.py parameters - -`OIDCENDPOINT_CONFIG`: The path containing the oidc-op configuration file. -`OIDC_OP_AUTHN_SALT_SIZE`: Salt size in byte, default: 4 (Integer). - -#### JWKs -These following files needed to be present in `data/oidc_op/private` otherwise they will be created on the first time automatically. - -1. session.json (JWK symmetric); - -These are not used anymore, disabled in op conf.yaml: -1. cookie_sign_jwk.json (JWK symmetric); -2. cookie_enc_jwk.json (JWK symmetric), optional, see `conf.yaml`. - -To create them by hands comment out `'read_only': False'` in `conf.yaml`, -otherwise they will be created automatically on each run. - -A JWK creation example would be: -```` -jwkgen --kty SYM > data/oidc_op/private/cookie_enc_jwk.json -```` - -## Django specific implementation - -This project rely interely on behaviour and features provided by oidcendpoint, to get an exaustive integration in Django it -adopt the following customizations. - -#### DataStore management -Oidcendpoint have some data persistence: -You can use oidcendpoint's standard `oidcmsg.storage.abfile.AbstractFileSystem` or Django models (Work in Progress). - -#### UserInfo endpoint - -Claims to be released are configured in `op.server_info.user_info` (in `conf.yaml`). -The attributes release and user authentication mechanism rely on classes implemented in `oidc_op.users.py`. - -Configuration Example: - -```` - userinfo: - class: oidc_op.users.UserInfo - kwargs: - # map claims to django user attributes here: - claims_map: - phone_number: telephone - family_name: last_name - given_name: first_name - email: email - verified_email: email -```` - -#### Relying-Party search panel - -See `oidc_op.models` and `oidc_op.admin`, an UI was built to configure new RP via Django admin backend. -![Alt text](images/rp_search.png) - -#### Relying-Party Registration -![Alt text](images/rp.png) - -#### Session management and token preview -![Alt text](images/oidc_session2.png) - -## OIDC endpoint url prefix -Can be configured in `urls.py` and also in oidc_op `conf.yaml`. - -- /oidc/endpoint/ - - -## Running tests - -running tests -```` -./manage.py test --pdb oidc_op.tests.01_client_db -```` - -## code coverage -```` -coverage erase -coverage run manage.py test -coverage report -```` +The Django oidc-op implementation is available here [django-oidc-op github page](https://github.com/peppelinux/django-oidc-op/tree/develop). diff --git a/example/django_op/example/.coveragerc b/example/django_op/example/.coveragerc deleted file mode 100644 index 7fe59bb4..00000000 --- a/example/django_op/example/.coveragerc +++ /dev/null @@ -1,9 +0,0 @@ -[run] -source = . - -[report] -fail_under = 100 -show_missing = True -skip_covered = True - - diff --git a/example/django_op/example/accounts/__init__.py b/example/django_op/example/accounts/__init__.py deleted file mode 100644 index b48c1ec8..00000000 --- a/example/django_op/example/accounts/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -default_app_config = 'accounts.apps.AccountsConfig' - diff --git a/example/django_op/example/accounts/admin.py b/example/django_op/example/accounts/admin.py deleted file mode 100644 index 08ca4e8d..00000000 --- a/example/django_op/example/accounts/admin.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.contrib import admin -from django.utils.translation import ugettext, ugettext_lazy as _ -from django.contrib.auth.admin import UserAdmin - -from .models import User, PersistentId -from .admin_inlines import PersistentIdInline - - -@admin.register(User) -class CustomUserAdmin(UserAdmin): - readonly_fields = ('date_joined', 'last_login',) - list_display = ('username', 'email', 'is_active', - 'is_staff', 'is_superuser', ) - list_editable = ('is_active', 'is_staff', 'is_superuser',) - inlines = [PersistentIdInline,] - fieldsets = ( - (None, {'fields': (('username', 'is_active', 'is_staff', 'is_superuser', ), - ('password'), - ('origin'), - ) - }), - (_('Anagrafica'), {'fields': (('first_name', 'last_name'), - 'email', - ('taxpayer_id',), - ('gender', - 'place_of_birth', 'birth_date',), - ) - }), - - (_('Permissions'), {'fields': ('groups', 'user_permissions'), - 'classes':('collapse',) - }), - - - (_('Date accessi sistema'), {'fields': (('date_joined', - 'last_login', )) - }), - ) - add_fieldsets = ( - (None, { - 'classes': ('wide',), - 'fields': ('username', 'password1', 'password2'), - }), - ) - -admin.site.unregister(User) -admin.site.register(User, CustomUserAdmin) - - -@admin.register(PersistentId) -class PersistentIdAdmin(admin.ModelAdmin): - list_display = ('user', 'persistent_id', 'recipient_id', 'created') - list_filter = ('created',) diff --git a/example/django_op/example/accounts/admin_inlines.py b/example/django_op/example/accounts/admin_inlines.py deleted file mode 100644 index 33b0047c..00000000 --- a/example/django_op/example/accounts/admin_inlines.py +++ /dev/null @@ -1,8 +0,0 @@ -from django import forms -from django.contrib import admin - -from .models import * - -class PersistentIdInline(admin.TabularInline): - model = PersistentId - extra = 0 diff --git a/example/django_op/example/accounts/apps.py b/example/django_op/example/accounts/apps.py deleted file mode 100644 index e4b019ea..00000000 --- a/example/django_op/example/accounts/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class AccountsConfig(AppConfig): - name = 'accounts' - verbose_name = "Autenticazione e Autorizzazione Utenti" diff --git a/example/django_op/example/accounts/forms.py b/example/django_op/example/accounts/forms.py deleted file mode 100644 index 95ec770d..00000000 --- a/example/django_op/example/accounts/forms.py +++ /dev/null @@ -1,2 +0,0 @@ -from django import forms -from .models import * diff --git a/example/django_op/example/accounts/migrations/0001_initial.py b/example/django_op/example/accounts/migrations/0001_initial.py deleted file mode 100644 index 6e41e8b7..00000000 --- a/example/django_op/example/accounts/migrations/0001_initial.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 2.2.5 on 2019-09-21 16:47 - -from django.conf import settings -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0011_update_proxy_permissions'), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('first_name', models.CharField(blank=True, max_length=30, null=True, verbose_name='Name')), - ('last_name', models.CharField(blank=True, max_length=30, null=True, verbose_name='Surname')), - ('is_active', models.BooleanField(default=True, verbose_name='active')), - ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='email address')), - ('taxpayer_id', models.CharField(blank=True, max_length=32, null=True, verbose_name="Taxpayer's identification number")), - ('gender', models.CharField(blank=True, choices=[('male', 'Maschio'), ('female', 'Femmina'), ('other', 'Altro')], max_length=12, null=True, verbose_name='Genere')), - ('place_of_birth', models.CharField(blank=True, choices=[('Aruba', 'Aruba'), ('Afghanistan', 'Afghanistan'), ('Angola', 'Angola'), ('Anguilla', 'Anguilla'), ('Åland Islands', 'Åland Islands'), ('Albania', 'Albania'), ('Andorra', 'Andorra'), ('United Arab Emirates', 'United Arab Emirates'), ('Argentina', 'Argentina'), ('Armenia', 'Armenia'), ('American Samoa', 'American Samoa'), ('Antarctica', 'Antarctica'), ('French Southern Territories', 'French Southern Territories'), ('Antigua and Barbuda', 'Antigua and Barbuda'), ('Australia', 'Australia'), ('Austria', 'Austria'), ('Azerbaijan', 'Azerbaijan'), ('Burundi', 'Burundi'), ('Belgium', 'Belgium'), ('Benin', 'Benin'), ('Bonaire, Sint Eustatius and Saba', 'Bonaire, Sint Eustatius and Saba'), ('Burkina Faso', 'Burkina Faso'), ('Bangladesh', 'Bangladesh'), ('Bulgaria', 'Bulgaria'), ('Bahrain', 'Bahrain'), ('Bahamas', 'Bahamas'), ('Bosnia and Herzegovina', 'Bosnia and Herzegovina'), ('Saint Barthélemy', 'Saint Barthélemy'), ('Belarus', 'Belarus'), ('Belize', 'Belize'), ('Bermuda', 'Bermuda'), ('Bolivia, Plurinational State of', 'Bolivia, Plurinational State of'), ('Brazil', 'Brazil'), ('Barbados', 'Barbados'), ('Brunei Darussalam', 'Brunei Darussalam'), ('Bhutan', 'Bhutan'), ('Bouvet Island', 'Bouvet Island'), ('Botswana', 'Botswana'), ('Central African Republic', 'Central African Republic'), ('Canada', 'Canada'), ('Cocos (Keeling) Islands', 'Cocos (Keeling) Islands'), ('Switzerland', 'Switzerland'), ('Chile', 'Chile'), ('China', 'China'), ("Côte d'Ivoire", "Côte d'Ivoire"), ('Cameroon', 'Cameroon'), ('Congo, The Democratic Republic of the', 'Congo, The Democratic Republic of the'), ('Congo', 'Congo'), ('Cook Islands', 'Cook Islands'), ('Colombia', 'Colombia'), ('Comoros', 'Comoros'), ('Cabo Verde', 'Cabo Verde'), ('Costa Rica', 'Costa Rica'), ('Cuba', 'Cuba'), ('Curaçao', 'Curaçao'), ('Christmas Island', 'Christmas Island'), ('Cayman Islands', 'Cayman Islands'), ('Cyprus', 'Cyprus'), ('Czechia', 'Czechia'), ('Germany', 'Germany'), ('Djibouti', 'Djibouti'), ('Dominica', 'Dominica'), ('Denmark', 'Denmark'), ('Dominican Republic', 'Dominican Republic'), ('Algeria', 'Algeria'), ('Ecuador', 'Ecuador'), ('Egypt', 'Egypt'), ('Eritrea', 'Eritrea'), ('Western Sahara', 'Western Sahara'), ('Spain', 'Spain'), ('Estonia', 'Estonia'), ('Ethiopia', 'Ethiopia'), ('Finland', 'Finland'), ('Fiji', 'Fiji'), ('Falkland Islands (Malvinas)', 'Falkland Islands (Malvinas)'), ('France', 'France'), ('Faroe Islands', 'Faroe Islands'), ('Micronesia, Federated States of', 'Micronesia, Federated States of'), ('Gabon', 'Gabon'), ('United Kingdom', 'United Kingdom'), ('Georgia', 'Georgia'), ('Guernsey', 'Guernsey'), ('Ghana', 'Ghana'), ('Gibraltar', 'Gibraltar'), ('Guinea', 'Guinea'), ('Guadeloupe', 'Guadeloupe'), ('Gambia', 'Gambia'), ('Guinea-Bissau', 'Guinea-Bissau'), ('Equatorial Guinea', 'Equatorial Guinea'), ('Greece', 'Greece'), ('Grenada', 'Grenada'), ('Greenland', 'Greenland'), ('Guatemala', 'Guatemala'), ('French Guiana', 'French Guiana'), ('Guam', 'Guam'), ('Guyana', 'Guyana'), ('Hong Kong', 'Hong Kong'), ('Heard Island and McDonald Islands', 'Heard Island and McDonald Islands'), ('Honduras', 'Honduras'), ('Croatia', 'Croatia'), ('Haiti', 'Haiti'), ('Hungary', 'Hungary'), ('Indonesia', 'Indonesia'), ('Isle of Man', 'Isle of Man'), ('India', 'India'), ('British Indian Ocean Territory', 'British Indian Ocean Territory'), ('Ireland', 'Ireland'), ('Iran, Islamic Republic of', 'Iran, Islamic Republic of'), ('Iraq', 'Iraq'), ('Iceland', 'Iceland'), ('Israel', 'Israel'), ('Italy', 'Italy'), ('Jamaica', 'Jamaica'), ('Jersey', 'Jersey'), ('Jordan', 'Jordan'), ('Japan', 'Japan'), ('Kazakhstan', 'Kazakhstan'), ('Kenya', 'Kenya'), ('Kyrgyzstan', 'Kyrgyzstan'), ('Cambodia', 'Cambodia'), ('Kiribati', 'Kiribati'), ('Saint Kitts and Nevis', 'Saint Kitts and Nevis'), ('Korea, Republic of', 'Korea, Republic of'), ('Kuwait', 'Kuwait'), ("Lao People's Democratic Republic", "Lao People's Democratic Republic"), ('Lebanon', 'Lebanon'), ('Liberia', 'Liberia'), ('Libya', 'Libya'), ('Saint Lucia', 'Saint Lucia'), ('Liechtenstein', 'Liechtenstein'), ('Sri Lanka', 'Sri Lanka'), ('Lesotho', 'Lesotho'), ('Lithuania', 'Lithuania'), ('Luxembourg', 'Luxembourg'), ('Latvia', 'Latvia'), ('Macao', 'Macao'), ('Saint Martin (French part)', 'Saint Martin (French part)'), ('Morocco', 'Morocco'), ('Monaco', 'Monaco'), ('Moldova, Republic of', 'Moldova, Republic of'), ('Madagascar', 'Madagascar'), ('Maldives', 'Maldives'), ('Mexico', 'Mexico'), ('Marshall Islands', 'Marshall Islands'), ('North Macedonia', 'North Macedonia'), ('Mali', 'Mali'), ('Malta', 'Malta'), ('Myanmar', 'Myanmar'), ('Montenegro', 'Montenegro'), ('Mongolia', 'Mongolia'), ('Northern Mariana Islands', 'Northern Mariana Islands'), ('Mozambique', 'Mozambique'), ('Mauritania', 'Mauritania'), ('Montserrat', 'Montserrat'), ('Martinique', 'Martinique'), ('Mauritius', 'Mauritius'), ('Malawi', 'Malawi'), ('Malaysia', 'Malaysia'), ('Mayotte', 'Mayotte'), ('Namibia', 'Namibia'), ('New Caledonia', 'New Caledonia'), ('Niger', 'Niger'), ('Norfolk Island', 'Norfolk Island'), ('Nigeria', 'Nigeria'), ('Nicaragua', 'Nicaragua'), ('Niue', 'Niue'), ('Netherlands', 'Netherlands'), ('Norway', 'Norway'), ('Nepal', 'Nepal'), ('Nauru', 'Nauru'), ('New Zealand', 'New Zealand'), ('Oman', 'Oman'), ('Pakistan', 'Pakistan'), ('Panama', 'Panama'), ('Pitcairn', 'Pitcairn'), ('Peru', 'Peru'), ('Philippines', 'Philippines'), ('Palau', 'Palau'), ('Papua New Guinea', 'Papua New Guinea'), ('Poland', 'Poland'), ('Puerto Rico', 'Puerto Rico'), ("Korea, Democratic People's Republic of", "Korea, Democratic People's Republic of"), ('Portugal', 'Portugal'), ('Paraguay', 'Paraguay'), ('Palestine, State of', 'Palestine, State of'), ('French Polynesia', 'French Polynesia'), ('Qatar', 'Qatar'), ('Réunion', 'Réunion'), ('Romania', 'Romania'), ('Russian Federation', 'Russian Federation'), ('Rwanda', 'Rwanda'), ('Saudi Arabia', 'Saudi Arabia'), ('Sudan', 'Sudan'), ('Senegal', 'Senegal'), ('Singapore', 'Singapore'), ('South Georgia and the South Sandwich Islands', 'South Georgia and the South Sandwich Islands'), ('Saint Helena, Ascension and Tristan da Cunha', 'Saint Helena, Ascension and Tristan da Cunha'), ('Svalbard and Jan Mayen', 'Svalbard and Jan Mayen'), ('Solomon Islands', 'Solomon Islands'), ('Sierra Leone', 'Sierra Leone'), ('El Salvador', 'El Salvador'), ('San Marino', 'San Marino'), ('Somalia', 'Somalia'), ('Saint Pierre and Miquelon', 'Saint Pierre and Miquelon'), ('Serbia', 'Serbia'), ('South Sudan', 'South Sudan'), ('Sao Tome and Principe', 'Sao Tome and Principe'), ('Suriname', 'Suriname'), ('Slovakia', 'Slovakia'), ('Slovenia', 'Slovenia'), ('Sweden', 'Sweden'), ('Eswatini', 'Eswatini'), ('Sint Maarten (Dutch part)', 'Sint Maarten (Dutch part)'), ('Seychelles', 'Seychelles'), ('Syrian Arab Republic', 'Syrian Arab Republic'), ('Turks and Caicos Islands', 'Turks and Caicos Islands'), ('Chad', 'Chad'), ('Togo', 'Togo'), ('Thailand', 'Thailand'), ('Tajikistan', 'Tajikistan'), ('Tokelau', 'Tokelau'), ('Turkmenistan', 'Turkmenistan'), ('Timor-Leste', 'Timor-Leste'), ('Tonga', 'Tonga'), ('Trinidad and Tobago', 'Trinidad and Tobago'), ('Tunisia', 'Tunisia'), ('Turkey', 'Turkey'), ('Tuvalu', 'Tuvalu'), ('Taiwan, Province of China', 'Taiwan, Province of China'), ('Tanzania, United Republic of', 'Tanzania, United Republic of'), ('Uganda', 'Uganda'), ('Ukraine', 'Ukraine'), ('United States Minor Outlying Islands', 'United States Minor Outlying Islands'), ('Uruguay', 'Uruguay'), ('United States', 'United States'), ('Uzbekistan', 'Uzbekistan'), ('Holy See (Vatican City State)', 'Holy See (Vatican City State)'), ('Saint Vincent and the Grenadines', 'Saint Vincent and the Grenadines'), ('Venezuela, Bolivarian Republic of', 'Venezuela, Bolivarian Republic of'), ('Virgin Islands, British', 'Virgin Islands, British'), ('Virgin Islands, U.S.', 'Virgin Islands, U.S.'), ('Viet Nam', 'Viet Nam'), ('Vanuatu', 'Vanuatu'), ('Wallis and Futuna', 'Wallis and Futuna'), ('Samoa', 'Samoa'), ('Yemen', 'Yemen'), ('South Africa', 'South Africa'), ('Zambia', 'Zambia'), ('Zimbabwe', 'Zimbabwe')], max_length=30, null=True, verbose_name='Luogo di nascita')), - ('birth_date', models.DateField(blank=True, null=True, verbose_name='Data di nascita')), - ('origin', models.CharField(blank=True, max_length=254, null=True, verbose_name='from which conenctor this user come from')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name_plural': 'Users', - 'ordering': ['username'], - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - migrations.CreateModel( - name='PersistentId', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('persistent_id', models.CharField(blank=True, max_length=254, null=True, verbose_name='SAML Persistent Stored ID')), - ('recipient_id', models.CharField(blank=True, max_length=254, null=True, verbose_name='SAML ServiceProvider entityID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Persistent Id', - 'verbose_name_plural': 'Persistent Id', - }, - ), - ] diff --git a/example/django_op/example/accounts/migrations/0002_auto_20191202_1526.py b/example/django_op/example/accounts/migrations/0002_auto_20191202_1526.py deleted file mode 100644 index 57e6923c..00000000 --- a/example/django_op/example/accounts/migrations/0002_auto_20191202_1526.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0 on 2019-12-02 15:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='place_of_birth', - field=models.CharField(blank=True, choices=[('Aruba', 'Aruba'), ('Afghanistan', 'Afghanistan'), ('Angola', 'Angola'), ('Anguilla', 'Anguilla'), ('Åland Islands', 'Åland Islands'), ('Albania', 'Albania'), ('Andorra', 'Andorra'), ('United Arab Emirates', 'United Arab Emirates'), ('Argentina', 'Argentina'), ('Armenia', 'Armenia'), ('American Samoa', 'American Samoa'), ('Antarctica', 'Antarctica'), ('French Southern Territories', 'French Southern Territories'), ('Antigua and Barbuda', 'Antigua and Barbuda'), ('Australia', 'Australia'), ('Austria', 'Austria'), ('Azerbaijan', 'Azerbaijan'), ('Burundi', 'Burundi'), ('Belgium', 'Belgium'), ('Benin', 'Benin'), ('Bonaire, Sint Eustatius and Saba', 'Bonaire, Sint Eustatius and Saba'), ('Burkina Faso', 'Burkina Faso'), ('Bangladesh', 'Bangladesh'), ('Bulgaria', 'Bulgaria'), ('Bahrain', 'Bahrain'), ('Bahamas', 'Bahamas'), ('Bosnia and Herzegovina', 'Bosnia and Herzegovina'), ('Saint Barthélemy', 'Saint Barthélemy'), ('Belarus', 'Belarus'), ('Belize', 'Belize'), ('Bermuda', 'Bermuda'), ('Bolivia, Plurinational State of', 'Bolivia, Plurinational State of'), ('Brazil', 'Brazil'), ('Barbados', 'Barbados'), ('Brunei Darussalam', 'Brunei Darussalam'), ('Bhutan', 'Bhutan'), ('Bouvet Island', 'Bouvet Island'), ('Botswana', 'Botswana'), ('Central African Republic', 'Central African Republic'), ('Canada', 'Canada'), ('Cocos (Keeling) Islands', 'Cocos (Keeling) Islands'), ('Switzerland', 'Switzerland'), ('Chile', 'Chile'), ('China', 'China'), ("Côte d'Ivoire", "Côte d'Ivoire"), ('Cameroon', 'Cameroon'), ('Congo, The Democratic Republic of the', 'Congo, The Democratic Republic of the'), ('Congo', 'Congo'), ('Cook Islands', 'Cook Islands'), ('Colombia', 'Colombia'), ('Comoros', 'Comoros'), ('Cabo Verde', 'Cabo Verde'), ('Costa Rica', 'Costa Rica'), ('Cuba', 'Cuba'), ('Curaçao', 'Curaçao'), ('Christmas Island', 'Christmas Island'), ('Cayman Islands', 'Cayman Islands'), ('Cyprus', 'Cyprus'), ('Czechia', 'Czechia'), ('Germany', 'Germany'), ('Djibouti', 'Djibouti'), ('Dominica', 'Dominica'), ('Denmark', 'Denmark'), ('Dominican Republic', 'Dominican Republic'), ('Algeria', 'Algeria'), ('Ecuador', 'Ecuador'), ('Egypt', 'Egypt'), ('Eritrea', 'Eritrea'), ('Western Sahara', 'Western Sahara'), ('Spain', 'Spain'), ('Estonia', 'Estonia'), ('Ethiopia', 'Ethiopia'), ('Finland', 'Finland'), ('Fiji', 'Fiji'), ('Falkland Islands (Malvinas)', 'Falkland Islands (Malvinas)'), ('France', 'France'), ('Faroe Islands', 'Faroe Islands'), ('Micronesia, Federated States of', 'Micronesia, Federated States of'), ('Gabon', 'Gabon'), ('United Kingdom', 'United Kingdom'), ('Georgia', 'Georgia'), ('Guernsey', 'Guernsey'), ('Ghana', 'Ghana'), ('Gibraltar', 'Gibraltar'), ('Guinea', 'Guinea'), ('Guadeloupe', 'Guadeloupe'), ('Gambia', 'Gambia'), ('Guinea-Bissau', 'Guinea-Bissau'), ('Equatorial Guinea', 'Equatorial Guinea'), ('Greece', 'Greece'), ('Grenada', 'Grenada'), ('Greenland', 'Greenland'), ('Guatemala', 'Guatemala'), ('French Guiana', 'French Guiana'), ('Guam', 'Guam'), ('Guyana', 'Guyana'), ('Hong Kong', 'Hong Kong'), ('Heard Island and McDonald Islands', 'Heard Island and McDonald Islands'), ('Honduras', 'Honduras'), ('Croatia', 'Croatia'), ('Haiti', 'Haiti'), ('Hungary', 'Hungary'), ('Indonesia', 'Indonesia'), ('Isle of Man', 'Isle of Man'), ('India', 'India'), ('British Indian Ocean Territory', 'British Indian Ocean Territory'), ('Ireland', 'Ireland'), ('Iran, Islamic Republic of', 'Iran, Islamic Republic of'), ('Iraq', 'Iraq'), ('Iceland', 'Iceland'), ('Israel', 'Israel'), ('Italy', 'Italy'), ('Jamaica', 'Jamaica'), ('Jersey', 'Jersey'), ('Jordan', 'Jordan'), ('Japan', 'Japan'), ('Kazakhstan', 'Kazakhstan'), ('Kenya', 'Kenya'), ('Kyrgyzstan', 'Kyrgyzstan'), ('Cambodia', 'Cambodia'), ('Kiribati', 'Kiribati'), ('Saint Kitts and Nevis', 'Saint Kitts and Nevis'), ('Korea, Republic of', 'Korea, Republic of'), ('Kuwait', 'Kuwait'), ("Lao People's Democratic Republic", "Lao People's Democratic Republic"), ('Lebanon', 'Lebanon'), ('Liberia', 'Liberia'), ('Libya', 'Libya'), ('Saint Lucia', 'Saint Lucia'), ('Liechtenstein', 'Liechtenstein'), ('Sri Lanka', 'Sri Lanka'), ('Lesotho', 'Lesotho'), ('Lithuania', 'Lithuania'), ('Luxembourg', 'Luxembourg'), ('Latvia', 'Latvia'), ('Macao', 'Macao'), ('Saint Martin (French part)', 'Saint Martin (French part)'), ('Morocco', 'Morocco'), ('Monaco', 'Monaco'), ('Moldova, Republic of', 'Moldova, Republic of'), ('Madagascar', 'Madagascar'), ('Maldives', 'Maldives'), ('Mexico', 'Mexico'), ('Marshall Islands', 'Marshall Islands'), ('North Macedonia', 'North Macedonia'), ('Mali', 'Mali'), ('Malta', 'Malta'), ('Myanmar', 'Myanmar'), ('Montenegro', 'Montenegro'), ('Mongolia', 'Mongolia'), ('Northern Mariana Islands', 'Northern Mariana Islands'), ('Mozambique', 'Mozambique'), ('Mauritania', 'Mauritania'), ('Montserrat', 'Montserrat'), ('Martinique', 'Martinique'), ('Mauritius', 'Mauritius'), ('Malawi', 'Malawi'), ('Malaysia', 'Malaysia'), ('Mayotte', 'Mayotte'), ('Namibia', 'Namibia'), ('New Caledonia', 'New Caledonia'), ('Niger', 'Niger'), ('Norfolk Island', 'Norfolk Island'), ('Nigeria', 'Nigeria'), ('Nicaragua', 'Nicaragua'), ('Niue', 'Niue'), ('Netherlands', 'Netherlands'), ('Norway', 'Norway'), ('Nepal', 'Nepal'), ('Nauru', 'Nauru'), ('New Zealand', 'New Zealand'), ('Oman', 'Oman'), ('Pakistan', 'Pakistan'), ('Panama', 'Panama'), ('Pitcairn', 'Pitcairn'), ('Peru', 'Peru'), ('Philippines', 'Philippines'), ('Palau', 'Palau'), ('Papua New Guinea', 'Papua New Guinea'), ('Poland', 'Poland'), ('Puerto Rico', 'Puerto Rico'), ("Korea, Democratic People's Republic of", "Korea, Democratic People's Republic of"), ('Portugal', 'Portugal'), ('Paraguay', 'Paraguay'), ('Palestine, State of', 'Palestine, State of'), ('French Polynesia', 'French Polynesia'), ('Qatar', 'Qatar'), ('Réunion', 'Réunion'), ('Romania', 'Romania'), ('Russian Federation', 'Russian Federation'), ('Rwanda', 'Rwanda'), ('Saudi Arabia', 'Saudi Arabia'), ('Sudan', 'Sudan'), ('Senegal', 'Senegal'), ('Singapore', 'Singapore'), ('South Georgia and the South Sandwich Islands', 'South Georgia and the South Sandwich Islands'), ('Saint Helena, Ascension and Tristan da Cunha', 'Saint Helena, Ascension and Tristan da Cunha'), ('Svalbard and Jan Mayen', 'Svalbard and Jan Mayen'), ('Solomon Islands', 'Solomon Islands'), ('Sierra Leone', 'Sierra Leone'), ('El Salvador', 'El Salvador'), ('San Marino', 'San Marino'), ('Somalia', 'Somalia'), ('Saint Pierre and Miquelon', 'Saint Pierre and Miquelon'), ('Serbia', 'Serbia'), ('South Sudan', 'South Sudan'), ('Sao Tome and Principe', 'Sao Tome and Principe'), ('Suriname', 'Suriname'), ('Slovakia', 'Slovakia'), ('Slovenia', 'Slovenia'), ('Sweden', 'Sweden'), ('Eswatini', 'Eswatini'), ('Sint Maarten (Dutch part)', 'Sint Maarten (Dutch part)'), ('Seychelles', 'Seychelles'), ('Syrian Arab Republic', 'Syrian Arab Republic'), ('Turks and Caicos Islands', 'Turks and Caicos Islands'), ('Chad', 'Chad'), ('Togo', 'Togo'), ('Thailand', 'Thailand'), ('Tajikistan', 'Tajikistan'), ('Tokelau', 'Tokelau'), ('Turkmenistan', 'Turkmenistan'), ('Timor-Leste', 'Timor-Leste'), ('Tonga', 'Tonga'), ('Trinidad and Tobago', 'Trinidad and Tobago'), ('Tunisia', 'Tunisia'), ('Turkey', 'Turkey'), ('Tuvalu', 'Tuvalu'), ('Taiwan, Province of China', 'Taiwan, Province of China'), ('Tanzania, United Republic of', 'Tanzania, United Republic of'), ('Uganda', 'Uganda'), ('Ukraine', 'Ukraine'), ('United States Minor Outlying Islands', 'United States Minor Outlying Islands'), ('Uruguay', 'Uruguay'), ('United States', 'United States'), ('Uzbekistan', 'Uzbekistan'), ('Holy See (Vatican City State)', 'Holy See (Vatican City State)'), ('Saint Vincent and the Grenadines', 'Saint Vincent and the Grenadines'), ('Venezuela, Bolivarian Republic of', 'Venezuela, Bolivarian Republic of'), ('Virgin Islands, British', 'Virgin Islands, British'), ('Virgin Islands, U.S.', 'Virgin Islands, U.S.'), ('Viet Nam', 'Viet Nam'), ('Vanuatu', 'Vanuatu'), ('Wallis and Futuna', 'Wallis and Futuna'), ('Samoa', 'Samoa'), ('Yemen', 'Yemen'), ('South Africa', 'South Africa'), ('Zambia', 'Zambia'), ('Zimbabwe', 'Zimbabwe')], max_length=56, null=True, verbose_name='Luogo di nascita'), - ), - ] diff --git a/example/django_op/example/accounts/migrations/__init__.py b/example/django_op/example/accounts/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/example/django_op/example/accounts/models.py b/example/django_op/example/accounts/models.py deleted file mode 100644 index bc994780..00000000 --- a/example/django_op/example/accounts/models.py +++ /dev/null @@ -1,82 +0,0 @@ -import pycountry - -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import AbstractUser -from django.contrib.auth import get_user_model -from django.contrib.contenttypes.models import ContentType -from django.conf import settings - - -class User(AbstractUser): - GENDER= ( - ( 'male', _('Maschio')), - ( 'female', _('Femmina')), - ( 'other', _('Altro')), - ) - - first_name = models.CharField(_('Name'), max_length=30, - blank=True, null=True) - last_name = models.CharField(_('Surname'), max_length=30, - blank=True, null=True) - is_active = models.BooleanField(_('active'), default=True) - email = models.EmailField('email address', blank=True, null=True) - taxpayer_id = models.CharField(_('Taxpayer\'s identification number'), - max_length=32, - blank=True, null=True) - gender = models.CharField(_('Genere'), choices=GENDER, - max_length=12, blank=True, null=True) - place_of_birth = models.CharField('Luogo di nascita', max_length=56, - blank=True, null=True, - choices=[(i.name, i.name) for i in pycountry.countries]) - birth_date = models.DateField('Data di nascita', - null=True, blank=True) - origin = models.CharField(_('from which conenctor this user come from'), - max_length=254, - blank=True, null=True) - - class Meta: - ordering = ['username'] - verbose_name_plural = _("Users") - - @property - def uid(self): - return self.username.split('@')[0] - - def persistent_id(self, entityid): - """ returns persistent id related to a recipient (sp) entity id - """ - pid = PersistentId.objects.filter(user=self, - recipient_id=entityid).last() - if pid: - return pid.persistent_id - - def get_oidc_birthdate(self): - return self.birth_date.strftime('%Y-%m-%d') if self.birth_date else '' - - def get_oidc_lastlogin(self): - return self.last_login.timestamp() if self.last_login else '' - - def __str__(self): - return '{} {}'.format(self.first_name, self.last_name) - - -class PersistentId(models.Model): - user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) - persistent_id = models.CharField(_('SAML Persistent Stored ID'), - max_length=254, - blank=True, null=True) - recipient_id = models.CharField(_('SAML ServiceProvider entityID'), - max_length=254, - blank=True, null=True) - created = models.DateTimeField(auto_now_add=True) - - class Meta: - verbose_name = _('Persistent Id') - verbose_name_plural = _('Persistent Id') - - def __str__(self): - return '{}: {} to {} [{}]'.format(self.user, - self.persistent_id, - self.recipient_id, - self.created) diff --git a/example/django_op/example/accounts/templatetags/__init__.py b/example/django_op/example/accounts/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/example/django_op/example/accounts/templatetags/has_group.py b/example/django_op/example/accounts/templatetags/has_group.py deleted file mode 100644 index aa402dab..00000000 --- a/example/django_op/example/accounts/templatetags/has_group.py +++ /dev/null @@ -1,9 +0,0 @@ -from django import template -from django.contrib.auth.models import Group - -register = template.Library() - -@register.filter(name='has_group') -def has_group(user, group_name): - group = Group.objects.get(name=group_name) - return group in user.groups.all() diff --git a/example/django_op/example/accounts/tests.py b/example/django_op/example/accounts/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/example/django_op/example/accounts/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/example/django_op/example/accounts/urls.py b/example/django_op/example/accounts/urls.py deleted file mode 100644 index 7bd77a54..00000000 --- a/example/django_op/example/accounts/urls.py +++ /dev/null @@ -1,26 +0,0 @@ -"""betaCRM URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.urls import path -from .views import * - -app_name="accounts" - -urlpatterns = [ - - # url(r'^login/$', Login, name='login'), - # path('logout', Logout, name='logout'), - -] diff --git a/example/django_op/example/accounts/views.py b/example/django_op/example/accounts/views.py deleted file mode 100644 index 3050ccaf..00000000 --- a/example/django_op/example/accounts/views.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseNotFound - -from django.shortcuts import render -from django.contrib.auth.decorators import login_required -from django.shortcuts import get_object_or_404 -from .models import * -from .forms import * - -from django.utils.translation import ugettext_lazy as _ -from django.core.exceptions import ValidationError - -from django.template import RequestContext -from django.core.urlresolvers import reverse - -from django.contrib.auth import authenticate, login, logout -# from dal import autocomplete -# -# class UserAutocomplete(autocomplete.Select2QuerySetView): - # def get_queryset(self): - # if not self.request.user.is_authenticated(): - # return User.objects.none() - # qs = User.objects.all() - # if self.q: - # qs = qs.filter( - # username__icontains=self.q - # ) - # return qs diff --git a/example/django_op/example/data/oidc_op/certs/cert.pem b/example/django_op/example/data/oidc_op/certs/cert.pem deleted file mode 100644 index 210fd240..00000000 --- a/example/django_op/example/data/oidc_op/certs/cert.pem +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFUDCCAzigAwIBAgIJAJWgBcizyJrFMA0GCSqGSIb3DQEBCwUAMD0xCzAJBgNV -BAYTAlNFMQ0wCwYDVQQKDARPSURGMR8wHQYDVQQDDBZGZWQgYXdhcmUgUlAgdGVz -dCB0b29sMB4XDTE3MDIxMzE5MDg1MloXDTE4MDIxMzE5MDg1MlowPTELMAkGA1UE -BhMCU0UxDTALBgNVBAoMBE9JREYxHzAdBgNVBAMMFkZlZCBhd2FyZSBSUCB0ZXN0 -IHRvb2wwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC3NrEL+VKs00NT -R+ZpGRxvDoeLhD7EM+uf7IqHl6IN3H6pflAOE8YqnTepdglhGH4a7nyftINTZjDU -86anR+OKPoY2Padf4E+YceJOcaT6lB5XOWxBu4j3wDRHb6jMUwMDUXHsmh389Bvx -X44KSYe/mhjkrIV8bolhT9NpNjPVUdUvpwpSxDOhSjq7BCmfdvXJrNNYElEQaDSc -yJ4h6BAOp/FfdnWKAeiVDpIF5QqZgr0gzKiV5LEvwsNfHynsLgrlgK2+Fd8qIqbC -/fHtB1BEL3h01dlBR1Y4ocMM5we23Phe4lwQs8QojPTnnr14fWynrjNi0Km0TcMT -TDHVnw5qO5dSr4LpBcfIo82YWpj6lTEKQwKin+SPz0k0kD4E83rtsGp8n3FWHVAo -BsIJ4O58REi3YTh1NCe/bjsQWiFOPW0N9GOl0UTOUj90cGVbO9i91aDFHHQWOIiA -VsmZ35yOjQ031It9Kzv4YcmWXQcdKYnzUQ5eSXZPmJFoebKgQF6neFlg1hp6uDKi -NRxkaPWGVCVZXPmgRwVcFdbxI8OpNqPEFQGskUPGJS5CF1o8o6wuVwPSSwxDVoYM -12TTdATH1he4cK69ej/1F2oHCVQ0KE46fNABaxNKxGls0bPPPJBPrQBjoAR2qxgg -iFz2DjumVC3EySwXLsH4tXTjyuVbSQIDAQABo1MwUTAdBgNVHQ4EFgQUiD0bTabj -Q0Pf0vVJneGr5TQRO+4wHwYDVR0jBBgwFoAUiD0bTabjQ0Pf0vVJneGr5TQRO+4w -DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAnYCE5MdqVXHBxMGZ -1bIZxwLg9pe5poaX7l7XGdXxnBKWxfqwCx2UHQZIBdV3eIt8lgtuOL1en9ZCHAIY -X0OZCafQ1Jzx3nXV4qOoolfmri0DQs60LPozoXKW61mah8fFhf/XdjuZxYH+XVV6 -39E08MY4ZWDzzNoDe5zhGWw+IOfowx5wNTtZ8CipWUv4FiO9cUZJ/1hnJgE0CQNH -v4v0g0lIuWs7eArbzvxTu3jHWx/+eYvl2TSYxEHpVulbesnI27M34nS0OePqbywO -eGBtM65UuCCBh27FO+O7qJWA3sRPuw/cll0vi69WVYHO5rk7yji1hiTT2MKTEizP -GmdT/FXG4nEsM6WaEe4FMJN6cZf49BUzRcEdW6k8i2YIysHf8fi3Xv1JF74OB5bF -TogV/Fu/LzXsfA/XTj9ki0hUNmueyNT/xBD5tOH4FqHQvMWpjpzfwI90ENVeY+Ad -BCU2Ck1HBEuUhUNaC1d6QkU6pn3voPvaWK49+T9NyrFVMNHVWHeLUHJ/i9kgWXLl -TgAbTCmnJOHTxxCVCf40EjOpPR3hlCadYr8vOGyuHPk1M2Lppgh2kQtFX5ubhhfW -IKP5TPKuZlu3z9RjfUvIxqWC6cbwjlOGIx2K0uCnIbpTzTuaLHJSWWRUpDzNL6lg -V620B7/n1jo2JDudjhjD2uLekJg= ------END CERTIFICATE----- diff --git a/example/django_op/example/data/oidc_op/certs/key.pem b/example/django_op/example/data/oidc_op/certs/key.pem deleted file mode 100644 index 15449664..00000000 --- a/example/django_op/example/data/oidc_op/certs/key.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC3NrEL+VKs00NT -R+ZpGRxvDoeLhD7EM+uf7IqHl6IN3H6pflAOE8YqnTepdglhGH4a7nyftINTZjDU -86anR+OKPoY2Padf4E+YceJOcaT6lB5XOWxBu4j3wDRHb6jMUwMDUXHsmh389Bvx -X44KSYe/mhjkrIV8bolhT9NpNjPVUdUvpwpSxDOhSjq7BCmfdvXJrNNYElEQaDSc -yJ4h6BAOp/FfdnWKAeiVDpIF5QqZgr0gzKiV5LEvwsNfHynsLgrlgK2+Fd8qIqbC -/fHtB1BEL3h01dlBR1Y4ocMM5we23Phe4lwQs8QojPTnnr14fWynrjNi0Km0TcMT -TDHVnw5qO5dSr4LpBcfIo82YWpj6lTEKQwKin+SPz0k0kD4E83rtsGp8n3FWHVAo -BsIJ4O58REi3YTh1NCe/bjsQWiFOPW0N9GOl0UTOUj90cGVbO9i91aDFHHQWOIiA -VsmZ35yOjQ031It9Kzv4YcmWXQcdKYnzUQ5eSXZPmJFoebKgQF6neFlg1hp6uDKi -NRxkaPWGVCVZXPmgRwVcFdbxI8OpNqPEFQGskUPGJS5CF1o8o6wuVwPSSwxDVoYM -12TTdATH1he4cK69ej/1F2oHCVQ0KE46fNABaxNKxGls0bPPPJBPrQBjoAR2qxgg -iFz2DjumVC3EySwXLsH4tXTjyuVbSQIDAQABAoICAQCoZ801hGdKFKa91kkkMcDB -FEnjJBvNnSvoRDTRjb+XniWPBlvvlJ2CbiDL04OrjCfd+Xj0E6ji7/vSwmNdP+cX -G4GiOemvZy/CoGu0TyGmcp+w7Udk5Exx7moff7NYnLUYR7TAFqmZ6YgFxh95tTzi -EXLwPuQ0DCabHBTnkLr0SdP7iT8j9NTAXMq/PIRF38LtLb7WJX/95Mr3kjBIWlbo -IdbsOKaxxC9VU59Fa9LiaBoQHA6aOSvlCtEqjiqqvWemrTEGmHQY9uDyOxo1FZPi -GQBP5IFeT4Qhag8vvOyKWXKzRL37XEHiRC6Y+ICQUDmfp6/0FHjpEtFM26yy/xDv -ZtL7/b7TEQMmp2CWD8WV8a9oalTRqyrGTBeeSg6CV5tnx3wnM0krkCvJ+Eadki23 -Wp34s7v8NPmVMTqG/UIW21tmzb40KjXNI8MgNXASBIKm9W2z2xXQ07xELsSfWm9O -p0umh1xHLqX7rNmigg/odW3K9aocF8NOhuc4aYgVZH18sMhkhja3dgwCe8YSImyW -0uHZC6wKIXnD44lS2BmdYsIY/k+uZKNum6lE7x/F1V2vbzkzShuJ7VCD3IhQW6nK -XNQBXju/CnMiMW6mpZsSZG8mIjx8hNKLYv492ZNgnbeP2HHM5WAsKTOKLO0FldFS -sbRSXTTM40j7AcurS7DKQQKCAQEA2WdkRhGXOuOlHSq/W4YZ6Mq2kydp46ARQS8b -zKbUXX6+7GyU6TSB71eblP4003NGx8rdasyZTpexRH4sTKv0/GLM2eSDEi7/GV1w -HISwdIa8NlHiOT9qPONqdhH0KDy5lDrCTMa9B5QpbYo4l/F/4O52zJc1CuRacpyi -58hY3Me2UND2yHqb2TKxOwwHumE8FEMs9CqixLE4oAaoiNdJi08pyg7o/6oxPaUE -CKmGX6r9eW5piFCLGAkmfAgBjYejrFDAp8eY6Yx5dRWMdLddQnm/5tl0rzFho+71 -UwtOIZtowKeWms1N/+duOmcfYyDsRJ/Ec3pzxphzcHrWEllP9wKCAQEA171qkSxv -+53viIJbaJ636emDg4kZ3asGLODefEcbe0XS0xHmsb+WZpRIBkNMJFj3k2IYcUSO -7DObemF4ln9CJY+DxHZJzr/mo8T3X0yt0aK8O75+fXHQ/991kUMcx21BmXMjybYj -TA5vv956AYV9Kt37ye87dYMtEINtchdukYqyrLZ9+0lBV1XrGKALMC68EyyTtDFs -AtJzKVTYnKNkYFWkA6cq+GZvlEbx74dZopH/yVo+P/wGiU5AH1bq5847uq5LIwIU -j2ZkKBJr8Y3YvFjAaRNRGNXOhHUo3BPkgkYZGnC2WP9UJT3w7PgjwyUpbFZurwIr -Sz1QdbNZ+spevwKCAQBgyN6jMwGYfe/r5DP8kt7F/Dj7mfhSFdiYpFhD66FvXhWx -O0Wv7GhMHTxuQB1UZWWFXJLmEN/PVUjdrS4blBIkqfd4qXqQhcubhzV5/Lhxp+ny -ZNHJmqm5IaUrmyKPJzmW+/G0LGXLEfK/iWFYg3LiuEa7HjXG+5IopAMCHPcyktZf -dCfpaGwpbZ/pIZnvJ4qPmrhQmwqLdjo3Q7+T7AQZuMxp3+lqqGHzh5scIBxqSr09 -aiIhRXom4Sv427eVQmVjOTALgZhZoOgRb95vt5IVHg6IvxZrSBin2qHsroPCAmXI -HtO1ZuDqpCU2auJWRznn8xiKMGGKcCQ0VvsmgAxRAoIBAQDPsB7OQRxQ+3skTHIZ -Jmrg+ZdM4oiPGFyqiZRFyeKP6ukJnvsadNkiSW+I7/J2L1uve8kSCbEZfJkZ2InR -QBN6u01brZBiQ+WSFUUbbmMLJIHXdgypUQ+ltAanYBdteSWkxu5V+kzCpEc6S7/i -hRK5WNhTT0ZLW4vfkNak9h/QZtiZYlmntp77p8/adgAvU15liw1qdAWKNfT9fhvF -t5ojD28EwUKhvWN/OEkikYdd9PVsbr7ss//K4RTj1rXvkF952N6mhhMq9aRH22wl -L6vNrhcVUK5KnVHhvDQoodHjA/6YsJcq2Cq2a4nrZvpum/DjxdVqD0mEdjNmC9H8 -mCNbAoIBAHbkApjatORw6Bb+zAbfLs2vKLMs0sVABmA2AzTukm8+k3Clji4npGxh -IGj4c2kBa93yOd25qONoNvFfcig+LbCnq5aT8qSLTl7iecRNvvAlxA1r7MHRqjYO -bFGAM5cCZC+hpOmXF80IOmQMfaV33tCHJ0uf1fOvkreAQxPOJqEskYGFHqN8zfeW -zsSMnea+oHvfAhHmQcikJV/YiomYb0Urz838o5o+JLTkBs+miwPNTZW5iVEnYLUh -NtABZU3c1ohXAw8i4Z/Jdmxzsro75D3ekRfa/coPCcnUK0MqYd8C/uEVe5rgXOWZ -Svp9rK9sO9LqfKBeV9NKW9/wb/X6lU4= ------END PRIVATE KEY----- diff --git a/example/django_op/example/data/oidc_rp/conf.django.yaml b/example/django_op/example/data/oidc_rp/conf.django.yaml deleted file mode 100644 index 699a6f89..00000000 --- a/example/django_op/example/data/oidc_rp/conf.django.yaml +++ /dev/null @@ -1,159 +0,0 @@ -logging: - version: 1 - disable_existing_loggers: False - root: - handlers: - - default - - console - level: DEBUG - loggers: - idp: - level: DEBUG - handlers: - default: - class: logging.FileHandler - filename: 'debug.log' - formatter: default - console: - class: logging.StreamHandler - stream: 'ext://sys.stdout' - formatter: default - formatters: - default: - format: '%(asctime)s %(name)s %(levelname)s %(message)s' - -port: 8099 -base_url: "https://127.0.0.1:8099" - -# If BASE is https these has to be specified -webserver: - port: '{port}' - server_cert: "certs/cert.pem" - server_key: "certs/key.pem" - domain: '{domain}' - -# This is just for testing an local usage. In all other cases it MUST be True -httpc_params: - verify: False - -key_defs: &keydef - - - "type": "RSA" - "key": '' - "use": ["sig"] - - - "type": "EC" - "crv": "P-256" - "use": ["sig"] - -# html_home: 'html' -# secret_key: 'secret_key' -# session_cookie_name: 'rp_session' -# preferred_url_scheme: 'https' - -rp_keys: - 'private_path': './private/jwks.json' - 'key_defs': *keydef - 'public_path': './static/jwks.json' - # this will create the jwks files if they absent - 'read_only': False - -# information used when registering the client, this may be the same for all OPs -client_preferences: &prefs - application_name: rp_test - application_type: web - contacts: - - ops@example.com - response_types: - - code - scope: - - openid - - that_scope - - profile - - email - - address - - phone - token_endpoint_auth_method: - - client_secret_basic - - client_secret_post - -services: &services - discovery: &disc - class: oidcservice.oidc.provider_info_discovery.ProviderInfoDiscovery - kwargs: {} - registration: ®ist - class: oidcservice.oidc.registration.Registration - kwargs: {} - authorization: &authz - class: oidcservice.oidc.authorization.Authorization - kwargs: {} - accesstoken: &acctok - class: oidcservice.oidc.access_token.AccessToken - kwargs: {} - userinfo: &userinfo - class: oidcservice.oidc.userinfo.UserInfo - kwargs: {} - end_session: &sess - class: oidcservice.oidc.end_session.EndSession - kwargs: {} - -clients: - # The ones that support webfinger, OP discovery and client registration - # This is the default, any client that is not listed here is expected to - # support dynamic discovery and registration. - "": - client_preferences: *prefs - redirect_uris: None - services: *services - - django_oidc_op: - client_preferences: *prefs - - # if you create a client through ADMIN UI ... - #client_id: 1UUl6cwNigmj - #client_secret: 78be88872d5877c4ddb209335f4eb2fc5118a481a195a454c8b2ebcb - # this redirect_uri must be statically configured in op's rp cdb! - redirect_uris: - - https://127.0.0.1:8099/authz_cb/django_oidc_op - issuer: https://127.0.0.1:8000/ - jwks_uri: https://127.0.0.1:8099/static/jwks.json - - services: - discovery: *disc - registration: *regist - authorization: *authz - accesstoken: *acctok - userinfo: *userinfo - end_session: *sess - add_ons: - pkce: - function: oidcservice.oidc.add_on.pkce.add_pkce_support - kwargs: - code_challenge_length: 64 - code_challenge_method: S256 - - shib_oidc_op: - client_preferences: *prefs - - # if you create a client through ADMIN UI ... - client_id: demo_rp - client_secret: topsecret2020_______ - # this redirect_uri must be statically configured in op's rp cdb! - redirect_uris: - - https://127.0.0.1:8099/authz_cb/shib_oidc_op - issuer: https://idp.testunical.it/ - jwks_uri: https://127.0.0.1:8099/static/jwks.json - - services: - discovery: *disc - registration: *regist - authorization: *authz - accesstoken: *acctok - userinfo: *userinfo - end_session: *sess - add_ons: - pkce: - function: oidcservice.oidc.add_on.pkce.add_pkce_support - kwargs: - code_challenge_length: 64 - code_challenge_method: S256 diff --git a/example/django_op/example/data/oidc_rp/conf.json b/example/django_op/example/data/oidc_rp/conf.json deleted file mode 100644 index e12541e2..00000000 --- a/example/django_op/example/data/oidc_rp/conf.json +++ /dev/null @@ -1,322 +0,0 @@ -{ - "logging": { - "version": 1, - "disable_existing_loggers": false, - "root": { - "handlers": [ - "console", - "file" - ], - "level": "DEBUG" - }, - "loggers": { - "idp": { - "level": "DEBUG" - } - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout", - "formatter": "default" - }, - "file": { - "class": "logging.FileHandler", - "filename": "debug.log", - "formatter": "default" - } - }, - "formatters": { - "default": { - "format": "%(asctime)s %(name)s %(levelname)s %(message)s" - } - } - }, - "port": 8090, - "domain": "127.0.0.1", - "base_url": "https://{domain}:{port}", - "httpc_params": { - "verify": false - }, - "keydefs": [ - { - "type": "RSA", - "key": "", - "use": [ - "sig" - ] - }, - { - "type": "EC", - "crv": "P-256", - "use": [ - "sig" - ] - } - ], - "rp_keys": { - "private_path": "private/jwks.json", - "key_defs": [ - { - "type": "RSA", - "key": "", - "use": [ - "sig" - ] - }, - { - "type": "EC", - "crv": "P-256", - "use": [ - "sig" - ] - } - ], - "public_path": "static/jwks.json", - "read_only": false - }, - "client_preferences": { - "application_name": "rphandler", - "application_type": "web", - "contacts": [ - "ops@example.com" - ], - "response_types": [ - "code" - ], - "scope": [ - "openid", - "profile", - "email", - "address", - "phone" - ], - "token_endpoint_auth_method": [ - "client_secret_basic", - "client_secret_post" - ] - }, - "services": { - "discovery": { - "class": "oidcrp.oidc.provider_info_discovery.ProviderInfoDiscovery", - "kwargs": {} - }, - "registration": { - "class": "oidcrp.oidc.registration.Registration", - "kwargs": {} - }, - "authorization": { - "class": "oidcrp.oidc.authorization.Authorization", - "kwargs": {} - }, - "accesstoken": { - "class": "oidcrp.oidc.access_token.AccessToken", - "kwargs": {} - }, - "userinfo": { - "class": "oidcrp.oidc.userinfo.UserInfo", - "kwargs": {} - }, - "end_session": { - "class": "oidcrp.oidc.end_session.EndSession", - "kwargs": {} - } - }, - "clients": { - "": { - "client_preferences": { - "application_name": "rphandler", - "application_type": "web", - "contacts": [ - "ops@example.com" - ], - "response_types": [ - "code" - ], - "scope": [ - "openid", - "profile", - "email", - "address", - "phone" - ], - "token_endpoint_auth_method": [ - "client_secret_basic", - "client_secret_post" - ] - }, - "redirect_uris": "None", - "services": { - "discovery": { - "class": "oidcrp.oidc.provider_info_discovery.ProviderInfoDiscovery", - "kwargs": {} - }, - "registration": { - "class": "oidcrp.oidc.registration.Registration", - "kwargs": {} - }, - "authorization": { - "class": "oidcrp.oidc.authorization.Authorization", - "kwargs": {} - }, - "accesstoken": { - "class": "oidcrp.oidc.access_token.AccessToken", - "kwargs": {} - }, - "userinfo": { - "class": "oidcrp.oidc.userinfo.UserInfo", - "kwargs": {} - }, - "end_session": { - "class": "oidcrp.oidc.end_session.EndSession", - "kwargs": {} - } - } - }, - "flask_provider": { - "client_preferences": { - "application_name": "rphandler", - "application_type": "web", - "contacts": [ - "ops@example.com" - ], - "response_types": [ - "code" - ], - "scope": [ - "openid", - "profile", - "email", - "address", - "phone" - ], - "token_endpoint_auth_method": [ - "client_secret_basic", - "client_secret_post" - ] - }, - "issuer": "https://127.0.0.1:5000/", - "redirect_uris": [ - "https://{domain}:{port}/authz_cb/local" - ], - "post_logout_redirect_uris": [ - "https://{domain}:{port}/session_logout/local" - ], - "frontchannel_logout_uri": "https://{domain}:{port}/fc_logout/local", - "frontchannel_logout_session_required": true, - "backchannel_logout_uri": "https://{domain}:{port}/bc_logout/local", - "backchannel_logout_session_required": true, - "services": { - "discovery": { - "class": "oidcrp.oidc.provider_info_discovery.ProviderInfoDiscovery", - "kwargs": {} - }, - "registration": { - "class": "oidcrp.oidc.registration.Registration", - "kwargs": {} - }, - "authorization": { - "class": "oidcrp.oidc.authorization.Authorization", - "kwargs": {} - }, - "accesstoken": { - "class": "oidcrp.oidc.access_token.AccessToken", - "kwargs": {} - }, - "userinfo": { - "class": "oidcrp.oidc.userinfo.UserInfo", - "kwargs": {} - }, - "end_session": { - "class": "oidcrp.oidc.end_session.EndSession", - "kwargs": {} - } - }, - "add_ons": { - "pkce": { - "function": "oidcrp.oauth2.add_on.pkce.add_support", - "kwargs": { - "code_challenge_length": 64, - "code_challenge_method": "S256" - } - } - } - }, - "django_provider": { - "client_preferences": { - "application_name": "rphandler", - "application_type": "web", - "contacts": [ - "ops@example.com" - ], - "response_types": [ - "code" - ], - "scope": [ - "openid", - "profile", - "email", - "address", - "phone" - ], - "token_endpoint_auth_method": [ - "client_secret_basic", - "client_secret_post" - ] - }, - "issuer": "https://127.0.0.1:8000/", - "redirect_uris": [ - "https://{domain}:{port}/authz_cb/django" - ], - "post_logout_redirect_uris": [ - "https://{domain}:{port}/session_logout/django" - ], - "frontchannel_logout_uri": "https://{domain}:{port}/fc_logout/django", - "frontchannel_logout_session_required": true, - "backchannel_logout_uri": "https://{domain}:{port}/bc_logout/django", - "backchannel_logout_session_required": true, - "services": { - "discovery": { - "class": "oidcrp.oidc.provider_info_discovery.ProviderInfoDiscovery", - "kwargs": {} - }, - "registration": { - "class": "oidcrp.oidc.registration.Registration", - "kwargs": {} - }, - "authorization": { - "class": "oidcrp.oidc.authorization.Authorization", - "kwargs": {} - }, - "accesstoken": { - "class": "oidcrp.oidc.access_token.AccessToken", - "kwargs": {} - }, - "userinfo": { - "class": "oidcrp.oidc.userinfo.UserInfo", - "kwargs": {} - }, - "end_session": { - "class": "oidcrp.oidc.end_session.EndSession", - "kwargs": {} - } - }, - "add_ons": { - "pkce": { - "function": "oidcrp.oauth2.add_on.pkce.add_support", - "kwargs": { - "code_challenge_length": 64, - "code_challenge_method": "S256" - } - } - } - } - }, - "webserver": { - "port": 8090, - "domain": "127.0.0.1", - "server_cert": "certs/cert.pem", - "server_key": "certs/key.pem", - "debug": true - } -} diff --git a/example/django_op/example/example/__init__.py b/example/django_op/example/example/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/example/django_op/example/example/oidc_op.conf.yaml b/example/django_op/example/example/oidc_op.conf.yaml deleted file mode 100644 index 955e7de4..00000000 --- a/example/django_op/example/example/oidc_op.conf.yaml +++ /dev/null @@ -1,329 +0,0 @@ -# TODO: the following four should be handled in settings.py -# session_cookie_name: django_oidc_op -base_url: &base_url 'https://127.0.0.1:8000' -#base_data_path: 'data/oidc_op/' - -key_def: &key_def - - - type: RSA - use: - - sig - - - type: EC - crv: "P-256" - use: - - sig - -OIDC_KEYS: &oidc_keys - 'private_path': data/oidc_op/private/jwks.json - 'key_defs': *key_def - 'public_path': data/static/jwks.json - 'read_only': False - # otherwise OP metadata will have jwks_uri: https://127.0.0.1:5000/None! - 'uri_path': 'static/jwks.json' - -op: - server_info: - issuer: *base_url - httpc_params: - verify: False - seed: asdasdasdasd-random - session_key: - filename: data/oidc_op/private/session_jwks.json - type: OCT - use: sig - capabilities: - # indicates that unknow/unavailable scopes requested by a RP - # would get a 403 error message instead of be declined implicitly. - # If False the op will only release the available scopes and ignoring the missings. - # Default to False - #deny_unknown_scopes: true - subject_types_supported: - - public - - pairwise - grant_types_supported: - - authorization_code - - implicit - - urn:ietf:params:oauth:grant-type:jwt-bearer - - refresh_token - service_documentation: https://that.url.org/service_documentation - ui_locales_supported: [en-US, it-IT] - op_policy_uri: https://that.url.org/op_policy_uri - op_tos_uri: https://that.url.org/op_tos_uri - id_token: - class: oidcendpoint.id_token.IDToken - kwargs: - base_claims: - email: - essential: True - email_verified: - essential: True - token_handler_args: - jwks_def: - private_path: data/oidc_op/private/token_jwks.json - read_only: False - key_defs: - - - type: oct - bytes: 24 - use: - - enc - kid: code - - - type: oct - bytes: 24 - use: - - enc - kid: refresh - code: - lifetime: 600 - token: - class: oidcendpoint.token.jwt_token.JWTToken - lifetime: 3600 - add_claims: - - email - - email_verified - - phone_number - - phone_number_verified - add_claim_by_scope: True - aud: - - *base_url - refresh: - lifetime: 86400 - keys: - *oidc_keys - - template_dir: oidc_op/templates - template_handler: django.template.Template - - endpoint: - webfinger: - path: '.well-known/webfinger' - class: oidcendpoint.oidc.discovery.Discovery - kwargs: - # TODO: optionally manage discovery service authn - client_authn_method: null - provider_info: - path: ".well-known/openid-configuration" - class: oidcendpoint.oidc.provider_config.ProviderConfiguration - kwargs: - # TODO: optionally manage openid-configuration authn - client_authn_method: null - registration: - path: registration - class: oidcendpoint.oidc.registration.Registration - kwargs: - # TODO: make a authn method for 'client_authn_method' - client_authn_method: null - client_secret_expiration_time: 432000 - # this way the secret will never expire. - # client_secret_expires: False - registration_api: - path: registration_api - class: oidcendpoint.oidc.read_registration.RegistrationRead - kwargs: - client_authn_method: - - bearer_header - introspection: - path: introspection - class: oidcendpoint.oauth2.introspection.Introspection - kwargs: - client_authn_method: - client_secret_post: ClientSecretPost - release: - - username - authorization: - path: authorization - class: oidcendpoint.oidc.authorization.Authorization - kwargs: - client_authn_method: null - 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: oidcendpoint.oidc.token.Token - kwargs: - allow_refresh: False - client_authn_method: - - client_secret_post - - client_secret_basic - - client_secret_jwt - - private_key_jwt - userinfo: - path: userinfo - class: oidcendpoint.oidc.userinfo.UserInfo - kwargs: - claim_types_supported: - - normal - - aggregated - - distributed - end_session: - path: session - class: oidcendpoint.oidc.session.Session - kwargs: - logout_verify_url: verify_logout - post_logout_uri_path: post_logout - signing_alg: "ES256" - frontchannel_logout_supported: True - frontchannel_logout_session_supported: True - backchannel_logout_supported: True - backchannel_logout_session_supported: True - check_session_iframe: 'check_session_iframe' - userinfo: - class: oidc_op.users.UserInfo - kwargs: - # map claims to django user attributes here: - claims_map: - phone_number: telephone - family_name: last_name - given_name: first_name - email: email - verified_email: email - gender: gender - birthdate: get_oidc_birthdate - updated_at: get_oidc_lastlogin - authentication: - user: - acr: oidcendpoint.user_authn.authn_context.INTERNETPROTOCOLPASSWORD - class: oidc_op.users.UserPassDjango - kwargs: - # this would override web resource where credentials will be POSTed - # append the trailing slash or add APPEND_SLASH=False in Django settings.py - verify_endpoint: 'verify/oidc_user_login/' - template: oidc_login.html - - # args1: - # class: oidcendpoint.util.JSONDictDB - # kwargs: - # args1_1: data/oidc_op/things.json - - page_header: "Testing log in" - submit_btn: "Get me in!" - user_label: "Nickname" - passwd_label: "Secret sauce" - #anon: - #acr: oidcendpoint.user_authn.authn_context.UNSPECIFIED - #class: oidcendpoint.user_authn.user.NoAuthn - #kwargs: - #user: thatusername - # cookie_dealer: - # class: oidcendpoint.cookie.CookieDealer - # kwargs: - # these should be updated... - # sign_jwk: - # filename: 'data/oidc_op/private/cookie_sign_jwk.json' - # type: OCT - # kid: cookie_sign_key_id - # enc_jwk: - ## otherwise do it yourself: jwkgen --kty SYM > data/oidc_op/private/cookie_enc_jwk.json - # filename: 'data/oidc_op/private/cookie_enc_jwk.json' - # type: OCT - # kid: cookie_enc_key_id - - ## the manual one here... - # sign_jwk: data/oidc_op/private/cookie_sign_jwk.json - # sign_alg: 'SHA256' - - # ## jwkgen --kty SYM > data/oidc_op/private/cookie_enc_jwk.json - # enc_jwk: 'data/oidc_op/private/cookie_enc_jwk.json' - - # default_values: - # name: oidc_op - # domain: *base_url - # path: / - # max_age: 3600 - - login_hint2acrs: - class: oidcendpoint.login_hint.LoginHint2Acrs - kwargs: - scheme_map: - email: - - oidcendpoint.user_authn.authn_context.INTERNETPROTOCOLPASSWORD - - # this adds PKCE support as mandatory - disable it if needed (essential: False) - add_on: - pkce: - function: oidcendpoint.oidc.add_on.pkce.add_pkce_support - kwargs: - essential: True - code_challenge_method: - #plain - S256 - S384 - S512 - claims: - function: oidcendpoint.oidc.add_on.custom_scopes.add_custom_scopes - kwargs: - # that_nice_scope: - # - email - research_and_scholarship: - - name - - given_name - - family_name - - email - - email_verified - - sub - - iss - - eduperson_scoped_affiliation - - db_conf: - # abstract_storage_cls: oidcmsg.storage.extension.LabeledAbstractStorage - keyjar: - handler: oidcmsg.storage.abfile.LabeledAbstractFileSystem - fdir: storage/keyjar - key_conv: oidcmsg.storage.converter.QPKey - value_conv: cryptojwt.serialize.item.KeyIssuer - label: 'keyjar' - default: - handler: oidcmsg.storage.abfile.LabeledAbstractFileSystem - fdir: storage/default - key_conv: oidcmsg.storage.converter.QPKey - value_conv: oidcmsg.storage.converter.JSON - client: - handler: oidc_op.db_interfaces.OidcClientDb - # state: - # handler: oidcmsg.storage.abfile.LabeledAbstractFileSystem - # fdir: storage/state - # key_conv: oidcmsg.storage.converter.QPKey - # value_conv: oidcmsg.storage.converter.JSON - jti: - handler: oidcmsg.storage.abfile.LabeledAbstractFileSystem - fdir: storage/jti - key_conv: oidcmsg.storage.converter.QPKey - value_conv: oidcmsg.storage.converter.JSON - session: - # handler: oidcmsg.storage.abfile.AbstractFileSystem - handler: oidc_op.db_interfaces.OidcSessionDb - fdir: storage/session - key_conv: oidcmsg.storage.converter.QPKey - value_conv: oidcmsg.storage.converter.JSON - sso: - # handler: oidcmsg.storage.abfile.AbstractFileSystem - handler: oidc_op.db_interfaces.OidcSsoDb - fdir: storage/sso - key_conv: oidcmsg.storage.converter.QPKey - value_conv: oidcmsg.storage.converter.JSON - -# not used with gunicorn or any other production server ... -# webserver: - # server_cert: 'certs/89296913_127.0.0.1.cert' - # server_key: 'certs/89296913_127.0.0.1.key' - # ca_bundle: null - # verify_user: false - # port: *port - # domain: *domain - # debug: true diff --git a/example/django_op/example/example/oidc_provider_settings.py b/example/django_op/example/example/oidc_provider_settings.py deleted file mode 100644 index 8a887f77..00000000 --- a/example/django_op/example/example/oidc_provider_settings.py +++ /dev/null @@ -1,354 +0,0 @@ -OIDCOP_CONF = { - "port": 8000, - "domain": "127.0.0.1", - "server_name": "{domain}:{port}", - "base_url": "https://{domain}:{port}", - "key_def": [ - { - "type": "RSA", - "use": [ - "sig" - ] - }, - { - "type": "EC", - "crv": "P-256", - "use": [ - "sig" - ] - } - ], - "OIDC_KEYS": { - "private_path": "data/oidc_op/private/jwks.json", - "key_defs": [ - { - "type": "RSA", - "use": [ - "sig" - ] - }, - { - "type": "EC", - "crv": "P-256", - "use": [ - "sig" - ] - } - ], - "public_path": "data/static/jwks.json", - "read_only": False, - "uri_path": "static/jwks.json" - }, - "op": { - # "seed": "CHANGE-THIS-RANDOMNESS!!!", - "server_info": { - "add_on": { - "pkce": { - "function": "oidcop.oidc.add_on.pkce.add_pkce_support", - "kwargs": { - "essential": False, - "code_challenge_method": "S256 S384 S512" - } - }, - "claims": { - "function": "oidcop.oidc.add_on.custom_scopes.add_custom_scopes", - "kwargs": { - "research_and_scholarship": [ - "name", - "given_name", - "family_name", - "email", - "email_verified", - "sub", - "iss", - "eduperson_scoped_affiliation" - ] - } - } - }, - "authz": { - "class": "oidcop.authz.AuthzHandling", - "kwargs": { - "grant_config": { - "usage_rules": { - "authorization_code": { - "supports_minting": ["access_token", "refresh_token", "id_token"], - "max_usage": 1 - }, - "access_token": {}, - "refresh_token": { - "supports_minting": ["access_token", "refresh_token"] - } - }, - "expires_in": 43200 - } - } - }, - "authentication": { - "user": { - "acr": "oidcop.user_authn.authn_context.INTERNETPROTOCOLPASSWORD", - "class": "oidc_provider.users.UserPassDjango", - "kwargs": { - "verify_endpoint": "verify/oidc_user_login/", - "template": "oidc_login.html", - - "page_header": "Testing log in", - "submit_btn": "Get me in!", - "user_label": "Nickname", - "passwd_label": "Secret sauce" - } - } - }, - "capabilities": { - "subject_types_supported": [ - "public", - "pairwise" - ], - "grant_types_supported": [ - "authorization_code", - "implicit", - "urn:ietf:params:oauth:grant-type:jwt-bearer", - "refresh_token" - ], - # indicates that unknow/unavailable scopes requested by a RP - # would get a 403 error message instead of be declined implicitly. - # If False the op will only release the available scopes and ignoring the missings. - # Default to False - #deny_unknown_scopes: True - }, - "cookie_handler": { - "class": "oidcop.cookie_handler.CookieHandler", - "kwargs": { - "keys": { - "private_path": "data/oidc_op/private/cookie_jwks.json", - "key_defs": [ - {"type": "OCT", "use": ["enc"], "kid": "enc"}, - {"type": "OCT", "use": ["sig"], "kid": "sig"} - ], - "read_only": False - }, - "name": { - "session": "oidc_op", - "register": "oidc_op_rp", - "session_management": "sman" - } - } - }, - "endpoint": { - "webfinger": { - "path": ".well-known/webfinger", - "class": "oidcop.oidc.discovery.Discovery", - "kwargs": { - "client_authn_method": None - } - }, - "provider_info": { - "path": ".well-known/openid-configuration", - "class": "oidcop.oidc.provider_config.ProviderConfiguration", - "kwargs": { - "client_authn_method": None - } - }, - "registration": { - "path": "registration", - "class": "oidcop.oidc.registration.Registration", - "kwargs": { - "client_authn_method": None, - "client_secret_expiration_time": 432000 - } - }, - "registration_api": { - "path": "registration_api", - "class": "oidcop.oidc.read_registration.RegistrationRead", - "kwargs": { - "client_authn_method": [ - "bearer_header" - ] - } - }, - "introspection": { - "path": "introspection", - "class": "oidcop.oauth2.introspection.Introspection", - "kwargs": { - "client_authn_method": [ - "client_secret_post" - ], - "release": [ - "username" - ] - } - }, - "authorization": { - "path": "authorization", - "class": "oidcop.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": "oidcop.oidc.token.Token", - "kwargs": { - "client_authn_method": [ - "client_secret_post", - "client_secret_basic", - "client_secret_jwt", - "private_key_jwt" - ] - } - }, - "userinfo": { - "path": "userinfo", - "class": "oidcop.oidc.userinfo.UserInfo", - "kwargs": { - "claim_types_supported": [ - "normal", - "aggregated", - "distributed" - ] - } - }, - "end_session": { - "path": "session", - "class": "oidcop.oidc.session.Session", - "kwargs": { - "logout_verify_url": "verify_logout", - "post_logout_uri_path": "post_logout", - "signing_alg": "ES256", - "frontchannel_logout_supported": True, - "frontchannel_logout_session_supported": True, - "backchannel_logout_supported": True, - "backchannel_logout_session_supported": True, - "check_session_iframe": "check_session_iframe" - } - } - }, - "httpc_params": { - "verify": False - }, - "issuer": "https://{domain}:{port}", - "keys": { - "private_path": "data/oidc_op/private/jwks.json", - "key_defs": [ - { - "type": "RSA", - "use": [ - "sig" - ] - }, - { - "type": "EC", - "crv": "P-256", - "use": [ - "sig" - ] - } - ], - "public_path": "data/static/jwks.json", - "read_only": False, - "uri_path": "static/jwks.json" - }, - "login_hint2acrs": { - "class": "oidcop.login_hint.LoginHint2Acrs", - "kwargs": { - "scheme_map": { - "email": [ - "oidcop.user_authn.authn_context.INTERNETPROTOCOLPASSWORD" - ] - } - } - }, - "session_key": { - "filename": "data/oidc_op/private/session_jwk.json", - "type": "OCT", - "use": "sig" - }, - "template_dir": "templates", - "token_handler_args": { - "jwks_def": { - "private_path": "data/oidc_op/private/token_jwks.json", - "read_only": False, - "key_defs": [ - { - "type": "oct", - "bytes": 24, - "use": [ - "enc" - ], - "kid": "code" - }, - { - "type": "oct", - "bytes": 24, - "use": [ - "enc" - ], - "kid": "refresh" - } - ] - }, - "code": { - "kwargs": { - "lifetime": 600 - } - }, - "token": { - "class": "oidcop.token.jwt_token.JWTToken", - "kwargs": { - "lifetime": 3600, - "add_claims": [ - "email", - "email_verified", - "phone_number", - "phone_number_verified" - ], - "add_claim_by_scope": True, - "aud": [ - "https://example.org/appl" - ] - } - }, - "refresh": { - "kwargs": { - "lifetime": 86400 - } - } - }, - "userinfo": { - "class": "oidc_provider.users.UserInfo", - "kwargs": { - # map claims to django user attributes here: - "claims_map": { - "phone_number": "telephone", - "family_name": "last_name", - "given_name": "first_name", - "email": "email", - "verified_email": "email", - "gender": "gender", - "birthdate": "get_oidc_birthdate", - "updated_at": "get_oidc_lastlogin" - } - } - } - } - } - -} diff --git a/example/django_op/example/example/settings.py b/example/django_op/example/example/settings.py deleted file mode 100644 index 828b452b..00000000 --- a/example/django_op/example/example/settings.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -Django settings for example project. - -Generated by 'django-admin startproject' using Django 2.2.5. - -For more information on this file, see -https://docs.djangoproject.com/en/2.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/2.2/ref/settings/ -""" - -import os - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'hm8_+266i)9@*9#ncep59m+mp$_+8d_#19rd5x0ujtwlp140s@' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ['*'] - - -# Application definition - -INSTALLED_APPS = [ - 'accounts', - - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'oidc_provider', - -] - -if 'oidc_provider' in INSTALLED_APPS: - from . oidc_provider_settings import OIDCOP_CONF - - - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'example.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'example.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/2.2/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - -AUTH_USER_MODEL = "accounts.User" -# Password validation -# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/2.2/topics/i18n/ - -LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' -USE_I18N = True -USE_L10N = True -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/2.2/howto/static-files/ - -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'data/static') - -# LOGGING -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - # exact format is not important, this is the minimum information - 'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s', - }, - 'detailed': { - 'format': '[%(asctime)s] %(message)s [(%(levelname)s) %(name)s.%(funcName)s:%(lineno)s]' - }, - }, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - }, - 'console': { - 'formatter': 'detailed', - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - }, - }, - 'loggers': { - 'django_test': { - 'handlers': ['console'], - 'level': 'DEBUG', - }, - 'django': { - 'handlers': ['console', 'mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - # 'django.server': { - # 'handlers': ['console'], - # 'level': 'INFO', - # 'propagate': False, - # }, - 'oidc_provider': { - 'handlers': ['console', 'mail_admins'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'oidc_op': { - 'handlers': ['console', 'mail_admins'], - 'level': 'DEBUG', - 'propagate': False, - }, - # 'oidcop.endpoint_context': { - # 'handlers': ['console', 'mail_admins'], - # 'level': 'DEBUG', - # 'propagate': False, - # }, - # 'oidcop.sso_db': { - # 'handlers': ['console', 'mail_admins'], - # 'level': 'DEBUG', - # 'propagate': False, - # }, - # 'oidcop.session': { - # 'handlers': ['console', 'mail_admins'], - # 'level': 'DEBUG', - # 'propagate': False, - # }, - # 'oidcmsg': { - # 'handlers': ['console', 'mail_admins'], - # 'level': 'INFO', - # 'propagate': False, - # }, - } -} diff --git a/example/django_op/example/example/urls.py b/example/django_op/example/example/urls.py deleted file mode 100644 index 69c9bef1..00000000 --- a/example/django_op/example/example/urls.py +++ /dev/null @@ -1,30 +0,0 @@ -"""example URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/2.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -from django.conf import settings -from django.conf.urls.static import static -from django.contrib import admin -from django.urls import include, path - -urlpatterns = [ - path('admin/', admin.site.urls), -] - -urlpatterns += static(settings.STATIC_URL, - document_root=settings.STATIC_ROOT) - -if 'oidc_provider' in settings.INSTALLED_APPS: - import oidc_provider.urls - urlpatterns += path('', include((oidc_provider.urls, 'oidc_op',))), diff --git a/example/django_op/example/example/wsgi.py b/example/django_op/example/example/wsgi.py deleted file mode 100644 index f18f865c..00000000 --- a/example/django_op/example/example/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for example project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') - -application = get_wsgi_application() diff --git a/example/django_op/example/manage.py b/example/django_op/example/manage.py deleted file mode 100755 index 2f9e225a..00000000 --- a/example/django_op/example/manage.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" -import os -import sys - - -def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == '__main__': - main() diff --git a/example/django_op/example/oidc_provider b/example/django_op/example/oidc_provider deleted file mode 120000 index 6e773bec..00000000 --- a/example/django_op/example/oidc_provider +++ /dev/null @@ -1 +0,0 @@ -../oidc_provider \ No newline at end of file diff --git a/example/django_op/example/requirements.txt b/example/django_op/example/requirements.txt deleted file mode 100644 index fbb2b950..00000000 --- a/example/django_op/example/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pycountry diff --git a/example/django_op/example/run.bash b/example/django_op/example/run.bash deleted file mode 100644 index 113f6617..00000000 --- a/example/django_op/example/run.bash +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -./manage.py migrate -./manage.py collectstatic --no-input - -gunicorn example.wsgi -b0.0.0.0:8000 --keyfile=./data/oidc_op/certs/key.pem --certfile=./data/oidc_op/certs/cert.pem --reload --timeout 3600 --capture-output --enable-stdio-inheritance - diff --git a/example/django_op/oidc-op.dev.notes.md b/example/django_op/oidc-op.dev.notes.md deleted file mode 100644 index 83dbc9d3..00000000 --- a/example/django_op/oidc-op.dev.notes.md +++ /dev/null @@ -1,260 +0,0 @@ -# Provider discovery - -```` -endpoint -http_info -req_args.__dict__['_dict'] -current_app.server.endpoint_context.cdb -current_app.server.endpoint_context.session_manager.dump() - - -{'db': {}, 'salt': 'P3e1EPrBvoml1VDE8hBHXzALYI0AsMUP'} -```` - -# useful hints ... -- http_info -- req_args.to_json() -- req_args.__dict__['_dict'] - - -# Registration - -Dynamic client registration endpoint - -```` -endpoint -http_info -req_args.__dict__['_dict'] -current_app.server.endpoint_context.cdb -current_app.server.endpoint_context.session_manager.dump() - - - - -{'86M1io6O2Vdy': - {'client_id': '86M1io6O2Vdy', - 'client_salt': 'ehXmVjYE', - 'registration_access_token': 'lRail9TKK3Cj4kZdSt3KDorKVxyQvVGL', - 'registration_client_uri': 'https://127.0.0.1:5000/registration_api?client_id=86M1io6O2Vdy', - 'client_id_issued_at': 1619384394, - 'client_secret': '9f9a5b6dc23daca606c3766a1c6a0de29a2009b007be3d1da7ff8ca5', - 'client_secret_expires_at': 1621976394, - 'application_type': 'web', - 'response_types': ['code'], - 'contacts': ['ops@example.com'], - 'token_endpoint_auth_method': 'client_secret_basic', - 'post_logout_redirect_uris': [('https://127.0.0.1:8090/session_logout/local', '')], - 'jwks_uri': 'https://127.0.0.1:8090/static/jwks.json', - 'frontchannel_logout_uri': 'https://127.0.0.1:8090/fc_logout/local', - 'frontchannel_logout_session_required': True, - 'backchannel_logout_uri': 'https://127.0.0.1:8090/bc_logout/local', - 'grant_types': ['authorization_code'], - 'redirect_uris': [('https://127.0.0.1:8090/authz_cb/local', {})] - } -} - - - -{'db': {}, 'salt': 'P3e1EPrBvoml1VDE8hBHXzALYI0AsMUP'} -```` - -# Authorization endpont - -```` -http_info - -endpoint; current_app.server.endpoint_context.session_manager.dump() - - - -```` - - -Session dump -```` -{'eWM0Hi7tcdJ5': {'client_id': 'eWM0Hi7tcdJ5', 'client_salt': 'mb45L2cF', 'registration_access_token': 'Tob3Jw0hZ29yqd2HMJj7VhdF98G6jnqu', 'registration_client_uri': 'https://127.0.0.1:5000/registration_api?client_id=eWM0Hi7tcdJ5', 'client_id_issued_at': 1619260359, 'client_secret': 'a7439bd659c5058dbe667a1a5f6c837336f31102d35d435e9f090a2e', 'client_secret_expires_at': 1621852359, 'application_type': 'web', 'response_types': ['code'], 'contacts': ['ops@example.com'], 'token_endpoint_auth_method': 'client_secret_basic', 'post_logout_redirect_uris': [('https://127.0.0.1:8090/session_logout/local', '')], 'jwks_uri': 'https://127.0.0.1:8090/static/jwks.json', 'frontchannel_logout_uri': 'https://127.0.0.1:8090/fc_logout/local', 'frontchannel_logout_session_required': True, 'backchannel_logout_uri': 'https://127.0.0.1:8090/bc_logout/local', 'grant_types': ['authorization_code'], 'redirect_uris': [('https://127.0.0.1:8090/authz_cb/local', {})]}} -{'db': {}, 'salt': 'P3e1EPrBvoml1VDE8hBHXzALYI0AsMUP'} -```` - -# Token endpoint - -```` -http_info - -endpoint; current_app.server.endpoint_context.session_manager.dump() - -# current_app.server.endpoint_context.cdb not changes from the previous ... - -# session dump -{ - "db": { - "diana": [ - "oidcop.session.info.UserSessionInfo", - { - "subordinate": [ - "86M1io6O2Vdy" - ], - "revoked": false, - "type": "UserSessionInfo", - "extra_args": {}, - "user_id": "diana" - } - ], - "diana;;86M1io6O2Vdy": [ - "oidcop.session.info.ClientSessionInfo", - { - "subordinate": [ - "fcc1c962a60911eb9d4d57d896f78a5d" - ], - "revoked": false, - "type": "ClientSessionInfo", - "extra_args": {}, - "client_id": "86M1io6O2Vdy" - } - ], - "diana;;86M1io6O2Vdy;;fcc1c962a60911eb9d4d57d896f78a5d": [ - "oidcop.session.grant.Grant", - { - "expires_at": 1619427939, - "issued_at": 1619384739, - "not_before": 0, - "revoked": false, - "usage_rules": { - "authorization_code": { - "supports_minting": [ - "access_token", - "refresh_token", - "id_token" - ], - "max_usage": 1 - }, - "access_token": {}, - "refresh_token": { - "supports_minting": [ - "access_token", - "refresh_token" - ] - } - }, - "used": 2, - "authentication_event": { - "oidcop.authn_event.AuthnEvent": { - "uid": "diana", - "authn_info": "oidcop.user_authn.authn_context.INTERNETPROTOCOLPASSWORD", - "authn_time": 1619384739, - "valid_until": 1619388339 - } - }, - "authorization_request": { - "oidcmsg.oidc.AuthorizationRequest": { - "redirect_uri": "https://127.0.0.1:8090/authz_cb/local", - "scope": "openid profile email address phone", - "response_type": "code", - "nonce": "TXwiaGM9I8kEB4BbC4nqHNWc", - "state": "uKZM2ciKxWbg4x4xtsltzoy4PvjoQf4T", - "code_challenge": "WYVBXCNsPiDTe0lClNPG69qRB_yl6mJ2Lwop9XWjhYA", - "code_challenge_method": "S256", - "client_id": "86M1io6O2Vdy" - } - }, - "claims": { - "userinfo": { - "sub": null, - "name": null, - "given_name": null, - "family_name": null, - "middle_name": null, - "nickname": null, - "profile": null, - "picture": null, - "website": null, - "gender": null, - "birthdate": null, - "zoneinfo": null, - "locale": null, - "updated_at": null, - "preferred_username": null, - "email": null, - "email_verified": null, - "address": null, - "phone_number": null, - "phone_number_verified": null - }, - "introspection": {}, - "id_token": {}, - "access_token": {} - }, - "issued_token": [ - { - "expires_at": 0, - "issued_at": 1619384739, - "not_before": 0, - "revoked": false, - "usage_rules": { - "supports_minting": [ - "access_token", - "refresh_token", - "id_token" - ], - "max_usage": 1 - }, - "used": 1, - "claims": {}, - "id": "fcc1c963a60911eb9d4d57d896f78a5d", - "name": "AuthorizationCode", - "resources": [], - "scope": [], - "type": "authorization_code", - "value": "Z0FBQUFBQmdoZG1qdTlNZ0hzNGZzcVhZNnRJTXE2bkZZNGlVeXZTaDYwWlV4Vm1yMnFQWUZSSVFmb25HQjluQy1ZVWNXTWJlZ082OE03dVB1NmdBVG8xbkwxV1BWRTBZVkIzYXctY0xhTDB6c2hXUzhmeTRBNE9Ua3RxVVlmU0dDSElPeUJRb1VHQndtT21PR25nRWx3QXdoSG1DdklFM0REdjhWa2I2bWNtQzhFazdrRzBybWd4VV9oX19hcEt4MDZ3Uk5lNGpvbXllMVVmNkt4VXNRaW1FVHRTdS13ajVxczVibmtaXzRhXzhMcW9DOEFXVGtZND0=" - }, - { - "expires_at": 0, - "issued_at": 1619384739, - "not_before": 0, - "revoked": false, - "usage_rules": {}, - "used": 0, - "based_on": "Z0FBQUFBQmdoZG1qdTlNZ0hzNGZzcVhZNnRJTXE2bkZZNGlVeXZTaDYwWlV4Vm1yMnFQWUZSSVFmb25HQjluQy1ZVWNXTWJlZ082OE03dVB1NmdBVG8xbkwxV1BWRTBZVkIzYXctY0xhTDB6c2hXUzhmeTRBNE9Ua3RxVVlmU0dDSElPeUJRb1VHQndtT21PR25nRWx3QXdoSG1DdklFM0REdjhWa2I2bWNtQzhFazdrRzBybWd4VV9oX19hcEt4MDZ3Uk5lNGpvbXllMVVmNkt4VXNRaW1FVHRTdS13ajVxczVibmtaXzRhXzhMcW9DOEFXVGtZND0=", - "claims": {}, - "id": "fcc4fc72a60911eb9d4d57d896f78a5d", - "name": "AccessToken", - "resources": [], - "scope": [], - "type": "access_token", - "value": "eyJhbGciOiJFUzI1NiIsImtpZCI6IlNWUXpPV1ZVUm1oNWIxcHVVVmx1UlY4dGVVUlpVVlZTZFhkcFdVUTJTbTVMY1U0M01EWm1WV2REVlEifQ.eyJzY29wZSI6IFsib3BlbmlkIiwgInByb2ZpbGUiLCAiZW1haWwiLCAiYWRkcmVzcyIsICJwaG9uZSJdLCAiYXVkIjogWyI4Nk0xaW82TzJWZHkiXSwgInNpZCI6ICJkaWFuYTs7ODZNMWlvNk8yVmR5OztmY2MxYzk2MmE2MDkxMWViOWQ0ZDU3ZDg5NmY3OGE1ZCIsICJ0dHlwZSI6ICJUIiwgImlzcyI6ICJodHRwczovLzEyNy4wLjAuMTo1MDAwIiwgImlhdCI6IDE2MTkzODQ3MzksICJleHAiOiAxNjE5Mzg4MzM5fQ.Brva_I8bBM5z_1ZxFBWSRFN3U95y_YQxnLG5-51NrUmu862M-KSj4kd5v5vFGHiHF0iFvBuDLD6pSZL1RHXHCg" - } - ], - "resources": [ - "86M1io6O2Vdy" - ], - "scope": [ - "openid", - "profile", - "email", - "address", - "phone" - ], - "sub": "93be77e1b212f1643e0ee9dd5e477e2a2a231dc6ca22dd3273345e63eb156a23" - } - ], - "8ea62b28f57646fe8db31b4bdea0e262": [ - "oidcop.session.info.SessionInfo", - { - "subordinate": [], - "revoked": false, - "type": "", - "extra_args": {} - } - ] - }, - "salt": "1Kih63fBe5ympYSWi5z2aVXXCVKxqMvN" -} - -```` - -# Userinfo endpoint - -```` - -{'db': {'diana': ['oidcop.session.info.UserSessionInfo', {'subordinate': ['eWM0Hi7tcdJ5'], 'revoked': False, 'type': 'UserSessionInfo', 'extra_args': {}, 'user_id': 'diana'}], 'diana;;eWM0Hi7tcdJ5': ['oidcop.session.info.ClientSessionInfo', {'subordinate': ['c75b0e0ea4e811eba57a51f2252cef26'], 'revoked': False, 'type': 'ClientSessionInfo', 'extra_args': {}, 'client_id': 'eWM0Hi7tcdJ5'}], 'diana;;eWM0Hi7tcdJ5;;c75b0e0ea4e811eba57a51f2252cef26': ['oidcop.session.grant.Grant', {'expires_at': 0, 'issued_at': 1619260524, 'not_before': 0, 'revoked': False, 'usage_rules': {}, 'used': 2, 'authentication_event': {'oidcop.authn_event.AuthnEvent': {'uid': 'diana', 'authn_info': 'oidcop.user_authn.authn_context.INTERNETPROTOCOLPASSWORD', 'authn_time': 1619260524, 'valid_until': 1619264124}}, 'authorization_request': {'oidcmsg.oidc.AuthorizationRequest': {'redirect_uri': 'https://127.0.0.1:8090/authz_cb/local', 'scope': 'openid profile email address phone', 'response_type': 'code', 'nonce': 'xgR3dwSaW6s2q7sJ7Ar5KGEJ', 'state': 'AxNY1bnoRd5xCmTYDQIGRlq3XUCMEXoP', 'code_challenge': 'tfXn2btZVqbzkrSkyPUw1jAjtQXH2M2fUgLyqSMS0ak', 'code_challenge_method': 'S256', 'client_id': 'eWM0Hi7tcdJ5'}}, 'claims': {}, 'issued_token': [{'expires_at': 0, 'issued_at': 1619260524, 'not_before': 0, 'revoked': False, 'usage_rules': {'supports_minting': ['access_token', 'refresh_token'], 'max_usage': 1}, 'used': 1, 'claims': {}, 'id': 'c75b0e0fa4e811eba57a51f2252cef26', 'name': 'AuthorizationCode', 'resources': [], 'scope': [], 'type': 'authorization_code', 'value': 'Z0FBQUFBQmdnX1JzTEZ0ZDV0LWpic1RvYm95cEUtNG1BTV9XTzZyaFV1RW1rM2ppMnNCVThzb3RfemVMQ0hSd296Y1VyVF9OUXBXNGVRNjVjYkRqMl9leF9sQ2xnY3h3ZWh4X1FFeXFLMlhDZE9NTWtEcFZkU3RXNURTbTRrZ1Q5dEh5TWZrVlhmYnU2N0dwenBlM2J1WlNpYzY4cWRjTHUzYXZvWEc2TG0zSEtnY3ZKMGlvOFF4X19pWks0Zl9DUTRuU09ndnRNRTdJRmtNZ2NqU09aWDcxUlhjdl8tZmd6Z1NNcWViS1FjQjdnMGlZN21xcVJnRT0='}, {'expires_at': 0, 'issued_at': 1619260756, 'not_before': 0, 'revoked': False, 'usage_rules': {}, 'used': 0, 'based_on': 'Z0FBQUFBQmdnX1JzTEZ0ZDV0LWpic1RvYm95cEUtNG1BTV9XTzZyaFV1RW1rM2ppMnNCVThzb3RfemVMQ0hSd296Y1VyVF9OUXBXNGVRNjVjYkRqMl9leF9sQ2xnY3h3ZWh4X1FFeXFLMlhDZE9NTWtEcFZkU3RXNURTbTRrZ1Q5dEh5TWZrVlhmYnU2N0dwenBlM2J1WlNpYzY4cWRjTHUzYXZvWEc2TG0zSEtnY3ZKMGlvOFF4X19pWks0Zl9DUTRuU09ndnRNRTdJRmtNZ2NqU09aWDcxUlhjdl8tZmd6Z1NNcWViS1FjQjdnMGlZN21xcVJnRT0=', 'claims': {}, 'id': '519d54e6a4e911eba57a51f2252cef26', 'name': 'AccessToken', 'resources': [], 'scope': [], 'type': 'access_token', 'value': 'eyJhbGciOiJFUzI1NiIsImtpZCI6IlNWUXpPV1ZVUm1oNWIxcHVVVmx1UlY4dGVVUlpVVlZTZFhkcFdVUTJTbTVMY1U0M01EWm1WV2REVlEifQ.eyJzY29wZSI6IFtdLCAiYXVkIjogW10sICJzaWQiOiAiZGlhbmE7O2VXTTBIaTd0Y2RKNTs7Yzc1YjBlMGVhNGU4MTFlYmE1N2E1MWYyMjUyY2VmMjYiLCAidHR5cGUiOiAiVCIsICJpc3MiOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NTAwMCIsICJpYXQiOiAxNjE5MjYwNzU2LCAiZXhwIjogMTYxOTI2NDM1Nn0.j-YSAF7M6naaq2w8ntPOi-55shCIpWWFKmluYS18wkPrp5L5NFViuhmhLRY1CHr_xtbWv944Ud06m0RKP7Gd0Q'}], 'resources': [], 'scope': [], 'sub': '8fb4f8ee2bad3d54e58fcc2bb4f56200391427fb587bbcb95ca535cd818fd914'}], 'c6171c52f7dd4dcfa51e28f9af5833fd': ['oidcop.session.info.SessionInfo', {'subordinate': [], 'revoked': False, 'type': '', 'extra_args': {}}]}, 'salt': 'P3e1EPrBvoml1VDE8hBHXzALYI0AsMUP'} -```` diff --git a/example/django_op/oidc_provider/__init__.py b/example/django_op/oidc_provider/__init__.py deleted file mode 100644 index 4bd5dbb5..00000000 --- a/example/django_op/oidc_provider/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'oidc_provider.apps.OidcOpConfig' diff --git a/example/django_op/oidc_provider/admin.py b/example/django_op/oidc_provider/admin.py deleted file mode 100644 index 7a3c7456..00000000 --- a/example/django_op/oidc_provider/admin.py +++ /dev/null @@ -1,214 +0,0 @@ -import logging - -from django import forms -from django.contrib import admin -from django.contrib.sessions.models import Session -from django.utils.safestring import mark_safe - -from . models import * -from . utils import decode_token - - -logger = logging.getLogger(__name__) - - -class OidcRPContactModelForm(forms.ModelForm): - class Meta: - model = OidcRPContact - fields = ('__all__') - - -class OidcRPContactInline(admin.TabularInline): - model = OidcRPContact - form = OidcRPContactModelForm - extra = 0 - - -class OidcRPRedirectUriModelForm(forms.ModelForm): - class Meta: - model = OidcRPRedirectUri - fields = ('__all__') - - -class OidcRPRedirectUriInline(admin.TabularInline): - model = OidcRPRedirectUri - form = OidcRPRedirectUriModelForm - extra = 0 - - -class OidcRPGrantTypeModelForm(forms.ModelForm): - class Meta: - model = OidcRPGrantType - fields = ('__all__') - - -class OidcRPGrantTypeInline(admin.TabularInline): - model = OidcRPGrantType - form = OidcRPGrantTypeModelForm - extra = 0 - - -class OidcRPResponseTypeModelForm(forms.ModelForm): - class Meta: - model = OidcRPResponseType - fields = ('__all__') - - -class OidcRPResponseTypeInline(admin.TabularInline): - model = OidcRPResponseType - form = OidcRPResponseTypeModelForm - extra = 0 - - -class OidcRPScopeModelForm(forms.ModelForm): - class Meta: - model = OidcRPScope - fields = ('__all__') - - -class OidcRPScopeInline(admin.TabularInline): - model = OidcRPScope - form = OidcRPScopeModelForm - extra = 0 - - -@admin.register(OidcRelyingParty) -class OidcRelyingPartyAdmin(admin.ModelAdmin): - list_filter = ('created', 'modified', 'is_active') - list_display = ('client_id', 'created', - 'last_seen', 'is_active') - search_fields = ('client_id',) - list_editable = ('is_active',) - inlines = (OidcRPScopeInline, - OidcRPResponseTypeInline, - OidcRPGrantTypeInline, - OidcRPContactInline, - OidcRPRedirectUriInline) - fieldsets = ( - (None, { - 'fields': ( - ('client_id', 'client_secret',), - ('client_salt', 'jwks_uri'), - ('registration_client_uri',), - ('registration_access_token',), - ('application_type', - 'token_endpoint_auth_method'), - ('is_active', ) - ) - }, - ), - ('Temporal values', - { - 'fields': ( - (('client_id_issued_at', - 'client_secret_expires_at', - 'last_seen')), - - ), - - }, - ), - ) - - # def save_model(self, request, obj, form, change): - # res = False - # msg = '' - # try: - # json.dumps(obj.as_pysaml2_mdstore_row()) - # res = obj.validate() - # super(MetadataStoreAdmin, self).save_model(request, obj, form, change) - # except Exception as excp: - # obj.is_valid = False - # obj.save() - # msg = str(excp) - - # if not res: - # messages.set_level(request, messages.ERROR) - # _msg = _("Storage {} is not valid, if 'mdq' at least a " - # "valid url must be inserted. " - # "If local: at least a file or a valid path").format(obj.name) - # if msg: _msg = _msg + '. ' + msg - # messages.add_message(request, messages.ERROR, _msg) - - -@admin.register(Session) -class SessionAdmin(admin.ModelAdmin): - def _session_data(self, obj): - return obj.get_decoded() - list_display = ['session_key', '_session_data', 'expire_date'] - - -@admin.register(OidcSession) -class OidcSessionAdmin(admin.ModelAdmin): - list_filter = ('created', 'modified', 'valid_until') - list_display = ('client', 'state', 'sso', 'created') - search_fields = ('state', 'sso__user__username') - readonly_fields = ('sid', 'client', 'sso', 'state', 'valid_until', 'info_session_preview', - 'access_token_preview', 'id_token_preview') - - fieldsets = ( - (None, { - 'fields': ( - ('client', ), - ('sso', ), - ('state',), - ('sid',), - ('valid_until',), - ) - }, - ), - ('Session info', - { - 'classes': ('collapse',), - 'fields': ('info_session_preview',), - } - ), - - ('Token previews', - { - 'classes': ('collapse',), - 'fields': ( - ('access_token_preview'), - ('id_token_preview'), - ) - - }, - ), - ) - - def info_session_preview(self, obj): - msg = json.loads(obj.session_info or '{}') - dumps = json.dumps(msg, indent=2) - return mark_safe(dumps.replace('\n', '
').replace('\s', ' ')) - info_session_preview.short_description = 'Info Session preview' - - def access_token_preview(self, obj): - try: - msg = decode_token(obj.session_info or {}, 'access_token') - dumps = json.dumps(msg.to_dict(), indent=2) - return mark_safe(dumps.replace('\n', '
').replace('\s', ' ')) - except Exception as e: - logger.tracelog(e) - access_token_preview.short_description = 'Access Token preview' - - def id_token_preview(self, obj): - try: - msg = decode_token(obj.session_info or {}, 'id_token') - dumps = json.dumps(msg.to_dict(), indent=2) - return mark_safe(dumps.replace('\n', '
').replace('\s', ' ')) - except Exception as e: - logger.tracelog(e) - id_token_preview.short_description = 'ID Token preview' - - class Media: - js = ('js/textarea_autosize.js',) - # css = {'default': ('css/textarea_large.css',)} - - -@admin.register(OidcSessionSso) -class OidcSessionSsoAdmin(admin.ModelAdmin): - list_filter = ('created', 'modified') - list_display = ('user', - 'sub', 'created') - search_fields = ('user',) - readonly_fields = ('sub', 'user') diff --git a/example/django_op/oidc_provider/application.py b/example/django_op/oidc_provider/application.py deleted file mode 100644 index 7dba7f2a..00000000 --- a/example/django_op/oidc_provider/application.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import os - -from django.conf import settings -from oidcop.endpoint_context import EndpointContext -from oidcop.server import Server - -from urllib.parse import urlparse -from oidcop.configure import Configuration - -folder = os.path.dirname(os.path.realpath(__file__)) -logger = logging.getLogger(__name__) - - -def init_oidc_op_endpoints(app): - _config = app.srv_config.op - _server_info_config = _config['server_info'] - - iss = _server_info_config['issuer'] - if '{domain}' in iss: - iss = iss.format(domain=app.srv_config.domain, - port=app.srv_config.port) - _server_info_config['issuer'] = iss - - server = Server(_server_info_config, cwd=folder) - - for endp in server.endpoint.values(): - p = urlparse(endp.endpoint_path) - _vpath = p.path.split('/') - if _vpath[0] == '': - endp.vpath = _vpath[1:] - else: - endp.vpath = _vpath - - return server - - -def oidc_provider_init_app(config, name='oidc_op', **kwargs): - name = name or __name__ - app = type('OIDCAppEndpoint', (object,), {"srv_config": config}) - # Initialize the oidc_provider after views to be able to set correct urls - app.endpoint_context = init_oidc_op_endpoints(app) - return app - - -def oidcop_application(conf = settings.OIDCOP_CONF): - config = Configuration(conf = conf) - app = oidc_provider_init_app(config) - return app diff --git a/example/django_op/oidc_provider/apps.py b/example/django_op/oidc_provider/apps.py deleted file mode 100644 index a25c1655..00000000 --- a/example/django_op/oidc_provider/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class OidcOpConfig(AppConfig): - name = 'oidc_provider' - verbose_name = "OpenID Connect Provider" diff --git a/example/django_op/oidc_provider/migrations/0001_initial.py b/example/django_op/oidc_provider/migrations/0001_initial.py deleted file mode 100644 index c4a063df..00000000 --- a/example/django_op/oidc_provider/migrations/0001_initial.py +++ /dev/null @@ -1,155 +0,0 @@ -# Generated by Django 3.1.1 on 2021-04-27 14:11 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='OidcRelyingParty', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('client_id', models.CharField(max_length=255, unique=True)), - ('client_salt', models.CharField(blank=True, max_length=255, null=True)), - ('registration_access_token', models.CharField(blank=True, max_length=255, null=True)), - ('registration_client_uri', models.URLField(blank=True, max_length=255, null=True)), - ('client_id_issued_at', models.DateTimeField(blank=True, null=True)), - ('client_secret', models.CharField(blank=True, help_text='It is not needed for Clients selecting a token_endpoint_auth_method of private_key_jwt', max_length=255, null=True)), - ('client_secret_expires_at', models.DateTimeField(blank=True, help_text='REQUIRED if client_secret is issued', null=True)), - ('application_type', models.CharField(blank=True, default='web', max_length=255, null=True)), - ('token_endpoint_auth_method', models.CharField(blank=True, choices=[('client_secret_post', 'client_secret_post'), ('client_secret_basic', 'client_secret_basic'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], default='client_secret_basic', max_length=33, null=True)), - ('jwks_uri', models.URLField(blank=True, max_length=255, null=True)), - ('is_active', models.BooleanField(default=True, verbose_name='active')), - ('last_seen', models.DateTimeField(blank=True, null=True)), - ], - options={ - 'verbose_name': 'Relying Party', - 'verbose_name_plural': 'Relying Parties', - }, - ), - migrations.CreateModel( - name='OidcSessionSso', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('sub', models.CharField(blank=True, max_length=255, null=True)), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'SSO Session SSO', - 'verbose_name_plural': 'SSO Sessions SSO', - }, - ), - migrations.CreateModel( - name='OidcSession', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('user_uid', models.CharField(max_length=120)), - ('state', models.CharField(blank=True, max_length=255, null=True)), - ('session_info', models.TextField(blank=True, null=True)), - ('grant', models.TextField(blank=True, null=True)), - ('grant_id', models.CharField(blank=True, max_length=255, null=True)), - ('issued_at', models.DateTimeField(blank=True, null=True)), - ('valid_until', models.DateTimeField(blank=True, null=True)), - ('code', models.CharField(blank=True, max_length=1024, null=True)), - ('sid', models.CharField(blank=True, max_length=255, null=True)), - ('sub', models.CharField(blank=True, max_length=255, null=True)), - ('client', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcrelyingparty')), - ('sso', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcsessionsso')), - ], - options={ - 'verbose_name': 'SSO Session', - 'verbose_name_plural': 'SSO Sessions', - }, - ), - migrations.CreateModel( - name='OidcRPRedirectUri', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('uri', models.CharField(blank=True, max_length=254, null=True)), - ('values', models.CharField(blank=True, max_length=254, null=True)), - ('type', models.CharField(choices=[('redirect_uris', 'redirect_uris'), ('post_logout_redirect_uris', 'post_logout_redirect_uris')], max_length=33)), - ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcrelyingparty')), - ], - options={ - 'verbose_name': 'Relying Party URI', - 'verbose_name_plural': 'Relying Parties URIs', - }, - ), - migrations.CreateModel( - name='OidcRPScope', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('scope', models.CharField(blank=True, max_length=254, null=True)), - ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcrelyingparty')), - ], - options={ - 'verbose_name': 'Relying Party Scope', - 'verbose_name_plural': 'Relying Parties Scopes', - 'unique_together': {('client', 'scope')}, - }, - ), - migrations.CreateModel( - name='OidcRPResponseType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('response_type', models.CharField(choices=[('code', 'code'), ('token', 'token'), ('id_token', 'id_token'), ('code token', 'code token'), ('code id_token', 'code id_token'), ('id_token token', 'id_token token'), ('code id_token token', 'code id_token token'), ('none', 'none')], max_length=60)), - ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcrelyingparty')), - ], - options={ - 'verbose_name': 'Relying Party Response Type', - 'verbose_name_plural': 'Relying Parties Response Types', - 'unique_together': {('client', 'response_type')}, - }, - ), - migrations.CreateModel( - name='OidcRPGrantType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('grant_type', models.CharField(choices=[('authorization_code', 'authorization_code'), ('implicit', 'implicit'), ('urn:ietf:params:oauth:grant-type:jwt-bearer', 'urn:ietf:params:oauth:grant-type:jwt-bearer'), ('refresh_token', 'refresh_token')], max_length=60)), - ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcrelyingparty')), - ], - options={ - 'verbose_name': 'Relying Party GrantType', - 'verbose_name_plural': 'Relying Parties GrantTypes', - 'unique_together': {('client', 'grant_type')}, - }, - ), - migrations.CreateModel( - name='OidcRPContact', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('contact', models.CharField(blank=True, max_length=254, null=True)), - ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.oidcrelyingparty')), - ], - options={ - 'verbose_name': 'Relying Party Contact', - 'verbose_name_plural': 'Relying Parties Contacts', - 'unique_together': {('client', 'contact')}, - }, - ), - ] diff --git a/example/django_op/oidc_provider/migrations/__init__.py b/example/django_op/oidc_provider/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/example/django_op/oidc_provider/models.py b/example/django_op/oidc_provider/models.py deleted file mode 100644 index b8fb1aba..00000000 --- a/example/django_op/oidc_provider/models.py +++ /dev/null @@ -1,582 +0,0 @@ -import datetime -import json - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.db import models -from django.utils import timezone -from oidcop.utils import load_yaml_config - - -OIDC_RESPONSE_TYPES = settings.OIDCOP_CONF['op']['server_info'][ - 'endpoint']['authorization']['kwargs']['response_types_supported'] - -OIDC_TOKEN_AUTHN_METHODS = settings.OIDCOP_CONF['op']['server_info'][ - 'endpoint']['token']['kwargs']['client_authn_method'] - -OIDC_GRANT_TYPES = settings.OIDCOP_CONF['op']['server_info']['capabilities']['grant_types_supported'] - -TIMESTAMP_FIELDS = ['client_id_issued_at', 'client_secret_expires_at'] - -# configured in oidcop -# it's not a fixed value, it depends by clients. Here it just references JWTConnect-Python-OidcRP -OIDC_OP_STATE_VALUE_LEN = 32 -OIDC_OP_SID_VALUE_LEN = 56 -OIDC_OP_SUB_VALUE_LEN = 64 - - -# TODO: these test should be improved once oidcop will have specialized objects as values instead of simple strings -def is_state(value): - # USELESS: state is always generated by client/RP! - return len(value) == OIDC_OP_STATE_VALUE_LEN - - -def is_sid(value): - return len(value) == OIDC_OP_SID_VALUE_LEN - - -def is_sub(value): - return len(value) == OIDC_OP_SUB_VALUE_LEN - - -def is_code(value): - # Z0FBQUFBQmZEc1Z5Z1dMRVptX1J6d3AwTDVMdkVtbU1Rcm41VkVVbm03N3pwY21qYlpXc1M0ME1TU25fVlZMdm9MVnFKSW1zb3E4TW1aS0MzeVk4OWF2VjYtZ3FmZ0FXQkluUnVuSEJyWFhtcDFhOEdpTnFiVTdJME1qTFZoWWM2X3lQaGY0VGI0QWZNVUNJc3p6RnRMMWlOUUZzc0gtV3BsdVJvcTBIR3hsbk5SSmV1NVJ0M1N0UXcwV3JLeUR3N1NHYU54U21XVEFpYnBCSnBjN0dYeXFETVByT0J3YnZTSmlqblZSb3JXQmtuazFYdkU3cnNMaz0= - return len(value) > 256 - - -def get_client_by_id(client_id): - client = OidcRelyingParty.objects.filter( - client_id = client_id, - is_active = True - - ) - if client: - return client.last() - - -class TimeStampedModel(models.Model): - """ - An abstract base class model that provides self-updating - ``created`` and ``modified`` fields. - """ - created = models.DateTimeField(auto_now_add=True, editable=False) - modified = models.DateTimeField(auto_now=True, editable=False) - - class Meta: - abstract = True - - -class OidcRelyingParty(TimeStampedModel): - """ - See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata - - unique if available (check on save): - client_secret should be - registration_access_token unique if available - - issued -> number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time - client_salt -> must be autogenerated on save - """ - _TOKEN_AUTH_CHOICES = ((i, i) for i in OIDC_TOKEN_AUTHN_METHODS) - - client_id = models.CharField( - max_length=255, blank=False, null=False, unique=True - ) - client_salt = models.CharField( - max_length=255, blank=True, null=True - ) - registration_access_token = models.CharField( - max_length=255, blank=True, null=True - ) - registration_client_uri = models.URLField( - max_length=255, blank=True, null=True - ) - client_id_issued_at = models.DateTimeField(blank=True, null=True) - client_secret = models.CharField( - max_length=255, - blank=True, null=True, - help_text=('It is not needed for Clients ' - 'selecting a token_endpoint_auth_method ' - 'of private_key_jwt') - ) - client_secret_expires_at = models.DateTimeField( - blank=True, null=True, - help_text=('REQUIRED if client_secret is issued') - ) - application_type = models.CharField( - max_length=255, blank=True, - null=True, default='web' - ) - token_endpoint_auth_method = models.CharField( - choices=_TOKEN_AUTH_CHOICES, - max_length=33, - blank=True, null=True, - default="client_secret_basic" - ) - jwks_uri = models.URLField(max_length=255, blank=True, null=True) - post_logout_redirect_uris = models.CharField( - max_length=254, blank=True, null=True - ) - redirect_uris = models.CharField( - max_length=254, blank=True, null=True - ) - is_active = models.BooleanField(('active'), default=True) - last_seen = models.DateTimeField(blank=True, null=True) - - @property - def allowed_scopes(self): - scopes = self.oidcrpscope_set.filter(client=self) - if scopes: - return [i.scope for i in scopes] - else: - return ['openid'] - - @allowed_scopes.setter - def allowed_scopes(self, values): - for i in values: - scope = self.oidcrpscope_set.create(client=self, scope=i) - - @property - def contacts(self): - return [elem.contact - for elem in self.oidcrpcontact_set.filter(client=self)] - - @contacts.setter - def contacts(self, values): - old = self.oidcrpcontact_set.filter(client=self) - old.delete() - if isinstance(values, str): - value = [values] - for value in values: - self.oidcrpcontact_set.create(client=self, - contact=value) - - @property - def grant_types(self): - return [elem.grant_type - for elem in - self.oidcrpgranttype_set.filter(client=self)] - - @grant_types.setter - def grant_types(self, values): - old = self.oidcrpgranttype_set.filter(client=self) - old.delete() - if isinstance(values, str): - value = [values] - for value in values: - self.oidcrpgranttype_set.create(client=self, - grant_type=value) - - @property - def response_types(self): - return [ - elem.response_type - for elem in - self.oidcrpresponsetype_set.filter(client=self) - ] - - @response_types.setter - def response_types(self, values): - old = self.oidcrpresponsetype_set.filter(client=self) - old.delete() - if isinstance(values, str): - value = [values] - for value in values: - self.oidcrpresponsetype_set.create( - client=self, response_type=value - ) - - @property - def post_logout_redirect_uris(self): - l = [] - for elem in self.oidcrpredirecturi_set.\ - filter(client=self, type='post_logout_redirect_uris'): - l.append((elem.uri, json.loads(elem.values))) - return l - - @post_logout_redirect_uris.setter - def post_logout_redirect_uris(self, values): - old = self.oidcrpredirecturi_set.filter(client=self) - old.delete() - for value in values: - args = json.dumps(value[1] if value[1] else []) - self.oidcrpredirecturi_set.create( - client=self, - uri=value[0], - values=args, - type='post_logout_redirect_uris' - ) - - @property - def redirect_uris(self): - l = [] - for elem in self.oidcrpredirecturi_set.filter( - client=self, type='redirect_uris'): - l.append((elem.uri, json.loads(elem.values))) - return l - - @redirect_uris.setter - def redirect_uris(self, values): - old = self.oidcrpredirecturi_set.filter(client=self) - old.delete() - for value in values: - self.oidcrpredirecturi_set.create(client=self, - uri=value[0], - values=json.dumps(value[1]), - type='redirect_uris') - - class Meta: - verbose_name = ('Relying Party') - verbose_name_plural = ('Relying Parties') - - def copy(self): - """ - Compability with rohe approach based on dictionaries - """ - d = {k: v for k, v in self.__dict__.items() if k[0] != '_'} - disabled = ('created', 'modified', 'is_active', 'last_seen') - for dis in disabled: - d.pop(dis) - for key in TIMESTAMP_FIELDS: - if d.get(key): - d[key] = int(datetime.datetime.timestamp(d[key])) - - d['contacts'] = self.contacts - d['grant_types'] = self.grant_types - d['response_types'] = self.response_types - d['post_logout_redirect_uris'] = self.post_logout_redirect_uris - d['redirect_uris'] = self.redirect_uris - d['allowed_scopes'] = self.allowed_scopes - return d - - def __str__(self): - return '{}'.format(self.client_id) - - -class OidcRPResponseType(TimeStampedModel): - client = models.ForeignKey(OidcRelyingParty, on_delete=models.CASCADE) - response_type = models.CharField(choices=[(i, i) for i in OIDC_RESPONSE_TYPES], - max_length=60) - - class Meta: - verbose_name = ('Relying Party Response Type') - verbose_name_plural = ('Relying Parties Response Types') - unique_together = ('client', 'response_type') - - def __str__(self): - return '{}, [{}]'.format(self.client, self.response_type) - - -class OidcRPGrantType(TimeStampedModel): - client = models.ForeignKey(OidcRelyingParty, - on_delete=models.CASCADE) - grant_type = models.CharField(choices=[(i, i) - for i in OIDC_GRANT_TYPES], - max_length=60) - - class Meta: - verbose_name = ('Relying Party GrantType') - verbose_name_plural = ('Relying Parties GrantTypes') - unique_together = ('client', 'grant_type') - - def __str__(self): - return '{}, [{}]'.format(self.client, self.grant_type) - - -class OidcRPContact(TimeStampedModel): - client = models.ForeignKey(OidcRelyingParty, - on_delete=models.CASCADE) - contact = models.CharField(max_length=254, - blank=True, null=True,) - - class Meta: - verbose_name = ('Relying Party Contact') - verbose_name_plural = ('Relying Parties Contacts') - unique_together = ('client', 'contact') - - def __str__(self): - return '{}, [{}]'.format(self.client, self.contact) - - -class OidcRPRedirectUri(TimeStampedModel): - client = models.ForeignKey(OidcRelyingParty, - on_delete=models.CASCADE) - uri = models.CharField(max_length=254, - blank=True, null=True) - values = models.CharField(max_length=254, - blank=True, null=True) - type = models.CharField(choices=(('redirect_uris', 'redirect_uris'), - ('post_logout_redirect_uris', - 'post_logout_redirect_uris')), - max_length=33) - - class Meta: - verbose_name = ('Relying Party URI') - verbose_name_plural = ('Relying Parties URIs') - - def __str__(self): - return '{} [{}] {}'.format(self.client, self.uri, self.type) - - -class OidcRPScope(TimeStampedModel): - client = models.ForeignKey(OidcRelyingParty, - on_delete=models.CASCADE) - scope = models.CharField(max_length=254, - blank=True, null=True,) - - class Meta: - verbose_name = ('Relying Party Scope') - verbose_name_plural = ('Relying Parties Scopes') - unique_together = ('client', 'scope') - - def __str__(self): - return '{}, [{}]'.format(self.client, self.scope) - - -class OidcSessionSso(TimeStampedModel): - """ - """ - - user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, - blank=True, null=True) - sub = models.CharField(max_length=255, - blank=True, null=True) - - class Meta: - verbose_name = ('SSO Session SSO') - verbose_name_plural = ('SSO Sessions SSO') - - def get_session(self): - return OidcSession.objects.filter(sso=self).first() - - @property - def state(self): - session = self.get_session() - if session: - return session.state or '' - return '' - - @property - def username(self): - if self.user: - return self.user.username or '' - return '' - - def __iter__(self): - session = self.get_session() - yield session.sid - - def __contains__(self, k): - if getattr(self, k, None): - return True - else: - return False - - def __delitem__(self, name): - OidcSession.objects.filter(sso=self).delete() - self.delete() - - def __getitem__(self, name): - if is_sid(name): - if OidcSession.objects.filter(sid=name, sso=self): - return self - elif name == 'sid': - return self.sid - else: - if OidcSession.objects.filter(state=name, sso=self): - return self - return getattr(self, name) - - def get(self, name, default=None): - return self.__getattribute__(name) - - def __getattribute__(self, name): - if name == 'state': - return self - elif name == 'uid': - return self.user.username - elif name == 'sid': - return self - else: - return models.Model.__getattribute__(self, name) - - def __setattribute__(self, name, value): - if name == 'state': - self.sid = value - return - elif name == 'uid': - user = get_user_model().objects.filter(username=value[0]).first() - self.user = user - self.save() - return - elif name == 'sub': - self.sub = value[0] - self.save() - else: - return models.Model.__setattribute__(self, name, value) - - def __setitem__(self, key, value): - return self.__setattribute__(key, value) - - def append(self, value): - """multiple sid to a sso - """ - if is_sid(value): - if not isinstance(value, list): - value = [value] - - session = self.get_session() - session.sid = value[0] if isinstance(value, list) else value - session.save() - else: - # import pdb; pdb.set_trace() - _msg = '{} .append({}) with missing handler!' - logger.warn(_msg.format(self.__class__.name, value)) - - def __str__(self): - return 'user: {} - sub: {}'.format(self.username, - self.sub) - - -class OidcSession(TimeStampedModel): - """ - Store the session information in this model - """ - user_uid = models.CharField(max_length=120, - blank=False, null=False) - state = models.CharField(max_length=255, - blank=True, null=True) - client = models.ForeignKey(OidcRelyingParty, on_delete=models.CASCADE, - blank=True, null=True) - session_info = models.TextField(blank=True, null=True) - - grant = models.TextField(blank=True, null=True) - grant_id = models.CharField(max_length=255, - blank=True, null=True) - - issued_at = models.DateTimeField(blank=True, null=True) - valid_until = models.DateTimeField(blank=True, null=True) - - sso = models.ForeignKey(OidcSessionSso, on_delete=models.CASCADE, - blank=True, null=True) - code = models.CharField(max_length=1024, - blank=True, null=True) - sid = models.CharField(max_length=255, - blank=True, null=True) - sub = models.CharField(max_length=255, - blank=True, null=True) - - class Meta: - verbose_name = ('SSO Session') - verbose_name_plural = ('SSO Sessions') - - - @classmethod - def create_by(cls, **data): - - if data.get('client_id'): - client = get_client_by_id(data['client_id']) - data['client'] = client - data.pop('client_id') - - if data.get('session_info'): - data['session_info'] = json.dumps(data['session_info'].__dict__) - - res = cls.objects.create(**data) - return res - - - @classmethod - def get_by_sid(cls, value): - sids = cls.objects.filter(sid=value, - valid_until__gt=timezone.localtime()) - if sids: - return sids.last() - - - @classmethod - def get_session_by(cls, **data): - - if data.get('client_id'): - client = get_client_by_id(data['client_id']) - data.pop('client_id') - data['client'] = client - - - - data['valid_until__gt'] = timezone.localtime() - res = cls.objects.filter(**data) - if res: - return res.last() - - - @classmethod - def get_by_client_id(self, uid): - res = cls.objects.filter(uid=value, - valid_until__gt=timezone.localtime()) - if res: - return self.session_info - - - def set_grant(self, grant): - """ - {'issued_at': 1615403213, 'not_before': 0, 'expires_at': 0, - 'revoked': False, 'used': 0, 'usage_rules': {}, 'scope': [], - 'authorization_details': None, - 'authorization_request': , - 'authentication_event': , - 'claims': {}, 'resources': [], 'issued_token': [], - 'id': 'c695a5e881d311eb905343ee297b1c98', - 'sub': '204176ab8fe8917ee4788683bcee4ebc04bfe1ab659485ec61b2b2b4108c5272', - 'token_map': { - 'authorization_code': , - 'access_token': , - 'refresh_token': } - } - """ - self.issued_at = timezone.make_aware(timezone.datetime.fromtimestamp(grant.issued_at)) - self.sub = grant.sub - self.grant_id = grant.id - - grant.authorization_request = grant.authorization_request.to_json() - grant.authentication_event = grant.authentication_event.to_json() - # breakpoint() - # grant.token_map['authorization_code'] = grant.token_map['authorization_code'].to_json() - # grant.token_map['access_token'] = grant.token_map['access_token'].to_json() - # grant.token_map['refresh_token'] = grant.token_map['refresh_token'].to_json() - grant.token_map.pop('authorization_code') - grant.token_map.pop('access_token') - grant.token_map.pop('refresh_token') - - self.grant = grant.to_json() - self.save() - return grant - - @classmethod - def get_by_session_id(cls, user_uid, client_id, grant_id): - grant = cls.objects.filter(user_uid = user_uid, - client__client_id = client_id, - grant_id = grant_id) - if grant: - return grant.last() - - - - def copy(self): - return dict(sid=self.sid or [], - state=self.state or '', - session_info=self.session_info) - - - def append(self, value): - """Not used, only back compatibility - """ - - - def __iter__(self): - for i in (self.sid,): - yield i - - - def __str__(self): - return 'state: {}'.format(self.state or '') diff --git a/example/django_op/oidc_provider/templates/check_session_iframe.html b/example/django_op/oidc_provider/templates/check_session_iframe.html deleted file mode 100644 index fcd49607..00000000 --- a/example/django_op/oidc_provider/templates/check_session_iframe.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - 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 deleted file mode 100644 index 0cca93c1..00000000 --- a/example/django_op/oidc_provider/templates/frontchannel_logout.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - 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 deleted file mode 100644 index 5b8e91ea..00000000 --- a/example/django_op/oidc_provider/templates/logout.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - 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 deleted file mode 100644 index 9add475c..00000000 --- a/example/django_op/oidc_provider/templates/oidc_login.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - 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 deleted file mode 100644 index 509c1f7a..00000000 --- a/example/django_op/oidc_provider/templates/post_logout.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - 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 deleted file mode 100644 index 9add475c..00000000 --- a/example/django_op/oidc_provider/templates/user_pass.jinja2 +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - Please login - - - -

{{ page_header }}

- -
- - -

- - -

- -

- - -

- -

- {{ logo_label }} -

-

- {{ tos_label }} -

-

- {{ policy_label }} -

- - -
- - diff --git a/example/django_op/oidc_provider/tests.py b/example/django_op/oidc_provider/tests.py deleted file mode 100644 index 49290204..00000000 --- a/example/django_op/oidc_provider/tests.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your tests here. diff --git a/example/django_op/oidc_provider/tests/01_client_db.py b/example/django_op/oidc_provider/tests/01_client_db.py deleted file mode 100644 index 3653f310..00000000 --- a/example/django_op/oidc_provider/tests/01_client_db.py +++ /dev/null @@ -1,102 +0,0 @@ -import datetime -import logging -import json -import random -import string -import pytz - -from django.test import TestCase -from oidc_provider.db_interfaces import OidcClientDatabase -from oidc_provider.models import TIMESTAMP_FIELDS, OidcRelyingParty - - -logger = logging.getLogger('django_test') - - -def randomString(stringLength=10): - """Generate a random string of fixed length """ - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(stringLength)) - - -CLIENT_ID = randomString() -CLIENT_TEST = {'client_id': '{}'.format(CLIENT_ID), - 'client_salt': '6flfsj0Z', - 'registration_access_token': 'z3PCMmC1HZ1QmXeXGOQMJpWQNQynM4xY', - 'registration_client_uri': 'https://127.0.0.1:8000/registration_api?client_id={}'.format(CLIENT_ID), - 'client_id_issued_at': 1575460012, - 'client_secret': '19cc69b70d0108f630e52f72f7a3bd37ba4e11678ad1a7434e9818e1', - 'client_secret_expires_at': 1575892012, - 'application_type': 'web', - 'contacts': ['ops@example.com'], - 'token_endpoint_auth_method': 'client_secret_basic', - 'jwks_uri': 'https://127.0.0.1:8099/static/jwks.json', - 'redirect_uris': [('https://127.0.0.1:8099/authz_cb/django_oidc_op', {})], - 'post_logout_redirect_uris': [('https://127.0.0.1:8099', None)], - 'response_types': ['code'], - 'grant_types': ['authorization_code'] - } - - -class TestRP(TestCase): - rp = randomString().upper() - cdb = OidcClientDatabase() - now = pytz.utc.localize(datetime.datetime.utcnow()) - - def setUp(self): - self.client_obj = OidcRelyingParty.objects.create(client_id=self.rp, - client_secret_expires_at=self.now, - client_id_issued_at=self.now, - is_active=True) - self.client = self.cdb[self.rp] - print('Created and fetched RP: {}'.format(self.client)) - - def test_get_set_client_db(self): - for key in TIMESTAMP_FIELDS: - value = self.client[key] - assert isinstance(value, int) or \ - isinstance(value, float) - - # test_set_timestamp - for key in TIMESTAMP_FIELDS: - dt_value = self.now+datetime.timedelta(minutes=-60) - self.client[key] = datetime.datetime.timestamp(dt_value) - assert isinstance(self.client[key], int) or \ - isinstance(self.client[key], float) - - # contacts - vt = ['testami@ora.it'] - self.client['contacts'] = vt - assert self.client['contacts'] == vt - # self.client_obj.contacts == vt - - # grant_types - vt = ['authorization_code'] - self.client['grant_types'] = vt - assert self.client['grant_types'] == vt - # self.client_obj.grant_type == vt - - # response_types - vt = ['code'] - self.client['response_types'] = vt - assert self.client['response_types'] == vt - # self.client_obj.response_types == vt - - # post_logout_redirect_uris - vt = [('https://127.0.0.1:8099', None)] - self.client['post_logout_redirect_uris'] = vt - assert self.client['post_logout_redirect_uris'] == vt - # self.client_obj.post_logout_redirect_uris == vt - - # redirect_uris - vt = [('https://127.0.0.1:8099/authz_cb/django_oidc_op', {})] - self.client['redirect_uris'] = vt - assert self.client['redirect_uris'] == vt - # self.client_obj.redirect_uris == vt - - logger.info(json.dumps(self.client.copy(), indent=2)) - - def test_create_as_dict(self): - logger.info('Test creare ad Dict') - self.cdb[CLIENT_ID] = CLIENT_TEST - logger.info(json.dumps(self.cdb[CLIENT_ID], indent=2)) diff --git a/example/django_op/oidc_provider/tests/__init__.py b/example/django_op/oidc_provider/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/example/django_op/oidc_provider/urls.py b/example/django_op/oidc_provider/urls.py deleted file mode 100644 index 5d58e825..00000000 --- a/example/django_op/oidc_provider/urls.py +++ /dev/null @@ -1,33 +0,0 @@ -from django.urls import path - -from . import views - -app_name = 'oidc_provider' - - -urlpatterns = [ - path('.well-known/', views.well_known, - name="oidc_op_well_known"), - path('registration', views.registration, - name="oidc_op_registration"), - path('registration_api', views.registration_api, - name="oidc_op_registration_api"), - - path('authorization', views.authorization, - name="oidc_op_authorization"), - - path('verify/oidc_user_login/', views.verify_user, - name="oidc_op_verify_user"), - path('token', views.token, name="oidc_op_token"), - path('userinfo', views.userinfo, name="oidc_op_userinfo"), - - - path('check_session_iframe', views.check_session_iframe, - name="oidc_op_check_session_iframe"), - path('session', views.session_endpoint, name="oidc_op_session"), - # logout - path('verify_logout', views.verify_logout, - name="oidc_op_verify_logout"), - path('post_logout', views.post_logout, name="oidc_op_post_logout"), - path('rp_logout', views.rp_logout, name="oidc_op_rp_logout"), -] diff --git a/example/django_op/oidc_provider/users.py b/example/django_op/oidc_provider/users.py deleted file mode 100644 index 65c584bb..00000000 --- a/example/django_op/oidc_provider/users.py +++ /dev/null @@ -1,139 +0,0 @@ -import copy - -from django.contrib.auth import authenticate, get_user_model -from django.contrib.auth import get_user_model -from django.template.loader import render_to_string - -from oidcop.user_authn.user import (create_signed_jwt, LABELS) -from oidcop.user_authn.user import UserAuthnMethod - - -class UserPassDjango(UserAuthnMethod): - """ - see oidcop.authn_context - oidcop.endpoint_context - https://docs.djangoproject.com/en/2.2/ref/templates/api/#rendering-a-context - """ - - # TODO: get this though settings conf - url_endpoint = "/verify/user_pass_django" - - def __init__(self, - # template_handler=render_to_string, - template="oidc_login.html", - server_get=None, verify_endpoint='', **kwargs): - """ - template_handler is only for backwards compatibility - it will be always replaced by Django's default - """ - super(UserPassDjango, self).__init__(server_get=server_get) - - self.kwargs = kwargs - self.kwargs.setdefault("page_header", "Log in") - self.kwargs.setdefault("user_label", "Username") - self.kwargs.setdefault("passwd_label", "Password") - self.kwargs.setdefault("submit_btn", "Log in") - self.kwargs.setdefault("tos_uri", "") - self.kwargs.setdefault("logo_uri", "") - self.kwargs.setdefault("policy_uri", "") - self.kwargs.setdefault("tos_label", "") - self.kwargs.setdefault("logo_label", "") - self.kwargs.setdefault("policy_label", "") - - # TODO this could be taken from args - self.template_handler = render_to_string - self.template = template - - self.action = verify_endpoint or self.url_endpoint - self.kwargs['action'] = self.action - - def __call__(self, **kwargs): - _ec = 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(_ec.issuer, _ec.keyjar, **kwargs) - - self.kwargs['token'] = jws - - _kwargs = self.kwargs.copy() - for attr in ['policy', 'tos', 'logo']: - _uri = '{}_uri'.format(attr) - _kwargs[_uri] = kwargs.get(_uri) - _label = '{}_label'.format(attr) - _kwargs[_label] = LABELS[_uri] - - return self.template_handler(self.template, _kwargs) - - def verify(self, *args, **kwargs): - username = kwargs["username"] - password = kwargs["password"] - - user = authenticate(username=username, - password=password) - - if username: - return user - else: - raise FailedAuthentication() - - -class UserInfo(object): - """ Read only interface to a user info store """ - - def __init__(self, *args, **kwargs): - self.claims_map = kwargs.get('claims_map', {}) - - def filter(self, user, user_info_claims=None): - """ - Return only those claims that are asked for. - It's a best effort task; if essential claims are not present - no error is flagged. - - :param userinfo: A dictionary containing the available info for one user - :param user_info_claims: A dictionary specifying the asked for claims - :return: A dictionary of filtered claims. - """ - result = {} - if not user.is_active: - return result - - if user_info_claims is None: - return copy.copy(user.__dict__) - else: - missing = [] - optional = [] - for key, restr in user_info_claims.items(): - if key in self.claims_map: - # manage required and optional: TODO extends this approach - if not hasattr(user, self.claims_map.get(key)) and \ - restr == {"essential": True}: - missing.append(key) - continue - else: - optional.append(key) - # - uattr = getattr(user, self.claims_map[key], None) - if not uattr: - continue - result[key] = uattr() if callable(uattr) else uattr - return result - - def __call__(self, user_id, client_id, - user_info_claims=None, **kwargs): - """ - user_id = username - client_id = client id, ex: 'mHwpZsDeWo5g' - """ - user = get_user_model().objects.filter(username=user_id).first() - if not user: - # Todo: raise exception here, this wouldn't be possible. - return {} - - return self.filter(user, user_info_claims) - - def search(self, **kwargs): - for uid, args in self.db.items(): - if dict_subset(kwargs, args): - return uid - - raise KeyError('No matching user') diff --git a/example/django_op/oidc_provider/utils.py b/example/django_op/oidc_provider/utils.py deleted file mode 100644 index 1700178d..00000000 --- a/example/django_op/oidc_provider/utils.py +++ /dev/null @@ -1,31 +0,0 @@ -import datetime -import json -import pytz - -from oidcmsg.message import Message -from cryptojwt.key_jar import KeyJar - -from . views import oidcop_app - - -def timestamp2dt(value): - return int(datetime.datetime.timestamp(value)) - - -def dt2timestamp(value): - pytz.utc.localize(datetime.datetime.fromtimestamp(value)) - - -def decode_token(txt, attr_name='access_token', verify_sign=True): - issuer = oidcop_app.srv_config.conf['op']['server_info']['issuer'] - jwks_path = oidcop_app.srv_config.conf['OIDC_KEYS']['private_path'] - jwks = json.loads(open(jwks_path).read()) - - key_jar = KeyJar() - key_jar.import_jwks(jwks, issuer=issuer) - - jwt = json.loads(txt) - msg = Message().from_jwt(jwt.get(attr_name, ''), - keyjar=key_jar, - verify=verify_sign) - return msg diff --git a/example/django_op/oidc_provider/views.py b/example/django_op/oidc_provider/views.py deleted file mode 100644 index 89b7f023..00000000 --- a/example/django_op/oidc_provider/views.py +++ /dev/null @@ -1,351 +0,0 @@ -import base64 -import logging -import json -import os -import urllib - -from django.conf import settings -from django.http import (HttpResponse, - HttpResponseBadRequest, - HttpResponseRedirect, - JsonResponse) -from django.http.request import QueryDict -from django.views.decorators.csrf import csrf_exempt -from django.shortcuts import render -from oidcop.authn_event import create_authn_event -from oidcop.exception import UnAuthorizedClient -from oidcop.exception import UnAuthorizedClientScope # experimental -from oidcop.exception import InvalidClient -from oidcop.exception import UnknownClient -from oidcop.session.token import AccessToken -from oidcop.oidc.token import Token -from oidcmsg.oauth2 import ResponseMessage -from oidcmsg.oidc import AccessTokenRequest -from oidcmsg.oidc import AuthorizationRequest -from urllib import parse as urlib_parse -from urllib.parse import urlparse - - -from . application import oidcop_application -oidcop_app = oidcop_application() - -logger = logging.getLogger(__name__) -IGNORE = ["cookie", "user-agent"] - - -def _add_cookie(resp, cookie_spec): - kwargs = {'value': cookie_spec["value"]} - for param in ['expires', 'max-age']: - if param in cookie_spec: - kwargs[param] = cookie_spec[param] - kwargs["path"] = "/" - resp.set_cookie(cookie_spec["name"], **kwargs) - - -def add_cookie(resp, cookie_spec): - if isinstance(cookie_spec, list): - for _spec in cookie_spec: - _add_cookie(resp, _spec) - elif isinstance(cookie_spec, dict): - _add_cookie(resp, cookie_spec) - - -def do_response(endpoint, req_args, error='', **args): - info = endpoint.do_response(request=req_args, error=error, **args) - _response_placement = info.get('response_placement') - if not _response_placement: - _response_placement = endpoint.response_placement - - info_response = info['response'] - # Debugging things - try: - response_params = json.dumps(json.loads(info_response), indent=2) - logger.debug('Response params: {}\n'.format(response_params)) - except: - url, args = urllib.parse.splitquery(info_response) - response_params = urllib.parse.parse_qs(args) - resp = json.dumps(response_params, indent=2) - logger.debug('Response params: {}\n{}\n\n'.format(url, resp)) - # end debugging - - if error: - if _response_placement == 'body': - logger.debug('Error Response [Body]: {}'.format(info_response)) - resp = HttpResponse(info_response, status=400) - else: # _response_placement == 'url': - logger.debug('Redirect to: {}'.format(info_response)) - resp = HttpResponseRedirect(info_response) - else: - if _response_placement == 'body': - #logger.debug('Response [Body]: {}'.format(info_response)) - resp = HttpResponse(info_response, status=200) - else: # _response_placement == 'url': - #logger.debug('Redirect to: {}'.format(info_response)) - resp = HttpResponseRedirect(info_response) - - for key, value in info['http_headers']: - # set response headers - resp[key] = value - - if 'cookie' in info: - add_cookie(resp, info['cookie']) - - return resp - - -def fancy_debug(request): - """ - fancy logging of JWT things - """ - _headers = json.dumps(dict(request.headers), indent=2) - logger.debug('Request Headers: {}\n\n'.format(_headers)) - - _get = json.dumps(dict(request.GET), indent=2) - if request.GET: - logger.debug('Request arguments GET: {}\n'.format(_get)) - if request.POST or request.body: - _post = request.POST or request.body - if isinstance(_post, bytes): - _post = json.dumps(json.loads(_post.decode()), indent=2) - elif isinstance(_post, QueryDict): - _post = json.dumps({k: v for k, v in _post.items()}, - indent=2) - logger.debug('Request arguments POST: {}\n'.format(_post)) - - -def service_endpoint(request, endpoint): - """ - TODO: documentation here - """ - logger.info('\n\nRequest at the "{}" endpoint'.format(endpoint.name)) - # if logger.level == 0: - # fancy_debug(request) - - http_info = { - "headers": {k.lower(): v for k, v in request.headers.items() if k not in IGNORE}, - "method": request.method, - "url": request.get_raw_uri(), - # name is not unique - "cookie": [{"name": k, "value": v} for k, v in request.COOKIES.items()] - } - - if request.method == 'GET': - data = {k: v for k, v in request.GET.items()} - elif request.body: - data = request.body \ - if isinstance(request.body, str) else \ - request.body.decode() - # - if 'authorization' in http_info.get('headers', ()): - data = {k: v[0] for k, v in urlib_parse.parse_qs(data).items()} - else: - data = {k: v for k, v in request.POST.items()} - - req_args = endpoint.parse_request(data, http_info=http_info) - - try: - if isinstance(endpoint, Token): - args = endpoint.process_request( - AccessTokenRequest(**req_args), http_info=http_info - ) - else: - args = endpoint.process_request(req_args, http_info=http_info) - except (InvalidClient, UnknownClient, UnAuthorizedClient) as err: - logger.error(err) - return JsonResponse(json.dumps({ - 'error': 'unauthorized_client', - 'error_description': str(err) - }), safe=False, status=400) - except Exception as err: - logger.error(err) - return JsonResponse(json.dumps({ - 'error': 'invalid_request', - 'error_description': str(err), - 'method': request.method - }), safe=False, status=400) - - if isinstance(req_args, ResponseMessage) and 'error' in req_args: - return JsonResponse(req_args.__dict__, status=400) - - if 'redirect_location' in args: - return HttpResponseRedirect(args['redirect_location']) - if 'http_response' in args: - return HttpResponse(args['http_response'], status=200) - - return do_response(endpoint, req_args, **args) - - -def well_known(request, service): - """ - /.well-known/ - """ - if service == 'openid-configuration': - _endpoint = oidcop_app.endpoint_context.endpoint['provider_config'] - # TODO fedservice integration here - # if service == 'openid-federation': - # _endpoint = oidcop_app.endpoint_context.endpoint['provider_info'] - elif service == 'webfinger': - _endpoint = oidcop_app.endpoint_context.endpoint['webfinger'] - else: - return HttpResponseBadRequest('Not supported', status=400) - return service_endpoint(request, _endpoint) - - -@csrf_exempt -def registration(request): - logger.info('registration request') - _endpoint = oidcop_app.endpoint_context.endpoint['registration'] - return service_endpoint(request, _endpoint) - - -@csrf_exempt -def registration_api(): - logger.info('registration API') - return service_endpoint( - request, - oidcop_app.endpoint_context.endpoint['registration_api'] - ) - - -def authorization(request): - _endpoint = oidcop_app.endpoint_context.endpoint['authorization'] - return service_endpoint(request, _endpoint) - - -@csrf_exempt -def verify_user(request): - """csrf is not needed because it uses oidc token in the post - """ - token = request.POST.get('token') - if not token: - return HttpResponse('Access forbidden: invalid token.', status=403) - authn_method = oidcop_app.endpoint_context.endpoint_context.authn_broker.get_method_by_id('user') - - kwargs = dict([(k, v) for k, v in request.POST.items()]) - user = authn_method.verify(**kwargs) - if not user: - return HttpResponse('Authentication failed', status=403) - - auth_args = authn_method.unpack_token(kwargs['token']) - authz_request = AuthorizationRequest().from_urlencoded(auth_args['query']) - - # salt size can be customized in settings.OIDC_OP_AUTHN_SALT_SIZE - salt_size = getattr(settings, 'OIDC_OP_AUTHN_SALT_SIZE', 4) - authn_event = create_authn_event( - uid=user.username, - salt=base64.b64encode(os.urandom(salt_size)).decode(), - authn_info=auth_args['authn_class_ref'], - authn_time=auth_args['iat'] - ) - - endpoint = oidcop_app.endpoint_context.endpoint['authorization'] - # cinfo = endpoint.endpoint_context.cdb[authz_request["client_id"]] - - # {'session_id': 'diana;;client_3;;38044288819611eb905343ee297b1c98', 'identity': {'uid': 'diana'}, 'user': 'diana'} - client_id = authz_request["client_id"] - _token_usage_rules = endpoint.server_get("endpoint_context").authn_broker.get_method_by_id('user') - - session_manager = oidcop_app.endpoint_context.endpoint_context.session_manager - _session_id = session_manager.create_session( - authn_event=authn_event, - auth_req=authz_request, - user_id=user.username, - client_id=client_id, - token_usage_rules=_token_usage_rules - ) - - try: - args = endpoint.authz_part2(user=user.username, - session_id = _session_id, - request=authz_request, - authn_event=authn_event) - except ValueError as excp: - msg = 'Something wrong with your Session ... {}'.format(excp) - return HttpResponse(msg, status=403) - - if isinstance(args, ResponseMessage) and 'error' in args: - return HttpResponse(args.to_json(), status=400) - - response = do_response(endpoint, request, **args) - return response - - -@csrf_exempt -def token(request): - logger.info('token request') - _endpoint = oidcop_app.endpoint_context.endpoint['token'] - return service_endpoint(request, _endpoint) - - -@csrf_exempt -def userinfo(request): - logger.info('userinfo request') - _endpoint = oidcop_app.endpoint_context.endpoint['userinfo'] - return service_endpoint(request, _endpoint) - - -######## -# LOGOUT -######## -def session_endpoint(request): - return service_endpoint( - request, - oidcop_app.endpoint_context.endpoint['session'] - ) - - -def check_session_iframe(request): - if request.method == 'GET': - req_args = request.GET - elif request.method == 'POST': - req_args = json.loads(request.POST) - else: - req_args = dict([(k, v) for k, v in request.body.items()]) - - if req_args: - # will contain client_id and origin - if req_args['origin'] != oidcop_app.endpoint_context.conf['issuer']: - return 'error' - if req_args['client_id'] != current_app.endpoint_context.cdb: - return 'error' - return 'OK' - - logger.debug('check_session_iframe: {}'.format(req_args)) - res = render(request, template_name='check_session_iframe.html') - return res - - -@csrf_exempt -def rp_logout(request): - _endp = oidcop_app.endpoint_context.endpoint['session'] - _info = _endp.unpack_signed_jwt(request.POST['sjwt']) - alla = None # request.POST.get('logout') - - _iframes = _endp.do_verified_logout(alla=alla, **_info) - if _iframes: - d = dict(frames=" ".join(_iframes), - size=len(_iframes), - timeout=5000, - postLogoutRedirectUri=_info['redirect_uri']) - res = render(request, 'frontchannel_logout.html', d) - - else: - res = HttpResponseRedirect(_info['redirect_uri']) - try: - _endp.kill_cookies() - except AttributeError: - logger.debug('Cookie not implemented or not working.') - #_add_cookie(res, _kakor) - return res - - -def verify_logout(request): - part = urlparse(oidcop_app.endpoint_context.conf['issuer']) - d = dict(op=part.hostname, - do_logout='rp_logout', - sjwt=request.GET['sjwt'] or request.POST['sjwt']) - return render(request, 'logout.html', d) - - -def post_logout(request): - return render(request, 'post_logout.html') diff --git a/example/django_op/requirements.txt b/example/django_op/requirements.txt deleted file mode 100644 index 263f1c08..00000000 --- a/example/django_op/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -Django>3.1,<4.0 -oidcop -gunicorn -filelock - -git+https://github.com/IdentityPython/JWTConnect-Python-CryptoJWT -git+https://github.com/IdentityPython/JWTConnect-Python-OidcMsg -git+https://github.com/IdentityPython/JWTConnect-Python-OidcService -git+https://github.com/IdentityPython/oidcendpoint.git -git+https://github.com/IdentityPython/JWTConnect-Python-OidcRP diff --git a/example/django_op/snippets/db_interfaces.py b/example/django_op/snippets/db_interfaces.py deleted file mode 100644 index 7c41d478..00000000 --- a/example/django_op/snippets/db_interfaces.py +++ /dev/null @@ -1,287 +0,0 @@ -import datetime -import json -import logging -import pytz - -from django.contrib.auth import get_user_model -from django.utils import timezone -from oidcop.session.database import Database -from oidcop.session.info import UserSessionInfo -from . models import (OidcRelyingParty, - OidcRPResponseType, - OidcRPGrantType, - OidcRPContact, - OidcRPRedirectUri, - OidcSession, - OidcSessionSso, - TIMESTAMP_FIELDS, - is_state, - is_sid, - is_sub, - is_code) - - -logger = logging.getLogger(__name__) - - -class OidcClientDb(object): - """ - Adaptation of a Django model as if it were a dict - """ - model = OidcRelyingParty - - def __init__(self, *args, **kwargs): - pass - - def __contains__(self, key): - if self.model.objects.filter(client_id=key).first(): - return 1 - - def __iter__(self): - values = self.model.objects.all().values_list('client_id') - self.clients = [cid[0] for cid in values] - for value in (self.clients): - yield value - - def get(self, key, excp=None, as_obj=False): - client = self.model.objects.filter(client_id=key, - is_active=True).first() - if not client: - return excp - - # set last_seen - client.last_seen = timezone.localtime() - client.save() - if as_obj: - return client - return client.copy() - - def __getitem__(self, key): - value = self.get(key) - if not value: - raise KeyError - return value - - def keys(self): - return self.model.objects.values_list('client_id', flat=True) - - def __setitem__(self, key, value): - return self.set(key, value) - - def set(self, key, value): - dv = value.copy() - - for k, v in dv.items(): - if isinstance(v, int) or isinstance(v, float): - if k in TIMESTAMP_FIELDS: - dt = datetime.datetime.fromtimestamp(v) - dv[k] = pytz.utc.localize(dt) - - client = None - # if the client already exists - if dv.get('id'): - client = self.model.objects.\ - filter(pk=dv['id']).first() - - if dv.get('client_id'): - client = self.model.objects.\ - filter(client_id=dv['client_id']).first() - - if not client: - client_id = dv.pop('client_id') - client = self.model.objects.create(client_id=client_id) - - for k, v in dv.items(): - setattr(client, k, v) - - client.save() - - def __str__(self): - return self.__dict__ - - -class OidcSessionDb(Database): - """ - Adaptation of a Django model as if it were a dict - - This class acts like a NoSQL storage but stores informations - into a pure Django DB model - """ - - def __init__(self, conf_db=None, session_db=None, sso_db=None, cdb=None): - self.conf_db = conf_db - self.db = session_db or OidcSession - self.sso_db = sso_db or OidcSessionSso - self.cdb = cdb or OidcClientDb() - - def get_valid_sessions(self): - return self.db.objects.filter().exclude(valid_until__lte=timezone.localtime()) - - def get_by_sid(self, value): - session = self.db.get_by_sid(value) - if session: - return session - - def get_by_state(self, value): - session = self.get_valid_sessions().filter(state=value) - if session: - return session.last() - - def create_by_state(self, state): - return self.db.objects.create(state=state) - - def __iter__(self): - self.elems = self.keys() - for value in (self.elems): - yield value - - def __getitem__(self, *args, **kwargs): - return self.get(*args, **kwargs) - - def get(self, key, excp=None): - if is_sid(key): - elem = self.db.get_by_sid(key) - elif is_code(key): - elem = self.get_valid_sessions().filter(code=key).last() - else: - # state is unpredictable, it's client side. - # elem = self.get_valid_sessions().filter(state=key).last() - elem = self.get_valid_sessions().filter(uid=key).last() - - if not elem: - return - elif elem.sid and elem.sid == key: - return json.loads(elem.session_info) - # elif elem.state == key: - elif elem.uid == key: - return elem.sso.sid - - def set_session_info(self, info_dict): - # info_dict = {'user_id': 'wert', 'subordinate': [], 'revoked': False, 'type': 'UserSessionInfo'} - - session = self.get_valid_sessions().get( - state=info_dict['authn_req']['state']) - session.session_info = json.dumps(info_dict) - session.code = info_dict.get('code') - authn_event = info_dict.get('authn_event') - valid_until = authn_event.get('valid_until') - if valid_until: - dt = datetime.datetime.fromtimestamp(valid_until) - session.valid_until = pytz.utc.localize(dt) - - client_id = info_dict.get('client_id') - session.client = self.cdb.get(key=client_id, as_obj=True) - session.save() - - def set(self, key, value): - if is_sid(key): - # info_dict = {'code': 'Z0FBQUFBQmZESFowazFBWWJteTNMOTZQa25KZmV0N1U1VzB4VEZCVEN3SThQVnVFRWlSQ2FrODhpb3Yyd3JMenJQT01QWGpuMnJZQmQ4YVh3bF9sbUxqMU43VG1RQ01BbW9JdV8tbTNNSzREMUk2U2N4YXVwZ3ZWQ1ZvbXdFanRsbWJIaWQyVWZON0N5LU9mUlhZUGgwdFRDQkpRZ3dSR0lVQjBBT0s4OHc3REJOdUlPUGVOUU9ZRlZvU3FBdVU2LThUUWNhRDVocl9QWEswMmo3Y2VtLUNvWklsX0ViN1NfWFRJWksxSXhxNVVNQW9ySngtc2RCST0=', 'oauth_state': 'authz', 'client_id': 'Mz2LUfvqCbRQ', 'authn_req': {'redirect_uri': 'https://127.0.0.1:8099/authz_cb/django_oidc_op', 'scope': 'openid profile email address phone', 'response_type': 'code', 'nonce': 'mpuLL5IxgDvFDGAqlE05LwHO', 'state': 'eOzFkkGFHLT16zO6SqpOmc2rv6DZmf3g', 'code_challenge': 'lAs7I04g1Qh8mhTG8wxV0BfmrhzrSrl1ASp04C3Zmog', 'code_challenge_method': 'S256', 'client_id': 'Mz2LUfvqCbRQ'}, 'authn_event': {'uid': 'wert', 'salt': 'fc7AGQ==', 'authn_info': 'oidcop.user_authn.authn_context.INTERNETPROTOCOLPASSWORD', 'authn_time': 1594652276, 'valid_until': 1594655876}} - info_dict = value - self.set_session_info(info_dict) - logger.debug('Session DB - set - {}'.format(session.copy())) - - def __setitem__(self, sid, instance): - if is_sid(sid): - try: - instance.to_json() - except ValueError: - json.dumps(instance) - except AttributeError: - # it's a dict - pass - - self.set_session_info(instance) - - elif isinstance(instance, UserSessionInfo): - # {'_dict': {'user_id': 'wert', 'subordinate': [], 'revoked': False, 'type': 'UserSessionInfo'}, 'lax': False, 'jwt': None, 'jws_header': None, 'jwe_header': None, 'verify_ssl': True} - self.set_session_info(instance.__dict__['_dict']) - - else: - logger.error('{} tries __setitem__ {} in {}'.format(sid, - instance, - self.__class__.__name__)) - - def __delitem__(self, key): - if is_sid(key): - ses = self.db.get_by_sid(key) - if ses: - ses.sso.delete() - ses.delete() - - -class OidcSsoDb(object): - """ - Adaptation of a Django model as if it were a dict - - This class acts like a NoSQL storage but stores informations - into a pure Django DB model - """ - - def __init__(self, db_conf={}, db=None, session_handler=None): - self._db = db or OidcSessionSso - self._db_conf = db_conf - self.session_handler = session_handler or db_conf.get( - 'session_hanlder') or OidcSessionDb() - - def _get_or_create(self, sid): - sso = self._db.objects.filter(sid=sid).first() - if not sso: - sso = self._db.objects.create(sid=sid) - return sso - - def __setitem__(self, k, value): - if isinstance(value, dict): - if value.get('state'): - session = self.session_handler.create_by_state(k) - session.sid = value['state'][0] \ - if isinstance(value['state'], list) else value - sso = self._db.objects.create() - session.sso = sso - session.save() - else: - # it would be quite useless for this implementation ... - # k = '81c58c4037ab1939423ab4fb8b472fdd5fc3a3939e4debc81f52ed37' - # value = - pass - - - def set(self, k, v): - logging.info('{}:{} - already there'.format(k, v)) - - - def get(self, k, default): - session = self.session_handler.get_by_state(k) - if session: - return session - - if is_sub(k): - # sub - return self._db.objects.filter(sub=k).last() or {} - elif is_sid(k): - # sid - session = self.session_handler.get_by_sid(k) - return session.sso if session else {} - else: - logger.debug(("{} can't find any attribute " - "with this name as attribute: {}").format(self, k)) - user = get_user_model().objects.filter(username=k).first() - if user: - logger.debug( - 'Tryng to match to a username: Found {}'.format(user)) - return self._db.objects.filter(user=user).last() - else: - return {} - - def __delitem__(self, name): - self.delete(name) - - def delete(self, name): - session = self.session_handler.get_by_state(name) - - if is_sid(name): - session = self.session_handler.get_by_sid(name) - elif is_sub(name): - sso = self._db.objects.filter(sub=name) - sso.delete() - if session: - session.delete() diff --git a/example/django_op/snippets/msg_test.py b/example/django_op/snippets/msg_test.py deleted file mode 100644 index 922e571d..00000000 --- a/example/django_op/snippets/msg_test.py +++ /dev/null @@ -1,39 +0,0 @@ -import json -import requests - -from cryptojwt.key_jar import KeyJar -from oidcmsg.message import Message - - -issuer = "https://127.0.0.1:8000" -issuer_metadata_url = '{}/.well-known/openid-configuration'.format(issuer) -issuer_metadata = requests.get(issuer_metadata_url, verify=False).text -issuer_jwks_uri = json.loads(issuer_metadata)['jwks_uri'] - -jwks_raw = requests.get(issuer_jwks_uri, verify=False).text -jwks = json.loads(jwks_raw) - - -# built keyjar -key_jar = KeyJar() -key_jar.import_jwks(jwks, issuer=issuer) - -# it depends by JWT, is the signing key identifier could be extracted from it then it should be verified, otherwise not -# see difference between at and unverifiable_at -verify_sign = 1 - -at = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJXdG9SekV4VXkxak9GVXlSV2hwZUdkbFREWlBaME55TW1ka05ERlFaakJSUzJreVQwaExVazVJUVEifQ.eyJzdWIiOiAiODAzMjcwNDJiOTZiOWYxYzAwZDlkMDRkYjgxNmU4NGFmNGUzNjE2ZGIxZDA2OTRiMTNhYjg2ZjQ5ZmQyNTFiZiIsICJhdXRoX3RpbWUiOiAxNTc2MDU4MTc4LCAiYWNyIjogIm9pZGNlbmRwb2ludC51c2VyX2F1dGhuLmF1dGhuX2NvbnRleHQuSU5URVJORVRQUk9UT0NPTFBBU1NXT1JEIiwgImVtYWlsIjogImdpdXNlcHBlLmRlbWFyY29AdW5pY2FsLml0IiwgIm5vbmNlIjogImFFbERSTUNBUWhHZWVBVUs1b0RyRG1PdSIsICJpc3MiOiAiaHR0cHM6Ly8xMjcuMC4wLjE6ODAwMCIsICJpYXQiOiAxNTc2MDU4MTc4LCAiZXhwIjogMTU3NjA1ODQ3OCwgImF1ZCI6IFsiejl3VU90dG1GM3FjIl19.lAKU1F_77T11gNcDvRMHy3hEJANaCkjTtbyR5D7DmOFOTKTYBWIT_ZqEmGnBmGpDBhnGcwbupFavltAOq03PGyJYu-Bxk3ktqTCjfcLTQroE-o3KTx_xYjVDh60p3RAAK9iD5ligmH2I5vFtFl-5ckTl2tX9Zn_IAqJrpuCIvgwBHzje-61upXKyXzBthOKa2dWAlOMHXYKlFV_4mhnr3Q3RNP8hmgFkltD3d-INo8tlysRQebuxnQ7LizQtOIhWACioHZosfpQIu2_L89PiTdcUuFeAeRNa_kUe9Oztk7ffCcDk2Xqa5C-8gxpgHTX6STEa59jeG07q7_s4pj7Fig" -unverifiable_at = "eyJhbGciOiJFUzI1NiIsImtpZCI6IlQwZGZTM1ZVYUcxS1ZubG9VVTQwUXpJMlMyMHpjSHBRYlMxdGIzZ3hZVWhCYzNGaFZWTlpTbWhMTUEifQ.eyJzaWQiOiAiNzUyYTc2NWZlMjg4MGE4NmU2OTlkNTcxZWM4YTE2MWE2NTkzZjVhMmJlNzdkMDkzZDRmNzU2MmUiLCAidHR5cGUiOiAiVCIsICJzdWIiOiAiODAzMjcwNDJiOTZiOWYxYzAwZDlkMDRkYjgxNmU4NGFmNGUzNjE2ZGIxZDA2OTRiMTNhYjg2ZjQ5ZmQyNTFiZiIsICJlbWFpbCI6ICJnaXVzZXBwZS5kZW1hcmNvQHVuaWNhbC5pdCIsICJnZW5kZXIiOiAibWFsZSIsICJpc3MiOiAiIiwgImlhdCI6IDE1NzYwNTI3ODMsICJleHAiOiAxNTc2MDU2MzgzLCAiYXVkIjogWyJ6OXdVT3R0bUYzcWMiLCAiaHR0cHM6Ly8xMjcuMC4wLjE6ODAwMCJdfQ.xjkeVPF8fcHZVVZa3wYHxatyoVizML10iD579T_HB1tj7Cm_JNRdKB0nUoq7HddfeeNG911YNWHHl0lD9TQ2gA" - -m = Message().from_jwt(at, keyjar=key_jar, verify=verify_sign) - - -print('Access Token header: ', m.jws_header) -print('Access Token payload: ', m) - -t = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJXdG9SekV4VXkxak9GVXlSV2hwZUdkbFREWlBaME55TW1ka05ERlFaakJSUzJreVQwaExVazVJUVEifQ.eyJzdWIiOiAiODAzMjcwNDJiOTZiOWYxYzAwZDlkMDRkYjgxNmU4NGFmNGUzNjE2ZGIxZDA2OTRiMTNhYjg2ZjQ5ZmQyNTFiZiIsICJhdXRoX3RpbWUiOiAxNTc2MDUyNzc3LCAiYWNyIjogIm9pZGNlbmRwb2ludC51c2VyX2F1dGhuLmF1dGhuX2NvbnRleHQuSU5URVJORVRQUk9UT0NPTFBBU1NXT1JEIiwgImVtYWlsIjogImdpdXNlcHBlLmRlbWFyY29AdW5pY2FsLml0IiwgIm5vbmNlIjogIlRKdnFjYW85RjRubkp5bTBPZnlNeXlBbSIsICJpc3MiOiAiaHR0cHM6Ly8xMjcuMC4wLjE6ODAwMCIsICJpYXQiOiAxNTc2MDUyNzgzLCAiZXhwIjogMTU3NjA1MzA4MywgImF1ZCI6IFsiejl3VU90dG1GM3FjIl19.kQq0WBXRTUcKY7RI8A0Yb1CyoOds5-EnDrc7mhc4pzp5xig9lOUWaBts6Q5vwoI29-iadPUf62SIcrBuGPAyvAFHC-pqSy-vcXbYFpWN18n9SaL7O2fkQD-PlPl088X6fJ-Muqt_RvkRjY_cwY-VuJNh3_uXXZdsLnUimsLI3E4q6aCMkoaqDajbq35L40_xcjRhmQHh9Rn_4pIfnRGKZdsQICYJ6ivLSO5dxxydDtyA1nip1ER8hV3ISj8qfJq1_tFe5JrvcEWXbs9MMmsnlXTTB6X-71ysmzqVcjY2o-CviZGQTwEXwJYY54_u24NUwwM72pWVL97N1ug7nIlusA" -m = Message().from_jwt(t, keyjar=key_jar, verify=verify_sign) - - -print('ID Token header: ', m.jws_header) -print('ID Token payload: ', m) diff --git a/example/django_op/snippets/rp_handler.py b/example/django_op/snippets/rp_handler.py deleted file mode 100644 index 27ae4e57..00000000 --- a/example/django_op/snippets/rp_handler.py +++ /dev/null @@ -1,178 +0,0 @@ -import os -import re -import requests -import json -import urllib -import urllib3 - -from cryptojwt import KeyJar -from cryptojwt.key_jar import init_key_jar - -from oidcmsg.message import Message -from oidcrp import rp_handler -from oidcrp.util import load_yaml_config - -RPHandler = rp_handler.RPHandler - -def decode_token(bearer_token, keyjar, verify_sign=True): - msg = Message().from_jwt(bearer_token, - keyjar=keyjar, - verify=verify_sign) - return msg.to_dict() - - -def init_oidc_rp_handler(app): - _rp_conf = app.config - - if _rp_conf.get('rp_keys'): - _kj = init_key_jar(**_rp_conf['rp_keys']) - _path = _rp_conf['rp_keys']['public_path'] - # removes ./ and / from the begin of the string - _path = re.sub('^(.)/', '', _path) - else: - _kj = KeyJar() - _path = '' - _kj.httpc_params = _rp_conf['httpc_params'] - hash_seed = app.config.get('hash_seed', "BabyHoldOn") - rph = RPHandler(_rp_conf['base_url'], _rp_conf['clients'], services=_rp_conf['services'], - hash_seed=hash_seed, keyjar=_kj, jwks_path=_path, - httpc_params=_rp_conf['httpc_params']) #, verify_ssl=False) - - return rph - - -def get_rph(config_fname): - config = load_yaml_config(config_file) - app = type('RPApplication', (object,), {"config": config}) - rph = init_oidc_rp_handler(app) - return rph - - -def fancy_print(msg, dict_obj): - print('\n\n{}\n'.format(msg), - json.dumps(dict_obj, indent=2) if dict_obj else '') - - -def run_rp_session(rph, issuer_id, username, password): - # register client (provider info and client registration) - info = rph.begin(issuer_id) - - issuer_fqdn = rph.hash2issuer[issuer_id] - issuer_keyjar = rph.issuer2rp[issuer_fqdn] - - fancy_print("Client registration done...\n" - "Connecting to Authorization url:", - info) - - # info contains the url to the authorization endpoint - # {'url': 'https://127.0.0.1:8000/authorization?redirect_uri=https%3A%2F%2F127.0.0.1%3A8099%2Fauthz_cb%2Fdjango_oidc_op&scope=openid+profile+email+address+phone&response_type=code&nonce=HhDGhvuIoQ9MaVLSqXi3D6r4&state=AdgqZVTxwdHaE9kjRUCLTnI78GpoQq90&code_challenge=v3UDlTl4qOrbA1owsEBdKMHwSubmvheGrjUiBeCQqhk&code_challenge_method=S256&client_id=shoInN4jcqIe', 'state': 'AdgqZVTxwdHaE9kjRUCLTnI78GpoQq90'} - - print('\nStarting connection to Authorization URL') - res = requests.get(info['url'], verify=rph.verify_ssl) - - # this contains the html form - # res.text - - #'\n\n\n\n \n Please login\n\n\n\n

Testing log in

\n\n
\n \n\n

\n \n \n

\n\n

\n \n \n

\n\n

\n \n

\n

\n \n

\n

\n \n

\n\n \n
\n\n\n' - try: - auth_code = re.search('value="(?P[a-zA-Z\-\.\_0-9]*)"', res.text).groupdict()['token'] - auth_url = re.search('action="(?P[a-zA-Z0-9\/\-\_\.\:]*)"', res.text).groupdict()['url'] - except Exception as e: - print(res.text) - raise Exception(res.text) - - fancy_print("The Authorization endpoint returns a " - "HTML authentication form with a token", - {'token': auth_code, - 'url': auth_url}) - - fancy_print('Authorization code content: ', - decode_token(auth_code, - keyjar=issuer_keyjar.get_service_context().keyjar -, - verify_sign=False)) - - auth_dict = {'username': username, - 'password': password, - 'token': auth_code} - - auth_url = '/'.join((issuer_fqdn, auth_url)) - print('Credential form submit ...') - req = requests.post(auth_url, - data=auth_dict, verify=rph.verify_ssl, - allow_redirects=False) - print('Credential form submitted!') - - # req is a 302, a redirect to the https://127.0.0.1:8099/authz_cb/django_oidc_op - if req.status_code != 302: - raise Exception(req.content) - rp_final_url = req.headers['Location'] - - fancy_print("The Authorization returns a " - "HttpRedirect (302) to {}".format(rp_final_url), None) - - # what we found later in request_args ... - # code = urllib.parse.parse_qs( urllib.parse.urlparse(rp_final_url).query) - # fancy_print("That contains: ", code) - - ws, args = urllib.parse.splitquery(rp_final_url) - request_args = urllib.parse.parse_qs(args) - - # from list to str - request_args = {k:v[0] for k,v in request_args.items()} - - fancy_print("Going to finalize the session ...", request_args) - - # oidcrp.RPHandler.finalize() will parse the authorization response and depending on the configuration run the needed RP service - result = rph.finalize(request_args['iss'], request_args) - - fancy_print("Got Access Token.", None) - - # Tha't the access token, the bearer used to access to userinfo endpoint - # result['token'] - fancy_print("Bearer Access Token", result['token']) - - # get the keyjar related to the issuer - decoded_access_token = decode_token(result['token'], - keyjar=issuer_keyjar.get_service_context().keyjar) - fancy_print("Access Token", decoded_access_token) - - # id_token - # import pdb; pdb.set_trace() - # result['id_token'].to_dict() - fancy_print("ID Token", result['id_token'].to_dict()) - - # userinfo - result['userinfo'].to_dict() - fancy_print("Userinfo endpoint result:", result['userinfo'].to_dict()) - - -if __name__ == '__main__': - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument('-conf', required=True, - help="settings file where RP configuration is") - parser.add_argument('-u', required=True, - help="username") - parser.add_argument('-p', required=True, - help="password") - parser.add_argument('-iss', required=True, - help="The issuer Id you want to " - "requests authorization. Example: " - "django_oidc_op") - args = parser.parse_args() - - # 'django-oidc-op/example/data/oidc_rp/conf.django.yaml' - config_file = args.conf - rph = get_rph(config_file) - - rph.verify_ssl = rph.httpc_params['verify'] - if not rph.verify_ssl: - urllib3.disable_warnings() - - # select the OP you want to, example: "django_oidc_op" - issuer_id = args.iss - run_rp_session(rph, issuer_id, args.u, args.p) - -# python3 snippets/rp_handler.py -conf example/data/oidc_rp/conf.django.yaml -u user -p pass -iss django_oidc_op From 828b0753011f3e2cacb6a3ad6fd175745a7c08be Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 22 May 2021 23:35:30 +0200 Subject: [PATCH 10/13] fix: removed unused imports --- src/oidcop/session/info.py | 1 - src/oidcop/session/manager.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/oidcop/session/info.py b/src/oidcop/session/info.py index 1b96acfa..50be3acb 100644 --- a/src/oidcop/session/info.py +++ b/src/oidcop/session/info.py @@ -1,6 +1,5 @@ from typing import List from typing import Optional -from typing import Tuple from oidcmsg.impexp import ImpExp diff --git a/src/oidcop/session/manager.py b/src/oidcop/session/manager.py index 4a1d1a63..92693b53 100644 --- a/src/oidcop/session/manager.py +++ b/src/oidcop/session/manager.py @@ -10,7 +10,6 @@ from oidcop import rndstr from oidcop.authn_event import AuthnEvent from oidcop.exception import ConfigurationError -from oidcop.session.info import SessionInfo from oidcop.token import handler from oidcop.util import Crypt from .database import Database From 82393762334e9a8c009a316e3dfb711b6b6deded Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 22 May 2021 23:40:31 +0200 Subject: [PATCH 11/13] fix: removed cryptojwt warning tests/test_23_oidc_registration_endpoint.py::TestEndpoint::test_register_alg_keys /home/wert/DEV3/OIDC-Project/env/lib/python3.7/site-packages/cryptojwt/utils.py:257: DeprecationWarning: owner is deprecated; use issuer_id --- src/oidcop/oidc/registration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidcop/oidc/registration.py b/src/oidcop/oidc/registration.py index 11e4d49c..33b91b55 100755 --- a/src/oidcop/oidc/registration.py +++ b/src/oidcop/oidc/registration.py @@ -239,7 +239,7 @@ def do_client_registration(self, request, client_id, ignore=None): for iss in ["", _context.issuer]: _k.extend( _context.keyjar.get_signing_key( - ktyp, alg=request[item], owner=iss + ktyp, alg=request[item], issuer_id=iss ) ) if not _k: From 4286cc8a9d76c4da094ea756a9c9ecb1897ca84e Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sun, 23 May 2021 15:37:30 +0200 Subject: [PATCH 12/13] fix: Added flask_op introspection endpoint in views.py --- example/flask_op/config.json | 7 +++++-- example/flask_op/views.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/example/flask_op/config.json b/example/flask_op/config.json index 598914f7..6a69b116 100644 --- a/example/flask_op/config.json +++ b/example/flask_op/config.json @@ -185,8 +185,11 @@ "class": "oidcop.oauth2.introspection.Introspection", "kwargs": { "client_authn_method": [ - "client_secret_post" - ], + "client_secret_post", + "client_secret_basic", + "client_secret_jwt", + "private_key_jwt" + ] "release": [ "username" ] diff --git a/example/flask_op/views.py b/example/flask_op/views.py index 7d8f913c..06a7f180 100644 --- a/example/flask_op/views.py +++ b/example/flask_op/views.py @@ -183,6 +183,10 @@ def token(): return service_endpoint( current_app.server.server_get("endpoint", 'token')) +@oidc_op_views.route('/introspection', methods=['POST']) +def introspection_endpoint(): + return service_endpoint( + current_app.server.server_get("endpoint", 'introspection')) @oidc_op_views.route('/userinfo', methods=['GET', 'POST']) def userinfo(): @@ -244,7 +248,6 @@ def service_endpoint(endpoint): _log.info('request: {}'.format(req_args)) if isinstance(req_args, ResponseMessage) and 'error' in req_args: return make_response(req_args.to_json(), 400) - try: if isinstance(endpoint, Token): args = endpoint.process_request(AccessTokenRequest(**req_args), http_info=http_info) From 3fa00781b4b181924413ee73fd5c437eb34ea8c8 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sun, 23 May 2021 16:08:31 +0200 Subject: [PATCH 13/13] chore: Documentation usage examples about consuming Introspection endpoint --- doc/source/contents/conf.rst | 7 ++++-- doc/source/contents/usage.md | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/doc/source/contents/conf.rst b/doc/source/contents/conf.rst index b49c1bd2..73a7b6d7 100644 --- a/doc/source/contents/conf.rst +++ b/doc/source/contents/conf.rst @@ -227,8 +227,11 @@ An example:: "class": "oidcop.oauth2.introspection.Introspection", "kwargs": { "client_authn_method": [ - "client_secret_post" - ], + "client_secret_post", + "client_secret_basic", + "client_secret_jwt", + "private_key_jwt" + ] "release": [ "username" ] diff --git a/doc/source/contents/usage.md b/doc/source/contents/usage.md index fff1bf67..6c51e765 100644 --- a/doc/source/contents/usage.md +++ b/doc/source/contents/usage.md @@ -47,3 +47,46 @@ The identity representation with the information fetched from the user info endp ![Logout](../_images/4.png) We can even test the single logout + + +Introspection endpoint +---------------------- + +Here an example about how to consume oidc-op introspection endpoint. +This example uses a client with an HTTP Basic Authentication:: + + import base64 + import requests + + TOKEN = "eyJhbGciOiJFUzI1NiIsImtpZCI6IlQwZGZTM1ZVYUcxS1ZubG9VVTQwUXpJMlMyMHpjSHBRYlMxdGIzZ3hZVWhCYzNGaFZWTlpTbWhMTUEifQ.eyJzY29wZSI6IFsib3BlbmlkIiwgInByb2ZpbGUiLCAiZW1haWwiLCAiYWRkcmVzcyIsICJwaG9uZSJdLCAiYXVkIjogWyJvTHlSajdzSkozWHZBWWplRENlOHJRIl0sICJqdGkiOiAiOWQzMjkzYjZiYmNjMTFlYmEzMmU5ODU0MWIwNzE1ZWQiLCAiY2xpZW50X2lkIjogIm9MeVJqN3NKSjNYdkFZamVEQ2U4clEiLCAic3ViIjogIm9MeVJqN3NKSjNYdkFZamVEQ2U4clEiLCAic2lkIjogIlowRkJRVUZCUW1keGJIVlpkRVJKYkZaUFkxQldaa0pQVUVGc1pHOUtWWFZ3VFdkZmVEY3diMVprYmpSamRrNXRMVzB4YTNnelExOHlRbHBHYTNRNVRHZEdUUzF1UW1sMlkzVnhjRE5sUm01dFRFSmxabGRXYVhJeFpFdHVSV2xtUzBKcExWTmFaRzV3VjJodU0yNXlSbTU0U1ZWVWRrWTRRM2x2UWs1TlpVUk9SazlGVlVsRWRteGhjWGx2UWxWRFdubG9WbTFvZGpORlVUSnBkaTFaUTFCcFptZFRabWRDVWt0YVNuaGtOalZCWVhkcGJFNXpaV2xOTTFCMk0yaE1jMDV0ZGxsUlRFc3dObWxsYUcxa1lrTkhkemhuU25OaWFWZE1kVUZzZDBwWFdWbzFiRWhEZFhGTFFXWTBPVzl5VjJOUk4zaGtPRDA9IiwgInR0eXBlIjogIlQiLCAiaXNzIjogImh0dHBzOi8vMTI3LjAuMC4xOjgwMDAiLCAiaWF0IjogMTYyMTc3NzMwNSwgImV4cCI6IDE2MjE3ODA5MDV9.pVqxUNznsoZu9ND18IEMJIHDOT6_HxzoFiTLsniNdbAdXTuOoiaKeRTqtDyjT9WuUPszdHkVjt5xxeFX8gQMuA" + + data = { + 'token': TOKEN, + 'token_type_hint': 'access_token' + } + + _basic_secret = base64.b64encode( + f'{"oLyRj7sJJ3XvAYjeDCe8rQ"}:{"53fb49f2a6501ec775355c89750dc416744a3253138d5a04e409b313"}'.encode() + ) + headers = { + 'Authorization': f"Basic {_basic_secret.decode()}" + } + + requests.post('https://127.0.0.1:8000/introspection', verify=False, data=data, headers=headers) + + +oidc-op will return a json response like this:: + + { + "active": true, + "scope": "openid profile email address phone", + "client_id": "oLyRj7sJJ3XvAYjeDCe8rQ", + "token_type": "access_token", + "exp": 0, + "iat": 1621777305, + "sub": "a7b0dea2958aec275a789d7d7dc8e7d09c6316dd4fc6ae92742ed3297e14dded", + "iss": "https://127.0.0.1:8000", + "aud": [ + "oLyRj7sJJ3XvAYjeDCe8rQ" + ] + }