From 6d14a8002a9c2705348e20ae53269b0a1abe1612 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Tue, 23 Jul 2024 08:34:09 -0300 Subject: [PATCH 01/13] Allow skip logout --- README.md | 2 ++ ckanext/saml2auth/plugin.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aef2ea..f4316f4 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,8 @@ Optional: # Saml logout request preferred binding settings variable # Default: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST ckanext.saml2auth.logout_expected_binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST + # If you don't want to logout from external source you can use + ckanext.saml2auth.logout_expected_binding = skip-external-logout # Default fallback endpoint to redirect to if no RelayState provided in the SAML Response # Default: user.me (ie /dashboard) diff --git a/ckanext/saml2auth/plugin.py b/ckanext/saml2auth/plugin.py index 8e0ed57..bdb1468 100644 --- a/ckanext/saml2auth/plugin.py +++ b/ckanext/saml2auth/plugin.py @@ -97,7 +97,11 @@ def update_config(self, config_): def logout(self): - response = _perform_slo() + config = sp_config() + if config.get('logout_expected_binding') == 'skip-external-logout': + response = None + else: + response = _perform_slo() if response: domain = h.get_site_domain_for_cookie() From e738d36892f02d5c5caf36f12f882ee0ddcb99f5 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Tue, 23 Jul 2024 08:46:10 -0300 Subject: [PATCH 02/13] Improve logout --- ckanext/saml2auth/plugin.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ckanext/saml2auth/plugin.py b/ckanext/saml2auth/plugin.py index bdb1468..7976222 100644 --- a/ckanext/saml2auth/plugin.py +++ b/ckanext/saml2auth/plugin.py @@ -97,11 +97,7 @@ def update_config(self, config_): def logout(self): - config = sp_config() - if config.get('logout_expected_binding') == 'skip-external-logout': - response = None - else: - response = _perform_slo() + response = _perform_slo() if response: domain = h.get_site_domain_for_cookie() @@ -124,9 +120,12 @@ def _perform_slo(): response = None - client = h.saml_client( - sp_config() - ) + config = sp_config() + if config.get('logout_expected_binding') == 'skip-external-logout': + log.debug('Skipping external logout') + return + + client = h.saml_client(config) saml_session_info = get_saml_session_info(session) subject_id = get_subject_id(session) From c613c9de67c944b939d6d07856f5ea63f0b78348 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Fri, 26 Jul 2024 15:39:22 -0300 Subject: [PATCH 03/13] Trigger CKAN failed login signal --- ckanext/saml2auth/views/saml2auth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ckanext/saml2auth/views/saml2auth.py b/ckanext/saml2auth/views/saml2auth.py index 36a6c0f..70d4fef 100644 --- a/ckanext/saml2auth/views/saml2auth.py +++ b/ckanext/saml2auth/views/saml2auth.py @@ -28,7 +28,7 @@ import ckan.model as model import ckan.plugins as plugins import ckan.lib.dictization.model_dictize as model_dictize -from ckan.lib import base +from ckan.lib import base, signals from ckan.views.user import set_repoze_user from ckan.common import config, g, request @@ -227,6 +227,8 @@ def acs(): if error is not None: log.error(error) extra_vars = {u'code': [400], u'content': error} + # Trigger the CKAN failed login signal + signals.failed_login.send('Unknown_SAML2_user') return base.render(u'error_document_template.html', extra_vars), 400 auth_response.get_identity() From 40520347a37713734943925063328eb1e054fa93 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 13:14:22 -0300 Subject: [PATCH 04/13] Fix flake8 --- .github/workflows/ci.yml | 2 +- ckanext/saml2auth/tests/test_helpers.py | 2 +- setup.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7d2a8f..02692e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - name: Lint with flake8 run: | - flake8 . --count --max-complexity=10 --max-line-length=127 --statistics --exclude ckan,ckanext-saml2auth + flake8 . --count --max-complexity=12 --max-line-length=127 --statistics --exclude ckan,ckanext-saml2auth test: runs-on: ubuntu-latest diff --git a/ckanext/saml2auth/tests/test_helpers.py b/ckanext/saml2auth/tests/test_helpers.py index 46d692e..196cfe1 100644 --- a/ckanext/saml2auth/tests/test_helpers.py +++ b/ckanext/saml2auth/tests/test_helpers.py @@ -30,7 +30,7 @@ def test_generate_password(): password = h.generate_password() assert len(password) == 8 - assert type(password) == str + assert isinstance(password, str) def test_default_login_disabled_by_default(): diff --git a/setup.py b/setup.py index cc2da1c..e905d59 100644 --- a/setup.py +++ b/setup.py @@ -41,8 +41,7 @@ long_description_content_type='text/markdown', # The project's main homepage. - url='https://github.com/keitaroinc/'\ - 'ckanext-saml2auth', + url='https://github.com/keitaroinc/ckanext-saml2auth', # Author details author='''Keitaro Inc''', From 32d67f8b815f8a25df54155d546bec62e205ee79 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 13:17:50 -0300 Subject: [PATCH 05/13] drop Py37 and CKAN 2.9 support --- .github/workflows/ci.yml | 53 ++----------------- bin/setup-ckan.bash | 5 +- .../saml2auth/tests/responses/unsigned0.xml | 2 +- .../tests/test_blueprint_get_request.py | 5 +- dev-requirements.txt | 1 + setup.py | 6 +-- 6 files changed, 17 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02692e5..bdee55b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.10' - name: Install flake8 run: | @@ -33,8 +33,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.7', '3.8', '3.9'] - ckan-version: ["2.9", "2.10"] + python-version: ['3.10'] + ckan-version: ["2.10"] name: Python ${{ matrix.python-version }} extension test services: @@ -61,9 +61,7 @@ jobs: - 6379:6379 ckan-solr: - # Workflow level env variables are not addressable on job level, only on steps level - # image: ghcr.io/keitaroinc/ckan-solr-dev:{{ env.CKANVERSION }} - image: ghcr.io/keitaroinc/ckan-solr-dev:2.9 + image: ckan/ckan-solr:2.10 ports: - 8983:8983 @@ -90,46 +88,5 @@ jobs: - name: Test with pytest run: | + echo "Running SAML2AUTH tests" pytest --ckan-ini=subdir/test.ini --cov=ckanext.saml2auth --disable-warnings ckanext/saml2auth/tests - - - name: Coveralls - uses: AndreMiras/coveralls-python-action@develop - with: - parallel: true - flag-name: Python ${{ matrix.python-version }} Unit Test - - publish: - needs: test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.8' - - - name: Install setup requirements - run: | - python -m pip install --upgrade setuptools wheel twine - - - name: Build and package - run: | - python setup.py sdist bdist_wheel - twine check dist/* - - - name: Publish package - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} - - coveralls_finish: - needs: test - runs-on: ubuntu-latest - steps: - - name: Coveralls Finished - uses: AndreMiras/coveralls-python-action@develop - with: - parallel-finished: true diff --git a/bin/setup-ckan.bash b/bin/setup-ckan.bash index 3ace425..a1172a2 100755 --- a/bin/setup-ckan.bash +++ b/bin/setup-ckan.bash @@ -46,9 +46,10 @@ cd ckan ckan -c test-core.ini db init cd - -echo "Installing ckanext-saml2auth and its requirements..." -python setup.py develop +echo "Installing saml2 requirements..." pip install -r dev-requirements.txt +echo "Installing ckanext-saml2auth..." +pip install -e . echo "Moving test.ini into a subdir..." mkdir subdir diff --git a/ckanext/saml2auth/tests/responses/unsigned0.xml b/ckanext/saml2auth/tests/responses/unsigned0.xml index 7398230..f2f74d5 100644 --- a/ckanext/saml2auth/tests/responses/unsigned0.xml +++ b/ckanext/saml2auth/tests/responses/unsigned0.xml @@ -17,7 +17,7 @@ _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 diff --git a/ckanext/saml2auth/tests/test_blueprint_get_request.py b/ckanext/saml2auth/tests/test_blueprint_get_request.py index 4095f54..a844df6 100644 --- a/ckanext/saml2auth/tests/test_blueprint_get_request.py +++ b/ckanext/saml2auth/tests/test_blueprint_get_request.py @@ -58,7 +58,7 @@ def _prepare_unsigned_response(): 'entity_id': 'urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity', 'destination': 'http://test.ckan.net/acs', 'recipient': 'http://test.ckan.net/acs', - 'issue_instant': datetime.now().isoformat() + 'issue_instant': datetime.now().isoformat(), } t = Template(unsigned_response) final_response = t.render(**context) @@ -118,6 +118,9 @@ def test_unsigned_request(self, app): 'SAMLResponse': encoded_response } response = app.post(url=url, params=data) + if response.status_code != 200: + assert False, f'Failed test_unsigned_request: {response.body}' + # Can't use response, too old (now=2024-07-31T17:42:38Z + slack=0 > not_on_or_after=2024-01-18T06:21:48Z assert 200 == response.status_code def render_file(self, path, context, save_as=None): diff --git a/dev-requirements.txt b/dev-requirements.txt index 61015ae..fcd5547 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,2 +1,3 @@ flake8 # for the CI build pysaml2 +packaging>=22.0 diff --git a/setup.py b/setup.py index e905d59..f433b8b 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # http://packaging.python.org/en/latest/tutorial.html#version - version='1.3.0', + version='1.3.1', description='''An extension to enable Single Sign On(SSO) for CKAN data portals via SAML2 Authentication.''', long_description=long_description, @@ -64,9 +64,9 @@ # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ], From d40a753c115cd642116bb2add78f30e3eeeae9cc Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 15:05:10 -0300 Subject: [PATCH 06/13] Increse coverage --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdee55b..56c61c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10'] - ckan-version: ["2.10"] + python-version: ['3.8', '3.9', '3.10'] + ckan-version: ["2.9", "2.10"] name: Python ${{ matrix.python-version }} extension test services: From 86d7cb7de85f15ad42ed0bf876c64212dafd40bb Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 15:10:59 -0300 Subject: [PATCH 07/13] drop failing CKAN 2.9 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56c61c7..a63f371 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: python-version: ['3.8', '3.9', '3.10'] - ckan-version: ["2.9", "2.10"] + ckan-version: ["2.10"] name: Python ${{ matrix.python-version }} extension test services: From f59f258978e5a68219ca389a7dd0d7b62fd931d8 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 15:12:18 -0300 Subject: [PATCH 08/13] README notes --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f4316f4..bfbb573 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ [![CI][]][1] [![Coverage][]][2] [![Gitter][]][3] [![Pypi][]][4] [![Python][]][5] [![CKAN][]][6] +# Temporary fork + +**This is a temporary fork from OKFN** to work with CKAN 2.10 waiting for upstream repo at https://github.com/keitaroinc/ckanext-saml2auth to be updated. + # ckanext-saml2auth A [CKAN](https://ckan.org) extension to enable Single Sign-On (SSO) for CKAN data portals via SAML2 Authentication. From 0abb4ddc298d2aa86526d830ec131d3601978765 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 15:14:01 -0300 Subject: [PATCH 09/13] rename test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a63f371..c0bcde2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: matrix: python-version: ['3.8', '3.9', '3.10'] ckan-version: ["2.10"] - name: Python ${{ matrix.python-version }} extension test + name: Python ${{ matrix.python-version }} CKAN {{ matrix.ckan-version }} extension test services: postgresql: From 2774947f960acaf482b3644a11670df0b707ceca Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 31 Jul 2024 15:18:19 -0300 Subject: [PATCH 10/13] $ --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0bcde2..7c14330 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: matrix: python-version: ['3.8', '3.9', '3.10'] ckan-version: ["2.10"] - name: Python ${{ matrix.python-version }} CKAN {{ matrix.ckan-version }} extension test + name: Python ${{ matrix.python-version }} CKAN ${{ matrix.ckan-version }} extension test services: postgresql: From d8ee89b06a53592f3b1159a988a72167b92ee659 Mon Sep 17 00:00:00 2001 From: avdata99 Date: Wed, 23 Oct 2024 13:51:07 -0300 Subject: [PATCH 11/13] Search email ignoring case Do not allow to multiple users with same email --- ckanext/saml2auth/views/saml2auth.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ckanext/saml2auth/views/saml2auth.py b/ckanext/saml2auth/views/saml2auth.py index 70d4fef..798dfad 100644 --- a/ckanext/saml2auth/views/saml2auth.py +++ b/ckanext/saml2auth/views/saml2auth.py @@ -23,7 +23,7 @@ from flask import Blueprint, session from saml2 import entity from saml2.authn_context import requested_authn_context - +from sqlalchemy.sql import func import ckan.plugins.toolkit as toolkit import ckan.model as model import ckan.plugins as plugins @@ -76,13 +76,18 @@ def _get_user_by_saml_id(saml_id): def _get_user_by_email(email): - user = model.User.by_email(email) - if user and isinstance(user, list): - user = user[0] + users = model.Session.query(model.User).filter( + func.lower(model.User.email) == func.lower(email) + ).all() - h.activate_user_if_deleted(user) + if len(users) == 0: + return None + if len(users) > 1: + raise toolkit.ValidationError(f'Multiple users with the same email found {email}') - return _dictize_user(user) if user else None + user = users[0] + h.activate_user_if_deleted(user) + return _dictize_user(user) def _update_user(user_dict): From 67fe0a8deadbf07bb6ab36362064ec05ef836ce7 Mon Sep 17 00:00:00 2001 From: Andres Vazquez Date: Mon, 28 Oct 2024 10:50:13 -0300 Subject: [PATCH 12/13] Add test for _get_user_by_email (#6) Add test for _get_user_by_email --- .../saml2auth/tests/test_get_user_by_email.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 ckanext/saml2auth/tests/test_get_user_by_email.py diff --git a/ckanext/saml2auth/tests/test_get_user_by_email.py b/ckanext/saml2auth/tests/test_get_user_by_email.py new file mode 100644 index 0000000..1a25e14 --- /dev/null +++ b/ckanext/saml2auth/tests/test_get_user_by_email.py @@ -0,0 +1,46 @@ +import pytest +from types import SimpleNamespace +import ckan.model as model +from ckan.tests import factories +from ckan.plugins import toolkit +from ckanext.saml2auth.views.saml2auth import _get_user_by_email + + +@pytest.fixture +def tdv_data(): + """TestDatasetViews setup data""" + obj = SimpleNamespace() + obj.user1 = factories.User( + email='user1@example.com', + plugin_extras={'saml2auth': {'saml_id': 'saml_id1'}} + ) + obj.user2 = factories.User( + email='user2@example.com', + plugin_extras={'saml2auth': {'saml_id': 'saml_id2'}} + ) + return obj + + +@pytest.mark.usefixtures(u'clean_db', u'clean_index') +@pytest.mark.ckan_config(u'ckan.plugins', u'saml2auth') +class TestDatasetViews(object): + def test_get_user_by_email_empty(self, tdv_data): + """ The the function _get_user_by_email for empty response """ + ret = _get_user_by_email('user3@example.com') + assert ret is None + + def test_get_user_by_email_ok(self, tdv_data): + """ The the function _get_user_by_email for empty response """ + ret = _get_user_by_email(tdv_data.user1['email']) + assert ret is not None + assert ret['email'] == tdv_data.user1['email'] + + def test_get_user_by_email_multiple(self, tdv_data): + """ The the function _get_user_by_email for duplicated emails """ + # Generate a duplciate email + user2 = model.User.get(tdv_data.user2['id']) + user2.email = tdv_data.user1['email'].upper() + model.Session.commit() + + with pytest.raises(toolkit.ValidationError): + _get_user_by_email(tdv_data.user1['email']) From faad525aa1ed6f9f3c13a1f14e0270a0583bd39c Mon Sep 17 00:00:00 2001 From: avdata99 Date: Tue, 14 Jan 2025 13:10:02 -0300 Subject: [PATCH 13/13] Test CKAN 2.11 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c14330..0e2df0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10'] - ckan-version: ["2.10"] + python-version: ['3.9', '3.10'] + ckan-version: ["2.10", "2.11"] name: Python ${{ matrix.python-version }} CKAN ${{ matrix.ckan-version }} extension test services: