profile: add csam account-(un)linking capabilities (#60837)

This commit is contained in:
Paul Marillonnet 2022-01-21 11:46:46 +01:00
parent 1114ee4c12
commit f8566f4619
12 changed files with 468 additions and 11 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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'),
]

View File

@ -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')

View File

@ -50,6 +50,7 @@ def user(db):
email_verified=True,
)
user.set_password('john.doe')
user.save()
return user

19
tests/metadata.xml Normal file
View File

@ -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>

20
tests/saml.crt Normal file
View File

@ -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-----

28
tests/saml.key Normal file
View File

@ -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-----

View File

@ -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

View File

@ -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)

View File

@ -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)