0005-misc-split-auth_saml-tests-69720.patch
tests/auth_saml/conftest.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2019 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import pathlib |
|
18 | ||
19 |
import lasso |
|
20 |
import pytest |
|
21 | ||
22 |
from authentic2.custom_user.models import User |
|
23 |
from authentic2.models import Attribute |
|
24 |
from authentic2_auth_saml.adapters import AuthenticAdapter |
|
25 |
from authentic2_auth_saml.models import SAMLAuthenticator, SetAttributeAction |
|
26 | ||
27 | ||
28 |
@pytest.fixture |
|
29 |
def adapter(): |
|
30 |
return AuthenticAdapter() |
|
31 | ||
32 | ||
33 |
base_path = pathlib.Path(__file__).parent |
|
34 | ||
35 | ||
36 |
@pytest.fixture |
|
37 |
def idp(db, settings): |
|
38 |
settings.MELLON_PRIVATE_KEY = str((base_path / './private_key.pem').resolve()) |
|
39 |
settings.MELLON_PUBLIC_KEY = str((base_path / './public_key.pem').resolve()) |
|
40 |
authenticator = SAMLAuthenticator.objects.create( |
|
41 |
enabled=True, |
|
42 |
metadata=(base_path / './metadata.xml').read_text(), |
|
43 |
slug='idp1', |
|
44 |
) |
|
45 |
SetAttributeAction.objects.create( |
|
46 |
authenticator=authenticator, |
|
47 |
user_field='email', |
|
48 |
saml_attribute='mail', |
|
49 |
mandatory=True, |
|
50 |
) |
|
51 |
SetAttributeAction.objects.create( |
|
52 |
authenticator=authenticator, |
|
53 |
user_field='title', |
|
54 |
saml_attribute='title', |
|
55 |
) |
|
56 |
SetAttributeAction.objects.create( |
|
57 |
authenticator=authenticator, |
|
58 |
user_field='first_name', |
|
59 |
saml_attribute='http://nice/attribute/givenName', |
|
60 |
) |
|
61 |
return authenticator.settings |
|
62 | ||
63 | ||
64 |
@pytest.fixture |
|
65 |
def title_attribute(db): |
|
66 |
return Attribute.objects.create(kind='title', name='title', label='title') |
|
67 | ||
68 | ||
69 |
@pytest.fixture |
|
70 |
def saml_attributes(): |
|
71 |
return { |
|
72 |
'issuer': 'https://idp.com/', |
|
73 |
'name_id_content': 'xxx', |
|
74 |
'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT, |
|
75 |
'mail': ['john.doe@example.com'], |
|
76 |
'title': ['Mr.'], |
|
77 |
'http://nice/attribute/givenName': ['John'], |
|
78 |
} |
|
79 | ||
80 | ||
81 |
@pytest.fixture |
|
82 |
def user(db): |
|
83 |
return User.objects.create() |
tests/auth_saml/metadata.xml | ||
---|---|---|
1 |
<?xml version="1.0"?> |
|
2 |
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" |
|
3 |
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" |
|
4 |
xmlns:ds="http://www.w3.org/2000/09/xmldsig#" |
|
5 |
entityID="http://idp5/metadata"> |
|
6 |
<IDPSSODescriptor |
|
7 |
WantAuthnRequestsSigned="true" |
|
8 |
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> |
|
9 |
<KeyDescriptor use="signing"> |
|
10 |
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> |
|
11 |
<ds:X509Data><ds:X509Certificate> |
|
12 |
MIIC/TCCAeWgAwIBAgIUe/2RmSPWPz90rF3xm4q+jPPrGlcwDQYJKoZIhvcNAQEL |
|
13 |
BQAwDjEMMAoGA1UEAwwDSURQMB4XDTE5MDQwMzEwMDEwM1oXDTQ2MDgxOTEwMDEw |
|
14 |
M1owDjEMMAoGA1UEAwwDSURQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC |
|
15 |
AQEA39a9HT0PFMAy5Tcdik+LEWuDqqEdt8UrZr7TH/GpfAneDC0skDeHi9ErsEet |
|
16 |
ZYuBkk7YDpNvpaXprhG7EWwO9LnBN5oxN7Jp7PEOyD8+v4GSKjySbmTubaGcR5F3 |
|
17 |
3EfPVp9yin79kN+iIi/VtoL6cacfzsIBjNmBBzs4RhIjoSce+0uTuV+EN73p5ZSt |
|
18 |
mThamA/qnUeRDnVG5Y/hya3ldsg+rb6ObahnUYcAcP9sR/SKku3YQNVG0f4u1JYY |
|
19 |
in7gKGZ8ty7YeVI6ulVNmG/fZXo8nw3OJ9VDG1Ye3yOz7tqhGCh9HjUsoVikPsoD |
|
20 |
iDXQAgVynaEqo33SZGmgceGZowIDAQABo1MwUTAdBgNVHQ4EFgQUg0v91gd9cDen |
|
21 |
b+C5YH/Kfj+1db4wHwYDVR0jBBgwFoAUg0v91gd9cDenb+C5YH/Kfj+1db4wDwYD |
|
22 |
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAiAvkSPyv5oCuJmETM/B/ |
|
23 |
HKd252g90yzdKM38gs1fFXt6IErcI5t6UlFZFrIs6K1yE5dEjgxFZFKFbakO618C |
|
24 |
xdh6MI8obfhAbqCDLVSkWtm9M0HX1I1HxJ/b+0BR6RtT9w8gDRL4ZRb/+y+82GRH |
|
25 |
Sm+9A8VXgWaTKRsUnRXUQPVXrQ4mU0R+f5tXpa1CVpH3Z8krYbvZSzB086alim12 |
|
26 |
5Kbe21CSN83wCZm0mjkKwFrrjCnKv3wSNqHHXQoYeGfON6B33d0rJRLwjIWJ7BDC |
|
27 |
tkks8tLgCsYhKGNwprDy8Eo/lDCzQe03Ob1HPEh2XaENJoAx0XT6kJDyX41N8JPK |
|
28 |
tA== |
|
29 |
</ds:X509Certificate></ds:X509Data> |
|
30 |
</ds:KeyInfo> |
|
31 |
</KeyDescriptor> |
|
32 |
<ArtifactResolutionService isDefault="true" index="0" |
|
33 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" |
|
34 |
Location="http://idp5/artifact" /> |
|
35 |
<SingleLogoutService |
|
36 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" |
|
37 |
Location="http://idp5/singleLogoutSOAP" /> |
|
38 |
<SingleLogoutService |
|
39 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" |
|
40 |
Location="http://idp5/singleLogout" |
|
41 |
ResponseLocation="http://idp5/singleLogoutReturn" /> |
|
42 |
<ManageNameIDService |
|
43 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" |
|
44 |
Location="http://idp5/manageNameIdSOAP" /> |
|
45 |
<ManageNameIDService |
|
46 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" |
|
47 |
Location="http://idp5/manageNameId" |
|
48 |
ResponseLocation="http://idp5/manageNameIdReturn" /> |
|
49 |
<SingleSignOnService |
|
50 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" |
|
51 |
Location="http://idp5/singleSignOn" /> |
|
52 |
<SingleSignOnService |
|
53 |
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" |
|
54 |
Location="http://idp5/singleSignOnSOAP" /> |
|
55 |
</IDPSSODescriptor> |
|
56 |
<AuthnAuthorityDescriptor |
|
57 |
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> |
|
58 |
<AuthnQueryService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://idp6/authnQueryService"/> |
|
59 |
<AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://idp6/authnAuthAssertionIDRequestService"/> |
|
60 |
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat> |
|
61 |
</AuthnAuthorityDescriptor> |
|
62 |
<PDPDescriptor |
|
63 |
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> |
|
64 |
<AuthzService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://idp6/authzService"/> |
|
65 |
<AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://idp6/PDPAuthAssertionIDRequestService"/> |
|
66 |
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:kerberos</NameIDFormat> |
|
67 |
</PDPDescriptor> |
|
68 |
<AttributeAuthorityDescriptor |
|
69 |
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> |
|
70 |
<AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://idp6/attributeService"/> |
|
71 |
<AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://idp6/AttributeAuthAssertionIDRequestService"/> |
|
72 |
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat> |
|
73 |
</AttributeAuthorityDescriptor> |
|
74 |
<Organization> |
|
75 |
<OrganizationName xml:lang="en">Entr'ouvert</OrganizationName> |
|
76 |
</Organization> |
|
77 | ||
78 |
</EntityDescriptor> |
tests/auth_saml/private_key.pem | ||
---|---|---|
1 |
-----BEGIN PRIVATE KEY----- |
|
2 |
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJ4MpsYAEt52vT |
|
3 |
sbQzo15UE1ZyUNb5sz77iaZZXX3k6pS6z4qOF83p2y7CpRWyjfPsqxVTTbP70BVe |
|
4 |
yI9G4cPw2BeCoyNxPLTjh4J/DUT3z6iCatJfKVUKZNIYKBGPb7wAA1OHGk4MOGw6 |
|
5 |
gL0uC/m3yTN4Q8RhRD55GTHW0vb8PJOizWESr0ycvgIJyhmzhA++9DoAJj1jphj8 |
|
6 |
AZWLYWDcBMVo4D/6TvcwWtaxfj8eXmmbIzB+igsvQSLbIyRxzXJaMj7cYocDoSS4 |
|
7 |
eE8kRaL+ndUNd8gqNn6DpMbYolY24atjrT4+Zvqx3uDs0NMIN9rGagayNtTe+XTJ |
|
8 |
FTx2nlL7AgMBAAECggEAVu2QvHHqkBWifJl8eu/R4mohQ0BEEWl5qV5wXvK/Dx9j |
|
9 |
w70ycFUXuadDz1S+rxApBLP2jtRauAe17AZ4i5ETilXCaeJNlKkLSx5CturD0+F7 |
|
10 |
Mg1FYOyvTbZ0MSqvxQ/b6DWGdhqBmQmCsP5Wd8l4Ugc1PogPu8JjFEohB9v3tkyr |
|
11 |
jdJJB5hHB34YfpXfSqbo1tNuU1CLyzaL+D02BND7ompKuQRLG1MtTuvZ/7IS4l5v |
|
12 |
t1CVfdOUfPZeF9DJmFYrTaDHSrsCSIJl/djNOl2dn2rZnd/TDnuxg0UdSz4myjhj |
|
13 |
xdhlXpDK+VheDHqDwVYDul4F5xc3fg4AI2cm+Hy+YQKBgQDjYP0SG770n7lEpI5S |
|
14 |
K8c8D2BtQPX1Qj9wyNr69T4dIphfCRNJvaQnUxBXDBD/Tup82zbXWr7jOaCBWJy+ |
|
15 |
0Ik+nE+t85NY2sZcoBEd5dsYfriXYIjomhXzloNkwbR3HqN6DTbSqatLbwkdczw8 |
|
16 |
K8ZR6mlGX5F7jRSf7sH+NU4+HQKBgQDjShH/Q0X97YOY0An6QPWVVIhSp5zbyB2M |
|
17 |
KVfAV5bVF8AaJJccoaGgM5HBsQBd7IrgCJodt51E6TpZK+Im/VOzG8sUmNA5MeuF |
|
18 |
8XFJ0TRwLsFFlFFh2KN2uWYIIe3RFbXY0Kxil2YSOxNiHG2m5qkBucTgWoKG4kzF |
|
19 |
mY9+RHjp9wKBgQCo6/UW7uX+dmr9RAM3qK5rQEEy6X/QpVbcQ1vr9SYgHwN0Fxnt |
|
20 |
PqYlUOBiyuQVyFsMRw+HDjOiO72yWlKYr/RGP3oykTJ2YJHdXk1ZGNqcaAha7azI |
|
21 |
oTCNttQGlqGrnWd11TtVZheMAwGSj3nAegTr9mofjgBW+YjJCGe2o4NtQQKBgChQ |
|
22 |
MvD7laZ8QiMQgzSH4Qcjfypp4EB7NgJuMspCvtX86G26n7LMWEZ53xhjtJT0J42k |
|
23 |
+PDcaGCYnWjDh9EyjW3vOA3nLMd4OzX+pQFawdpD8LPOosCgFB4ytA4tNmknWKGk |
|
24 |
IW87OzdwkveL40b/Emrj8C963jveV4+UtQbITknxAoGAAMRnKZiY5oCUdNF3b5CF |
|
25 |
u/c7rBBvXvD6LoD/I+DciW+vzzLTpyHXe0O1LXAXbh6Vhbdl/JUEqQ9GY0WHUF2i |
|
26 |
GGsC/A8cUZPBW/KRTp9Gvba/cLVVJJMg17Yeif1vLBipKtjhBOe2J32oi2sFBzU5 |
|
27 |
ftuBuQmlGE+LRrRFtGu+fS8= |
|
28 |
-----END PRIVATE KEY----- |
tests/auth_saml/public_key.pem | ||
---|---|---|
1 |
-----BEGIN CERTIFICATE----- |
|
2 |
MIIC/TCCAeWgAwIBAgIUUpf0AbWHRr58otdjpctRwYP/uEYwDQYJKoZIhvcNAQEL |
|
3 |
BQAwDjEMMAoGA1UEAwwDSURQMB4XDTE5MDQwMzEwMDExMloXDTQ2MDgxOTEwMDEx |
|
4 |
MlowDjEMMAoGA1UEAwwDSURQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC |
|
5 |
AQEAyeDKbGABLedr07G0M6NeVBNWclDW+bM++4mmWV195OqUus+KjhfN6dsuwqUV |
|
6 |
so3z7KsVU02z+9AVXsiPRuHD8NgXgqMjcTy044eCfw1E98+ogmrSXylVCmTSGCgR |
|
7 |
j2+8AANThxpODDhsOoC9Lgv5t8kzeEPEYUQ+eRkx1tL2/DyTos1hEq9MnL4CCcoZ |
|
8 |
s4QPvvQ6ACY9Y6YY/AGVi2Fg3ATFaOA/+k73MFrWsX4/Hl5pmyMwfooLL0Ei2yMk |
|
9 |
cc1yWjI+3GKHA6EkuHhPJEWi/p3VDXfIKjZ+g6TG2KJWNuGrY60+Pmb6sd7g7NDT |
|
10 |
CDfaxmoGsjbU3vl0yRU8dp5S+wIDAQABo1MwUTAdBgNVHQ4EFgQUuJZGqJa7ljZZ |
|
11 |
LWZ3AqbvbdipCBIwHwYDVR0jBBgwFoAUuJZGqJa7ljZZLWZ3AqbvbdipCBIwDwYD |
|
12 |
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAPaaW02Y17FAIJv9wk7tc |
|
13 |
MURL364jlaGrNYcv/og2uJw2WxKsCKahE8fY3Yu3fceSt6eMannWkvDpAGC9COIX |
|
14 |
Lr9VKK9c2eUbFyeCWu4eizQaUTKtrQIyxyL1geQdmJZPcJfvDfJM4lUxt0gTx1R5 |
|
15 |
ouMwDAtIFfDpOKQyXthqeXoGrrraxHr+GzJcgdHeR9c4eiKXf7C1JEJhhv6a3zDz |
|
16 |
v3uOwiLhlKIQ430623MK75jdEzo+2/aUzur8UttkRBdalumYR5SM+CKLhPYc9L6p |
|
17 |
55pHYinL190yAjIDuY9WN+d+8C/2UrUI5iiHOc/D2kYCN8dJWDwhXlRKhRZ6f0jq |
|
18 |
Lw== |
|
19 |
-----END CERTIFICATE----- |
tests/auth_saml/test_adapter.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import re |
|
18 |
from unittest import mock |
|
19 | ||
20 |
import lasso |
|
21 |
import pytest |
|
22 |
from mellon.adapters import UserCreationError |
|
23 | ||
24 |
from authentic2.custom_user.models import User |
|
25 |
from authentic2_auth_saml.adapters import MappingError |
|
26 |
from authentic2_auth_saml.models import AddRoleAction, SAMLAuthenticator, SetAttributeAction |
|
27 | ||
28 | ||
29 |
def test_lookup_user_ok(adapter, idp, saml_attributes, title_attribute): |
|
30 |
assert User.objects.count() == 0 |
|
31 | ||
32 |
user = adapter.lookup_user(idp, saml_attributes) |
|
33 |
user.refresh_from_db() |
|
34 |
assert user.email == 'john.doe@example.com' |
|
35 |
assert user.attributes.title == 'Mr.' |
|
36 |
assert user.first_name == 'John' |
|
37 |
assert user.attributes.title == 'Mr.' |
|
38 |
assert user.ou.default is True |
|
39 | ||
40 | ||
41 |
def test_lookup_user_missing_mandatory_attribute(adapter, idp, saml_attributes, title_attribute): |
|
42 |
del saml_attributes['mail'] |
|
43 | ||
44 |
assert User.objects.count() == 0 |
|
45 |
assert adapter.lookup_user(idp, saml_attributes) is None |
|
46 |
assert User.objects.count() == 0 |
|
47 | ||
48 | ||
49 |
def test_apply_attribute_mapping_missing_attribute_logged( |
|
50 |
caplog, adapter, idp, saml_attributes, title_attribute, user |
|
51 |
): |
|
52 |
caplog.set_level('WARNING') |
|
53 |
saml_attributes['http://nice/attribute/givenName'] = [] |
|
54 |
adapter.provision_a2_attributes(user, idp, saml_attributes) |
|
55 |
assert re.match('.*no value.*first_name', caplog.records[-1].message) |
|
56 | ||
57 | ||
58 |
@pytest.mark.parametrize('action_name', ['add-role', 'toggle-role']) |
|
59 |
class TestAddRole: |
|
60 |
@pytest.fixture |
|
61 |
def idp(self, action_name, simple_role): |
|
62 |
authenticator = SAMLAuthenticator.objects.create( |
|
63 |
enabled=True, |
|
64 |
metadata='meta1.xml', |
|
65 |
slug='idp1', |
|
66 |
) |
|
67 |
AddRoleAction.objects.create(authenticator=authenticator, role=simple_role) |
|
68 |
return authenticator.settings |
|
69 | ||
70 |
@pytest.fixture |
|
71 |
def saml_attributes(self): |
|
72 |
return { |
|
73 |
'issuer': 'https://idp.com/', |
|
74 |
'name_id_content': 'xxx', |
|
75 |
'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT, |
|
76 |
} |
|
77 | ||
78 |
def test_lookup_user_success(self, adapter, simple_role, idp, saml_attributes): |
|
79 |
user = adapter.lookup_user(idp, saml_attributes) |
|
80 |
assert simple_role in user.roles.all() |
|
81 | ||
82 | ||
83 |
def test_apply_attribute_mapping_missing_attribute_exception( |
|
84 |
adapter, idp, saml_attributes, title_attribute, user, rf |
|
85 |
): |
|
86 |
saml_attributes['http://nice/attribute/givenName'] = [] |
|
87 |
SetAttributeAction.objects.filter(user_field='first_name').update(mandatory=True) |
|
88 |
with pytest.raises(MappingError, match='no value'): |
|
89 |
adapter.provision_a2_attributes(user, idp, saml_attributes) |
|
90 | ||
91 |
request = rf.get('/') |
|
92 |
request._messages = mock.Mock() |
|
93 |
adapter.request = request |
|
94 |
with pytest.raises(UserCreationError): |
|
95 |
adapter.finish_create_user(idp, saml_attributes, user) |
|
96 |
request._messages.add.assert_called_once_with( |
|
97 |
40, 'User creation failed: no value for attribute "first_name".', '' |
|
98 |
) |
tests/auth_saml/test_login.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import os |
|
18 |
from unittest import mock |
|
19 | ||
20 |
import pytest |
|
21 | ||
22 |
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator |
|
23 |
from authentic2_auth_saml.adapters import AuthenticAdapter |
|
24 |
from authentic2_auth_saml.models import SAMLAuthenticator |
|
25 | ||
26 | ||
27 |
@pytest.fixture |
|
28 |
def patched_adapter(monkeypatch): |
|
29 |
def load_idp(self, settings, order): |
|
30 |
settings['ENTITY_ID'] = 'idp1' |
|
31 |
return settings |
|
32 | ||
33 |
monkeypatch.setattr(AuthenticAdapter, 'load_idp', load_idp) |
|
34 | ||
35 | ||
36 |
def test_providers_on_login_page(db, app, settings): |
|
37 |
SAMLAuthenticator.objects.create( |
|
38 |
enabled=True, |
|
39 |
metadata='meta1.xml', |
|
40 |
slug='idp1', |
|
41 |
button_label='Test label', |
|
42 |
button_description='This is a test.', |
|
43 |
) |
|
44 | ||
45 |
response = app.get('/login/') |
|
46 |
assert response.pyquery('button[name="login-saml-idp1"]') |
|
47 |
assert not response.pyquery('button[name="login-saml-1"]') |
|
48 |
assert 'SAML' in response.text |
|
49 | ||
50 |
SAMLAuthenticator.objects.create(enabled=True, metadata='meta1.xml', slug='idp2') |
|
51 |
response = app.get('/login/') |
|
52 |
# two frontends should be present on login page |
|
53 |
assert response.pyquery('button[name="login-saml-idp1"]') |
|
54 |
assert response.pyquery('button[name="login-saml-idp2"]') |
|
55 |
assert 'Test label' in response.text |
|
56 |
assert 'This is a test.' in response.text |
|
57 | ||
58 | ||
59 |
def test_login_with_conditionnal_authenticators(db, app, settings, caplog): |
|
60 |
authenticator = SAMLAuthenticator.objects.create( |
|
61 |
enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1' |
|
62 |
) |
|
63 | ||
64 |
response = app.get('/login/') |
|
65 |
assert 'login-saml-idp1' in response |
|
66 | ||
67 |
authenticator.show_condition = 'remote_addr==\'0.0.0.0\'' |
|
68 |
authenticator.save() |
|
69 |
response = app.get('/login/') |
|
70 |
assert 'login-saml-idp1' not in response |
|
71 | ||
72 |
authenticator2 = SAMLAuthenticator.objects.create( |
|
73 |
enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp2' |
|
74 |
) |
|
75 |
response = app.get('/login/') |
|
76 |
assert 'login-saml-idp1' not in response |
|
77 |
assert 'login-saml-idp2' in response |
|
78 | ||
79 |
authenticator2.show_condition = 'remote_addr==\'0.0.0.0\'' |
|
80 |
authenticator2.save() |
|
81 |
response = app.get('/login/') |
|
82 |
assert 'login-saml-idp1' not in response |
|
83 |
assert 'login-saml-idp2' not in response |
|
84 | ||
85 | ||
86 |
def test_login_condition_dnsbl(db, app, settings, caplog): |
|
87 |
SAMLAuthenticator.objects.create( |
|
88 |
enabled=True, |
|
89 |
metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), |
|
90 |
slug='idp1', |
|
91 |
show_condition='remote_addr in dnsbl(\'dnswl.example.com\')', |
|
92 |
) |
|
93 |
SAMLAuthenticator.objects.create( |
|
94 |
enabled=True, |
|
95 |
metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), |
|
96 |
slug='idp2', |
|
97 |
show_condition='remote_addr not in dnsbl(\'dnswl.example.com\')', |
|
98 |
) |
|
99 |
with mock.patch('authentic2.utils.evaluate.check_dnsbl', return_value=True): |
|
100 |
response = app.get('/login/') |
|
101 |
assert 'login-saml-idp1' in response |
|
102 |
assert 'login-saml-idp2' not in response |
|
103 | ||
104 | ||
105 |
def test_login_autorun(db, app, settings, patched_adapter): |
|
106 |
response = app.get('/login/') |
|
107 | ||
108 |
authenticator = SAMLAuthenticator.objects.create( |
|
109 |
enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1' |
|
110 |
) |
|
111 |
# hide password block |
|
112 |
LoginPasswordAuthenticator.objects.update_or_create( |
|
113 |
slug='password-authenticator', defaults={'enabled': False} |
|
114 |
) |
|
115 |
response = app.get('/login/', status=302) |
|
116 |
assert '/accounts/saml/login/?entityID=' in response['Location'] |
|
117 | ||
118 |
authenticator.slug = 'slug_with_underscore' |
|
119 |
authenticator.save() |
|
120 |
response = app.get('/login/', status=302) |
|
121 |
assert '/accounts/saml/login/?entityID=' in response['Location'] |
tests/auth_saml/test_manager.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from mellon.models import Issuer, UserSAMLIdentifier |
|
18 | ||
19 |
from ..utils import login |
|
20 | ||
21 | ||
22 |
def test_manager_user_sidebar(app, superuser, simple_user): |
|
23 |
login(app, superuser, '/manage/') |
|
24 |
response = app.get('/manage/users/%s/' % simple_user.id) |
|
25 |
assert 'SAML' not in response |
|
26 | ||
27 |
issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/') |
|
28 |
UserSAMLIdentifier.objects.create(user=simple_user, issuer=issuer1, name_id='1234') |
|
29 | ||
30 |
response = app.get('/manage/users/%s/' % simple_user.id) |
|
31 |
assert 'SAML' in response |
|
32 |
assert 'https://idp1.com/' in response |
|
33 |
assert '1234' in response |
tests/auth_saml/test_migrations.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import os |
|
18 | ||
19 | ||
20 |
def test_saml_authenticator_data_migration(migration, settings): |
|
21 |
app = 'authentic2_auth_saml' |
|
22 |
migrate_from = [(app, '0001_initial')] |
|
23 |
migrate_to = [(app, '0002_auto_20220608_1559')] |
|
24 | ||
25 |
old_apps = migration.before(migrate_from) |
|
26 |
SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator') |
|
27 | ||
28 |
settings.A2_AUTH_SAML_ENABLE = True |
|
29 |
settings.MELLON_METADATA_CACHE_TIME = 42 |
|
30 |
settings.MELLON_METADATA_HTTP_TIMEOUT = 42 |
|
31 |
settings.MELLON_PROVISION = False |
|
32 |
settings.MELLON_VERIFY_SSL_CERTIFICATE = True |
|
33 |
settings.MELLON_TRANSIENT_FEDERATION_ATTRIBUTE = None |
|
34 |
settings.MELLON_USERNAME_TEMPLATE = 'test' |
|
35 |
settings.MELLON_NAME_ID_POLICY_ALLOW_CREATE = False |
|
36 |
settings.MELLON_FORCE_AUTHN = True |
|
37 |
settings.MELLON_ADD_AUTHNREQUEST_NEXT_URL_EXTENSION = False |
|
38 |
settings.MELLON_GROUP_ATTRIBUTE = 'role' |
|
39 |
settings.MELLON_CREATE_GROUP = True |
|
40 |
settings.MELLON_ERROR_URL = 'https://example.com/error/' |
|
41 |
settings.MELLON_AUTHN_CLASSREF = ('class1', 'class2') |
|
42 |
settings.MELLON_LOGIN_HINTS = ['hint1', 'hint2'] |
|
43 |
settings.AUTH_FRONTENDS_KWARGS = { |
|
44 |
'saml': { |
|
45 |
'priority': 1, |
|
46 |
'show_condition': { |
|
47 |
'0': 'first condition', |
|
48 |
'1': 'second condition', |
|
49 |
}, |
|
50 |
} |
|
51 |
} |
|
52 |
settings.MELLON_IDENTITY_PROVIDERS = [ |
|
53 |
{ |
|
54 |
'METADATA': os.path.join(os.path.dirname(__file__), 'metadata.xml'), |
|
55 |
'REALM': 'test', |
|
56 |
'METADATA_CACHE_TIME': 43, |
|
57 |
'METADATA_HTTP_TIMEOUT': 43, |
|
58 |
'PROVISION': True, |
|
59 |
'LOOKUP_BY_ATTRIBUTES': [], |
|
60 |
}, |
|
61 |
{ |
|
62 |
'METADATA_PATH': os.path.join(os.path.dirname(__file__), 'metadata.xml'), |
|
63 |
'NAME_ID_POLICY_ALLOW_CREATE': True, |
|
64 |
'FORCE_AUTHN': False, |
|
65 |
'ADD_AUTHNREQUEST_NEXT_URL_EXTENSION': True, |
|
66 |
'A2_ATTRIBUTE_MAPPING': [ |
|
67 |
{ |
|
68 |
'attribute': 'email', |
|
69 |
'saml_attribute': 'mail', |
|
70 |
}, |
|
71 |
], |
|
72 |
'LOOKUP_BY_ATTRIBUTES': [{'saml_attribute': 'email', 'user_field': 'email'}], |
|
73 |
}, |
|
74 |
{ |
|
75 |
'METADATA_URL': 'https://example.com/metadata.xml', |
|
76 |
'SLUG': 'third', |
|
77 |
'ATTRIBUTE_MAPPING': {'email': 'attributes[mail][0]'}, |
|
78 |
'SUPERUSER_MAPPING': {'roles': 'Admin'}, |
|
79 |
}, |
|
80 |
] |
|
81 | ||
82 |
new_apps = migration.apply(migrate_to) |
|
83 |
SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator') |
|
84 |
first_authenticator, second_authenticator, third_authenticator = SAMLAuthenticator.objects.all() |
|
85 |
assert first_authenticator.slug == '0' |
|
86 |
assert first_authenticator.order == 1 |
|
87 |
assert first_authenticator.show_condition == 'first condition' |
|
88 |
assert first_authenticator.enabled is True |
|
89 |
assert first_authenticator.metadata_path == os.path.join(os.path.dirname(__file__), 'metadata.xml') |
|
90 |
assert first_authenticator.metadata_url == '' |
|
91 |
assert first_authenticator.metadata_cache_time == 43 |
|
92 |
assert first_authenticator.metadata_http_timeout == 43 |
|
93 |
assert first_authenticator.provision is True |
|
94 |
assert first_authenticator.verify_ssl_certificate is True |
|
95 |
assert first_authenticator.transient_federation_attribute == '' |
|
96 |
assert first_authenticator.realm == 'test' |
|
97 |
assert first_authenticator.username_template == 'test' |
|
98 |
assert first_authenticator.name_id_policy_format == '' |
|
99 |
assert first_authenticator.name_id_policy_allow_create is False |
|
100 |
assert first_authenticator.force_authn is True |
|
101 |
assert first_authenticator.add_authnrequest_next_url_extension is False |
|
102 |
assert first_authenticator.group_attribute == 'role' |
|
103 |
assert first_authenticator.create_group is True |
|
104 |
assert first_authenticator.error_url == 'https://example.com/error/' |
|
105 |
assert first_authenticator.error_redirect_after_timeout == 120 |
|
106 |
assert first_authenticator.authn_classref == 'class1, class2' |
|
107 |
assert first_authenticator.login_hints == 'hint1, hint2' |
|
108 |
assert first_authenticator.lookup_by_attributes == [] |
|
109 |
assert first_authenticator.a2_attribute_mapping == [] |
|
110 |
assert first_authenticator.attribute_mapping == {} |
|
111 |
assert first_authenticator.superuser_mapping == {} |
|
112 | ||
113 |
assert second_authenticator.slug == '1' |
|
114 |
assert second_authenticator.order == 1 |
|
115 |
assert second_authenticator.show_condition == 'second condition' |
|
116 |
assert second_authenticator.enabled is True |
|
117 |
assert second_authenticator.metadata_path == os.path.join(os.path.dirname(__file__), 'metadata.xml') |
|
118 |
assert second_authenticator.metadata_url == '' |
|
119 |
assert second_authenticator.metadata_cache_time == 42 |
|
120 |
assert second_authenticator.metadata_http_timeout == 42 |
|
121 |
assert second_authenticator.provision is False |
|
122 |
assert second_authenticator.verify_ssl_certificate is True |
|
123 |
assert second_authenticator.transient_federation_attribute == '' |
|
124 |
assert second_authenticator.realm == 'saml' |
|
125 |
assert second_authenticator.username_template == 'test' |
|
126 |
assert second_authenticator.name_id_policy_format == '' |
|
127 |
assert second_authenticator.name_id_policy_allow_create is True |
|
128 |
assert second_authenticator.force_authn is False |
|
129 |
assert second_authenticator.add_authnrequest_next_url_extension is True |
|
130 |
assert second_authenticator.group_attribute == 'role' |
|
131 |
assert second_authenticator.create_group is True |
|
132 |
assert second_authenticator.error_url == 'https://example.com/error/' |
|
133 |
assert second_authenticator.error_redirect_after_timeout == 120 |
|
134 |
assert second_authenticator.authn_classref == 'class1, class2' |
|
135 |
assert second_authenticator.login_hints == 'hint1, hint2' |
|
136 |
assert second_authenticator.lookup_by_attributes == [{'saml_attribute': 'email', 'user_field': 'email'}] |
|
137 |
assert second_authenticator.a2_attribute_mapping == [ |
|
138 |
{ |
|
139 |
'attribute': 'email', |
|
140 |
'saml_attribute': 'mail', |
|
141 |
}, |
|
142 |
] |
|
143 |
assert first_authenticator.attribute_mapping == {} |
|
144 |
assert first_authenticator.superuser_mapping == {} |
|
145 | ||
146 |
assert third_authenticator.slug == 'third' |
|
147 |
assert third_authenticator.order == 1 |
|
148 |
assert third_authenticator.show_condition == '' |
|
149 |
assert third_authenticator.enabled is True |
|
150 |
assert third_authenticator.metadata_path == '' |
|
151 |
assert third_authenticator.metadata_url == 'https://example.com/metadata.xml' |
|
152 |
assert third_authenticator.metadata_cache_time == 42 |
|
153 |
assert third_authenticator.metadata_http_timeout == 42 |
|
154 |
assert third_authenticator.provision is False |
|
155 |
assert third_authenticator.verify_ssl_certificate is True |
|
156 |
assert third_authenticator.transient_federation_attribute == '' |
|
157 |
assert third_authenticator.realm == 'saml' |
|
158 |
assert third_authenticator.username_template == 'test' |
|
159 |
assert third_authenticator.name_id_policy_format == '' |
|
160 |
assert third_authenticator.name_id_policy_format == '' |
|
161 |
assert third_authenticator.name_id_policy_allow_create is False |
|
162 |
assert third_authenticator.force_authn is True |
|
163 |
assert third_authenticator.group_attribute == 'role' |
|
164 |
assert third_authenticator.create_group is True |
|
165 |
assert third_authenticator.error_url == 'https://example.com/error/' |
|
166 |
assert third_authenticator.error_redirect_after_timeout == 120 |
|
167 |
assert third_authenticator.authn_classref == 'class1, class2' |
|
168 |
assert third_authenticator.login_hints == 'hint1, hint2' |
|
169 |
assert third_authenticator.lookup_by_attributes == [ |
|
170 |
{'saml_attribute': 'email', 'user_field': 'email', 'ignore-case': True}, |
|
171 |
{'saml_attribute': 'username', 'user_field': 'username'}, |
|
172 |
] |
|
173 |
assert third_authenticator.a2_attribute_mapping == [] |
|
174 |
assert third_authenticator.attribute_mapping == {'email': 'attributes[mail][0]'} |
|
175 |
assert third_authenticator.superuser_mapping == {'roles': 'Admin'} |
|
176 | ||
177 | ||
178 |
def test_saml_authenticator_data_migration_empty_configuration(migration, settings): |
|
179 |
app = 'authentic2_auth_saml' |
|
180 |
migrate_from = [(app, '0001_initial')] |
|
181 |
migrate_to = [(app, '0002_auto_20220608_1559')] |
|
182 | ||
183 |
old_apps = migration.before(migrate_from) |
|
184 |
SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator') |
|
185 | ||
186 |
new_apps = migration.apply(migrate_to) |
|
187 |
SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator') |
|
188 |
assert not SAMLAuthenticator.objects.exists() |
|
189 | ||
190 | ||
191 |
def test_saml_authenticator_data_migration_bad_settings(migration, settings): |
|
192 |
app = 'authentic2_auth_saml' |
|
193 |
migrate_from = [(app, '0001_initial')] |
|
194 |
migrate_to = [(app, '0002_auto_20220608_1559')] |
|
195 | ||
196 |
old_apps = migration.before(migrate_from) |
|
197 |
SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator') |
|
198 | ||
199 |
settings.AUTH_FRONTENDS_KWARGS = {"saml": {"priority": None, "show_condition": None}} |
|
200 |
settings.MELLON_METADATA_CACHE_TIME = 2**16 |
|
201 |
settings.MELLON_METADATA_HTTP_TIMEOUT = -1 |
|
202 |
settings.MELLON_PROVISION = None |
|
203 |
settings.MELLON_USERNAME_TEMPLATE = 42 |
|
204 |
settings.MELLON_GROUP_ATTRIBUTE = None |
|
205 |
settings.MELLON_ERROR_URL = 'a' * 500 |
|
206 |
settings.MELLON_AUTHN_CLASSREF = 'not-a-list' |
|
207 |
settings.MELLON_IDENTITY_PROVIDERS = [ |
|
208 |
{ |
|
209 |
'METADATA': os.path.join(os.path.dirname(__file__), 'metadata.xml'), |
|
210 |
'ERROR_REDIRECT_AFTER_TIMEOUT': -1, |
|
211 |
'SUPERUSER_MAPPING': 'not-a-dict', |
|
212 |
}, |
|
213 |
] |
|
214 | ||
215 |
new_apps = migration.apply(migrate_to) |
|
216 |
SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator') |
|
217 |
authenticator = SAMLAuthenticator.objects.get() |
|
218 |
assert authenticator.slug == '0' |
|
219 |
assert authenticator.order == 3 |
|
220 |
assert authenticator.show_condition == '' |
|
221 |
assert authenticator.enabled is False |
|
222 |
assert authenticator.metadata_cache_time == 3600 |
|
223 |
assert authenticator.metadata_http_timeout == 10 |
|
224 |
assert authenticator.provision is True |
|
225 |
assert authenticator.username_template == '{attributes[name_id_content]}@{realm}' |
|
226 |
assert authenticator.group_attribute == '' |
|
227 |
assert authenticator.error_url == 'a' * 200 |
|
228 |
assert authenticator.error_redirect_after_timeout == 120 |
|
229 |
assert authenticator.authn_classref == '' |
|
230 |
assert authenticator.superuser_mapping == {} |
|
231 | ||
232 | ||
233 |
def test_saml_authenticator_data_migration_json_fields(migration, settings): |
|
234 |
migrate_from = [ |
|
235 |
( |
|
236 |
'authentic2_auth_saml', |
|
237 |
'0005_addroleaction_renameattributeaction_samlattributelookup_setattributeaction', |
|
238 |
), |
|
239 |
('a2_rbac', '0029_use_unique_constraints'), |
|
240 |
] |
|
241 |
migrate_to = [ |
|
242 |
('authentic2_auth_saml', '0006_migrate_jsonfields'), |
|
243 |
('a2_rbac', '0029_use_unique_constraints'), |
|
244 |
] |
|
245 | ||
246 |
old_apps = migration.before(migrate_from) |
|
247 |
SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
248 |
Role = old_apps.get_model('a2_rbac', 'Role') |
|
249 |
OU = old_apps.get_model('a2_rbac', 'OrganizationalUnit') |
|
250 | ||
251 |
ou = OU.objects.create(name='Test OU', slug='test-ou') |
|
252 |
role = Role.objects.create(name='Test role', slug='test-role', ou=ou) |
|
253 | ||
254 |
SAMLAuthenticator.objects.create( |
|
255 |
metadata='meta1.xml', |
|
256 |
slug='idp1', |
|
257 |
lookup_by_attributes=[ |
|
258 |
{'saml_attribute': 'email', 'user_field': 'email'}, |
|
259 |
{'saml_attribute': 'saml_name', 'user_field': 'first_name', 'ignore-case': True}, |
|
260 |
], |
|
261 |
a2_attribute_mapping=[ |
|
262 |
{ |
|
263 |
'attribute': 'email', |
|
264 |
'saml_attribute': 'mail', |
|
265 |
'mandatory': True, |
|
266 |
}, |
|
267 |
{'action': 'rename', 'from': 'a' * 1025, 'to': 'first_name'}, |
|
268 |
{ |
|
269 |
'attribute': 'first_name', |
|
270 |
'saml_attribute': 'first_name', |
|
271 |
}, |
|
272 |
{ |
|
273 |
'attribute': 'invalid', |
|
274 |
'saml_attribute': '', |
|
275 |
}, |
|
276 |
{ |
|
277 |
'attribute': 'invalid', |
|
278 |
'saml_attribute': None, |
|
279 |
}, |
|
280 |
{ |
|
281 |
'attribute': 'invalid', |
|
282 |
}, |
|
283 |
{ |
|
284 |
'action': 'add-role', |
|
285 |
'role': { |
|
286 |
'name': role.name, |
|
287 |
'ou': { |
|
288 |
'name': role.ou.name, |
|
289 |
}, |
|
290 |
}, |
|
291 |
'condition': "roles == 'A'", |
|
292 |
}, |
|
293 |
], |
|
294 |
) |
|
295 | ||
296 |
new_apps = migration.apply(migrate_to) |
|
297 |
SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
298 |
authenticator = SAMLAuthenticator.objects.get() |
|
299 | ||
300 |
attribute_lookup1, attribute_lookup2 = authenticator.attribute_lookups.all().order_by('pk') |
|
301 |
assert attribute_lookup1.saml_attribute == 'email' |
|
302 |
assert attribute_lookup1.user_field == 'email' |
|
303 |
assert attribute_lookup1.ignore_case is False |
|
304 |
assert attribute_lookup2.saml_attribute == 'saml_name' |
|
305 |
assert attribute_lookup2.user_field == 'first_name' |
|
306 |
assert attribute_lookup2.ignore_case is True |
|
307 | ||
308 |
set_attribute1, set_attribute2 = authenticator.set_attribute_actions.all().order_by('pk') |
|
309 |
assert set_attribute1.attribute == 'email' |
|
310 |
assert set_attribute1.saml_attribute == 'mail' |
|
311 |
assert set_attribute1.mandatory is True |
|
312 |
assert set_attribute2.attribute == 'first_name' |
|
313 |
assert set_attribute2.saml_attribute == 'first_name' |
|
314 |
assert set_attribute2.mandatory is False |
|
315 | ||
316 |
rename_attribute = authenticator.rename_attribute_actions.get() |
|
317 |
assert rename_attribute.from_name == 'a' * 1024 |
|
318 |
assert rename_attribute.to_name == 'first_name' |
|
319 | ||
320 |
add_role = authenticator.add_role_actions.get() |
|
321 |
assert add_role.role.pk == role.pk |
|
322 |
assert add_role.condition == "roles == 'A'" |
|
323 |
assert add_role.mandatory is False |
|
324 | ||
325 | ||
326 |
def test_saml_authenticator_data_migration_json_fields_log_errors(migration, settings, caplog): |
|
327 |
migrate_from = [ |
|
328 |
( |
|
329 |
'authentic2_auth_saml', |
|
330 |
'0005_addroleaction_renameattributeaction_samlattributelookup_setattributeaction', |
|
331 |
), |
|
332 |
('a2_rbac', '0029_use_unique_constraints'), |
|
333 |
] |
|
334 |
migrate_to = [ |
|
335 |
('authentic2_auth_saml', '0006_migrate_jsonfields'), |
|
336 |
('a2_rbac', '0029_use_unique_constraints'), |
|
337 |
] |
|
338 | ||
339 |
old_apps = migration.before(migrate_from) |
|
340 |
SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
341 | ||
342 |
SAMLAuthenticator.objects.create( |
|
343 |
metadata='meta1.xml', |
|
344 |
slug='idp1', |
|
345 |
lookup_by_attributes=[{'saml_attribute': 'email', 'user_field': 'email'}], |
|
346 |
a2_attribute_mapping=['bad'], |
|
347 |
) |
|
348 | ||
349 |
new_apps = migration.apply(migrate_to) |
|
350 |
SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
351 | ||
352 |
authenticator = SAMLAuthenticator.objects.get() |
|
353 |
assert not authenticator.attribute_lookups.exists() |
|
354 | ||
355 |
assert caplog.messages == [ |
|
356 |
'could not create related objects for authenticator SAMLAuthenticator object (%s)' % authenticator.pk, |
|
357 |
'attribute mapping for SAMLAuthenticator object (%s): ["bad"]' % authenticator.pk, |
|
358 |
'lookup by attributes for SAMLAuthenticator object (%s): [{"user_field": "email", "saml_attribute": "email"}]' |
|
359 |
% authenticator.pk, |
|
360 |
] |
|
361 | ||
362 | ||
363 |
def test_saml_authenticator_data_migration_rename_attributes(migration, settings): |
|
364 |
migrate_from = [('authentic2_auth_saml', '0008_auto_20220913_1105')] |
|
365 |
migrate_to = [('authentic2_auth_saml', '0009_statically_rename_attributes')] |
|
366 | ||
367 |
old_apps = migration.before(migrate_from) |
|
368 |
SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
369 |
RenameAttributeAction = old_apps.get_model('authentic2_auth_saml', 'RenameAttributeAction') |
|
370 |
SetAttributeAction = old_apps.get_model('authentic2_auth_saml', 'SetAttributeAction') |
|
371 |
SAMLAttributeLookup = old_apps.get_model('authentic2_auth_saml', 'SAMLAttributeLookup') |
|
372 | ||
373 |
authenticator = SAMLAuthenticator.objects.create(slug='idp1') |
|
374 |
RenameAttributeAction.objects.create( |
|
375 |
authenticator=authenticator, from_name='http://nice/attribute/givenName', to_name='first_name' |
|
376 |
) |
|
377 |
SAMLAttributeLookup.objects.create( |
|
378 |
authenticator=authenticator, user_field='first_name', saml_attribute='first_name' |
|
379 |
) |
|
380 |
SAMLAttributeLookup.objects.create( |
|
381 |
authenticator=authenticator, user_field='title', saml_attribute='title' |
|
382 |
) |
|
383 |
SetAttributeAction.objects.create( |
|
384 |
authenticator=authenticator, user_field='first_name', saml_attribute='first_name' |
|
385 |
) |
|
386 |
SetAttributeAction.objects.create(authenticator=authenticator, user_field='title', saml_attribute='title') |
|
387 | ||
388 |
new_apps = migration.apply(migrate_to) |
|
389 |
SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
390 |
authenticator = SAMLAuthenticator.objects.get() |
|
391 | ||
392 |
attribute_lookup1, attribute_lookup2 = authenticator.attribute_lookups.all().order_by('pk') |
|
393 |
assert attribute_lookup1.saml_attribute == 'http://nice/attribute/givenName' |
|
394 |
assert attribute_lookup1.user_field == 'first_name' |
|
395 |
assert attribute_lookup2.saml_attribute == 'title' |
|
396 |
assert attribute_lookup2.user_field == 'title' |
|
397 | ||
398 |
set_attribute1, set_attribute2 = authenticator.set_attribute_actions.all().order_by('pk') |
|
399 |
assert set_attribute1.saml_attribute == 'http://nice/attribute/givenName' |
|
400 |
assert set_attribute1.user_field == 'first_name' |
|
401 |
assert set_attribute2.saml_attribute == 'title' |
|
402 |
assert set_attribute2.user_field == 'title' |
|
0 |
- |