Skip to content

Commit 84b007b

Browse files
committed
Make python3-saml an extra dependency.
1 parent 6209b25 commit 84b007b

File tree

8 files changed

+89
-7
lines changed

8 files changed

+89
-7
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
venv
22
.vscode
3+
*.egg-info
4+
.eggs
5+
__pycache__
6+
dist
7+
.coverage

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ package, also consider
1313
## Installation
1414

1515
```bash
16-
pip install uw-saml
16+
pip install uw-saml[python3-saml]
1717
```
1818

19+
The extra `[python3-saml]` is because the SAML package can be cumbersome to
20+
install in a workstation environment, on account of needing the libxmlsec1-dev
21+
library. Therefore, it's an optional requirement, causing a runtime error
22+
instead of an install-time error. Alternatively, you can use a mock
23+
interface by setting `uw_saml2.python3_saml.MOCK = True`.
24+
1925
## Example login endpoint using flask
2026

2127
In this example you've gone to

setup.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
with open(os.path.join(BASE_DIR, 'README.md')) as f:
1010
LONG_DESCRIPTION = f.read()
1111

12+
saml_requires = ['python3-saml']
13+
tests_require = saml_requires + ['pytest', 'pytest-cov', 'mock', 'pycodestyle']
14+
15+
1216
setup(name='uw-saml',
1317
version=VERSION,
1418
url='https://github.com/UWIT-IAM/uw-saml-python',
@@ -20,6 +24,5 @@
2024
license='Apache License, Version 2.0',
2125
packages=find_packages(),
2226
include_package_data=True,
23-
install_requires=['python3-saml'],
24-
extras_require={'test': ['pytest', 'pytest-cov', 'mock', 'pycodestyle']}
27+
extras_require={'python3-saml': saml_requires, 'test': tests_require}
2528
)

tests/test_mock.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pytest
2+
import uw_saml2
3+
4+
5+
@pytest.fixture(autouse=True)
6+
def flick_mock_flag(monkeypatch):
7+
monkeypatch.setattr('uw_saml2.python3_saml.MOCK', True)
8+
9+
10+
def test_login_redirect():
11+
expected = ('mock-login?return_to=%2F&force_authn=False'
12+
'&idp=urn%3Amace%3Aincommon%3Awashington.edu')
13+
assert uw_saml2.login_redirect() == expected
14+
15+
16+
def test_process_response():
17+
post = {'idp': 'urn:mace:incommon:washington.edu', 'foo': 'bar'}
18+
expected = {'two_factor': False, **post}
19+
assert uw_saml2.process_response(post) == expected
20+
21+
22+
def test_process_respose_error():
23+
post = {'idp': 'badidp', 'foo': 'bar'}
24+
with pytest.raises(uw_saml2.SamlResponseError):
25+
uw_saml2.process_response(post)

uw_saml2/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .auth import login_redirect, process_response
1+
from .auth import login_redirect, process_response, SamlResponseError

uw_saml2/auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""UW-specific adapter for the python3-saml package."""
2-
from onelogin.saml2.auth import OneLogin_Saml2_Auth
2+
from .python3_saml import get_saml_authenticator
33
from .idp.uw import UwIdp
44
from .sp import Config, TWO_FACTOR_CONTEXT
55
from .idp import attribute
@@ -28,7 +28,7 @@ def login_redirect(entity_id=None, acs_url=None, return_to='/',
2828
"""
2929
sp = Config(entity_id, acs_url)
3030
config = sp.config(idp, two_factor=two_factor)
31-
auth = OneLogin_Saml2_Auth(sp.request(), old_settings=config)
31+
auth = get_saml_authenticator(sp.request(), old_settings=config)
3232
return auth.login(return_to=return_to, force_authn=force_authn)
3333

3434

@@ -50,7 +50,7 @@ def process_response(post, entity_id=None, acs_url=None, idp=UwIdp,
5050
"""
5151
sp = Config(entity_id, acs_url)
5252
config = sp.config(idp, two_factor=two_factor)
53-
auth = OneLogin_Saml2_Auth(sp.request(post), old_settings=config)
53+
auth = get_saml_authenticator(sp.request(post), old_settings=config)
5454
auth.process_response()
5555
errors = auth.get_errors()
5656
if errors:

uw_saml2/mock.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import urllib.parse
2+
3+
4+
class SamlAuthenticator:
5+
def __init__(self, request, old_settings, *args, **kwargs):
6+
self.request = request
7+
self.idp = old_settings['idp']['entityId']
8+
self.errors = []
9+
10+
def login(self, **kwargs):
11+
kwargs['idp'] = self.idp
12+
qs = urllib.parse.urlencode(kwargs)
13+
return f'mock-login?{qs}'
14+
15+
def process_response(self):
16+
idp = self.request['post_data'].get('idp')
17+
if idp != self.idp:
18+
self.errors.append(f'idp mismatch {idp} != {self.idp}')
19+
return
20+
21+
def get_attributes(self):
22+
"""Just reflect what's posted right back."""
23+
items = self.request['post_data'].items()
24+
return dict((key, [value]) for key, value in items)
25+
26+
def get_errors(self):
27+
return self.errors
28+
29+
def get_last_error_reason(self):
30+
return self.errors[0]
31+
32+
def get_last_authn_contexts(self):
33+
return []

uw_saml2/python3_saml.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
MOCK = False
2+
3+
4+
def get_saml_authenticator(*args, **kwargs):
5+
if MOCK:
6+
from . import mock
7+
return mock.SamlAuthenticator(*args, **kwargs)
8+
else:
9+
from onelogin.saml2.auth import OneLogin_Saml2_Auth
10+
return OneLogin_Saml2_Auth(*args, **kwargs)

0 commit comments

Comments
 (0)