Compare commits
35 Commits
Author | SHA1 | Date |
---|---|---|
Frédéric Péters | a3753c9bd2 | |
Daniel Muyshond | e257c5551d | |
Frédéric Péters | 563762da8a | |
Valentin Deniaud | fe520f541f | |
Valentin Deniaud | cc93edc608 | |
Valentin Deniaud | 8a0ccd8303 | |
Frédéric Péters | d78e9129ec | |
Frédéric Péters | b750ba777f | |
Frédéric Péters | fa85790460 | |
Frédéric Péters | 6cd2fc25a6 | |
Frédéric Péters | 6d92e2a30b | |
Frédéric Péters | 854b7b1d62 | |
Frédéric Péters | 45f99dade4 | |
Frédéric Péters | d18f51af20 | |
Frédéric Péters | 6a95789905 | |
Frédéric Péters | 3a43f1c597 | |
Frédéric Péters | 97bbe15e70 | |
Frédéric Péters | eef804ded9 | |
Frédéric Péters | 187c9b38b9 | |
Emmanuel Cazenave | a32dc95770 | |
Valentin Deniaud | 1408cc051e | |
Paul Marillonnet | 653ea603a7 | |
Frédéric Péters | dd1944e309 | |
Frédéric Péters | 90277e6eff | |
Agate | 317a950898 | |
Frédéric Péters | 4951c3b5fa | |
Frédéric Péters | cc60ef58ad | |
Frédéric Péters | 5e875dbd7c | |
Benjamin Dauvergne | 5e5ebf6961 | |
Valentin Deniaud | 32e1417b6d | |
Frédéric Péters | adea0363a9 | |
Benjamin Dauvergne | eb8ba992d0 | |
Benjamin Dauvergne | 4e60a12378 | |
Valentin Deniaud | 07eaca1b16 | |
Valentin Deniaud | badf4d6d68 |
|
@ -2,3 +2,5 @@
|
|||
7a234d5fe7ae6bee3ba1d0f688967e8e6cf209e3
|
||||
# trivial: apply isort & pyupgrade
|
||||
1abbbadd9469a3f2ff7eafb0ec6956c2b1c6763c
|
||||
# misc: apply double-quote-string-fixer (#79788)
|
||||
cc93edc60807663edcaa50f439364fbb6a449252
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: double-quote-string-fixer
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110']
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.7.0
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ['--profile', 'black', '--line-length', '110']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.20.0
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ['--keep-percent-format', '--py37-plus']
|
||||
|
|
|
@ -26,10 +26,18 @@ pipeline {
|
|||
stage('Packaging') {
|
||||
steps {
|
||||
script {
|
||||
if (env.JOB_NAME == 'authentic2-auth-fedict' && env.GIT_BRANCH == 'origin/main') {
|
||||
sh 'sudo -H -u eobuilder /usr/local/bin/eobuilder -d buster,bullseye authentic2-auth-fedict'
|
||||
env.SHORT_JOB_NAME=sh(
|
||||
returnStdout: true,
|
||||
// given JOB_NAME=gitea/project/PR-46, returns project
|
||||
// given JOB_NAME=project/main, returns project
|
||||
script: '''
|
||||
echo "${JOB_NAME}" | sed "s/gitea\\///" | awk -F/ '{print $1}'
|
||||
'''
|
||||
).trim()
|
||||
if (env.GIT_BRANCH == 'main' || env.GIT_BRANCH == 'origin/main') {
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d bullseye,bookworm ${SHORT_JOB_NAME}"
|
||||
} else if (env.GIT_BRANCH.startsWith('hotfix/')) {
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d buster,bullseye --branch ${env.GIT_BRANCH} --hotfix authentic2-auth-fedict"
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d bullseye,bookworm --branch ${env.GIT_BRANCH} --hotfix ${SHORT_JOB_NAME}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
README
5
README
|
@ -20,6 +20,11 @@ the django-mellon adapter:
|
|||
And appropriate django-mellon parameters (MELLON_PUBLIC_KEYS,
|
||||
MELLON_PRIVATE_KEY, MELLON_IDENTITY_PROVIDERS).
|
||||
|
||||
You also have to register the plugin:
|
||||
|
||||
INSTALLED_APPS += ('authentic2_auth_fedict')
|
||||
AUTHENTICATION_BACKENDS += ('authentic2_auth_fedict.backends.FedictBackend',)
|
||||
|
||||
|
||||
Code Style
|
||||
----------
|
||||
|
|
|
@ -12,6 +12,7 @@ DATABASES = {
|
|||
'ENGINE': 'django.db.backends.dummy',
|
||||
}
|
||||
}
|
||||
INSTALLED_APPS += ('authentic2_auth_fedict',)
|
||||
EOF
|
||||
TEMPFILE=`mktemp`
|
||||
trap "rm -f ${TEMPFILE} ${CHECK_MIGRATIONS_SETTINGS}" EXIT
|
||||
|
|
|
@ -10,4 +10,4 @@ else
|
|||
echo No pylint RC found
|
||||
exit 0
|
||||
fi
|
||||
pylint -f parseable --rcfile ${PYLINT_RC} "$@" > pylint.out || /bin/true
|
||||
pylint -f parseable --rcfile ${PYLINT_RC} "$@" | tee pylint.out; test $PIPESTATUS -eq 0
|
||||
|
|
7
setup.py
7
setup.py
|
@ -89,7 +89,7 @@ setup(
|
|||
description='Authentic2 Fedict plugin',
|
||||
author="Entr'ouvert",
|
||||
url='https://repos.entrouvert.org/authentic2-auth-fedict.git',
|
||||
author_email="info@entrouvert.com",
|
||||
author_email='info@entrouvert.com',
|
||||
packages=find_packages('src'),
|
||||
package_dir={
|
||||
'': 'src',
|
||||
|
@ -98,11 +98,6 @@ setup(
|
|||
install_requires=[
|
||||
'authentic2',
|
||||
],
|
||||
entry_points={
|
||||
'authentic2.plugin': [
|
||||
'authentic2-auth-fedict = authentic2_auth_fedict:Plugin',
|
||||
],
|
||||
},
|
||||
cmdclass={
|
||||
'build': build,
|
||||
'install_lib': install_lib,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# authentic2_auth_fedict - Fedict authentication for Authentic
|
||||
# Copyright (C) 2016 Entr'ouvert
|
||||
# Copyright (C) 2016-2022 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
|
@ -14,130 +14,4 @@
|
|||
# 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 json
|
||||
|
||||
import django.apps
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class AppConfig(django.apps.AppConfig):
|
||||
name = 'authentic2_auth_fedict'
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
||||
|
||||
user_logged_in.connect(signals.on_user_logged_in)
|
||||
|
||||
def a2_hook_event(self, name, **kwargs):
|
||||
if name == 'registration':
|
||||
if kwargs.get('authentication_method') == 'fedict':
|
||||
user = kwargs.get('user')
|
||||
user.backend = 'authentic2_auth_fedict.backends.FedictBackend'
|
||||
|
||||
|
||||
default_app_config = 'authentic2_auth_fedict.AppConfig'
|
||||
|
||||
|
||||
class Plugin:
|
||||
def get_before_urls(self):
|
||||
from . import urls
|
||||
|
||||
return urls.urlpatterns
|
||||
|
||||
def get_apps(self):
|
||||
return ['mellon', __name__]
|
||||
|
||||
def get_authentication_backends(self):
|
||||
return ['authentic2_auth_fedict.backends.FedictBackend']
|
||||
|
||||
def redirect_logout_list(self, request, next_url=None):
|
||||
from mellon.views import logout
|
||||
|
||||
if 'mellon_session' in request.session:
|
||||
response = logout(request)
|
||||
if 'Location' in response:
|
||||
return [response['Location']]
|
||||
return []
|
||||
|
||||
def registration_form_prefill(self, request):
|
||||
if request.token.get('first_name'):
|
||||
return [
|
||||
{
|
||||
'first_name': [request.token.get('first_name')],
|
||||
'last_name': [request.token.get('last_name')],
|
||||
}
|
||||
]
|
||||
else:
|
||||
return [{'first_name': [], 'last_name': []}]
|
||||
|
||||
def attribute_kinds(self):
|
||||
from . import fields
|
||||
|
||||
def attribute_json_loads(x):
|
||||
if not x:
|
||||
return x
|
||||
try:
|
||||
return json.loads(x)
|
||||
except json.JSONDecodeError:
|
||||
# "compatibility" with native date/phone kinds
|
||||
return x
|
||||
|
||||
return [
|
||||
{
|
||||
'label': _('National Register Number'),
|
||||
'name': 'nrn',
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'field_class': fields.NrnField,
|
||||
},
|
||||
{
|
||||
'label': _('Date'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'date',
|
||||
'field_class': fields.DateField,
|
||||
},
|
||||
{
|
||||
'label': _('Date'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'fedict_date',
|
||||
'field_class': fields.DateField,
|
||||
},
|
||||
{
|
||||
'label': _('Street'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'street',
|
||||
'field_class': fields.StreetField,
|
||||
},
|
||||
{
|
||||
'label': _('House number'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'num_house',
|
||||
'field_class': fields.NumHouseField,
|
||||
},
|
||||
{
|
||||
'label': _('Phone number'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'phone',
|
||||
'field_class': fields.NumPhoneField,
|
||||
},
|
||||
{
|
||||
'label': _('Phone number'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'fedict_phone',
|
||||
'field_class': fields.NumPhoneField,
|
||||
},
|
||||
{
|
||||
'label': _('Country'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'country',
|
||||
'field_class': fields.CountryField,
|
||||
},
|
||||
]
|
||||
default_app_config = '%s.apps.AppConfig' % __name__
|
||||
|
|
|
@ -15,21 +15,14 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
import lasso
|
||||
import mellon.utils as mellon_utils
|
||||
import requests
|
||||
from authentic2.a2_rbac.utils import get_default_ou
|
||||
from authentic2.models import Attribute
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files.storage import default_storage
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from mellon.adapters import DefaultAdapter, app_settings
|
||||
from mellon.adapters import DefaultAdapter
|
||||
|
||||
try:
|
||||
import authentic2.utils.misc as a2_utils_misc
|
||||
|
@ -55,34 +48,6 @@ class AuthenticAdapter(DefaultAdapter):
|
|||
def auth_login(self, request, user):
|
||||
a2_utils_misc.login(request, user, 'fedict')
|
||||
|
||||
def get_identity_providers_setting(self):
|
||||
providers = app_settings.IDENTITY_PROVIDERS
|
||||
cache_path = default_storage.path('fedict-cache')
|
||||
if not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
for idp in providers:
|
||||
if 'METADATA_URL' in idp and 'METADATA' not in idp:
|
||||
url_hash = hashlib.sha1(force_bytes(idp['METADATA_URL'])).hexdigest()
|
||||
metadata_cache_filename = os.path.join(cache_path, url_hash)
|
||||
if os.path.exists(metadata_cache_filename):
|
||||
stat_info = os.stat(metadata_cache_filename)
|
||||
if stat_info.st_size and stat_info.st_mtime > (time.time() - 86400):
|
||||
idp['METADATA'] = force_text(open(metadata_cache_filename).read())
|
||||
continue
|
||||
verify_ssl_certificate = mellon_utils.get_setting(idp, 'VERIFY_SSL_CERTIFICATE')
|
||||
try:
|
||||
response = requests.get(idp['METADATA_URL'], verify=verify_ssl_certificate)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
if os.path.exists(metadata_cache_filename):
|
||||
# accept older cache in case of error
|
||||
idp['METADATA'] = force_text(open(metadata_cache_filename).read())
|
||||
continue
|
||||
idp['METADATA'] = response.text
|
||||
with open(metadata_cache_filename, 'wb') as fd:
|
||||
fd.write(response.content)
|
||||
return providers
|
||||
|
||||
def lookup_user(self, idp, saml_attributes):
|
||||
if 'email' in saml_attributes:
|
||||
# XXX: remove email from received attributes for now, this
|
||||
|
@ -185,15 +150,21 @@ class AuthenticAdapter(DefaultAdapter):
|
|||
else:
|
||||
birthdate = ''
|
||||
try:
|
||||
Attribute.objects.get(name='birthdate').set_value(user, birthdate, verified=True)
|
||||
except AttributeError: # native authentic date field
|
||||
birthdate = datetime.datetime.strptime(birthdate, '%d/%m/%Y').date()
|
||||
Attribute.objects.get(name='birthdate').set_value(user, birthdate, verified=True)
|
||||
try:
|
||||
Attribute.objects.get(name='birthdate').set_value(user, birthdate, verified=True)
|
||||
except AttributeError: # native authentic date field
|
||||
birthdate = datetime.datetime.strptime(birthdate, '%d/%m/%Y').date()
|
||||
Attribute.objects.get(name='birthdate').set_value(user, birthdate, verified=True)
|
||||
except Attribute.DoesNotExist:
|
||||
pass
|
||||
if int(nrn[6:9]) % 2:
|
||||
title = 'Monsieur'
|
||||
else:
|
||||
title = 'Madame'
|
||||
Attribute.objects.get(name='title').set_value(user, title, verified=True)
|
||||
try:
|
||||
Attribute.objects.get(name='title').set_value(user, title, verified=True)
|
||||
except Attribute.DoesNotExist:
|
||||
pass
|
||||
|
||||
if saml_attributes.get('givenName'):
|
||||
Attribute.objects.get(name='first_name').set_value(
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
# authentic2_auth_fedict - Fedict authentication for Authentic
|
||||
# Copyright (C) 2016-2022 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 json
|
||||
|
||||
from authentic2_auth_saml.apps import AppConfig as SamlAppConfig
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class AppConfig(SamlAppConfig):
|
||||
name = 'authentic2_auth_fedict'
|
||||
|
||||
def ready(self):
|
||||
super().ready()
|
||||
|
||||
from . import signals
|
||||
|
||||
user_logged_in.connect(signals.on_user_logged_in)
|
||||
|
||||
def a2_hook_event(self, name, **kwargs):
|
||||
if name == 'registration':
|
||||
if kwargs.get('authentication_method') == 'fedict':
|
||||
user = kwargs.get('user')
|
||||
user.backend = 'authentic2_auth_fedict.backends.FedictBackend'
|
||||
|
||||
def get_a2_plugin(self):
|
||||
return Plugin()
|
||||
|
||||
|
||||
class Plugin:
|
||||
def get_before_urls(self):
|
||||
from . import urls
|
||||
|
||||
return urls.urlpatterns
|
||||
|
||||
def registration_form_prefill(self, request):
|
||||
if request.token.get('first_name'):
|
||||
return [
|
||||
{
|
||||
'first_name': [request.token.get('first_name')],
|
||||
'last_name': [request.token.get('last_name')],
|
||||
}
|
||||
]
|
||||
else:
|
||||
return [{'first_name': [], 'last_name': []}]
|
||||
|
||||
def attribute_kinds(self):
|
||||
from . import fields
|
||||
|
||||
def attribute_json_loads(x):
|
||||
if not x:
|
||||
return x
|
||||
try:
|
||||
return json.loads(x)
|
||||
except json.JSONDecodeError:
|
||||
# "compatibility" with native date/phone kinds
|
||||
return x
|
||||
|
||||
return [
|
||||
{
|
||||
'label': _('National Register Number'),
|
||||
'name': 'nrn',
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'field_class': fields.NrnField,
|
||||
},
|
||||
{
|
||||
'label': _('Date'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'date',
|
||||
'field_class': fields.DateField,
|
||||
},
|
||||
{
|
||||
'label': _('Date'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'fedict_date',
|
||||
'field_class': fields.DateField,
|
||||
},
|
||||
{
|
||||
'label': _('Street'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'street',
|
||||
'field_class': fields.StreetField,
|
||||
},
|
||||
{
|
||||
'label': _('House number'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'num_house',
|
||||
'field_class': fields.NumHouseField,
|
||||
},
|
||||
{
|
||||
'label': _('Phone number'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'phone',
|
||||
'field_class': fields.NumPhoneField,
|
||||
},
|
||||
{
|
||||
'label': _('Phone number'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'fedict_phone',
|
||||
'field_class': fields.NumPhoneField,
|
||||
},
|
||||
{
|
||||
'label': _('Country'),
|
||||
'serialize': json.dumps,
|
||||
'deserialize': attribute_json_loads,
|
||||
'name': 'country',
|
||||
'field_class': fields.CountryField,
|
||||
},
|
||||
]
|
|
@ -88,6 +88,6 @@ class FedictBackend(SAMLBackend):
|
|||
value = getattr(old_user.verified_attributes, attribute.name, None)
|
||||
if value:
|
||||
setattr(user.verified_attributes, attribute.name, value)
|
||||
logger.debug('deleting user %s, new fedict link manually created' % old_user)
|
||||
logger.debug('deleting user %s, new fedict link manually created', old_user)
|
||||
old_user.delete()
|
||||
return user
|
||||
|
|
|
@ -118,7 +118,9 @@ class CountryWidget(forms.Select):
|
|||
passerelle_url = list(settings.KNOWN_SERVICES['passerelle'].values())[0]['url']
|
||||
country_url = urljoin(passerelle_url, '/csvdatasource/pays/data')
|
||||
try:
|
||||
self.choices = [(x['id'], x['text']) for x in requests.get(country_url).json()['data']]
|
||||
self.choices = [
|
||||
(x['id'], x['text']) for x in requests.get(country_url, timeout=30).json()['data']
|
||||
]
|
||||
except ValueError:
|
||||
self.choices = []
|
||||
super(forms.Select, self).__init__(attrs=attrs, choices=self.choices)
|
||||
|
@ -135,7 +137,7 @@ class NumHouseField(forms.CharField):
|
|||
if not value:
|
||||
return
|
||||
try:
|
||||
if not re.match("^[1-9][0-9]*$", value):
|
||||
if not re.match('^[1-9][0-9]*$', value):
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise forms.ValidationError(getattr(settings, 'A2_NUMHOUSE_ERROR_MESSAGE', _('Invalid format')))
|
||||
|
@ -147,7 +149,7 @@ class NumPhoneField(forms.CharField):
|
|||
if not value:
|
||||
return
|
||||
try:
|
||||
if not re.match("^(0|\\+|00)(\\d{8,})", value):
|
||||
if not re.match('^(0|\\+|00)(\\d{8,})', value):
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise forms.ValidationError(getattr(settings, 'A2_NUMPHONE_ERROR_MESSAGE', _('Invalid format')))
|
||||
|
|
|
@ -14,13 +14,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 authentic2.apps.authenticators.forms import AuthenticatorFormMixin
|
||||
from django import forms
|
||||
|
||||
from .models import FedictAuthenticator
|
||||
|
||||
|
||||
class FedictAuthenticatorForm(AuthenticatorFormMixin, forms.ModelForm):
|
||||
class FedictAuthenticatorForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = FedictAuthenticator
|
||||
exclude = ('name', 'slug', 'ou')
|
||||
exclude = ('name', 'slug', 'ou') # pylint: disable=modelform-uses-exclude
|
||||
|
|
|
@ -15,16 +15,12 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from authentic2.apps.authenticators.models import BaseAuthenticator
|
||||
from authentic2.utils.misc import redirect_to_login
|
||||
from django.shortcuts import render
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mellon.utils import get_idp, get_idps
|
||||
|
||||
try:
|
||||
from authentic2.utils import redirect_to_login
|
||||
except ImportError:
|
||||
from authentic2.utils.misc import redirect_to_login
|
||||
|
||||
|
||||
class FedictAuthenticator(BaseAuthenticator):
|
||||
type = 'fedict'
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 35 KiB |
Binary file not shown.
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 27 KiB |
|
@ -1,4 +1,4 @@
|
|||
{% load i18n static %}<form method="post" id="csam-login">
|
||||
{% load i18n static %}<div class="cell--body"><form method="post" id="csam-login">
|
||||
<div>
|
||||
{% if has_itsme %}
|
||||
<img src="{% static "authentic2_auth_fedict/img/beid-itsme.png" %}" alt="">
|
||||
|
@ -26,3 +26,4 @@ $(function() {
|
|||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ from django.conf.urls import include, url
|
|||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^accounts/saml/', include('mellon.urls')),
|
||||
url(r'^accounts/saml/', include('mellon.urls'), kwargs={'logout_next_url': '/logout/'}),
|
||||
url(
|
||||
r'^accounts/fedict/login/$',
|
||||
views.login,
|
||||
|
|
|
@ -19,14 +19,11 @@ import urllib.parse
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core import signing
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect, resolve_url
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import View
|
||||
|
||||
try:
|
||||
import authentic2.utils.misc as a2_utils_misc
|
||||
|
|
|
@ -7,7 +7,6 @@ except ImportError:
|
|||
import pathlib2 as pathlib
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django_rbac.utils import get_ou_model
|
||||
|
||||
User = get_user_model()
|
||||
TEST_DIR = pathlib.Path(__file__).parent
|
||||
|
@ -19,7 +18,7 @@ def app(request, db, settings, tmpdir):
|
|||
wtm._patch_settings()
|
||||
request.addfinalizer(wtm._unpatch_settings)
|
||||
settings.MEDIA_DIR = str(tmpdir.mkdir('media'))
|
||||
return django_webtest.DjangoTestApp(extra_environ={'HTTP_HOST': 'localhost'})
|
||||
return django_webtest.DjangoTestApp(extra_environ={'HTTP_HOST': 'localhost', 'wsgi.url_scheme': 'https'})
|
||||
|
||||
|
||||
class AllHook:
|
||||
|
|
|
@ -19,22 +19,25 @@ if 'postgres' in DATABASES['default']['ENGINE']:
|
|||
LANGUAGE_CODE = 'en'
|
||||
A2_AUTH_SAML_ENABLE = False
|
||||
|
||||
MELLON_ADAPTER = ["authentic2_auth_fedict.adapters.AuthenticAdapter"]
|
||||
MELLON_LOGIN_URL = "fedict-login"
|
||||
MELLON_PUBLIC_KEYS = ["./tests/saml.crt"]
|
||||
MELLON_PRIVATE_KEY = "./tests/saml.key"
|
||||
MELLON_ADAPTER = ['authentic2_auth_fedict.adapters.AuthenticAdapter']
|
||||
MELLON_LOGIN_URL = 'fedict-login'
|
||||
MELLON_PUBLIC_KEYS = ['./tests/saml.crt']
|
||||
MELLON_PRIVATE_KEY = './tests/saml.key'
|
||||
MELLON_IDENTITY_PROVIDERS = [
|
||||
{
|
||||
"METADATA": open("./tests/metadata.xml").read(),
|
||||
"ENTITY_ID": "https://idp.com/",
|
||||
"SLUG": "idp",
|
||||
'METADATA': open('./tests/metadata.xml').read(),
|
||||
'ENTITY_ID': 'https://idp.com/',
|
||||
'SLUG': 'idp',
|
||||
},
|
||||
]
|
||||
|
||||
MELLON_ATTRIBUTE_MAPPING = {
|
||||
"last_name": "{attributes[surname][0]}",
|
||||
"first_name": "{attri,butes[givenName][0]}",
|
||||
'last_name': '{attributes[surname][0]}',
|
||||
'first_name': '{attri,butes[givenName][0]}',
|
||||
}
|
||||
|
||||
INSTALLED_APPS += ('authentic2_auth_fedict',)
|
||||
AUTHENTICATION_BACKENDS += ('authentic2_auth_fedict.backends.FedictBackend',)
|
||||
|
||||
# test hook handlers
|
||||
A2_HOOKS_PROPAGATE_EXCEPTIONS = True
|
||||
|
|
|
@ -194,11 +194,11 @@ def test_eid_unlink(app, settings, issuer, user, authenticator):
|
|||
)
|
||||
|
||||
response = login(app, user, path='/accounts/', password=user.username)
|
||||
assert "Unlink my account" in response.text
|
||||
assert 'Unlink my account' in response.text
|
||||
app.get('/accounts/fedict/unlink/').follow()
|
||||
|
||||
response = app.get('/accounts/')
|
||||
assert "Link my account to my eID card" in response.text
|
||||
assert 'Link my account to my eID card' in response.text
|
||||
|
||||
|
||||
def test_provision_new_attributes_verified(app, settings, issuer, user):
|
||||
|
@ -250,6 +250,50 @@ def test_provision_new_attributes_verified(app, settings, issuer, user):
|
|||
assert backend_user.last_name == 'Bar'
|
||||
|
||||
|
||||
def test_missing_title_attribute(app, settings, issuer, user):
|
||||
Attribute.objects.filter(kind='title').delete()
|
||||
# email & title verified
|
||||
user.email = 'john.doe@verified.publik.love'
|
||||
user.email_verified = True
|
||||
user.first_name = 'Johnny'
|
||||
user.last_name = 'Smith'
|
||||
user.save()
|
||||
UserSAMLIdentifier.objects.create(
|
||||
user=user,
|
||||
name_id='c54db0a8ddc24a02a2d057f857d3b102',
|
||||
issuer=Issuer.objects.first(),
|
||||
)
|
||||
|
||||
backend = FedictBackend()
|
||||
request = factory.get(path='/accounts/')
|
||||
request_user = User.objects.create(
|
||||
first_name='Foo',
|
||||
last_name='Bar',
|
||||
email='foo.bar@nowhere.null',
|
||||
)
|
||||
request.user = request_user
|
||||
saml_attributes = {
|
||||
'givenName': ['Doe'],
|
||||
'surname': ['John'],
|
||||
'last_name': ['Doe'],
|
||||
'first_name': ['John'],
|
||||
'username': ['john.doe'],
|
||||
'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
|
||||
'egovNRN': ['85073003328'],
|
||||
'is_superuser': ['false'],
|
||||
'issuer': 'https://idp.com/',
|
||||
'name_id_content': 'c54db0a8ddc24a02a2d057f857d3b102',
|
||||
'name_id_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
|
||||
'name_id_name_qualifier': 'https://idp.com/idp/saml2/metadata',
|
||||
'name_id_content_orig': 'c54db0a8ddc24a02a2d057f857d3b102',
|
||||
}
|
||||
credentials = {'saml_attributes': saml_attributes}
|
||||
SessionMiddleware().process_request(request)
|
||||
MessageMiddleware().process_request(request)
|
||||
backend_user = backend.authenticate(request, **credentials)
|
||||
assert backend_user == request_user
|
||||
|
||||
|
||||
def test_provision_old_account_deleted(app, settings, issuer, user):
|
||||
backend = FedictBackend()
|
||||
request = factory.get(path='/accounts/')
|
||||
|
@ -303,7 +347,7 @@ def test_fedict_authenticator_data_migration(settings):
|
|||
FedictAuthenticator = old_apps.get_model(app, 'FedictAuthenticator')
|
||||
|
||||
settings.AUTH_FRONTENDS_KWARGS = {
|
||||
"fedict": {"priority": 3, "show_condition": "'backoffice' not in login_hint"}
|
||||
'fedict': {'priority': 3, 'show_condition': "'backoffice' not in login_hint"}
|
||||
}
|
||||
settings.A2_AUTH_FEDICT_ENABLE = True
|
||||
|
||||
|
@ -320,35 +364,6 @@ def test_fedict_authenticator_data_migration(settings):
|
|||
assert authenticator.enabled is True
|
||||
|
||||
|
||||
def test_cleanup_saml_authenticator_data_migration():
|
||||
app = 'authentic2_auth_fedict'
|
||||
migrate_from = [(app, '0002_auto_20220706_1712'), ('authentic2_auth_saml', '0002_auto_20220608_1559')]
|
||||
migrate_to = [(app, '0003_auto_20220721_1724'), ('authentic2_auth_saml', '0002_auto_20220608_1559')]
|
||||
|
||||
executor = MigrationExecutor(connection)
|
||||
old_apps = executor.loader.project_state(migrate_from).apps
|
||||
executor.migrate(migrate_from)
|
||||
SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
|
||||
|
||||
SAMLAuthenticator.objects.all().delete()
|
||||
SAMLAuthenticator.objects.create(slug='0', enabled=False)
|
||||
SAMLAuthenticator.objects.create(
|
||||
slug='1', enabled=True, metadata_url='https://iamapps-public.belgium.be/saml/fas-metadata.xml'
|
||||
)
|
||||
SAMLAuthenticator.objects.create(
|
||||
slug='2', enabled=False, metadata_url='https://iamapps-public.belgium.be/saml/fas-metadata.xml'
|
||||
) # only this one should get deleted
|
||||
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.migrate(migrate_to)
|
||||
executor.loader.build_graph()
|
||||
new_apps = executor.loader.project_state(migrate_to).apps
|
||||
SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
|
||||
|
||||
assert SAMLAuthenticator.objects.count() == 2
|
||||
assert not SAMLAuthenticator.objects.filter(slug='2').exists()
|
||||
|
||||
|
||||
def test_manager(app, admin):
|
||||
resp = login(app, admin, path='/manage/authenticators/', index=0)
|
||||
|
||||
|
|
26
tox.ini
26
tox.ini
|
@ -6,13 +6,13 @@
|
|||
[tox]
|
||||
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/authentic2-auth-fedict/{env:BRANCH_NAME:}
|
||||
envlist =
|
||||
py3-dj22
|
||||
py3-dj32
|
||||
|
||||
[tox:jenkins]
|
||||
envlist =
|
||||
pylint
|
||||
code-style
|
||||
py3-dj22
|
||||
py3-dj32
|
||||
|
||||
|
||||
[testenv]
|
||||
|
@ -31,14 +31,14 @@ passenv=
|
|||
PGPASSWORD
|
||||
usedevelop = true
|
||||
deps =
|
||||
!local: https://git.entrouvert.org/authentic.git/snapshot/authentic-main.tar.gz
|
||||
!local: https://git.entrouvert.org/entrouvert/authentic/archive/main.tar.gz
|
||||
local: ../authentic2
|
||||
# dependency constraints for authentic
|
||||
py3: file-magic
|
||||
djangorestframework>=3.12,<3.13
|
||||
dj22: django<2.3
|
||||
django-tables<2.0
|
||||
psycopg2-binary<2.9
|
||||
dj32: django>=3.2.12,<3.3
|
||||
dj32: django-tables2==2.4.1
|
||||
psycopg2-binary
|
||||
oldldap: python-ldap<3
|
||||
ldaptools
|
||||
|
||||
|
@ -50,7 +50,6 @@ deps =
|
|||
pytest-random
|
||||
django-webtest
|
||||
pyquery
|
||||
astroid!=2.5.7
|
||||
|
||||
commands =
|
||||
py3: ./getlasso3.sh
|
||||
|
@ -61,12 +60,15 @@ commands =
|
|||
usedevelop = true
|
||||
basepython = python3
|
||||
deps =
|
||||
https://git.entrouvert.org/authentic.git/snapshot/authentic-main.tar.gz
|
||||
Django<2.3
|
||||
pylint<1.8
|
||||
pylint-django<0.8.1
|
||||
https://git.entrouvert.org/entrouvert/authentic/archive/main.tar.gz
|
||||
Django<3.3
|
||||
psycopg2-binary
|
||||
pylint<3
|
||||
astroid<3
|
||||
pylint-django
|
||||
commands =
|
||||
/bin/bash -c "./pylint.sh src/*/"
|
||||
./getlasso3.sh
|
||||
./pylint.sh src/authentic2_auth_fedict/
|
||||
|
||||
[testenv:code-style]
|
||||
skip_install = true
|
||||
|
|
Loading…
Reference in New Issue