Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update main with upstream main #1

Merged
merged 31 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f63e6a3
Fix broken login in CKAN>=2.9.6
amercader Sep 29, 2022
09d988e
Enable Python 3.9 tests in CI
amercader Sep 29, 2022
7831f13
Merge pull request #74 from okfn/fix-to-support-ckan-2.9.6
duskobogdanovski Sep 29, 2022
dd072cf
Merge pull request #75 from okfn/enable-py-3.9-tests
duskobogdanovski Sep 29, 2022
7ebd7c6
Bump version to 1.2.3
duskobogdanovski Sep 29, 2022
e00a4dd
Add python 3.9 support badge
duskobogdanovski Sep 29, 2022
706237f
bump action version
robert-bryson Nov 7, 2022
7aac52d
Merge pull request #76 from robert-bryson/bump-action-version
duskobogdanovski Nov 8, 2022
3c0532b
Add support for login into ckan 2.10
pdelboca Dec 9, 2022
bef100f
Test list is not empty before assignment
pdelboca Dec 12, 2022
b259428
Set cookie clear logic
pdelboca Dec 12, 2022
e8fbae8
Improve docstring
pdelboca Dec 12, 2022
3de1e0f
Run tests on 2.9 and 2.10
pdelboca Dec 13, 2022
d0300a9
Test cookie behaviour on CKAN 2.10
pdelboca Dec 13, 2022
a79049c
Merge pull request #77 from pdelboca/ckan-2.10-support
duskobogdanovski Dec 13, 2022
254b229
Code style fix
duskobogdanovski Dec 13, 2022
5ce86e0
Bump version to 1.3.0
duskobogdanovski Dec 14, 2022
e1e7a56
Add badge for CKAN 2.10 in README.md
duskobogdanovski Dec 14, 2022
43d6510
Update CKAN badge
duskobogdanovski Dec 14, 2022
4246be8
new: add successful login/logout messages
nickumia-reisys Jan 7, 2023
d35cc0d
new: add name<email> to log
nickumia-reisys Jan 9, 2023
17a5078
fix: scope of the logout message
nickumia-reisys Jan 9, 2023
67edef1
Revert "fix: scope of the logout message"
nickumia-reisys Jan 9, 2023
7a5ba9c
fix: empty user logout case
nickumia-reisys Jan 10, 2023
ec711ae
fix: don't fail fast
nickumia-reisys Jan 11, 2023
10f3769
fix: the empty_user is "" on ckan 2.10
nickumia-reisys Jan 11, 2023
0072d40
new: better way to test if user exists
nickumia-reisys Jan 11, 2023
1203f68
refactor: follow existing log format
nickumia-reisys Jan 12, 2023
9943fb2
Merge pull request #78 from GSA/logging-events
duskobogdanovski Jan 12, 2023
88d4cc5
pysqml2 7.4 requires python 3.9
avdata99 Feb 15, 2023
b2c6cfc
Merge pull request #82 from avdata99/patch-1
duskobogdanovski Feb 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ on:
pull_request:
branches: main

env:
CKANVERSION: 2.9

jobs:
code_quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.8'

Expand All @@ -34,8 +31,10 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ '3.7', '3.8' ]
python-version: [ '3.7', '3.8', '3.9']
ckan-version: ["2.9", "2.10"]
name: Python ${{ matrix.python-version }} extension test

services:
Expand Down Expand Up @@ -69,10 +68,10 @@ jobs:
- 8983:8983

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: x64
Expand All @@ -86,6 +85,7 @@ jobs:
env:
PGPASSWORD: postgres
run: |
export CKANVERSION=${{matrix.ckan-version}}
bash bin/setup-ckan.bash

- name: Test with pytest
Expand All @@ -102,10 +102,10 @@ jobs:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.8'

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ to PyPI follow these steps:
[3]: https://gitter.im/keitaroinc/ckan?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
[Pypi]: https://img.shields.io/pypi/v/ckanext-saml2auth
[4]: https://pypi.org/project/ckanext-saml2auth
[Python]: https://img.shields.io/badge/python-3.7%20%7C%203.8-blue
[Python]: https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9-blue
[5]: https://www.python.org
[CKAN]: https://img.shields.io/badge/ckan-2.9-red
[CKAN]: https://img.shields.io/badge/ckan-2.9%20%7C%202.10-yellow
[6]: https://www.ckan.org
13 changes: 9 additions & 4 deletions ckanext/saml2auth/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,18 @@ def logout(self):

if response:
domain = h.get_site_domain_for_cookie()

# Clear auth cookie in the browser
response.set_cookie('auth_tkt', domain=domain, expires=0)

# Clear session cookie in the browser
response.set_cookie('ckan', domain=domain, expires=0)

if not toolkit.check_ckan_version(min_version="2.10"):
# CKAN <= 2.9.x also sets auth_tkt cookie
response.set_cookie('auth_tkt', domain=domain, expires=0)

if g.userobj:
log.info(u'User {0}<{1}> logged out successfully'.format(g.userobj.name, g.userobj.email))
else:
log.info(u'No user was logged in!')

return response


Expand Down
45 changes: 44 additions & 1 deletion ckanext/saml2auth/tests/test_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import os
import pytest

from ckan.plugins import toolkit
from ckan.plugins.toolkit import url_for

here = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -65,7 +66,9 @@ def test_came_from_sent_as_relay_state(self, app):
os.path.join(extras_folder, 'provider2', 'idp.xml'))
@pytest.mark.usefixtures('with_request_context')
def test_cookies_cleared_on_slo(self, app):

if toolkit.check_ckan_version(min_version='2.10'):
# Remove when dropping support for 2.9
pytest.skip("auth_tkt cookie has been deprecated in 2.10")
url = url_for('user.logout')

import datetime
Expand Down Expand Up @@ -93,3 +96,43 @@ def test_cookies_cleared_on_slo(self, app):
assert cookie[cookie_name]['domain'] == 'test.ckan.net'
cookie_date = date_parse(cookie[cookie_name]['expires'], ignoretz=True)
assert cookie_date < datetime.datetime.now()

@pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local')
@pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path',
os.path.join(extras_folder, 'provider2', 'idp.xml'))
@pytest.mark.usefixtures('with_request_context')
def test_ckan_cookie_cleared_on_slo(self, app):
if not toolkit.check_ckan_version(min_version='2.10'):
# Remove when dropping support for 2.9
pytest.skip("This test logic introduced in CKAN 2.10")
url = url_for('user.logout')

import datetime
from unittest import mock
from http.cookies import SimpleCookie
from flask import make_response
from dateutil.parser import parse as date_parse

with mock.patch(
'ckanext.saml2auth.plugin._perform_slo',
return_value=make_response('')):
response = app.get(url=url, follow_redirects=False)

cookie_headers = [
h[1] for h in response.headers
if h[0].lower() == 'set-cookie']

# Starting 2.10, CKAN's SessionMiddleware will append a
# new Set-cookie header on every first response from the server.
# This includes test requests.
assert len(cookie_headers) == 2

first_cookie = cookie_headers[0]

cookie = SimpleCookie()
cookie.load(first_cookie)
cookie_name = [name for name in cookie.keys()][0]
assert cookie_name == 'ckan'
assert cookie[cookie_name]['domain'] == 'test.ckan.net'
cookie_date = date_parse(cookie[cookie_name]['expires'], ignoretz=True)
assert cookie_date < datetime.datetime.now()
46 changes: 44 additions & 2 deletions ckanext/saml2auth/tests/test_blueprint_get_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@
from jinja2 import Template
import os
import pytest
try:
from unittest import mock
except ImportError:
import mock

from ckan import model
from ckan.plugins import toolkit

from saml2.xmldsig import SIG_RSA_SHA256
from saml2.xmldsig import DIGEST_SHA256
Expand Down Expand Up @@ -382,7 +387,9 @@ def test_user_fullname_using_first_last_name(self, app):
response = app.post(url=url, params=data)
assert 200 == response.status_code

user = model.User.by_email('test@example.com')[0]
user = model.User.by_email('test@example.com')
if isinstance(user, list):
user = user[0]

assert user.fullname == 'John Smith'

Expand All @@ -406,7 +413,9 @@ def test_user_fullname_using_fullname(self, app):
response = app.post(url=url, params=data)
assert 200 == response.status_code

user = model.User.by_email('test@example.com')[0]
user = model.User.by_email('test@example.com')
if isinstance(user, list):
user = user[0]

assert user.fullname == 'John Smith (Operations)'

Expand Down Expand Up @@ -465,3 +474,36 @@ def test_no_relay_state_redirects_to_fallback_config(self, app):
response = app.post(url=url, params=data, follow_redirects=False)

assert response.headers['Location'] == 'http://test.ckan.net/dataset/'

@pytest.mark.ckan_config(u'ckanext.saml2auth.entity_id', u'urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity')
@pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local')
@pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path', os.path.join(extras_folder, 'provider0', 'idp.xml'))
@pytest.mark.ckan_config(u'ckanext.saml2auth.want_response_signed', u'False')
@pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_signed', u'False')
@pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_or_response_signed', u'False')
def test_repoze_user_id(self, app):
if not toolkit.check_ckan_version(max_version='2.9.6'):
# Remove when dropping support for 2.9.6
pytest.skip("set_repoze_user has been deprecated in 2.10")
encoded_response = _prepare_unsigned_response()
url = '/acs'

data = {
'SAMLResponse': encoded_response
}

with mock.patch("ckanext.saml2auth.views.saml2auth.set_repoze_user") as m:
app.post(url=url, params=data)

user = model.User.by_email('test@example.com')
if isinstance(user, list):
user = user[0]

assert m.called
# Check login worked fine by checking the Response object
assert m.call_args[0][1].headers["Location"].endswith("/user/me")

if toolkit.check_ckan_version(min_version="2.9.6"):
assert m.call_args[0][0] == user.id + ",1"
else:
assert m.call_args[0][0] == user.name
12 changes: 9 additions & 3 deletions ckanext/saml2auth/tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ def test_before_create_is_called(self, app):

assert plugin.calls["before_saml2_user_create"] == 1, plugin.calls

user = model.User.by_email('test@example.com')[0]
user = model.User.by_email('test@example.com')
if isinstance(user, list):
user = user[0]

assert user.fullname.endswith('TEST CREATE')

Expand Down Expand Up @@ -145,7 +147,9 @@ def test_before_update_is_called_on_saml_user(self, app):

assert plugin.calls["before_saml2_user_update"] == 1, plugin.calls

user = model.User.by_email('test@example.com')[0]
user = model.User.by_email('test@example.com')
if isinstance(user, list):
user = user[0]

assert user.fullname.endswith('TEST UPDATE')

Expand All @@ -172,7 +176,9 @@ def test_before_update_is_called_on_ckan_user(self, app):

assert plugin.calls["before_saml2_user_update"] == 1, plugin.calls

user = model.User.by_email('test@example.com')[0]
user = model.User.by_email('test@example.com')
if isinstance(user, list):
user = user[0]

assert user.fullname.endswith('TEST UPDATE')

Expand Down
38 changes: 31 additions & 7 deletions ckanext/saml2auth/views/saml2auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ def _get_user_by_saml_id(saml_id):

def _get_user_by_email(email):

user_obj = model.User.by_email(email)
if user_obj:
user_obj = user_obj[0]
user = model.User.by_email(email)
if user and isinstance(user, list):
user = user[0]

h.activate_user_if_deleted(user_obj)
h.activate_user_if_deleted(user)

return _dictize_user(user_obj) if user_obj else None
return _dictize_user(user) if user else None


def _update_user(user_dict):
Expand Down Expand Up @@ -263,8 +263,8 @@ def acs():

resp = toolkit.redirect_to(redirect_target)

# log the user in programmatically
set_repoze_user(g.user, resp)
_log_user_into_ckan(resp)

set_saml_session_info(session, session_info)
set_subject_id(session, session_info['name_id'])

Expand All @@ -274,6 +274,30 @@ def acs():
return resp


def _log_user_into_ckan(resp):
""" Log the user into different CKAN versions.

CKAN 2.10 introduces flask-login and login_user method.

CKAN 2.9.6 added a security change and identifies the user
with the internal id plus a serial autoincrement (currently static).

CKAN <= 2.9.5 identifies the user only using the internal id.
"""
if toolkit.check_ckan_version(min_version="2.10"):
from ckan.common import login_user
login_user(g.userobj)
return

if toolkit.check_ckan_version(min_version="2.9.6"):
user_id = "{},1".format(g.userobj.id)
else:
user_id = g.userobj.name
set_repoze_user(user_id, resp)

log.info(u'User {0}<{1}> logged in successfully'.format(g.userobj.name, g.userobj.email))


def saml2login():
u'''Redirects the user to the
configured identity provider for authentication
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.2',
version='1.3.0',

description='''An extension to enable Single Sign On(SSO) for CKAN data portals via SAML2 Authentication.''',
long_description=long_description,
Expand Down Expand Up @@ -79,7 +79,7 @@
packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
namespace_packages=['ckanext'],

install_requires=['pysaml2>=6.5.1'],
install_requires=['pysaml2>=6.5.1,<7.4'],

# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
Expand Down
Loading