apply isort and pyupgrade (#55990)

This commit is contained in:
Benjamin Dauvergne 2021-08-05 09:14:56 +02:00
parent 2704f4feaa
commit 4729ef9a3b
24 changed files with 130 additions and 147 deletions

View File

@ -6,3 +6,13 @@ repos:
hooks:
- id: black
args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110']
- repo: https://github.com/PyCQA/isort
rev: 5.7.0
hooks:
- id: isort
args: ['--profile', 'black', '--line-length', '110']
- repo: https://github.com/asottile/pyupgrade
rev: v2.20.0
hooks:
- id: pyupgrade
args: ['--keep-percent-format', '--py37-plus']

14
README
View File

@ -319,7 +319,7 @@ Unit tests are written using pytest and launched using tox, and can be run with:
tox
Code Style
----------
==========
black is used to format the code, using thoses parameters:
@ -328,6 +328,18 @@ black is used to format the code, using thoses parameters:
There is .pre-commit-config.yaml to use pre-commit to automatically run black
before commits. (execute `pre-commit install` to install the git hook.)
isort is used to format the imports, using those parameter:
isort --profile black --line-length 110
pyupgrade is used to automatically upgrade syntax, using those parameters:
pyupgrade --keep-percent-format --py37-plus
There is .pre-commit-config.yaml to use pre-commit to automatically run black,
isort and pyupgrade before commits. (execute `pre-commit install` to install
the git hook.)
Remarks
=======

View File

@ -13,33 +13,29 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from xml.etree import ElementTree as ET
import hashlib
import logging
import os
import threading
import time
import uuid
from xml.etree import ElementTree as ET
import lasso
import requests
import requests.exceptions
from atomicwrites import atomic_write
from django.core.exceptions import PermissionDenied, FieldDoesNotExist
from django.core.files.storage import default_storage
from django.contrib import auth
from django.contrib import auth, messages
from django.contrib.auth.models import Group
from django.contrib import messages
from django.core.exceptions import FieldDoesNotExist, PermissionDenied
from django.core.files.storage import default_storage
from django.utils import six
from django.utils.encoding import force_text
from django.utils.six.moves.urllib.parse import urlparse
from django.utils.translation import ugettext as _
from . import utils, app_settings, models
from . import app_settings, models, utils
User = auth.get_user_model()
@ -51,7 +47,7 @@ class UserCreationError(Exception):
def display_truncated_list(l, max_length=10):
s = '[' + ', '.join(map(six.text_type, l))
s = '[' + ', '.join(map(str, l))
if len(l) > max_length:
s += '..truncated more than %d items (%d)]' % (max_length, len(l))
else:
@ -59,7 +55,7 @@ def display_truncated_list(l, max_length=10):
return s
class DefaultAdapter(object):
class DefaultAdapter:
def __init__(self, request=None):
self.request = request
@ -153,7 +149,7 @@ class DefaultAdapter(object):
idp['METADATA'] = fd.read()
# use file cache mtime as last_update time, prevent too many loading from different workers
last_update = max(last_update, os.stat(file_cache_path).st_mtime)
except (IOError, OSError):
except OSError:
warning('metadata url %s : error when loading the file cache %s', url, file_cache_path)
# fresh cache, skip loading
@ -305,7 +301,7 @@ class DefaultAdapter(object):
if saml_attributes['name_id_format'] == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT:
if transient_federation_attribute and saml_attributes.get(transient_federation_attribute):
name_id = saml_attributes[transient_federation_attribute]
if not isinstance(name_id, six.string_types):
if not isinstance(name_id, str):
if len(name_id) == 1:
name_id = name_id[0]
else:

View File

@ -1,7 +1,7 @@
import sys
class AppSettings(object):
class AppSettings:
__PREFIX = 'MELLON_'
__DEFAULTS = {
'IDENTITY_PROVIDERS': [],

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import logging
from django.contrib.auth.backends import ModelBackend

View File

@ -13,12 +13,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from django.utils.http import urlencode
from django.http import HttpResponseRedirect
from django.utils.deprecation import MiddlewareMixin
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import urlencode
from . import app_settings, utils

View File

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -40,6 +37,6 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='usersamlidentifier',
unique_together=set([('issuer', 'name_id')]),
unique_together={('issuer', 'name_id')},
),
]

View File

@ -1,7 +1,7 @@
# Generated by Django 2.2.12 on 2020-04-24 05:14
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -13,13 +13,12 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from importlib import import_module
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
class UserSAMLIdentifier(models.Model):

View File

@ -29,10 +29,10 @@ class SessionStore(SessionStore):
session_not_on_or_after = self.get_session_not_on_or_after()
if session_not_on_or_after and 'expiry' not in kwargs:
kwargs['expiry'] = session_not_on_or_after
return super(SessionStore, self).get_expiry_age(**kwargs)
return super().get_expiry_age(**kwargs)
def get_expiry_date(self, **kwargs):
session_not_on_or_after = self.get_session_not_on_or_after()
if session_not_on_or_after and 'expiry' not in kwargs:
kwargs['expiry'] = session_not_on_or_after
return super(SessionStore, self).get_expiry_date(**kwargs)
return super().get_expiry_date(**kwargs)

View File

@ -1,11 +1,8 @@
from __future__ import unicode_literals
from django.conf.urls import url
import django
from django.conf.urls import url
from . import views
urlpatterns = [
url('login/$', views.login, name='mellon_login'),
url('login/debug/$', views.debug_login, name='mellon_debug_login'),

View File

@ -13,23 +13,22 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import logging
import datetime
import importlib
import logging
from functools import wraps
import isodate
from xml.parsers import expat
import isodate
import lasso
from django.conf import settings
from django.contrib import auth
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.timezone import make_aware, now, make_naive, is_aware, get_default_timezone
from django.conf import settings
from django.utils.six.moves.urllib.parse import urlparse
import lasso
from django.utils.timezone import get_default_timezone, is_aware, make_aware, make_naive, now
from . import app_settings
@ -133,8 +132,7 @@ def get_idp(entity_id):
def get_idps():
for adapter in get_adapters():
if hasattr(adapter, 'get_idps'):
for idp in adapter.get_idps():
yield idp
yield from adapter.get_idps()
def flatten_datetime(d):

View File

@ -13,53 +13,43 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import logging
import uuid
import xml.etree.ElementTree as ET
from contextlib import contextmanager, nullcontext
from importlib import import_module
from io import StringIO
import logging
import requests
import lasso
import uuid
from requests.exceptions import RequestException
from xml.sax.saxutils import escape
import xml.etree.ElementTree as ET
import django.http
from django.views.generic import View
from django.views.generic.base import RedirectView
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden
from django.contrib import auth
from django.contrib.auth import get_user_model
import lasso
import requests
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
from django.db import transaction
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
from django.shortcuts import render, resolve_url
from django.urls import reverse
from django.utils.http import urlencode
from django.utils import six
from django.utils.encoding import force_text, force_str
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.db import transaction
from django.utils.encoding import force_str, force_text
from django.utils.http import urlencode
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
from django.views.generic.base import RedirectView
from requests.exceptions import RequestException
from . import app_settings, utils, models
from . import app_settings, models, utils
RETRY_LOGIN_COOKIE = 'MELLON_RETRY_LOGIN'
lasso.setFlag('thin-sessions')
if six.PY3:
def lasso_decode(x):
return x
else:
def lasso_decode(x):
return x.decode('utf-8')
def lasso_decode(x):
return x
EO_NS = 'https://www.entrouvert.com/'
@ -71,16 +61,16 @@ User = get_user_model()
class HttpResponseBadRequest(django.http.HttpResponseBadRequest):
def __init__(self, *args, **kwargs):
kwargs['content_type'] = kwargs.get('content_type', 'text/plain')
super(HttpResponseBadRequest, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self['X-Content-Type-Options'] = 'nosniff'
class LogMixin(object):
class LogMixin:
"""Initialize a module logger in new objects"""
def __init__(self, *args, **kwargs):
self.log = logging.getLogger(__name__)
super(LogMixin, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def check_next_url(request, next_url):
@ -101,7 +91,7 @@ def check_next_url(request, next_url):
return next_url
class ProfileMixin(object):
class ProfileMixin:
profile = None
def set_next_url(self, next_url):
@ -507,7 +497,7 @@ class LoginView(ProfileMixin, LogMixin, View):
# configure requested AuthnClassRef
authn_classref = utils.get_setting(idp, 'AUTHN_CLASSREF')
if authn_classref:
authn_classref = tuple([str(x) for x in authn_classref])
authn_classref = tuple(str(x) for x in authn_classref)
req_authncontext = lasso.Samlp2RequestedAuthnContext()
authn_request.requestedAuthnContext = req_authncontext
req_authncontext.authnContextClassRef = authn_classref
@ -550,8 +540,7 @@ class LoginView(ProfileMixin, LogMixin, View):
assert hasattr(authn_request.extensions, 'any'), 'extension nodes need lasso > 2.5.1'
serialized = ET.tostring(node, 'utf-8')
# tostring return bytes in PY3, but lasso needs str
if six.PY3:
serialized = serialized.decode('utf-8')
serialized = serialized.decode('utf-8')
extension_content = authn_request.extensions.any or ()
extension_content += (serialized,)
authn_request.extensions.any = extension_content
@ -653,7 +642,7 @@ class LogoutView(ProfileMixin, LogMixin, View):
return HttpResponseBadRequest('error processing logout request: %r' % e)
issuer = force_text(logout.remoteProviderId)
session_indexes = set(force_text(sessionIndex) for sessionIndex in logout.request.sessionIndexes)
session_indexes = {force_text(sessionIndex) for sessionIndex in logout.request.sessionIndexes}
saml_identifier = (
models.UserSAMLIdentifier.objects.filter(

View File

@ -5,11 +5,12 @@
import os
import subprocess
from setuptools import setup, find_packages
from setuptools.command.install_lib import install_lib as _install_lib
from distutils.command.build import build as _build
from setuptools.command.sdist import sdist as _sdist
from distutils.cmd import Command
from distutils.command.build import build as _build
from setuptools import find_packages, setup
from setuptools.command.install_lib import install_lib as _install_lib
from setuptools.command.sdist import sdist as _sdist
class compile_translations(Command):
@ -24,6 +25,7 @@ class compile_translations(Command):
def run(self):
import os
from django.core.management import call_command
os.environ.pop('DJANGO_SETTINGS_MODULE', None)
@ -68,7 +70,7 @@ def get_version():
tag exists, take 0.0.0- and add the length of the commit log.
"""
if os.path.exists('VERSION'):
with open('VERSION', 'r') as v:
with open('VERSION') as v:
return v.read()
if os.path.exists('.git'):
p = subprocess.Popen(

View File

@ -13,11 +13,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import os
import pytest
import django_webtest
import pytest
@pytest.fixture(autouse=True)

View File

@ -13,24 +13,21 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import datetime
import re
import lasso
import time
from multiprocessing.pool import ThreadPool
from unittest import mock
import mock
import lasso
import pytest
from django.contrib import auth
from django.db import connection
from mellon.adapters import DefaultAdapter
from mellon.backends import SAMLBackend
pytestmark = pytest.mark.django_db
User = auth.get_user_model()
@ -119,7 +116,7 @@ def test_lookup_user_transaction(transactional_db, concurrency, idp, saml_attrib
users = p.map(f, range(concurrency))
assert len(users) == concurrency
assert len(set(user.pk for user in users)) == 1
assert len({user.pk for user in users}) == 1
def test_provision_user_attributes(settings, django_user_model, idp, saml_attributes, caplog):

View File

@ -13,32 +13,29 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import datetime
from html import unescape
import re
import base64
import zlib
import datetime
import re
import xml.etree.ElementTree as ET
import zlib
from html import unescape
import lasso
import pytest
from pytest import fixture
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
from django.urls import reverse
from django.utils import six
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.encoding import force_str
from django.utils.six.moves.urllib import parse as urlparse
from httmock import HTTMock, all_requests
from httmock import response as mock_response
from pytest import fixture
from mellon.utils import create_metadata
from mellon.views import lasso_decode
from httmock import all_requests, HTTMock, response as mock_response
@fixture
def idp_metadata():
@ -81,7 +78,7 @@ def sp_metadata(sp_settings, rf):
return create_metadata(request)
class MockIdp(object):
class MockIdp:
session_dump = None
identity_dump = None
@ -240,10 +237,10 @@ def test_sso_slo(db, app, idp, caplog, sp_settings):
assert 'created new user' in caplog.text
assert 'logged in using SAML' in caplog.text
assert urlparse.urlparse(response['Location']).path == '/whatever/'
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': str('/some/path')})
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': '/some/path'})
assert urlparse.urlparse(response['Location']).path == '/singleLogout'
# again, user is already logged out
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': str('/some/path')})
response = app.get(reverse('mellon_logout'), extra_environ={'HTTP_REFERER': '/some/path'})
assert urlparse.urlparse(response['Location']).path == '/some/path'
@ -433,18 +430,11 @@ def test_sso_request_denied(db, app, idp, caplog, sp_settings):
assert not relay_state
assert url.endswith(reverse('mellon_login'))
response = app.post(reverse('mellon_login'), params={'SAMLResponse': body, 'RelayState': relay_state})
if six.PY3:
assert (
"status is not success codes: ['urn:oasis:names:tc:SAML:2.0:status:Responder',\
assert (
"status is not success codes: ['urn:oasis:names:tc:SAML:2.0:status:Responder',\
'urn:oasis:names:tc:SAML:2.0:status:RequestDenied']"
in caplog.text
)
else:
assert (
"status is not success codes: [u'urn:oasis:names:tc:SAML:2.0:status:Responder',\
u'urn:oasis:names:tc:SAML:2.0:status:RequestDenied']"
in caplog.text
)
in caplog.text
)
@pytest.mark.urls('urls_tests_template_base')
@ -663,7 +653,7 @@ def test_passive_auth_middleware_ok(db, app, idp, caplog, settings):
settings.MELLON_OPENED_SESSION_COOKIE_NAME = 'IDP_SESSION'
assert 'MELLON_PASSIVE_TRIED' not in app.cookies
# webtest-lint is against unicode
app.set_cookie(str('IDP_SESSION'), str('1'))
app.set_cookie('IDP_SESSION', '1')
response = app.get('/', headers={'Accept': force_str('text/html')}, status=302)
assert urlparse.urlparse(response.location).path == '/login/'
assert urlparse.parse_qs(urlparse.urlparse(response.location).query, keep_blank_values=True) == {
@ -681,7 +671,7 @@ def test_passive_auth_middleware_ok(db, app, idp, caplog, settings):
assert 'MELLON_PASSIVE_TRIED' not in app.cookies
# check passive authentication is tried again
app.set_cookie(str('IDP_SESSION'), str('1'))
app.set_cookie('IDP_SESSION', '1')
response = app.get('/', headers={'Accept': force_str('text/html')}, status=302)
assert urlparse.urlparse(response.location).path == '/login/'
assert urlparse.parse_qs(urlparse.urlparse(response.location).query, keep_blank_values=True) == {
@ -695,7 +685,7 @@ def test_passive_auth_middleware_no_passive_auth_parameter(db, app, idp, caplog,
settings.MELLON_OPENED_SESSION_COOKIE_NAME = 'IDP_SESSION'
assert 'MELLON_PASSIVE_TRIED' not in app.cookies
# webtest-lint is against unicode
app.set_cookie(str('IDP_SESSION'), str('1'))
app.set_cookie('IDP_SESSION', '1')
app.get('/?no-passive-auth', headers={'Accept': force_str('text/html')}, status=200)

View File

@ -13,17 +13,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import datetime
from unittest import mock
import mock
import lasso
from mellon.utils import create_metadata, iso8601_to_datetime, flatten_datetime
from mellon.views import check_next_url
from xml_utils import assert_xml_constraints
from mellon.utils import create_metadata, flatten_datetime, iso8601_to_datetime
from mellon.views import check_next_url
def test_create_metadata(rf, private_settings, caplog):
ns = {
@ -144,17 +143,17 @@ def test_flatten_datetime():
'y': 1,
'z': 'u',
}
assert set(flatten_datetime(d).keys()) == set(['x', 'y', 'z'])
assert set(flatten_datetime(d).keys()) == {'x', 'y', 'z'}
assert flatten_datetime(d)['x'] == '2010-10-10T10:10:34'
assert flatten_datetime(d)['y'] == 1
assert flatten_datetime(d)['z'] == 'u'
def test_check_next_url(rf):
assert not check_next_url(rf.get('/'), u'')
assert not check_next_url(rf.get('/'), '')
assert not check_next_url(rf.get('/'), None)
assert not check_next_url(rf.get('/'), u'\x00')
assert not check_next_url(rf.get('/'), u'\u010e')
assert not check_next_url(rf.get('/'), u'https://example.invalid/')
assert not check_next_url(rf.get('/'), '\x00')
assert not check_next_url(rf.get('/'), '\u010e')
assert not check_next_url(rf.get('/'), 'https://example.invalid/')
# default hostname is testserver
assert check_next_url(rf.get('/'), u'http://testserver/ok/')
assert check_next_url(rf.get('/'), 'http://testserver/ok/')

View File

@ -13,23 +13,20 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import pytest
import mock
import lasso
from django.utils.six.moves.urllib.parse import parse_qs, urlparse
import base64
import hashlib
from httmock import HTTMock
from unittest import mock
import lasso
import pytest
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.http import urlencode
from xml_utils import assert_xml_constraints
from django.utils.six.moves.urllib.parse import parse_qs, urlparse
from httmock import HTTMock
from utils import error_500, html_response
from xml_utils import assert_xml_constraints
pytestmark = pytest.mark.django_db
@ -207,7 +204,7 @@ def test_sp_initiated_login(private_settings, client):
assert response.status_code == 302
params = parse_qs(urlparse(response['Location']).query)
assert response['Location'].startswith('http://idp5/singleSignOn?')
assert set(params.keys()) == set(['SAMLRequest', 'RelayState'])
assert set(params.keys()) == {'SAMLRequest', 'RelayState'}
assert len(params['SAMLRequest']) == 1
assert base64.b64decode(params['SAMLRequest'][0])
assert client.session['mellon_next_url_%s' % params['RelayState'][0]] == '/whatever'
@ -229,7 +226,7 @@ def test_sp_initiated_login_chosen(private_settings, client):
assert response.status_code == 302
params = parse_qs(urlparse(response['Location']).query)
assert response['Location'].startswith('http://idp5/singleSignOn?')
assert set(params.keys()) == set(['SAMLRequest', 'RelayState'])
assert set(params.keys()) == {'SAMLRequest', 'RelayState'}
assert len(params['SAMLRequest']) == 1
assert base64.b64decode(params['SAMLRequest'][0])
assert client.session['mellon_next_url_%s' % params['RelayState'][0]] == '/whatever'

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url, include
from django.conf.urls import include, url
from django.http import HttpResponse

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url, include
from django.conf.urls import include, url
from django.http import HttpResponse

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url, include
from django.conf.urls import include, url
from django.http import HttpResponse

View File

@ -1,4 +1,5 @@
import os
import django
from django.conf import global_settings

View File

@ -1,5 +1,5 @@
[tox]
envlist = black,py3-django22-coverage
envlist = code-style,py3-django22-coverage
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/django-mellon/
[testenv]
@ -55,12 +55,12 @@ commands =
./getlasso3.sh
django-admin {posargs:--help}
[testenv:black]
[testenv:code-style]
skip_install = true
deps =
pre-commit
commands =
pre-commit run black --all-files --show-diff-on-failure
pre-commit run --all-files --show-diff-on-failure
[pytest]
junit_family=legacy