0001-apply-isort-and-pyupgrade-55990.patch
.pre-commit-config.yaml | ||
---|---|---|
6 | 6 |
hooks: |
7 | 7 |
- id: black |
8 | 8 |
args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110'] |
9 |
- repo: https://github.com/PyCQA/isort |
|
10 |
rev: 5.7.0 |
|
11 |
hooks: |
|
12 |
- id: isort |
|
13 |
args: ['--profile', 'black', '--line-length', '110'] |
|
14 |
- repo: https://github.com/asottile/pyupgrade |
|
15 |
rev: v2.20.0 |
|
16 |
hooks: |
|
17 |
- id: pyupgrade |
|
18 |
args: ['--keep-percent-format', '--py37-plus'] |
README | ||
---|---|---|
319 | 319 |
tox |
320 | 320 | |
321 | 321 |
Code Style |
322 |
---------- |
|
322 |
========== |
|
323 | 323 | |
324 | 324 |
black is used to format the code, using thoses parameters: |
325 | 325 | |
... | ... | |
328 | 328 |
There is .pre-commit-config.yaml to use pre-commit to automatically run black |
329 | 329 |
before commits. (execute `pre-commit install` to install the git hook.) |
330 | 330 | |
331 |
isort is used to format the imports, using those parameter: |
|
332 | ||
333 |
isort --profile black --line-length 110 |
|
334 | ||
335 |
pyupgrade is used to automatically upgrade syntax, using those parameters: |
|
336 | ||
337 |
pyupgrade --keep-percent-format --py37-plus |
|
338 | ||
339 |
There is .pre-commit-config.yaml to use pre-commit to automatically run black, |
|
340 |
isort and pyupgrade before commits. (execute `pre-commit install` to install |
|
341 |
the git hook.) |
|
342 | ||
331 | 343 |
Remarks |
332 | 344 |
======= |
333 | 345 |
mellon/adapters.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 |
from xml.etree import ElementTree as ET |
|
19 | 17 |
import hashlib |
20 | ||
21 | 18 |
import logging |
22 | 19 |
import os |
23 | 20 |
import threading |
24 | 21 |
import time |
25 | 22 |
import uuid |
23 |
from xml.etree import ElementTree as ET |
|
26 | 24 | |
27 | 25 |
import lasso |
28 | 26 |
import requests |
29 | 27 |
import requests.exceptions |
30 | 28 |
from atomicwrites import atomic_write |
31 | ||
32 |
from django.core.exceptions import PermissionDenied, FieldDoesNotExist |
|
33 |
from django.core.files.storage import default_storage |
|
34 |
from django.contrib import auth |
|
29 |
from django.contrib import auth, messages |
|
35 | 30 |
from django.contrib.auth.models import Group |
36 |
from django.contrib import messages |
|
31 |
from django.core.exceptions import FieldDoesNotExist, PermissionDenied |
|
32 |
from django.core.files.storage import default_storage |
|
37 | 33 |
from django.utils import six |
38 | 34 |
from django.utils.encoding import force_text |
39 | 35 |
from django.utils.six.moves.urllib.parse import urlparse |
40 | 36 |
from django.utils.translation import ugettext as _ |
41 | 37 | |
42 |
from . import utils, app_settings, models
|
|
38 |
from . import app_settings, models, utils
|
|
43 | 39 | |
44 | 40 |
User = auth.get_user_model() |
45 | 41 | |
... | ... | |
51 | 47 | |
52 | 48 | |
53 | 49 |
def display_truncated_list(l, max_length=10): |
54 |
s = '[' + ', '.join(map(six.text_type, l))
|
|
50 |
s = '[' + ', '.join(map(str, l))
|
|
55 | 51 |
if len(l) > max_length: |
56 | 52 |
s += '..truncated more than %d items (%d)]' % (max_length, len(l)) |
57 | 53 |
else: |
... | ... | |
59 | 55 |
return s |
60 | 56 | |
61 | 57 | |
62 |
class DefaultAdapter(object):
|
|
58 |
class DefaultAdapter: |
|
63 | 59 |
def __init__(self, request=None): |
64 | 60 |
self.request = request |
65 | 61 | |
... | ... | |
153 | 149 |
idp['METADATA'] = fd.read() |
154 | 150 |
# use file cache mtime as last_update time, prevent too many loading from different workers |
155 | 151 |
last_update = max(last_update, os.stat(file_cache_path).st_mtime) |
156 |
except (IOError, OSError):
|
|
152 |
except OSError:
|
|
157 | 153 |
warning('metadata url %s : error when loading the file cache %s', url, file_cache_path) |
158 | 154 | |
159 | 155 |
# fresh cache, skip loading |
... | ... | |
305 | 301 |
if saml_attributes['name_id_format'] == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT: |
306 | 302 |
if transient_federation_attribute and saml_attributes.get(transient_federation_attribute): |
307 | 303 |
name_id = saml_attributes[transient_federation_attribute] |
308 |
if not isinstance(name_id, six.string_types):
|
|
304 |
if not isinstance(name_id, str):
|
|
309 | 305 |
if len(name_id) == 1: |
310 | 306 |
name_id = name_id[0] |
311 | 307 |
else: |
mellon/app_settings.py | ||
---|---|---|
1 | 1 |
import sys |
2 | 2 | |
3 | 3 | |
4 |
class AppSettings(object):
|
|
4 |
class AppSettings: |
|
5 | 5 |
__PREFIX = 'MELLON_' |
6 | 6 |
__DEFAULTS = { |
7 | 7 |
'IDENTITY_PROVIDERS': [], |
mellon/backends.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
16 | ||
17 | 17 |
import logging |
18 | 18 | |
19 | 19 |
from django.contrib.auth.backends import ModelBackend |
mellon/middleware.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 |
from django.utils.http import urlencode |
|
19 | 17 |
from django.http import HttpResponseRedirect |
20 |
from django.utils.deprecation import MiddlewareMixin |
|
21 | 18 |
from django.urls import reverse |
19 |
from django.utils.deprecation import MiddlewareMixin |
|
20 |
from django.utils.http import urlencode |
|
22 | 21 | |
23 | 22 |
from . import app_settings, utils |
24 | 23 |
mellon/migrations/0001_initial.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import models, migrations |
|
5 | 1 |
from django.conf import settings |
2 |
from django.db import migrations, models |
|
6 | 3 | |
7 | 4 | |
8 | 5 |
class Migration(migrations.Migration): |
... | ... | |
40 | 37 |
), |
41 | 38 |
migrations.AlterUniqueTogether( |
42 | 39 |
name='usersamlidentifier', |
43 |
unique_together=set([('issuer', 'name_id')]),
|
|
40 |
unique_together={('issuer', 'name_id')},
|
|
44 | 41 |
), |
45 | 42 |
] |
mellon/migrations/0002_sessionindex.py | ||
---|---|---|
1 | 1 |
# Generated by Django 2.2.12 on 2020-04-24 05:14 |
2 | 2 | |
3 |
from django.db import migrations, models |
|
4 | 3 |
import django.db.models.deletion |
4 |
from django.db import migrations, models |
|
5 | 5 | |
6 | 6 | |
7 | 7 |
class Migration(migrations.Migration): |
mellon/models.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 | 17 |
from importlib import import_module |
19 | 18 | |
19 |
from django.conf import settings |
|
20 | 20 |
from django.db import models |
21 | 21 |
from django.utils.translation import ugettext_lazy as _ |
22 |
from django.conf import settings |
|
23 | 22 | |
24 | 23 | |
25 | 24 |
class UserSAMLIdentifier(models.Model): |
mellon/sessions_backends/db.py | ||
---|---|---|
29 | 29 |
session_not_on_or_after = self.get_session_not_on_or_after() |
30 | 30 |
if session_not_on_or_after and 'expiry' not in kwargs: |
31 | 31 |
kwargs['expiry'] = session_not_on_or_after |
32 |
return super(SessionStore, self).get_expiry_age(**kwargs)
|
|
32 |
return super().get_expiry_age(**kwargs) |
|
33 | 33 | |
34 | 34 |
def get_expiry_date(self, **kwargs): |
35 | 35 |
session_not_on_or_after = self.get_session_not_on_or_after() |
36 | 36 |
if session_not_on_or_after and 'expiry' not in kwargs: |
37 | 37 |
kwargs['expiry'] = session_not_on_or_after |
38 |
return super(SessionStore, self).get_expiry_date(**kwargs) |
|
38 |
return super().get_expiry_date(**kwargs) |
mellon/urls.py | ||
---|---|---|
1 |
from __future__ import unicode_literals |
|
2 | ||
3 |
from django.conf.urls import url |
|
4 | 1 |
import django |
2 |
from django.conf.urls import url |
|
5 | 3 | |
6 | 4 |
from . import views |
7 | 5 | |
8 | ||
9 | 6 |
urlpatterns = [ |
10 | 7 |
url('login/$', views.login, name='mellon_login'), |
11 | 8 |
url('login/debug/$', views.debug_login, name='mellon_debug_login'), |
mellon/utils.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 |
import logging |
|
19 | 17 |
import datetime |
20 | 18 |
import importlib |
19 |
import logging |
|
21 | 20 |
from functools import wraps |
22 |
import isodate |
|
23 | 21 |
from xml.parsers import expat |
24 | 22 | |
23 |
import isodate |
|
24 |
import lasso |
|
25 |
from django.conf import settings |
|
25 | 26 |
from django.contrib import auth |
26 | 27 |
from django.template.loader import render_to_string |
27 | 28 |
from django.urls import reverse |
28 | 29 |
from django.utils.encoding import force_text |
29 |
from django.utils.timezone import make_aware, now, make_naive, is_aware, get_default_timezone |
|
30 |
from django.conf import settings |
|
31 | 30 |
from django.utils.six.moves.urllib.parse import urlparse |
32 |
import lasso |
|
31 |
from django.utils.timezone import get_default_timezone, is_aware, make_aware, make_naive, now |
|
33 | 32 | |
34 | 33 |
from . import app_settings |
35 | 34 | |
... | ... | |
133 | 132 |
def get_idps(): |
134 | 133 |
for adapter in get_adapters(): |
135 | 134 |
if hasattr(adapter, 'get_idps'): |
136 |
for idp in adapter.get_idps(): |
|
137 |
yield idp |
|
135 |
yield from adapter.get_idps() |
|
138 | 136 | |
139 | 137 | |
140 | 138 |
def flatten_datetime(d): |
mellon/views.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
17 |
import logging |
|
18 |
import uuid |
|
19 |
import xml.etree.ElementTree as ET |
|
18 | 20 |
from contextlib import contextmanager, nullcontext |
19 | 21 |
from importlib import import_module |
20 | 22 |
from io import StringIO |
21 |
import logging |
|
22 |
import requests |
|
23 |
import lasso |
|
24 |
import uuid |
|
25 |
from requests.exceptions import RequestException |
|
26 | 23 |
from xml.sax.saxutils import escape |
27 |
import xml.etree.ElementTree as ET |
|
28 | 24 | |
29 | 25 |
import django.http |
30 |
from django.views.generic import View |
|
31 |
from django.views.generic.base import RedirectView |
|
32 |
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden |
|
33 |
from django.contrib import auth |
|
34 |
from django.contrib.auth import get_user_model |
|
26 |
import lasso |
|
27 |
import requests |
|
35 | 28 |
from django.conf import settings |
36 |
from django.views.decorators.csrf import csrf_exempt |
|
29 |
from django.contrib import auth |
|
30 |
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model |
|
31 |
from django.db import transaction |
|
32 |
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect |
|
37 | 33 |
from django.shortcuts import render, resolve_url |
38 | 34 |
from django.urls import reverse |
39 |
from django.utils.http import urlencode |
|
40 | 35 |
from django.utils import six |
41 |
from django.utils.encoding import force_text, force_str |
|
42 |
from django.contrib.auth import REDIRECT_FIELD_NAME |
|
43 |
from django.db import transaction |
|
36 |
from django.utils.encoding import force_str, force_text |
|
37 |
from django.utils.http import urlencode |
|
44 | 38 |
from django.utils.translation import ugettext as _ |
39 |
from django.views.decorators.csrf import csrf_exempt |
|
40 |
from django.views.generic import View |
|
41 |
from django.views.generic.base import RedirectView |
|
42 |
from requests.exceptions import RequestException |
|
45 | 43 | |
46 |
from . import app_settings, utils, models |
|
47 | ||
44 |
from . import app_settings, models, utils |
|
48 | 45 | |
49 | 46 |
RETRY_LOGIN_COOKIE = 'MELLON_RETRY_LOGIN' |
50 | 47 | |
51 | 48 |
lasso.setFlag('thin-sessions') |
52 | 49 | |
53 |
if six.PY3: |
|
54 | ||
55 |
def lasso_decode(x): |
|
56 |
return x |
|
57 | ||
58 | ||
59 |
else: |
|
60 | 50 | |
61 |
def lasso_decode(x):
|
|
62 |
return x.decode('utf-8')
|
|
51 |
def lasso_decode(x): |
|
52 |
return x
|
|
63 | 53 | |
64 | 54 | |
65 | 55 |
EO_NS = 'https://www.entrouvert.com/' |
... | ... | |
71 | 61 |
class HttpResponseBadRequest(django.http.HttpResponseBadRequest): |
72 | 62 |
def __init__(self, *args, **kwargs): |
73 | 63 |
kwargs['content_type'] = kwargs.get('content_type', 'text/plain') |
74 |
super(HttpResponseBadRequest, self).__init__(*args, **kwargs)
|
|
64 |
super().__init__(*args, **kwargs) |
|
75 | 65 |
self['X-Content-Type-Options'] = 'nosniff' |
76 | 66 | |
77 | 67 | |
78 |
class LogMixin(object):
|
|
68 |
class LogMixin: |
|
79 | 69 |
"""Initialize a module logger in new objects""" |
80 | 70 | |
81 | 71 |
def __init__(self, *args, **kwargs): |
82 | 72 |
self.log = logging.getLogger(__name__) |
83 |
super(LogMixin, self).__init__(*args, **kwargs)
|
|
73 |
super().__init__(*args, **kwargs) |
|
84 | 74 | |
85 | 75 | |
86 | 76 |
def check_next_url(request, next_url): |
... | ... | |
101 | 91 |
return next_url |
102 | 92 | |
103 | 93 | |
104 |
class ProfileMixin(object):
|
|
94 |
class ProfileMixin: |
|
105 | 95 |
profile = None |
106 | 96 | |
107 | 97 |
def set_next_url(self, next_url): |
... | ... | |
507 | 497 |
# configure requested AuthnClassRef |
508 | 498 |
authn_classref = utils.get_setting(idp, 'AUTHN_CLASSREF') |
509 | 499 |
if authn_classref: |
510 |
authn_classref = tuple([str(x) for x in authn_classref])
|
|
500 |
authn_classref = tuple(str(x) for x in authn_classref)
|
|
511 | 501 |
req_authncontext = lasso.Samlp2RequestedAuthnContext() |
512 | 502 |
authn_request.requestedAuthnContext = req_authncontext |
513 | 503 |
req_authncontext.authnContextClassRef = authn_classref |
... | ... | |
550 | 540 |
assert hasattr(authn_request.extensions, 'any'), 'extension nodes need lasso > 2.5.1' |
551 | 541 |
serialized = ET.tostring(node, 'utf-8') |
552 | 542 |
# tostring return bytes in PY3, but lasso needs str |
553 |
if six.PY3: |
|
554 |
serialized = serialized.decode('utf-8') |
|
543 |
serialized = serialized.decode('utf-8') |
|
555 | 544 |
extension_content = authn_request.extensions.any or () |
556 | 545 |
extension_content += (serialized,) |
557 | 546 |
authn_request.extensions.any = extension_content |
... | ... | |
653 | 642 |
return HttpResponseBadRequest('error processing logout request: %r' % e) |
654 | 643 | |
655 | 644 |
issuer = force_text(logout.remoteProviderId) |
656 |
session_indexes = set(force_text(sessionIndex) for sessionIndex in logout.request.sessionIndexes)
|
|
645 |
session_indexes = {force_text(sessionIndex) for sessionIndex in logout.request.sessionIndexes}
|
|
657 | 646 | |
658 | 647 |
saml_identifier = ( |
659 | 648 |
models.UserSAMLIdentifier.objects.filter( |
setup.py | ||
---|---|---|
5 | 5 | |
6 | 6 |
import os |
7 | 7 |
import subprocess |
8 |
from setuptools import setup, find_packages |
|
9 |
from setuptools.command.install_lib import install_lib as _install_lib |
|
8 |
from distutils.cmd import Command |
|
10 | 9 |
from distutils.command.build import build as _build |
10 | ||
11 |
from setuptools import find_packages, setup |
|
12 |
from setuptools.command.install_lib import install_lib as _install_lib |
|
11 | 13 |
from setuptools.command.sdist import sdist as _sdist |
12 |
from distutils.cmd import Command |
|
13 | 14 | |
14 | 15 | |
15 | 16 |
class compile_translations(Command): |
... | ... | |
24 | 25 | |
25 | 26 |
def run(self): |
26 | 27 |
import os |
28 | ||
27 | 29 |
from django.core.management import call_command |
28 | 30 | |
29 | 31 |
os.environ.pop('DJANGO_SETTINGS_MODULE', None) |
... | ... | |
68 | 70 |
tag exists, take 0.0.0- and add the length of the commit log. |
69 | 71 |
""" |
70 | 72 |
if os.path.exists('VERSION'): |
71 |
with open('VERSION', 'r') as v:
|
|
73 |
with open('VERSION') as v: |
|
72 | 74 |
return v.read() |
73 | 75 |
if os.path.exists('.git'): |
74 | 76 |
p = subprocess.Popen( |
tests/conftest.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
import os |
|
17 | 16 |
import logging |
17 |
import os |
|
18 | 18 | |
19 |
import pytest |
|
20 | 19 |
import django_webtest |
20 |
import pytest |
|
21 | 21 | |
22 | 22 | |
23 | 23 |
@pytest.fixture(autouse=True) |
tests/test_default_adapter.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 | 17 |
import datetime |
19 | 18 |
import re |
20 |
import lasso |
|
21 | 19 |
import time |
22 | 20 |
from multiprocessing.pool import ThreadPool |
21 |
from unittest import mock |
|
23 | 22 | |
24 |
import mock
|
|
23 |
import lasso
|
|
25 | 24 |
import pytest |
26 | ||
27 | 25 |
from django.contrib import auth |
28 | 26 |
from django.db import connection |
29 | 27 | |
30 | 28 |
from mellon.adapters import DefaultAdapter |
31 | 29 |
from mellon.backends import SAMLBackend |
32 | 30 | |
33 | ||
34 | 31 |
pytestmark = pytest.mark.django_db |
35 | 32 | |
36 | 33 |
User = auth.get_user_model() |
... | ... | |
119 | 116 |
users = p.map(f, range(concurrency)) |
120 | 117 | |
121 | 118 |
assert len(users) == concurrency |
122 |
assert len(set(user.pk for user in users)) == 1
|
|
119 |
assert len({user.pk for user in users}) == 1
|
|
123 | 120 | |
124 | 121 | |
125 | 122 |
def test_provision_user_attributes(settings, django_user_model, idp, saml_attributes, caplog): |
tests/test_sso_slo.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
17 |
import base64 |
|
18 | 18 |
import datetime |
19 |
from html import unescape |
|
20 | 19 |
import re |
21 |
import base64 |
|
22 |
import zlib |
|
23 | 20 |
import xml.etree.ElementTree as ET |
21 |
import zlib |
|
22 |
from html import unescape |
|
24 | 23 | |
25 | 24 |
import lasso |
26 | ||
27 | 25 |
import pytest |
28 |
from pytest import fixture |
|
29 | ||
30 |
from django.contrib.sessions.models import Session |
|
31 | 26 |
from django.contrib.auth.models import User |
27 |
from django.contrib.sessions.models import Session |
|
32 | 28 |
from django.urls import reverse |
33 | 29 |
from django.utils import six |
34 |
from django.utils.six.moves.urllib import parse as urlparse |
|
35 | 30 |
from django.utils.encoding import force_str |
31 |
from django.utils.six.moves.urllib import parse as urlparse |
|
32 |
from httmock import HTTMock, all_requests |
|
33 |
from httmock import response as mock_response |
|
34 |
from pytest import fixture |
|
36 | 35 | |
37 | 36 |
from mellon.utils import create_metadata |
38 | 37 |
from mellon.views import lasso_decode |
39 | 38 | |
40 |
from httmock import all_requests, HTTMock, response as mock_response |
|
41 | ||
42 | 39 | |
43 | 40 |
@fixture |
44 | 41 |
def idp_metadata(): |
... | ... | |
81 | 78 |
return create_metadata(request) |
82 | 79 | |
83 | 80 | |
84 |
class MockIdp(object):
|
|
81 |
class MockIdp: |
|
85 | 82 |
session_dump = None |
86 | 83 |
identity_dump = None |
87 | 84 | |
... | ... | |
240 | 237 |
assert 'created new user' in caplog.text |
241 | 238 |
assert 'logged in using SAML' in caplog.text |
242 | 239 |
assert urlparse.urlparse(response['Location']).path == '/whatever/' |
243 |
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': str('/some/path')})
|
|
240 |
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': '/some/path'})
|
|
244 | 241 |
assert urlparse.urlparse(response['Location']).path == '/singleLogout' |
245 | 242 |
# again, user is already logged out |
246 |
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': str('/some/path')})
|
|
243 |
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': '/some/path'})
|
|
247 | 244 |
assert urlparse.urlparse(response['Location']).path == '/some/path' |
248 | 245 | |
249 | 246 | |
... | ... | |
433 | 430 |
assert not relay_state |
434 | 431 |
assert url.endswith(reverse('mellon_login')) |
435 | 432 |
response = app.post(reverse('mellon_login'), params={'SAMLResponse': body, 'RelayState': relay_state}) |
436 |
if six.PY3: |
|
437 |
assert ( |
|
438 |
"status is not success codes: ['urn:oasis:names:tc:SAML:2.0:status:Responder',\ |
|
433 |
assert ( |
|
434 |
"status is not success codes: ['urn:oasis:names:tc:SAML:2.0:status:Responder',\ |
|
439 | 435 |
'urn:oasis:names:tc:SAML:2.0:status:RequestDenied']" |
440 |
in caplog.text |
|
441 |
) |
|
442 |
else: |
|
443 |
assert ( |
|
444 |
"status is not success codes: [u'urn:oasis:names:tc:SAML:2.0:status:Responder',\ |
|
445 |
u'urn:oasis:names:tc:SAML:2.0:status:RequestDenied']" |
|
446 |
in caplog.text |
|
447 |
) |
|
436 |
in caplog.text |
|
437 |
) |
|
448 | 438 | |
449 | 439 | |
450 | 440 |
@pytest.mark.urls('urls_tests_template_base') |
... | ... | |
663 | 653 |
settings.MELLON_OPENED_SESSION_COOKIE_NAME = 'IDP_SESSION' |
664 | 654 |
assert 'MELLON_PASSIVE_TRIED' not in app.cookies |
665 | 655 |
# webtest-lint is against unicode |
666 |
app.set_cookie(str('IDP_SESSION'), str('1'))
|
|
656 |
app.set_cookie('IDP_SESSION', '1')
|
|
667 | 657 |
response = app.get('/', headers={'Accept': force_str('text/html')}, status=302) |
668 | 658 |
assert urlparse.urlparse(response.location).path == '/login/' |
669 | 659 |
assert urlparse.parse_qs(urlparse.urlparse(response.location).query, keep_blank_values=True) == { |
... | ... | |
681 | 671 |
assert 'MELLON_PASSIVE_TRIED' not in app.cookies |
682 | 672 | |
683 | 673 |
# check passive authentication is tried again |
684 |
app.set_cookie(str('IDP_SESSION'), str('1'))
|
|
674 |
app.set_cookie('IDP_SESSION', '1')
|
|
685 | 675 |
response = app.get('/', headers={'Accept': force_str('text/html')}, status=302) |
686 | 676 |
assert urlparse.urlparse(response.location).path == '/login/' |
687 | 677 |
assert urlparse.parse_qs(urlparse.urlparse(response.location).query, keep_blank_values=True) == { |
... | ... | |
695 | 685 |
settings.MELLON_OPENED_SESSION_COOKIE_NAME = 'IDP_SESSION' |
696 | 686 |
assert 'MELLON_PASSIVE_TRIED' not in app.cookies |
697 | 687 |
# webtest-lint is against unicode |
698 |
app.set_cookie(str('IDP_SESSION'), str('1'))
|
|
688 |
app.set_cookie('IDP_SESSION', '1')
|
|
699 | 689 |
app.get('/?no-passive-auth', headers={'Accept': force_str('text/html')}, status=200) |
700 | 690 | |
701 | 691 |
tests/test_utils.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 | 17 |
import datetime |
18 |
from unittest import mock |
|
19 | 19 | |
20 |
import mock |
|
21 | 20 |
import lasso |
21 |
from xml_utils import assert_xml_constraints |
|
22 | 22 | |
23 |
from mellon.utils import create_metadata, iso8601_to_datetime, flatten_datetime
|
|
23 |
from mellon.utils import create_metadata, flatten_datetime, iso8601_to_datetime
|
|
24 | 24 |
from mellon.views import check_next_url |
25 |
from xml_utils import assert_xml_constraints |
|
26 | 25 | |
27 | 26 | |
28 | 27 |
def test_create_metadata(rf, private_settings, caplog): |
... | ... | |
144 | 143 |
'y': 1, |
145 | 144 |
'z': 'u', |
146 | 145 |
} |
147 |
assert set(flatten_datetime(d).keys()) == set(['x', 'y', 'z'])
|
|
146 |
assert set(flatten_datetime(d).keys()) == {'x', 'y', 'z'}
|
|
148 | 147 |
assert flatten_datetime(d)['x'] == '2010-10-10T10:10:34' |
149 | 148 |
assert flatten_datetime(d)['y'] == 1 |
150 | 149 |
assert flatten_datetime(d)['z'] == 'u' |
151 | 150 | |
152 | 151 | |
153 | 152 |
def test_check_next_url(rf): |
154 |
assert not check_next_url(rf.get('/'), u'')
|
|
153 |
assert not check_next_url(rf.get('/'), '') |
|
155 | 154 |
assert not check_next_url(rf.get('/'), None) |
156 |
assert not check_next_url(rf.get('/'), u'\x00')
|
|
157 |
assert not check_next_url(rf.get('/'), u'\u010e')
|
|
158 |
assert not check_next_url(rf.get('/'), u'https://example.invalid/')
|
|
155 |
assert not check_next_url(rf.get('/'), '\x00') |
|
156 |
assert not check_next_url(rf.get('/'), '\u010e') |
|
157 |
assert not check_next_url(rf.get('/'), 'https://example.invalid/') |
|
159 | 158 |
# default hostname is testserver |
160 |
assert check_next_url(rf.get('/'), u'http://testserver/ok/') |
|
159 |
assert check_next_url(rf.get('/'), 'http://testserver/ok/') |
tests/test_views.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | 16 | |
18 |
import pytest |
|
19 |
import mock |
|
20 |
import lasso |
|
21 |
from django.utils.six.moves.urllib.parse import parse_qs, urlparse |
|
22 | 17 |
import base64 |
23 | 18 |
import hashlib |
24 |
from httmock import HTTMock
|
|
19 |
from unittest import mock
|
|
25 | 20 | |
21 |
import lasso |
|
22 |
import pytest |
|
26 | 23 |
from django.urls import reverse |
27 | 24 |
from django.utils.encoding import force_text |
28 | 25 |
from django.utils.http import urlencode |
29 | ||
30 |
from xml_utils import assert_xml_constraints |
|
31 | ||
26 |
from django.utils.six.moves.urllib.parse import parse_qs, urlparse |
|
27 |
from httmock import HTTMock |
|
32 | 28 |
from utils import error_500, html_response |
29 |
from xml_utils import assert_xml_constraints |
|
33 | 30 | |
34 | 31 |
pytestmark = pytest.mark.django_db |
35 | 32 | |
... | ... | |
207 | 204 |
assert response.status_code == 302 |
208 | 205 |
params = parse_qs(urlparse(response['Location']).query) |
209 | 206 |
assert response['Location'].startswith('http://idp5/singleSignOn?') |
210 |
assert set(params.keys()) == set(['SAMLRequest', 'RelayState'])
|
|
207 |
assert set(params.keys()) == {'SAMLRequest', 'RelayState'}
|
|
211 | 208 |
assert len(params['SAMLRequest']) == 1 |
212 | 209 |
assert base64.b64decode(params['SAMLRequest'][0]) |
213 | 210 |
assert client.session['mellon_next_url_%s' % params['RelayState'][0]] == '/whatever' |
... | ... | |
229 | 226 |
assert response.status_code == 302 |
230 | 227 |
params = parse_qs(urlparse(response['Location']).query) |
231 | 228 |
assert response['Location'].startswith('http://idp5/singleSignOn?') |
232 |
assert set(params.keys()) == set(['SAMLRequest', 'RelayState'])
|
|
229 |
assert set(params.keys()) == {'SAMLRequest', 'RelayState'}
|
|
233 | 230 |
assert len(params['SAMLRequest']) == 1 |
234 | 231 |
assert base64.b64decode(params['SAMLRequest'][0]) |
235 | 232 |
assert client.session['mellon_next_url_%s' % params['RelayState'][0]] == '/whatever' |
tests/urls_tests.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from django.conf.urls import url, include
|
|
16 |
from django.conf.urls import include, url
|
|
17 | 17 |
from django.http import HttpResponse |
18 | 18 | |
19 | 19 |
tests/urls_tests_template_base.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from django.conf.urls import url, include
|
|
16 |
from django.conf.urls import include, url
|
|
17 | 17 |
from django.http import HttpResponse |
18 | 18 | |
19 | 19 |
tests/urls_tests_template_hook.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from django.conf.urls import url, include
|
|
16 |
from django.conf.urls import include, url
|
|
17 | 17 |
from django.http import HttpResponse |
18 | 18 | |
19 | 19 |
testsettings.py | ||
---|---|---|
1 | 1 |
import os |
2 | ||
2 | 3 |
import django |
3 | 4 |
from django.conf import global_settings |
4 | 5 |
tox.ini | ||
---|---|---|
1 | 1 |
[tox] |
2 |
envlist = black,py3-django22-coverage
|
|
2 |
envlist = code-style,py3-django22-coverage
|
|
3 | 3 |
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/django-mellon/ |
4 | 4 | |
5 | 5 |
[testenv] |
... | ... | |
55 | 55 |
./getlasso3.sh |
56 | 56 |
django-admin {posargs:--help} |
57 | 57 | |
58 |
[testenv:black]
|
|
58 |
[testenv:code-style]
|
|
59 | 59 |
skip_install = true |
60 | 60 |
deps = |
61 | 61 |
pre-commit |
62 | 62 |
commands = |
63 |
pre-commit run black --all-files --show-diff-on-failure
|
|
63 |
pre-commit run --all-files --show-diff-on-failure |
|
64 | 64 | |
65 | 65 |
[pytest] |
66 | 66 |
junit_family=legacy |
67 |
- |