profile: add csam account-(un)linking capabilities (#60837)
This commit is contained in:
parent
1114ee4c12
commit
f8566f4619
|
@ -49,8 +49,6 @@ class FedictAuthenticator(BaseAuthenticator):
|
|||
def profile(self, request, *args, **kwargs):
|
||||
context = kwargs.get('context', {}).copy()
|
||||
user_saml_identifiers = request.user.saml_identifiers.all()
|
||||
if not user_saml_identifiers:
|
||||
return ''
|
||||
for user_saml_identifier in user_saml_identifiers:
|
||||
user_saml_identifier.idp = get_idp(user_saml_identifier.issuer)
|
||||
context['user_saml_identifiers'] = user_saml_identifiers
|
||||
|
|
|
@ -14,8 +14,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/>.
|
||||
|
||||
import logging
|
||||
|
||||
import lasso
|
||||
from authentic2.models import Attribute
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mellon.backends import SAMLBackend
|
||||
from mellon.utils import get_adapters, get_idp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FedictBackend(SAMLBackend):
|
||||
|
@ -28,3 +36,58 @@ class FedictBackend(SAMLBackend):
|
|||
# but we do not expose this detail to the service provider as all it
|
||||
# needs to know is "strong authentication".
|
||||
return lasso.SAML2_AUTHN_CONTEXT_SMARTCARD_PKI
|
||||
|
||||
def authenticate(self, request=None, **credentials):
|
||||
saml_attributes = credentials.get('saml_attributes') or {}
|
||||
if 'issuer' not in saml_attributes:
|
||||
logger.debug('no idp in saml_attributes')
|
||||
return None
|
||||
idp = get_idp(saml_attributes['issuer'])
|
||||
if not idp:
|
||||
logger.debug('unknown idp %s', saml_attributes['issuer'])
|
||||
return None
|
||||
adapters = get_adapters(idp, request=request)
|
||||
for adapter in adapters:
|
||||
if not hasattr(adapter, 'authorize'):
|
||||
continue
|
||||
if not adapter.authorize(idp, saml_attributes):
|
||||
return
|
||||
for adapter in adapters:
|
||||
if hasattr(adapter, 'lookup_by_attributes'):
|
||||
user = adapter.lookup_by_attributes(idp, saml_attributes)
|
||||
old_user = None
|
||||
for adapter in adapters:
|
||||
if not hasattr(adapter, 'lookup_user'):
|
||||
continue
|
||||
user = adapter.lookup_user(idp, saml_attributes)
|
||||
if user:
|
||||
request_user = getattr(request, 'user', None) if request else None
|
||||
if request_user != user and request_user.is_authenticated:
|
||||
old_user = user
|
||||
user = request_user
|
||||
saml_identifier = old_user.saml_identifier
|
||||
saml_identifier.user = user
|
||||
saml_identifier.save(update_fields=['user'])
|
||||
user.saml_identifier = saml_identifier
|
||||
messages.warning(request, _('Your account is now linked to your eID card.'))
|
||||
break
|
||||
else: # no user found
|
||||
return
|
||||
for adapter in adapters:
|
||||
if not hasattr(adapter, 'provision'):
|
||||
continue
|
||||
# we attempt to provision the old user and then only copy the relevant attributes
|
||||
adapter.provision(old_user or user, idp, saml_attributes)
|
||||
# final identifier and attributes copy before deletion of old account
|
||||
if old_user and old_user != user:
|
||||
# copy all verified attributes to newly provisionned user
|
||||
if old_user.email_verified:
|
||||
user.email = old_user.email
|
||||
user.email_verified = True
|
||||
for attribute in Attribute.objects.all():
|
||||
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)
|
||||
old_user.delete()
|
||||
return user
|
||||
|
|
|
@ -1,3 +1,24 @@
|
|||
<p>
|
||||
Ce compte est relié à votre carte d'identité électronique.
|
||||
</p>
|
||||
{% load i18n static %}
|
||||
<div>
|
||||
<img src="{% static "authentic2_auth_fedict/img/beid_image_mini.png" %}" alt="">
|
||||
{% if user_saml_identifiers %}
|
||||
<p>{% blocktrans %}This account is linked to your eID card.{% endblocktrans %}</p>
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
You may want to unlink your account, although you will not be able to
|
||||
connect to your account using your eID card (without re-doing the linking
|
||||
procedure).
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p><a class="button" href="{% url "fedict-unlink" %}">{% trans "Unlink my account" %}</a></p>
|
||||
{% else %}
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
You may want to link your existing Publik account to your eID. In order to
|
||||
do so, you will either need your eID card and its card reader
|
||||
or your list of personal tokens.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p><a class="button" href="{% url "fedict-login" %}?next=/accounts/">{% trans "Link my account to my eID card" %}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -21,4 +21,5 @@ from . import views
|
|||
urlpatterns = [
|
||||
url(r'^accounts/saml/', include('mellon.urls')),
|
||||
url(r'^accounts/fedict/login/$', views.login, name='fedict-login'),
|
||||
url(r'^accounts/fedict/unlink/$', views.unlink, name='fedict-unlink'),
|
||||
]
|
||||
|
|
|
@ -18,11 +18,13 @@ import random
|
|||
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 resolve_url
|
||||
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
|
||||
|
||||
|
@ -69,3 +71,15 @@ class LoginView(mellon.views.LoginView):
|
|||
|
||||
|
||||
login = transaction.non_atomic_requests(csrf_exempt(LoginView.as_view()))
|
||||
|
||||
|
||||
def unlink(request):
|
||||
if not hasattr(request, 'user') or not hasattr(request.user, 'saml_identifiers'):
|
||||
return
|
||||
unlink_performed = False
|
||||
for saml_identifier in request.user.saml_identifiers.all():
|
||||
saml_identifier.delete()
|
||||
unlink_performed = True
|
||||
if unlink_performed:
|
||||
messages.success(request, message=_('Unlinking complete.'))
|
||||
return redirect('account_management')
|
||||
|
|
|
@ -50,6 +50,7 @@ def user(db):
|
|||
email_verified=True,
|
||||
)
|
||||
user.set_password('john.doe')
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="http://www.w3.org/2000/09/xmldsig#" entityID="https://idp.com/idp/saml2/metadata"><ns0:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:KeyDescriptor><ns1:KeyInfo><ns1:X509Data><ns1:X509Certificate>MIIDNTCCAh2gAwIBAgIUToSxnhmCYk3jdVKvMxPB8S4HvBcwDQYJKoZIhvcNAQEL
|
||||
BQAwKjEoMCYGA1UEAwwfc3BhcmUtYXV0aGVudGljLmRldi5wdWJsaWsubG92ZTAe
|
||||
Fw0yMjAxMTcxNzQ2NThaFw0zMjAxMTcxNzQ2NThaMCoxKDAmBgNVBAMMH3NwYXJl
|
||||
LWF1dGhlbnRpYy5kZXYucHVibGlrLmxvdmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDlVPm7D3abG4fTh/i9VJz4s9Lplblw4OKZwJfdFj2CrZX4kehh
|
||||
zDEAcn8cE14gj5bzGCWcoeApw4KIFeoSzxZGpGn7dyiFTTny2IJugEuobWcT0IuC
|
||||
P3TcPzWrm0HXnIcZjBv6Rr/cFFa2vAKL1CjT4xgZidRjGKpQBNoT2aOnFuvTzlrz
|
||||
ZsGJ38+yRFZDQzoGS+5h4F3ZCHx6Y5fLNzWDW3Cu6/vv2/ev35omcHW+mSTVpds5
|
||||
Oo2uzIvumYAM83oLzgWRnPLnx14lH+kWoDNaXEpUWDrmLD1A9OgNYmkeT/85qdyN
|
||||
jzDbe1yG421e2jUaRXez412Nv1s1ua5U/dBlAgMBAAGjUzBRMB0GA1UdDgQWBBRQ
|
||||
Y1+34SVZXriQAFteCRbde5jSOTAfBgNVHSMEGDAWgBRQY1+34SVZXriQAFteCRbd
|
||||
e5jSOTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCwVWlzEE7w
|
||||
0wBmngfctrz6cSyc5oAqDGo6dzVw+7CYhjRGr0Z9C1zI5rYOPWdDceAqD/lOVapw
|
||||
1nawFszVpcClmY8Rt0Tiot8z18NRz0NCFZzbHx9cU5TGZUigOkuXXqB+D52ppuQB
|
||||
6t/RREpeZakiBEp0Y8pH9CalA2DCNf/2WGcvaMXQtAL7Ko0a8vPBoOMykwgaFvCj
|
||||
E+ZiZXHLLg4jw2QwX1s4Fap8vKEUn062qCx0TdgILX/PrUpmCkPvZRlA/fcQLFQw
|
||||
1vX1rInD3cpTrxNw2T9ywGYmbXG1DmpxoF5X4ju3fcTR968QVJlX3L5JMbbWNV9X
|
||||
iB4feKp2tzW1</ns1:X509Certificate></ns1:X509Data></ns1:KeyInfo></ns0:KeyDescriptor><ns0:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://idp.com/idp/saml2/artifact" index="0"/><ns0:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.com/idp/saml2/slo" ResponseLocation="https://idp.com/idp/saml2/slo_return"/><ns0:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://idp.com/idp/saml2/slo" ResponseLocation="https://idp.com/idp/saml2/slo_return"/><ns0:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://idp.com/idp/saml2/slo/soap"/><ns0:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.com/idp/saml2/sso"/><ns0:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://idp.com/idp/saml2/sso"/></ns0:IDPSSODescriptor></ns0:EntityDescriptor>
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNTCCAh2gAwIBAgIUToSxnhmCYk3jdVKvMxPB8S4HvBcwDQYJKoZIhvcNAQEL
|
||||
BQAwKjEoMCYGA1UEAwwfc3BhcmUtYXV0aGVudGljLmRldi5wdWJsaWsubG92ZTAe
|
||||
Fw0yMjAxMTcxNzQ2NThaFw0zMjAxMTcxNzQ2NThaMCoxKDAmBgNVBAMMH3NwYXJl
|
||||
LWF1dGhlbnRpYy5kZXYucHVibGlrLmxvdmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDlVPm7D3abG4fTh/i9VJz4s9Lplblw4OKZwJfdFj2CrZX4kehh
|
||||
zDEAcn8cE14gj5bzGCWcoeApw4KIFeoSzxZGpGn7dyiFTTny2IJugEuobWcT0IuC
|
||||
P3TcPzWrm0HXnIcZjBv6Rr/cFFa2vAKL1CjT4xgZidRjGKpQBNoT2aOnFuvTzlrz
|
||||
ZsGJ38+yRFZDQzoGS+5h4F3ZCHx6Y5fLNzWDW3Cu6/vv2/ev35omcHW+mSTVpds5
|
||||
Oo2uzIvumYAM83oLzgWRnPLnx14lH+kWoDNaXEpUWDrmLD1A9OgNYmkeT/85qdyN
|
||||
jzDbe1yG421e2jUaRXez412Nv1s1ua5U/dBlAgMBAAGjUzBRMB0GA1UdDgQWBBRQ
|
||||
Y1+34SVZXriQAFteCRbde5jSOTAfBgNVHSMEGDAWgBRQY1+34SVZXriQAFteCRbd
|
||||
e5jSOTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCwVWlzEE7w
|
||||
0wBmngfctrz6cSyc5oAqDGo6dzVw+7CYhjRGr0Z9C1zI5rYOPWdDceAqD/lOVapw
|
||||
1nawFszVpcClmY8Rt0Tiot8z18NRz0NCFZzbHx9cU5TGZUigOkuXXqB+D52ppuQB
|
||||
6t/RREpeZakiBEp0Y8pH9CalA2DCNf/2WGcvaMXQtAL7Ko0a8vPBoOMykwgaFvCj
|
||||
E+ZiZXHLLg4jw2QwX1s4Fap8vKEUn062qCx0TdgILX/PrUpmCkPvZRlA/fcQLFQw
|
||||
1vX1rInD3cpTrxNw2T9ywGYmbXG1DmpxoF5X4ju3fcTR968QVJlX3L5JMbbWNV9X
|
||||
iB4feKp2tzW1
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDlVPm7D3abG4fT
|
||||
h/i9VJz4s9Lplblw4OKZwJfdFj2CrZX4kehhzDEAcn8cE14gj5bzGCWcoeApw4KI
|
||||
FeoSzxZGpGn7dyiFTTny2IJugEuobWcT0IuCP3TcPzWrm0HXnIcZjBv6Rr/cFFa2
|
||||
vAKL1CjT4xgZidRjGKpQBNoT2aOnFuvTzlrzZsGJ38+yRFZDQzoGS+5h4F3ZCHx6
|
||||
Y5fLNzWDW3Cu6/vv2/ev35omcHW+mSTVpds5Oo2uzIvumYAM83oLzgWRnPLnx14l
|
||||
H+kWoDNaXEpUWDrmLD1A9OgNYmkeT/85qdyNjzDbe1yG421e2jUaRXez412Nv1s1
|
||||
ua5U/dBlAgMBAAECggEAFb2xkydLIjdA8C/Sx/gujXFWzGgyVV4sfVEB1KbYG/xi
|
||||
3FbQxfy6pIU2Qa4gXUvfjpo6bpf2DV+Ij2gsca4KOZY6qelJASIqHTijXOByy7vb
|
||||
ash5gVaMuJiRePxWCJ/BOw3KVTbB15ZiBh7ayvDJEhVUYo9rgB2Ff+KF3h3i4uMe
|
||||
cF6Z9aA0m9tYmEx88zk8kjxZMH7BhWX3C6H5FcGCy5ARhRQ4EoljDuIWD5+kgpMg
|
||||
0hnr2B+pP2AfA1p+Xowf56u17NyEK3QBfdrQp9GXfJaiiSVCqWR3foOqU59LiIjY
|
||||
TMp/7DgQqTPmLKifLPcZQcPtjU6dvBCl2NHZuPEBgQKBgQD5bi77KT3+UwePFPwy
|
||||
6lXoPAQryWpsQl8JpU3iFLZLx8uMUjtngDvQdU6D7XL5lB0b0gEOlIha2oedQT7F
|
||||
rhTm2Qiqj5pnxc8QU4xskSqudzF4WazmXdc2ajssiPWPpZtJj5FZPlw0WF/siFCR
|
||||
2gGCUcXwKe9vNv1Zn9N1KVNIBQKBgQDrX0aGY25YqIc6E9z/Y2KYJaH6rjf46p2Q
|
||||
RL8m6UiQheuRP2ePa1JLZQrDqXKp2ibZs+IyC1r+6ST7uCEDQjZcpxr+cLTp/ELY
|
||||
GtL17FXHjENfZm0JreeijJfUBZLqX0R6GTsi0kT0y8SQZiClpCZw51ovhIHLXrHG
|
||||
hWOVpAi04QKBgEshIQ2N0pp0L+atD3nWk6Gr0iXOOTv6kd256MecLXyN5YWSj0oR
|
||||
mfKkIs4iC2uZbVsf2immG5wiDo8TQ/EPCkSuQqn9Lyjqr//e6oEZCJ4cUM5LVITe
|
||||
5yAAx2oWpsBpxWhW0hTrb6JkrB/2vy3vWF0EfHZmazQ4f/8q4Op9VBRxAoGAKOKL
|
||||
5Zwv9saPds8sfFBPOA6RbHIG1v4qEH1glum+6RvaJ4jT/F2wFdifXg15FXgHd5l/
|
||||
mSHP1Ke6/N6nHWHK/50nWztIsbxYACHosz8yR09eBJxOJHhI3Dt/xByTwJJ72pm3
|
||||
Y/0SbVNX+Z1D3oH9C2+kgsyJn0H7r3hMLBoqSQECgYBTMbQ13v2B9U7I1UFryhYR
|
||||
ArfaO2VLseLWmEhTO/eSg8LJu9lDNRoagLl41qSRLNabxQzL/1bZTQb0i0S9wCL5
|
||||
tyx2YvLjN7wI0Pkd5WuZeT69ERroAFwASOINmzx96m83TxSe39c+OnxQbX4ujyQI
|
||||
iH1921Y1zSQbKsM7CeeFnw==
|
||||
-----END PRIVATE KEY-----
|
|
@ -1,5 +1,7 @@
|
|||
import os
|
||||
|
||||
from authentic2.settings import MIDDLEWARE
|
||||
|
||||
ALLOWED_HOSTS = ['localhost']
|
||||
|
||||
DATABASES = {
|
||||
|
@ -15,8 +17,25 @@ if 'postgres' in DATABASES['default']['ENGINE']:
|
|||
DATABASES['default'][key[2:]] = os.environ[key]
|
||||
|
||||
LANGUAGE_CODE = 'en'
|
||||
A2_FC_CLIENT_ID = ''
|
||||
A2_FC_CLIENT_SECRET = ''
|
||||
A2_AUTH_SAML_ENABLE = False
|
||||
A2_AUTH_FEDICT_ENABLE = True
|
||||
|
||||
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",
|
||||
},
|
||||
]
|
||||
|
||||
MELLON_ATTRIBUTE_MAPPING = {
|
||||
"last_name": "{attributes[surname][0]}",
|
||||
"first_name": "{attri,butes[givenName][0]}",
|
||||
}
|
||||
|
||||
# test hook handlers
|
||||
A2_HOOKS_PROPAGATE_EXCEPTIONS = True
|
||||
|
|
|
@ -1,11 +1,284 @@
|
|||
import datetime
|
||||
|
||||
import lasso
|
||||
import pytest
|
||||
from authentic2.a2_rbac.utils import get_default_ou
|
||||
from authentic2.models import Attribute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.messages.middleware import MessageMiddleware
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test.client import RequestFactory
|
||||
from mellon.models import Issuer, UserSAMLIdentifier
|
||||
from utils import login
|
||||
|
||||
from authentic2_auth_fedict.adapters import AuthenticAdapter
|
||||
from authentic2_auth_fedict.backends import FedictBackend
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
factory = RequestFactory()
|
||||
|
||||
def test_dummy(app):
|
||||
assert 1
|
||||
|
||||
@pytest.fixture
|
||||
def adapter():
|
||||
return AuthenticAdapter()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def idp_conf():
|
||||
return {
|
||||
'A2_ATTRIBUTE_MAPPING': [
|
||||
{
|
||||
'attribute': 'email',
|
||||
'saml_attribute': 'mail',
|
||||
},
|
||||
{
|
||||
'attribute': 'last_name',
|
||||
'saml_attribute': 'surname',
|
||||
},
|
||||
{
|
||||
'attribute': 'first_name',
|
||||
'saml_attribute': 'givenName',
|
||||
},
|
||||
{
|
||||
'attribute': 'uuid',
|
||||
'saml_attribute': 'urn:be:fedict:iam:attr:fedid',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def issuer():
|
||||
return Issuer.objects.create(entity_id='https://idp.com/', slug='idp')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fedict_attributes():
|
||||
return {
|
||||
'issuer': 'https://idp.com/',
|
||||
'name_id_content': 'c54db0a8ddc24a02a2d057f857d3b102',
|
||||
'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED,
|
||||
'surname': ['Doe'],
|
||||
'givenName': ['John'],
|
||||
'mail': ['john.doe@example.org'],
|
||||
'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
|
||||
}
|
||||
|
||||
|
||||
def test_custom_lookup_user(adapter, idp_conf, issuer, fedict_attributes):
|
||||
assert User.objects.count() == 0
|
||||
|
||||
user = adapter.lookup_user(idp_conf, fedict_attributes)
|
||||
user.refresh_from_db()
|
||||
assert user.email == '' # email purposely deleted
|
||||
assert user.first_name == '' # not provisionned yet
|
||||
assert user.last_name == '' # not provisionned yet
|
||||
assert user.username == 'c54db0a8ddc24a02a2d057f857d3b102@saml' # <NameID>@<source>
|
||||
assert user.ou == get_default_ou()
|
||||
|
||||
|
||||
def test_login_fedict_authenticator_displayed(app, settings, issuer):
|
||||
response = app.get('/login/')
|
||||
assert 'Belgian eID' in response
|
||||
assert 'csam-login' in response
|
||||
|
||||
|
||||
def test_authenticate_eid_provision(app, settings, issuer):
|
||||
backend = FedictBackend()
|
||||
request = factory.get(path='/accounts/')
|
||||
request.user = AnonymousUser()
|
||||
|
||||
saml_attributes = {
|
||||
'city': [],
|
||||
'zipcode': [],
|
||||
'givenName': ['Doe'],
|
||||
'surname': ['John'],
|
||||
'verified_attributes': [],
|
||||
'address': [],
|
||||
'last_name': ['Doe'],
|
||||
'first_name': ['John'],
|
||||
'title': [],
|
||||
'username': ['john.doe'],
|
||||
'urn:be:fedict:iam:attr:fedid': ['xyz'], # new fedict id
|
||||
'is_superuser': ['false'],
|
||||
'phone': [],
|
||||
'mobile': [],
|
||||
'issuer': 'https://idp.com/',
|
||||
'nonce': None,
|
||||
'force_authn': False,
|
||||
'name_id_content': 'xyz', # new fedict id
|
||||
'name_id_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
|
||||
'name_id_name_qualifier': 'https://idp.com/idp/saml2/metadata',
|
||||
'authn_instant': datetime.datetime(2022, 1, 27, 11, 26, 7, 301054),
|
||||
'session_not_on_or_after': datetime.datetime(2022, 1, 27, 16, 26, 7),
|
||||
'session_index': '_401EEEE3C700B6D203B83AA1826E3B80',
|
||||
'authn_context_class_ref': 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
|
||||
'name_id_content_orig': 'c54db0a8ddc24a02a2d057f857d3b102',
|
||||
}
|
||||
credentials = {'saml_attributes': saml_attributes}
|
||||
|
||||
assert len(User.objects.all()) == 0
|
||||
SessionMiddleware().process_request(request)
|
||||
MessageMiddleware().process_request(request)
|
||||
backend_user = backend.authenticate(request, **credentials)
|
||||
assert backend_user.is_authenticated
|
||||
assert backend_user.username == 'xyz@saml' # <NameID>@<source>
|
||||
|
||||
|
||||
def test_authenticate_eid_link_to_existing_user(app, settings, issuer, user):
|
||||
backend = FedictBackend()
|
||||
request = factory.get(path='/accounts/')
|
||||
request.user = user
|
||||
|
||||
UserSAMLIdentifier.objects.create(
|
||||
user=user,
|
||||
name_id='c54db0a8ddc24a02a2d057f857d3b102',
|
||||
issuer=Issuer.objects.first(),
|
||||
)
|
||||
|
||||
saml_attributes = {
|
||||
'city': [],
|
||||
'zipcode': [],
|
||||
'givenName': ['Doe'],
|
||||
'surname': ['John'],
|
||||
'verified_attributes': [],
|
||||
'address': [],
|
||||
'last_name': ['Doe'],
|
||||
'first_name': ['John'],
|
||||
'title': [],
|
||||
'username': ['john.doe'],
|
||||
'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
|
||||
'is_superuser': ['false'],
|
||||
'phone': [],
|
||||
'mobile': [],
|
||||
'issuer': 'https://idp.com/',
|
||||
'nonce': None,
|
||||
'force_authn': False,
|
||||
'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',
|
||||
'authn_instant': datetime.datetime(2022, 1, 27, 11, 26, 7, 301054),
|
||||
'session_not_on_or_after': datetime.datetime(2022, 1, 27, 16, 26, 7),
|
||||
'session_index': '_401EEEE3C700B6D203B83AA1826E3B80',
|
||||
'authn_context_class_ref': 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
|
||||
'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 == user
|
||||
|
||||
|
||||
def test_eid_unlink(app, settings, issuer, user):
|
||||
assert len(UserSAMLIdentifier.objects.all()) == 0
|
||||
|
||||
# create link
|
||||
UserSAMLIdentifier.objects.create(
|
||||
user=user,
|
||||
name_id='abc',
|
||||
issuer=Issuer.objects.first(),
|
||||
)
|
||||
|
||||
response = login(app, user, path='/accounts/', password=user.username)
|
||||
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
|
||||
|
||||
|
||||
def test_provision_new_attributes_verified(app, settings, issuer, user):
|
||||
Attribute.objects.create(name='title', kind='title', label='Titre')
|
||||
# email & title verified
|
||||
user.email = 'john.doe@verified.publik.love'
|
||||
user.email_verified = True
|
||||
user.verified_attributes.title = 'Mr'
|
||||
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'],
|
||||
'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
|
||||
assert backend_user.email == 'john.doe@verified.publik.love'
|
||||
assert backend_user.email_verified == True
|
||||
assert backend_user.verified_attributes.title == 'Mr'
|
||||
assert backend_user.first_name == 'Foo'
|
||||
assert backend_user.last_name == 'Bar'
|
||||
|
||||
|
||||
def test_provision_old_account_deleted(app, settings, issuer, user):
|
||||
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
|
||||
count = User.objects.count()
|
||||
user_uuid = user.uuid
|
||||
UserSAMLIdentifier.objects.create(
|
||||
user=user,
|
||||
name_id='c54db0a8ddc24a02a2d057f857d3b102',
|
||||
issuer=Issuer.objects.first(),
|
||||
)
|
||||
saml_attributes = {
|
||||
'givenName': ['Doe'],
|
||||
'surname': ['John'],
|
||||
'last_name': ['Doe'],
|
||||
'first_name': ['John'],
|
||||
'username': ['john.doe'],
|
||||
'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
|
||||
'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)
|
||||
# link has been created on currently logged-in user
|
||||
assert backend_user == request_user
|
||||
# previous user as been deactivated
|
||||
assert User.objects.count() == count - 1
|
||||
assert not User.objects.filter(uuid=user_uuid)
|
||||
|
|
|
@ -9,7 +9,7 @@ def login(app, user, path=None, password=None, remember_me=None):
|
|||
else:
|
||||
login_page = app.get(reverse('auth_login'))
|
||||
assert login_page.request.path == reverse('auth_login')
|
||||
form = login_page.form
|
||||
form = login_page.forms[0]
|
||||
form.set('username', user.username if hasattr(user, 'username') else user)
|
||||
# password is supposed to be the same as username
|
||||
form.set('password', password or user.username)
|
||||
|
|
Loading…
Reference in New Issue