Projet

Général

Profil

0002-profile-add-csam-account-un-linking-capabilities-608.patch

Paul Marillonnet, 28 janvier 2022 10:13

Télécharger (27,1 ko)

Voir les différences:

Subject: [PATCH 2/2] profile: add csam account-(un)linking capabilities
 (#60837)

 src/authentic2_auth_fedict/app_settings.py    |   4 +
 src/authentic2_auth_fedict/authenticators.py  |   2 -
 src/authentic2_auth_fedict/backends.py        |  71 +++++
 .../authentic2_auth_fedict/profile.html       |  27 +-
 src/authentic2_auth_fedict/urls.py            |   1 +
 src/authentic2_auth_fedict/views.py           |  16 +-
 tests/conftest.py                             |   1 +
 tests/metadata.xml                            |  19 ++
 tests/saml.crt                                |  20 ++
 tests/saml.key                                |  28 ++
 tests/settings.py                             |  23 +-
 tests/test_all.py                             | 277 +++++++++++++++++-
 tests/utils.py                                |   2 +-
 13 files changed, 480 insertions(+), 11 deletions(-)
 create mode 100644 tests/metadata.xml
 create mode 100644 tests/saml.crt
 create mode 100644 tests/saml.key
src/authentic2_auth_fedict/app_settings.py
36 36
    def enable(self):
37 37
        return self._setting('ENABLE', False)
38 38

  
39
    @property
40
    def PROVISION_VERIFIED_ATTRIBUTES(self):
41
        return self._setting('PROVISION_VERIFIED_ATTRIBUTES', ('first_name', 'last_name', 'email', 'title'))
42

  
39 43

  
40 44
import sys
41 45

  
src/authentic2_auth_fedict/authenticators.py
49 49
    def profile(self, request, *args, **kwargs):
50 50
        context = kwargs.get('context', {}).copy()
51 51
        user_saml_identifiers = request.user.saml_identifiers.all()
52
        if not user_saml_identifiers:
53
            return ''
54 52
        for user_saml_identifier in user_saml_identifiers:
55 53
            user_saml_identifier.idp = get_idp(user_saml_identifier.issuer)
56 54
        context['user_saml_identifiers'] = user_saml_identifiers
src/authentic2_auth_fedict/backends.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import logging
18

  
17 19
import lasso
20
from django.contrib import messages
21
from django.utils.translation import ugettext_lazy as _
18 22
from mellon.backends import SAMLBackend
23
from mellon.utils import get_adapters, get_idp
24

  
25
from authentic2_auth_fedict import app_settings
26

  
27
logger = logging.getLogger(__name__)
19 28

  
20 29

  
21 30
class FedictBackend(SAMLBackend):
......
28 37
        # but we do not expose this detail to the service provider as all it
29 38
        # needs to know is "strong authentication".
30 39
        return lasso.SAML2_AUTHN_CONTEXT_SMARTCARD_PKI
40

  
41
    def authenticate(self, request=None, **credentials):
42
        saml_attributes = credentials.get('saml_attributes') or {}
43
        if 'issuer' not in saml_attributes:
44
            logger.debug('no idp in saml_attributes')
45
            return None
46
        idp = get_idp(saml_attributes['issuer'])
47
        if not idp:
48
            logger.debug('unknown idp %s', saml_attributes['issuer'])
49
            return None
50
        adapters = get_adapters(idp, request=request)
51
        for adapter in adapters:
52
            if not hasattr(adapter, 'authorize'):
53
                continue
54
            if not adapter.authorize(idp, saml_attributes):
55
                return
56
        for adapter in adapters:
57
            if hasattr(adapter, 'lookup_by_attributes'):
58
                user = adapter.lookup_by_attributes(idp, saml_attributes)
59
        old_user = None
60
        for adapter in adapters:
61
            if not hasattr(adapter, 'lookup_user'):
62
                continue
63
            user = adapter.lookup_user(idp, saml_attributes)
64
            if user:
65
                request_user = getattr(request, 'user', None) if request else None
66
                if request_user != user and request_user.is_authenticated:
67
                    old_user = user
68
                    user = request_user
69
                    saml_identifier = old_user.saml_identifier
70
                    saml_identifier.user = user
71
                    saml_identifier.save(update_fields=['user'])
72
                    user.saml_identifier = saml_identifier
73
                    messages.warning(request, _('Your account is now linked to your eID card.'))
74
                break
75
        else:  # no user found
76
            return
77
        for adapter in adapters:
78
            if not hasattr(adapter, 'provision'):
79
                continue
80
            adapter.provision(user, idp, saml_attributes)
81
        # final identifier and attributes copy before deletion of old account
82
        if old_user and old_user != user:
83
            # copy all verified attributes to newly provisionned user
84
            for key in app_settings.PROVISION_VERIFIED_ATTRIBUTES:
85
                # corner case, email not accessible through AttributesDescriptor
86
                if key == 'email':
87
                    user.email = old_user.email
88
                    user.email_verified = old_user.email_verified
89
                    continue
90
                # fallback on AttributesDescriptor-managed attributes
91
                value = getattr(old_user.verified_attributes, key, None)
92
                if value:
93
                    setattr(user.verified_attributes, key, value)
94
                else:
95
                    # fallback on non-verified attributes
96
                    value = getattr(old_user.attributes, key, None)
97
                    if value:
98
                        setattr(user.attributes, key, value)
99
            logger.debug('deleting user %s, new fedict link manually created' % old_user)
100
            old_user.delete()
101
        return user
src/authentic2_auth_fedict/templates/authentic2_auth_fedict/profile.html
1
<p>
2
Ce compte est relié à votre carte d'identité électronique.
3
</p>
1
{% load i18n static %}
2
<div>
3
 <img src="{% static "authentic2_auth_fedict/img/beid_image_mini.png" %}" alt="">
4
{% if user_saml_identifiers %}
5
 <p>{% blocktrans %}This account is linked to your eID card.{% endblocktrans %}</p>
6
 <p>
7
 {% blocktrans %}
8
 You may want to unlink your account, although you will not be able to
9
 connect to your account using your eID card (without re-doing the linking
10
 procedure).
11
 {% endblocktrans %}
12
 </p>
13
 <p><a class="button" href="{% url "fedict-unlink" %}">{% trans "Unlink my account" %}</a></p>
14
{% else %}
15
 <p>
16
 {% blocktrans %}
17
 You may want to link your existing Publik account to your eID. In order to
18
 do so, you will either need your eID card and its card reader
19
 or your list of personal tokens.
20
 {% endblocktrans %}
21
 </p>
22
 <p><a class="button" href="{% url "fedict-login" %}?next=/accounts/">{% trans "Link my account to my eID card" %}</a></p>
23
{% endif %}
24
</div>
src/authentic2_auth_fedict/urls.py
21 21
urlpatterns = [
22 22
    url(r'^accounts/saml/', include('mellon.urls')),
23 23
    url(r'^accounts/fedict/login/$', views.login, name='fedict-login'),
24
    url(r'^accounts/fedict/unlink/$', views.unlink, name='fedict-unlink'),
24 25
]
src/authentic2_auth_fedict/views.py
18 18
import urllib.parse
19 19

  
20 20
from django.conf import settings
21
from django.contrib import messages
21 22
from django.core import signing
22 23
from django.db import transaction
23 24
from django.http import HttpResponseRedirect
24
from django.shortcuts import resolve_url
25
from django.shortcuts import redirect, resolve_url
25 26
from django.urls import reverse
27
from django.utils.translation import ugettext_lazy as _
26 28
from django.views.decorators.csrf import csrf_exempt
27 29
from django.views.generic import View
28 30

  
......
69 71

  
70 72

  
71 73
login = transaction.non_atomic_requests(csrf_exempt(LoginView.as_view()))
74

  
75

  
76
def unlink(request):
77
    if not hasattr(request, 'user') or not hasattr(request.user, 'saml_identifiers'):
78
        return
79
    unlink_performed = False
80
    for saml_identifier in request.user.saml_identifiers.all():
81
        saml_identifier.delete()
82
        unlink_performed = True
83
    if unlink_performed:
84
        messages.success(request, message=_('Unlinking complete.'))
85
    return redirect('account_management')
tests/conftest.py
50 50
        email_verified=True,
51 51
    )
52 52
    user.set_password('john.doe')
53
    user.save()
53 54
    return user
54 55

  
55 56

  
tests/metadata.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<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
3
BQAwKjEoMCYGA1UEAwwfc3BhcmUtYXV0aGVudGljLmRldi5wdWJsaWsubG92ZTAe
4
Fw0yMjAxMTcxNzQ2NThaFw0zMjAxMTcxNzQ2NThaMCoxKDAmBgNVBAMMH3NwYXJl
5
LWF1dGhlbnRpYy5kZXYucHVibGlrLmxvdmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
6
DwAwggEKAoIBAQDlVPm7D3abG4fTh/i9VJz4s9Lplblw4OKZwJfdFj2CrZX4kehh
7
zDEAcn8cE14gj5bzGCWcoeApw4KIFeoSzxZGpGn7dyiFTTny2IJugEuobWcT0IuC
8
P3TcPzWrm0HXnIcZjBv6Rr/cFFa2vAKL1CjT4xgZidRjGKpQBNoT2aOnFuvTzlrz
9
ZsGJ38+yRFZDQzoGS+5h4F3ZCHx6Y5fLNzWDW3Cu6/vv2/ev35omcHW+mSTVpds5
10
Oo2uzIvumYAM83oLzgWRnPLnx14lH+kWoDNaXEpUWDrmLD1A9OgNYmkeT/85qdyN
11
jzDbe1yG421e2jUaRXez412Nv1s1ua5U/dBlAgMBAAGjUzBRMB0GA1UdDgQWBBRQ
12
Y1+34SVZXriQAFteCRbde5jSOTAfBgNVHSMEGDAWgBRQY1+34SVZXriQAFteCRbd
13
e5jSOTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCwVWlzEE7w
14
0wBmngfctrz6cSyc5oAqDGo6dzVw+7CYhjRGr0Z9C1zI5rYOPWdDceAqD/lOVapw
15
1nawFszVpcClmY8Rt0Tiot8z18NRz0NCFZzbHx9cU5TGZUigOkuXXqB+D52ppuQB
16
6t/RREpeZakiBEp0Y8pH9CalA2DCNf/2WGcvaMXQtAL7Ko0a8vPBoOMykwgaFvCj
17
E+ZiZXHLLg4jw2QwX1s4Fap8vKEUn062qCx0TdgILX/PrUpmCkPvZRlA/fcQLFQw
18
1vX1rInD3cpTrxNw2T9ywGYmbXG1DmpxoF5X4ju3fcTR968QVJlX3L5JMbbWNV9X
19
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>
tests/saml.crt
1
-----BEGIN CERTIFICATE-----
2
MIIDNTCCAh2gAwIBAgIUToSxnhmCYk3jdVKvMxPB8S4HvBcwDQYJKoZIhvcNAQEL
3
BQAwKjEoMCYGA1UEAwwfc3BhcmUtYXV0aGVudGljLmRldi5wdWJsaWsubG92ZTAe
4
Fw0yMjAxMTcxNzQ2NThaFw0zMjAxMTcxNzQ2NThaMCoxKDAmBgNVBAMMH3NwYXJl
5
LWF1dGhlbnRpYy5kZXYucHVibGlrLmxvdmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
6
DwAwggEKAoIBAQDlVPm7D3abG4fTh/i9VJz4s9Lplblw4OKZwJfdFj2CrZX4kehh
7
zDEAcn8cE14gj5bzGCWcoeApw4KIFeoSzxZGpGn7dyiFTTny2IJugEuobWcT0IuC
8
P3TcPzWrm0HXnIcZjBv6Rr/cFFa2vAKL1CjT4xgZidRjGKpQBNoT2aOnFuvTzlrz
9
ZsGJ38+yRFZDQzoGS+5h4F3ZCHx6Y5fLNzWDW3Cu6/vv2/ev35omcHW+mSTVpds5
10
Oo2uzIvumYAM83oLzgWRnPLnx14lH+kWoDNaXEpUWDrmLD1A9OgNYmkeT/85qdyN
11
jzDbe1yG421e2jUaRXez412Nv1s1ua5U/dBlAgMBAAGjUzBRMB0GA1UdDgQWBBRQ
12
Y1+34SVZXriQAFteCRbde5jSOTAfBgNVHSMEGDAWgBRQY1+34SVZXriQAFteCRbd
13
e5jSOTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCwVWlzEE7w
14
0wBmngfctrz6cSyc5oAqDGo6dzVw+7CYhjRGr0Z9C1zI5rYOPWdDceAqD/lOVapw
15
1nawFszVpcClmY8Rt0Tiot8z18NRz0NCFZzbHx9cU5TGZUigOkuXXqB+D52ppuQB
16
6t/RREpeZakiBEp0Y8pH9CalA2DCNf/2WGcvaMXQtAL7Ko0a8vPBoOMykwgaFvCj
17
E+ZiZXHLLg4jw2QwX1s4Fap8vKEUn062qCx0TdgILX/PrUpmCkPvZRlA/fcQLFQw
18
1vX1rInD3cpTrxNw2T9ywGYmbXG1DmpxoF5X4ju3fcTR968QVJlX3L5JMbbWNV9X
19
iB4feKp2tzW1
20
-----END CERTIFICATE-----
tests/saml.key
1
-----BEGIN PRIVATE KEY-----
2
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDlVPm7D3abG4fT
3
h/i9VJz4s9Lplblw4OKZwJfdFj2CrZX4kehhzDEAcn8cE14gj5bzGCWcoeApw4KI
4
FeoSzxZGpGn7dyiFTTny2IJugEuobWcT0IuCP3TcPzWrm0HXnIcZjBv6Rr/cFFa2
5
vAKL1CjT4xgZidRjGKpQBNoT2aOnFuvTzlrzZsGJ38+yRFZDQzoGS+5h4F3ZCHx6
6
Y5fLNzWDW3Cu6/vv2/ev35omcHW+mSTVpds5Oo2uzIvumYAM83oLzgWRnPLnx14l
7
H+kWoDNaXEpUWDrmLD1A9OgNYmkeT/85qdyNjzDbe1yG421e2jUaRXez412Nv1s1
8
ua5U/dBlAgMBAAECggEAFb2xkydLIjdA8C/Sx/gujXFWzGgyVV4sfVEB1KbYG/xi
9
3FbQxfy6pIU2Qa4gXUvfjpo6bpf2DV+Ij2gsca4KOZY6qelJASIqHTijXOByy7vb
10
ash5gVaMuJiRePxWCJ/BOw3KVTbB15ZiBh7ayvDJEhVUYo9rgB2Ff+KF3h3i4uMe
11
cF6Z9aA0m9tYmEx88zk8kjxZMH7BhWX3C6H5FcGCy5ARhRQ4EoljDuIWD5+kgpMg
12
0hnr2B+pP2AfA1p+Xowf56u17NyEK3QBfdrQp9GXfJaiiSVCqWR3foOqU59LiIjY
13
TMp/7DgQqTPmLKifLPcZQcPtjU6dvBCl2NHZuPEBgQKBgQD5bi77KT3+UwePFPwy
14
6lXoPAQryWpsQl8JpU3iFLZLx8uMUjtngDvQdU6D7XL5lB0b0gEOlIha2oedQT7F
15
rhTm2Qiqj5pnxc8QU4xskSqudzF4WazmXdc2ajssiPWPpZtJj5FZPlw0WF/siFCR
16
2gGCUcXwKe9vNv1Zn9N1KVNIBQKBgQDrX0aGY25YqIc6E9z/Y2KYJaH6rjf46p2Q
17
RL8m6UiQheuRP2ePa1JLZQrDqXKp2ibZs+IyC1r+6ST7uCEDQjZcpxr+cLTp/ELY
18
GtL17FXHjENfZm0JreeijJfUBZLqX0R6GTsi0kT0y8SQZiClpCZw51ovhIHLXrHG
19
hWOVpAi04QKBgEshIQ2N0pp0L+atD3nWk6Gr0iXOOTv6kd256MecLXyN5YWSj0oR
20
mfKkIs4iC2uZbVsf2immG5wiDo8TQ/EPCkSuQqn9Lyjqr//e6oEZCJ4cUM5LVITe
21
5yAAx2oWpsBpxWhW0hTrb6JkrB/2vy3vWF0EfHZmazQ4f/8q4Op9VBRxAoGAKOKL
22
5Zwv9saPds8sfFBPOA6RbHIG1v4qEH1glum+6RvaJ4jT/F2wFdifXg15FXgHd5l/
23
mSHP1Ke6/N6nHWHK/50nWztIsbxYACHosz8yR09eBJxOJHhI3Dt/xByTwJJ72pm3
24
Y/0SbVNX+Z1D3oH9C2+kgsyJn0H7r3hMLBoqSQECgYBTMbQ13v2B9U7I1UFryhYR
25
ArfaO2VLseLWmEhTO/eSg8LJu9lDNRoagLl41qSRLNabxQzL/1bZTQb0i0S9wCL5
26
tyx2YvLjN7wI0Pkd5WuZeT69ERroAFwASOINmzx96m83TxSe39c+OnxQbX4ujyQI
27
iH1921Y1zSQbKsM7CeeFnw==
28
-----END PRIVATE KEY-----
tests/settings.py
1 1
import os
2 2

  
3
from authentic2.settings import MIDDLEWARE
4

  
3 5
ALLOWED_HOSTS = ['localhost']
4 6

  
5 7
DATABASES = {
......
15 17
            DATABASES['default'][key[2:]] = os.environ[key]
16 18

  
17 19
LANGUAGE_CODE = 'en'
18
A2_FC_CLIENT_ID = ''
19
A2_FC_CLIENT_SECRET = ''
20
A2_AUTH_SAML_ENABLE = False
21
A2_AUTH_FEDICT_ENABLE = True
22

  
23
MELLON_ADAPTER = ["authentic2_auth_fedict.adapters.AuthenticAdapter"]
24
MELLON_LOGIN_URL = "fedict-login"
25
MELLON_PUBLIC_KEYS = ["./tests/saml.crt"]
26
MELLON_PRIVATE_KEY = "./tests/saml.key"
27
MELLON_IDENTITY_PROVIDERS = [
28
    {
29
        "METADATA": open("./tests/metadata.xml").read(),
30
        "ENTITY_ID": "https://idp.com/",
31
        "SLUG": "idp",
32
    },
33
]
34

  
35
MELLON_ATTRIBUTE_MAPPING = {
36
    "last_name": "{attributes[surname][0]}",
37
    "first_name": "{attri,butes[givenName][0]}",
38
}
20 39

  
21 40
# test hook handlers
22 41
A2_HOOKS_PROPAGATE_EXCEPTIONS = True
tests/test_all.py
1
import datetime
2

  
3
import lasso
1 4
import pytest
5
from authentic2.a2_rbac.utils import get_default_ou
6
from authentic2.models import Attribute
2 7
from django.contrib.auth import get_user_model
8
from django.contrib.auth.models import AnonymousUser
9
from django.contrib.messages.middleware import MessageMiddleware
10
from django.contrib.sessions.middleware import SessionMiddleware
11
from django.test.client import RequestFactory
12
from mellon.models import Issuer, UserSAMLIdentifier
3 13
from utils import login
4 14

  
15
from authentic2_auth_fedict.adapters import AuthenticAdapter
16
from authentic2_auth_fedict.backends import FedictBackend
17

  
5 18
User = get_user_model()
6 19

  
7 20
pytestmark = pytest.mark.django_db
8 21

  
22
factory = RequestFactory()
23

  
24

  
25
@pytest.fixture
26
def adapter():
27
    return AuthenticAdapter()
28

  
29

  
30
@pytest.fixture
31
def idp_conf():
32
    return {
33
        'A2_ATTRIBUTE_MAPPING': [
34
            {
35
                'attribute': 'email',
36
                'saml_attribute': 'mail',
37
            },
38
            {
39
                'attribute': 'last_name',
40
                'saml_attribute': 'surname',
41
            },
42
            {
43
                'attribute': 'first_name',
44
                'saml_attribute': 'givenName',
45
            },
46
            {
47
                'attribute': 'uuid',
48
                'saml_attribute': 'urn:be:fedict:iam:attr:fedid',
49
            },
50
        ]
51
    }
52

  
53

  
54
@pytest.fixture
55
def issuer():
56
    return Issuer.objects.create(entity_id='https://idp.com/', slug='idp')
57

  
58

  
59
@pytest.fixture
60
def fedict_attributes():
61
    return {
62
        'issuer': 'https://idp.com/',
63
        'name_id_content': 'c54db0a8ddc24a02a2d057f857d3b102',
64
        'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED,
65
        'surname': ['Doe'],
66
        'givenName': ['John'],
67
        'mail': ['john.doe@example.org'],
68
        'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
69
    }
70

  
71

  
72
def test_custom_lookup_user(adapter, idp_conf, issuer, fedict_attributes):
73
    assert User.objects.count() == 0
74

  
75
    user = adapter.lookup_user(idp_conf, fedict_attributes)
76
    user.refresh_from_db()
77
    assert user.email == ''  #  email purposely deleted
78
    assert user.first_name == ''  #  not provisionned yet
79
    assert user.last_name == ''  #  not provisionned yet
80
    assert user.username == 'c54db0a8ddc24a02a2d057f857d3b102@saml'  #  <NameID>@<source>
81
    assert user.ou == get_default_ou()
82

  
83

  
84
def test_login_fedict_authenticator_displayed(app, settings, issuer):
85
    response = app.get('/login/')
86
    assert 'Belgian eID' in response
87
    assert 'csam-login' in response
88

  
89

  
90
def test_authenticate_eid_provision(app, settings, issuer):
91
    backend = FedictBackend()
92
    request = factory.get(path='/accounts/')
93
    request.user = AnonymousUser()
94

  
95
    saml_attributes = {
96
        'city': [],
97
        'zipcode': [],
98
        'givenName': ['Doe'],
99
        'surname': ['John'],
100
        'verified_attributes': [],
101
        'address': [],
102
        'last_name': ['Doe'],
103
        'first_name': ['John'],
104
        'title': [],
105
        'username': ['john.doe'],
106
        'urn:be:fedict:iam:attr:fedid': ['xyz'],  #  new fedict id
107
        'is_superuser': ['false'],
108
        'phone': [],
109
        'mobile': [],
110
        'issuer': 'https://idp.com/',
111
        'nonce': None,
112
        'force_authn': False,
113
        'name_id_content': 'xyz',  #  new fedict id
114
        'name_id_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
115
        'name_id_name_qualifier': 'https://idp.com/idp/saml2/metadata',
116
        'authn_instant': datetime.datetime(2022, 1, 27, 11, 26, 7, 301054),
117
        'session_not_on_or_after': datetime.datetime(2022, 1, 27, 16, 26, 7),
118
        'session_index': '_401EEEE3C700B6D203B83AA1826E3B80',
119
        'authn_context_class_ref': 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
120
        'name_id_content_orig': 'c54db0a8ddc24a02a2d057f857d3b102',
121
    }
122
    credentials = {'saml_attributes': saml_attributes}
123

  
124
    assert len(User.objects.all()) == 0
125
    SessionMiddleware().process_request(request)
126
    MessageMiddleware().process_request(request)
127
    backend_user = backend.authenticate(request, **credentials)
128
    assert backend_user.is_authenticated
129
    assert backend_user.username == 'xyz@saml'  #  <NameID>@<source>
130

  
131

  
132
def test_authenticate_eid_link_to_existing_user(app, settings, issuer, user):
133
    backend = FedictBackend()
134
    request = factory.get(path='/accounts/')
135
    request.user = user
136

  
137
    UserSAMLIdentifier.objects.create(
138
        user=user,
139
        name_id='c54db0a8ddc24a02a2d057f857d3b102',
140
        issuer=Issuer.objects.first(),
141
    )
142

  
143
    saml_attributes = {
144
        'city': [],
145
        'zipcode': [],
146
        'givenName': ['Doe'],
147
        'surname': ['John'],
148
        'verified_attributes': [],
149
        'address': [],
150
        'last_name': ['Doe'],
151
        'first_name': ['John'],
152
        'title': [],
153
        'username': ['john.doe'],
154
        'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
155
        'is_superuser': ['false'],
156
        'phone': [],
157
        'mobile': [],
158
        'issuer': 'https://idp.com/',
159
        'nonce': None,
160
        'force_authn': False,
161
        'name_id_content': 'c54db0a8ddc24a02a2d057f857d3b102',
162
        'name_id_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
163
        'name_id_name_qualifier': 'https://idp.com/idp/saml2/metadata',
164
        'authn_instant': datetime.datetime(2022, 1, 27, 11, 26, 7, 301054),
165
        'session_not_on_or_after': datetime.datetime(2022, 1, 27, 16, 26, 7),
166
        'session_index': '_401EEEE3C700B6D203B83AA1826E3B80',
167
        'authn_context_class_ref': 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
168
        'name_id_content_orig': 'c54db0a8ddc24a02a2d057f857d3b102',
169
    }
170
    credentials = {'saml_attributes': saml_attributes}
171

  
172
    SessionMiddleware().process_request(request)
173
    MessageMiddleware().process_request(request)
174
    backend_user = backend.authenticate(request, **credentials)
175
    assert backend_user == user
176

  
177

  
178
def test_eid_unlink(app, settings, issuer, user):
179
    assert len(UserSAMLIdentifier.objects.all()) == 0
180

  
181
    # create link
182
    UserSAMLIdentifier.objects.create(
183
        user=user,
184
        name_id='abc',
185
        issuer=Issuer.objects.first(),
186
    )
187

  
188
    response = login(app, user, path='/accounts/', password=user.username)
189
    assert "Unlink my account" in response.text
190
    app.get('/accounts/fedict/unlink/').follow()
191

  
192
    response = app.get('/accounts/')
193
    assert "Link my account to my eID card" in response.text
194

  
195

  
196
def test_provision_new_attributes_verified(app, settings, issuer, user):
197
    Attribute.objects.create(name='title', kind='title', label='Titre')
198
    # email & title verified
199
    user.email = 'john.doe@verified.publik.love'
200
    user.email_verified = True
201
    user.verified_attributes.title = 'Mr'
202
    user.first_name = 'Johnny'
203
    user.last_name = 'Smith'
204
    user.save()
205
    UserSAMLIdentifier.objects.create(
206
        user=user,
207
        name_id='c54db0a8ddc24a02a2d057f857d3b102',
208
        issuer=Issuer.objects.first(),
209
    )
210

  
211
    backend = FedictBackend()
212
    request = factory.get(path='/accounts/')
213
    request_user = User.objects.create(
214
        first_name='Foo',
215
        last_name='Bar',
216
        email='foo.bar@nowhere.null',
217
    )
218
    request.user = request_user
219
    saml_attributes = {
220
        'givenName': ['Doe'],
221
        'surname': ['John'],
222
        'last_name': ['Doe'],
223
        'first_name': ['John'],
224
        'username': ['john.doe'],
225
        'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
226
        'is_superuser': ['false'],
227
        'issuer': 'https://idp.com/',
228
        'name_id_content': 'c54db0a8ddc24a02a2d057f857d3b102',
229
        'name_id_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
230
        'name_id_name_qualifier': 'https://idp.com/idp/saml2/metadata',
231
        'name_id_content_orig': 'c54db0a8ddc24a02a2d057f857d3b102',
232
    }
233
    credentials = {'saml_attributes': saml_attributes}
234
    SessionMiddleware().process_request(request)
235
    MessageMiddleware().process_request(request)
236
    backend_user = backend.authenticate(request, **credentials)
237
    assert backend_user == request_user
238
    assert backend_user.email == 'john.doe@verified.publik.love'
239
    assert backend_user.email_verified == True
240
    assert backend_user.verified_attributes.title == 'Mr'
241
    assert backend_user.first_name == 'Johnny'
242
    assert backend_user.last_name == 'Smith'
243

  
244

  
245
def test_provision_old_account_deleted(app, settings, issuer, user):
246
    backend = FedictBackend()
247
    request = factory.get(path='/accounts/')
248
    request_user = User.objects.create(
249
        first_name='foo',
250
        last_name='bar',
251
        email='foo.bar@nowhere.null',
252
    )
253
    request.user = request_user
254
    count = User.objects.count()
255
    user_uuid = user.uuid
256
    UserSAMLIdentifier.objects.create(
257
        user=user,
258
        name_id='c54db0a8ddc24a02a2d057f857d3b102',
259
        issuer=Issuer.objects.first(),
260
    )
261
    saml_attributes = {
262
        'givenName': ['Doe'],
263
        'surname': ['John'],
264
        'last_name': ['Doe'],
265
        'first_name': ['John'],
266
        'username': ['john.doe'],
267
        'urn:be:fedict:iam:attr:fedid': ['c54db0a8ddc24a02a2d057f857d3b102'],
268
        'is_superuser': ['false'],
269
        'issuer': 'https://idp.com/',
270
        'name_id_content': 'c54db0a8ddc24a02a2d057f857d3b102',
271
        'name_id_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
272
        'name_id_name_qualifier': 'https://idp.com/idp/saml2/metadata',
273
        'name_id_content_orig': 'c54db0a8ddc24a02a2d057f857d3b102',
274
    }
275
    credentials = {'saml_attributes': saml_attributes}
9 276

  
10
def test_dummy(app):
11
    assert 1
277
    SessionMiddleware().process_request(request)
278
    MessageMiddleware().process_request(request)
279
    backend_user = backend.authenticate(request, **credentials)
280
    # link has been created on currently logged-in user
281
    assert backend_user == request_user
282
    # previous user as been deactivated
283
    assert User.objects.count() == count - 1
284
    assert not User.objects.filter(uuid=user_uuid)
tests/utils.py
9 9
    else:
10 10
        login_page = app.get(reverse('auth_login'))
11 11
    assert login_page.request.path == reverse('auth_login')
12
    form = login_page.form
12
    form = login_page.forms[0]
13 13
    form.set('username', user.username if hasattr(user, 'username') else user)
14 14
    # password is supposed to be the same as username
15 15
    form.set('password', password or user.username)
16
-