0001-use-unicode_literals-34008.patch
mellon/adapters.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
from xml.etree import ElementTree as ET |
17 | 19 |
import hashlib |
20 | ||
18 | 21 |
import logging |
19 | 22 |
import os |
20 | 23 |
import threading |
... | ... | |
230 | 233 |
try: |
231 | 234 |
doc = ET.fromstring(metadata) |
232 | 235 |
except (TypeError, ET.ParseError): |
233 |
logger.error(u'METADATA of %d-th idp is invalid', i)
|
|
236 |
logger.error('METADATA of %d-th idp is invalid', i) |
|
234 | 237 |
return None |
235 | 238 |
if doc.tag != '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF: |
236 |
logger.error(u'METADATA of %d-th idp has no EntityDescriptor root tag', i)
|
|
239 |
logger.error('METADATA of %d-th idp has no EntityDescriptor root tag', i) |
|
237 | 240 |
return None |
238 | 241 | |
239 | 242 |
if 'entityID' not in doc.attrib: |
240 | 243 |
logger.error( |
241 |
u'METADATA of %d-th idp has no entityID attribute on its root tag', i)
|
|
244 |
'METADATA of %d-th idp has no entityID attribute on its root tag', i) |
|
242 | 245 |
return None |
243 | 246 |
return doc.attrib['entityID'] |
244 | 247 | |
... | ... | |
264 | 267 |
username = force_text(username_template).format( |
265 | 268 |
realm=realm, attributes=saml_attributes, idp=idp)[:30] |
266 | 269 |
except ValueError: |
267 |
logger.error(u'invalid username template %r', username_template)
|
|
270 |
logger.error('invalid username template %r', username_template) |
|
268 | 271 |
except (AttributeError, KeyError, IndexError) as e: |
269 | 272 |
logger.error( |
270 |
u'invalid reference in username template %r: %s', username_template, e)
|
|
273 |
'invalid reference in username template %r: %s', username_template, e) |
|
271 | 274 |
except Exception: |
272 |
logger.exception(u'unknown error when formatting username')
|
|
275 |
logger.exception('unknown error when formatting username') |
|
273 | 276 |
else: |
274 | 277 |
return username |
275 | 278 | |
... | ... | |
380 | 383 |
logger.debug('looking for users by attribute %r and user field %r with value %r: not found', |
381 | 384 |
saml_attribute, user_field, value) |
382 | 385 |
continue |
383 |
logger.info(u'looking for user by attribute %r and user field %r with value %r: found %s',
|
|
386 |
logger.info('looking for user by attribute %r and user field %r with value %r: found %s', |
|
384 | 387 |
saml_attribute, user_field, value, display_truncated_list(users_found)) |
385 | 388 |
users.update(users_found) |
386 | 389 |
if len(users) == 1: |
387 | 390 |
user = list(users)[0] |
388 |
logger.info(u'looking for user by attributes %r: found user %s', lookup_by_attributes, user)
|
|
391 |
logger.info('looking for user by attributes %r: found user %s', lookup_by_attributes, user) |
|
389 | 392 |
return user |
390 | 393 |
elif len(users) > 1: |
391 |
logger.warning(u'looking for user by attributes %r: too many users found(%d), failing',
|
|
394 |
logger.warning('looking for user by attributes %r: too many users found(%d), failing', |
|
392 | 395 |
lookup_by_attributes, len(users)) |
393 | 396 |
return None |
394 | 397 | |
... | ... | |
413 | 416 |
try: |
414 | 417 |
value = force_text(tpl).format(realm=realm, attributes=saml_attributes, idp=idp) |
415 | 418 |
except ValueError: |
416 |
logger.warning(u'invalid attribute mapping template %r', tpl)
|
|
419 |
logger.warning('invalid attribute mapping template %r', tpl) |
|
417 | 420 |
except (AttributeError, KeyError, IndexError, ValueError) as e: |
418 | 421 |
logger.warning( |
419 |
u'invalid reference in attribute mapping template %r: %s', tpl, e)
|
|
422 |
'invalid reference in attribute mapping template %r: %s', tpl, e) |
|
420 | 423 |
else: |
421 | 424 |
model_field = user._meta.get_field(field) |
422 | 425 |
if hasattr(model_field, 'max_length'): |
... | ... | |
425 | 428 |
old_value = getattr(user, field) |
426 | 429 |
setattr(user, field, value) |
427 | 430 |
attribute_set = True |
428 |
logger.info(u'set field %s of user %s to value %r (old value %r)', field, user, value, old_value)
|
|
431 |
logger.info('set field %s of user %s to value %r (old value %r)', field, user, value, old_value) |
|
429 | 432 |
if attribute_set: |
430 | 433 |
user.save() |
431 | 434 | |
... | ... | |
478 | 481 |
groups.append(group) |
479 | 482 |
for group in Group.objects.filter(pk__in=[g.pk for g in groups]).exclude(user=user): |
480 | 483 |
logger.info( |
481 |
u'adding group %s (%s) to user %s (%s)', group, group.pk, user, user.pk)
|
|
484 |
'adding group %s (%s) to user %s (%s)', group, group.pk, user, user.pk) |
|
482 | 485 |
User.groups.through.objects.get_or_create(group=group, user=user) |
483 | 486 |
qs = User.groups.through.objects.exclude( |
484 | 487 |
group__pk__in=[g.pk for g in groups]).filter(user=user) |
485 | 488 |
for rel in qs: |
486 |
logger.info(u'removing group %s (%s) from user %s (%s)', rel.group, rel.group.pk, rel.user, rel.user.pk)
|
|
489 |
logger.info('removing group %s (%s) from user %s (%s)', rel.group, rel.group.pk, rel.user, rel.user.pk) |
|
487 | 490 |
qs.delete() |
mellon/backends.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
from django.contrib.auth.backends import ModelBackend |
17 | 19 | |
18 | 20 |
from . import utils |
mellon/middleware.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
from django.utils.http import urlencode |
17 | 19 |
from django.http import HttpResponseRedirect |
18 | 20 |
from django.core.urlresolvers import reverse |
mellon/models.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
from django.db import models |
17 | 19 |
from django.utils.translation import ugettext_lazy as _ |
18 | 20 |
from django.conf import settings |
mellon/urls.py | ||
---|---|---|
1 |
from __future__ import unicode_literals |
|
2 | ||
1 | 3 |
from django.conf.urls import url |
2 | 4 |
import django |
3 | 5 |
mellon/utils.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
import logging |
17 | 19 |
import datetime |
18 | 20 |
import importlib |
... | ... | |
99 | 101 |
try: |
100 | 102 |
server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, idp['METADATA']) |
101 | 103 |
except lasso.Error as e: |
102 |
logger.error(u'bad metadata in idp %s, %s', idp['ENTITY_ID'], e)
|
|
104 |
logger.error('bad metadata in idp %s, %s', idp['ENTITY_ID'], e) |
|
103 | 105 |
cache[root] = server |
104 | 106 |
settings._MELLON_SERVER_CACHE = cache |
105 | 107 |
return settings._MELLON_SERVER_CACHE.get(root) |
mellon/views.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
import logging |
17 | 19 |
import requests |
18 | 20 |
import lasso |
... | ... | |
114 | 116 | |
115 | 117 |
def show_message_status_is_not_success(self, profile, prefix): |
116 | 118 |
status_codes, idp_message = utils.get_status_codes_and_message(profile) |
117 |
args = [u'%s: status is not success codes: %r', prefix, status_codes]
|
|
119 |
args = ['%s: status is not success codes: %r', prefix, status_codes] |
|
118 | 120 |
if idp_message: |
119 |
args[0] += u' message: %s'
|
|
121 |
args[0] += ' message: %s' |
|
120 | 122 |
args.append(idp_message) |
121 | 123 |
self.log.warning(*args) |
122 | 124 | |
... | ... | |
196 | 198 |
for at in ats.attribute: |
197 | 199 |
values = attributes.setdefault(at.name, []) |
198 | 200 |
for value in at.attributeValue: |
199 |
content = [any.exportToXml() for any in value.any]
|
|
200 |
content = ''.join(content) |
|
201 |
values.append(lasso_decode(content))
|
|
201 |
contents = [lasso_decode(any.exportToXml()) for any in value.any]
|
|
202 |
content = ''.join(contents)
|
|
203 |
values.append(content)
|
|
202 | 204 |
attributes['issuer'] = login.remoteProviderId |
203 | 205 |
if login.nameIdentifier: |
204 | 206 |
name_id = login.nameIdentifier |
... | ... | |
295 | 297 |
try: |
296 | 298 |
login.initRequest(message, method) |
297 | 299 |
except lasso.ProfileInvalidArtifactError: |
298 |
self.log.warning(u'artifact is malformed %r', artifact)
|
|
299 |
return HttpResponseBadRequest(u'artifact is malformed %r' % artifact)
|
|
300 |
self.log.warning('artifact is malformed %r', artifact) |
|
301 |
return HttpResponseBadRequest('artifact is malformed %r' % artifact) |
|
300 | 302 |
except lasso.ServerProviderNotFoundError: |
301 | 303 |
self.log.warning('no entity id found for artifact %s', artifact) |
302 | 304 |
return HttpResponseBadRequest( |
... | ... | |
425 | 427 |
# lasso>2.5.1 introduced a better API |
426 | 428 |
if hasattr(authn_request.extensions, 'any'): |
427 | 429 |
authn_request.extensions.any = ( |
428 |
'<eo:next_url xmlns:eo="https://www.entrouvert.com/">%s</eo:next_url>' % eo_next_url,) |
|
430 |
str('<eo:next_url xmlns:eo="https://www.entrouvert.com/">%s</eo:next_url>' % eo_next_url), |
|
431 |
) |
|
429 | 432 |
else: |
430 | 433 |
authn_request.extensions.setOriginalXmlnode( |
431 |
'''<samlp:Extensions |
|
434 |
str('''<samlp:Extensions
|
|
432 | 435 |
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" |
433 | 436 |
xmlns:eo="https://www.entrouvert.com/"> |
434 | 437 |
<eo:next_url>%s</eo:next_url> |
435 |
</samlp:Extensions>''' % eo_next_url |
|
438 |
</samlp:Extensions>''' % eo_next_url)
|
|
436 | 439 |
) |
437 | 440 |
self.set_next_url(next_url) |
438 | 441 |
self.add_login_hints(idp, authn_request, request=request, next_url=next_url) |
... | ... | |
502 | 505 |
self.log.warning('error validating logout request: %r' % e) |
503 | 506 |
issuer = request.session.get('mellon_session', {}).get('issuer') |
504 | 507 |
if issuer == logout.remoteProviderId: |
505 |
self.log.info(u'user logged out by IdP SLO request')
|
|
508 |
self.log.info('user logged out by IdP SLO request') |
|
506 | 509 |
auth.logout(request) |
507 | 510 |
try: |
508 | 511 |
logout.buildResponseMsg() |
... | ... | |
539 | 542 |
# set next_url after local logout, as the session is wiped by auth.logout |
540 | 543 |
if logout: |
541 | 544 |
self.set_next_url(next_url) |
542 |
self.log.info(u'user logged out, SLO request sent to IdP')
|
|
545 |
self.log.info('user logged out, SLO request sent to IdP') |
|
543 | 546 |
else: |
544 | 547 |
self.log.warning('logout refused referer %r is not of the same origin', referer) |
545 | 548 |
return HttpResponseRedirect(next_url) |
tests/test_default_adapter.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
import datetime |
17 | 19 |
import re |
18 | 20 |
import lasso |
... | ... | |
120 | 122 |
def test_provision_user_attributes(settings, django_user_model, idp, saml_attributes, caplog): |
121 | 123 |
settings.MELLON_IDENTITY_PROVIDERS = [idp] |
122 | 124 |
settings.MELLON_ATTRIBUTE_MAPPING = { |
123 |
'email': u'{attributes[email][0]}',
|
|
124 |
'first_name': u'{attributes[first_name][0]}',
|
|
125 |
'last_name': u'{attributes[last_name][0]}',
|
|
125 |
'email': '{attributes[email][0]}', |
|
126 |
'first_name': '{attributes[first_name][0]}', |
|
127 |
'last_name': '{attributes[last_name][0]}', |
|
126 | 128 |
} |
127 | 129 |
user = SAMLBackend().authenticate(saml_attributes=saml_attributes) |
128 | 130 |
assert user.username == 'x' * 30 |
... | ... | |
205 | 207 |
assert len(caplog.records) == 4 |
206 | 208 |
assert 'created new user' in caplog.text |
207 | 209 |
assert 'set field first_name' in caplog.text |
208 |
assert 'to value %r ' % (u'y' * 30) in caplog.text
|
|
210 |
assert 'to value %r ' % ('y' * 30) in caplog.text |
|
209 | 211 |
assert 'set field last_name' in caplog.text |
210 | 212 |
assert 'set field email' in caplog.text |
211 | 213 |
tests/test_sso_slo.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
18 |
import datetime |
|
16 | 19 |
import re |
17 | 20 |
import base64 |
18 | 21 |
import zlib |
... | ... | |
25 | 28 |
from django.core.urlresolvers import reverse |
26 | 29 |
from django.utils import six |
27 | 30 |
from django.utils.six.moves.urllib import parse as urlparse |
31 |
from django.utils.encoding import force_str |
|
28 | 32 | |
29 | 33 |
from mellon.utils import create_metadata |
30 | 34 | |
... | ... | |
100 | 104 |
pass |
101 | 105 |
else: |
102 | 106 |
login.buildAssertion(lasso.SAML_AUTHENTICATION_METHOD_PASSWORD, |
103 |
"FIXME", |
|
104 |
"FIXME", |
|
105 |
"FIXME", |
|
106 |
"FIXME") |
|
107 |
datetime.datetime.now().isoformat(), |
|
108 |
None, |
|
109 |
datetime.datetime.now().isoformat(), |
|
110 |
datetime.datetime.now().isoformat()) |
|
111 | ||
112 |
def add_attribute(name, *values, **kwargs): |
|
113 |
fmt = kwargs.get('fmt', lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC) |
|
114 |
statements = login.response.assertion[0].attributeStatement or [lasso.Saml2AttributeStatement()] |
|
115 |
statement = statements[0] |
|
116 |
login.response.assertion[0].attributeStatement = statements |
|
117 |
attributes = list(statement.attribute) |
|
118 |
for attribute in attributes: |
|
119 |
if attribute.name == name and attribute.nameFormat == fmt: |
|
120 |
break |
|
121 |
else: |
|
122 |
attribute = lasso.Saml2Attribute() |
|
123 |
attributes.append(attribute) |
|
124 |
statement.attribute = attributes |
|
125 |
attribute_values = list(attribute.attributeValue) |
|
126 |
atv = lasso.Saml2AttributeValue() |
|
127 |
attribute_values.append(atv) |
|
128 |
attribute.attributeValue = attribute_values |
|
129 |
value_any = [] |
|
130 |
for value in values: |
|
131 |
if isinstance(value, lasso.Node): |
|
132 |
value_any.append(value) |
|
133 |
else: |
|
134 |
mtn = lasso.MiscTextNode.newWithString(force_str(value)) |
|
135 |
mtn.textChild = True |
|
136 |
value_any.append(mtn) |
|
137 |
atv.any = value_any |
|
138 |
add_attribute('email', 'john', '.doe@gmail.com') |
|
139 |
add_attribute('wtf', 'john', lasso.MiscTextNode.newWithXmlNode('<a>coucou</a>')) |
|
140 | ||
107 | 141 |
if not auth_result and msg: |
108 | 142 |
login.response.status.statusMessage = msg |
109 | 143 |
if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART: |
... | ... | |
174 | 208 |
url, body, relay_state = idp.process_authn_request_redirect( |
175 | 209 |
response['Location'], |
176 | 210 |
auth_result=False, |
177 |
msg=u'User is not allowed to login')
|
|
211 |
msg='User is not allowed to login') |
|
178 | 212 |
assert not relay_state |
179 | 213 |
assert url.endswith(reverse('mellon_login')) |
180 | 214 |
response = app.post(reverse('mellon_login'), params={'SAMLResponse': body, 'RelayState': relay_state}) |
... | ... | |
195 | 229 |
url, body, relay_state = idp.process_authn_request_redirect( |
196 | 230 |
response['Location'], |
197 | 231 |
auth_result=False, |
198 |
msg=u'User is not allowed to login')
|
|
232 |
msg='User is not allowed to login') |
|
199 | 233 |
assert not relay_state |
200 | 234 |
assert body is None |
201 | 235 |
assert reverse('mellon_login') in url |
tests/test_utils.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
import datetime |
17 | 19 | |
18 | 20 |
import mock |
19 | 21 |
import lasso |
20 | 22 | |
21 | 23 |
from mellon.utils import create_metadata, iso8601_to_datetime, flatten_datetime |
24 |
from mellon.views import check_next_url |
|
22 | 25 |
from xml_utils import assert_xml_constraints |
23 | 26 | |
24 | 27 | |
... | ... | |
84 | 87 |
d = { |
85 | 88 |
'x': datetime.datetime(2010, 10, 10, 10, 10, 34), |
86 | 89 |
'y': 1, |
87 |
'z': 'uu',
|
|
90 |
'z': 'u', |
|
88 | 91 |
} |
89 | 92 |
assert set(flatten_datetime(d).keys()) == set(['x', 'y', 'z']) |
90 | 93 |
assert flatten_datetime(d)['x'] == '2010-10-10T10:10:34' |
91 | 94 |
assert flatten_datetime(d)['y'] == 1 |
92 |
assert flatten_datetime(d)['z'] == 'uu' |
|
95 |
assert flatten_datetime(d)['z'] == 'u' |
|
96 | ||
97 | ||
98 |
def test_check_next_url(rf): |
|
99 |
assert not check_next_url(rf.get('/'), u'') |
|
100 |
assert not check_next_url(rf.get('/'), None) |
|
101 |
assert not check_next_url(rf.get('/'), u'\x00') |
|
102 |
assert not check_next_url(rf.get('/'), u'\u010e') |
|
103 |
assert not check_next_url(rf.get('/'), u'https://example.invalid/') |
|
104 |
# default hostname is testserver |
|
105 |
assert check_next_url(rf.get('/'), u'http://testserver/ok/') |
tests/test_views.py | ||
---|---|---|
13 | 13 |
# You should have received a copy of the GNU Affero General Public License |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | |
16 |
from __future__ import unicode_literals |
|
17 | ||
16 | 18 |
import pytest |
17 | 19 |
import mock |
18 | 20 |
import lasso |
... | ... | |
208 | 210 |
def test_sp_initiated_login_requested_authn_context(private_settings, client): |
209 | 211 |
private_settings.MELLON_IDENTITY_PROVIDERS = [{ |
210 | 212 |
'METADATA': open('tests/metadata.xml').read(), |
211 |
'AUTHN_CLASSREF': [u'urn:be:fedict:iam:fas:citizen:eid',
|
|
212 |
u'urn:be:fedict:iam:fas:citizen:token'],
|
|
213 |
'AUTHN_CLASSREF': ['urn:be:fedict:iam:fas:citizen:eid', |
|
214 |
'urn:be:fedict:iam:fas:citizen:token'], |
|
213 | 215 |
}] |
214 | 216 |
response = client.get('/login/') |
215 | 217 |
assert response.status_code == 302 |
216 |
- |