4 |
4 |
import uuid
|
5 |
5 |
from requests.exceptions import RequestException
|
6 |
6 |
from xml.sax.saxutils import escape
|
|
7 |
import xml.etree.ElementTree as ET
|
|
8 |
|
7 |
9 |
|
8 |
10 |
from django.core.urlresolvers import reverse
|
9 |
11 |
from django.views.generic import View
|
... | ... | |
31 |
33 |
def lasso_decode(x):
|
32 |
34 |
return x.decode('utf-8')
|
33 |
35 |
|
|
36 |
EO_NS = 'https://www.entrouvert.com/'
|
|
37 |
LOGIN_HINT = '{%s}login-hint' % EO_NS
|
|
38 |
|
34 |
39 |
|
35 |
40 |
class LogMixin(object):
|
36 |
41 |
"""Initialize a module logger in new objects"""
|
... | ... | |
39 |
44 |
super(LogMixin, self).__init__(*args, **kwargs)
|
40 |
45 |
|
41 |
46 |
|
|
47 |
def check_next_url(request, next_url):
|
|
48 |
log = logging.getLogger(__name__)
|
|
49 |
if not next_url:
|
|
50 |
return
|
|
51 |
if not utils.is_nonnull(next_url):
|
|
52 |
log.warning('next parameter ignored, as it contains null characters')
|
|
53 |
return
|
|
54 |
try:
|
|
55 |
next_url.encode('ascii')
|
|
56 |
except UnicodeDecodeError:
|
|
57 |
log.warning('next parameter ignored, as is\'s not an ASCII string')
|
|
58 |
return
|
|
59 |
if not utils.same_origin(next_url, request.build_absolute_uri()):
|
|
60 |
log.warning('next parameter ignored as it is not of the same origin')
|
|
61 |
return
|
|
62 |
return next_url
|
|
63 |
|
|
64 |
|
42 |
65 |
class ProfileMixin(object):
|
43 |
66 |
profile = None
|
44 |
67 |
|
45 |
68 |
def set_next_url(self, next_url):
|
46 |
|
if not next_url:
|
47 |
|
return
|
48 |
|
if not utils.is_nonnull(next_url):
|
49 |
|
self.log.warning('next parameter ignored, as it contains null characters')
|
50 |
|
return
|
51 |
|
try:
|
52 |
|
next_url.encode('ascii')
|
53 |
|
except UnicodeDecodeError:
|
54 |
|
self.log.warning('next parameter ignored, as is\'s not an ASCII string')
|
55 |
|
return
|
56 |
|
if not utils.same_origin(next_url, self.request.build_absolute_uri()):
|
57 |
|
self.log.warning('next parameter ignored as it is not of the same origin')
|
|
69 |
if not check_next_url(self.request, next_url):
|
58 |
70 |
return
|
59 |
71 |
self.set_state('next_url', next_url)
|
60 |
72 |
|
... | ... | |
345 |
357 |
return self.request_discovery_service(
|
346 |
358 |
request, is_passive=request.GET.get('passive') == '1')
|
347 |
359 |
|
348 |
|
next_url = request.GET.get(REDIRECT_FIELD_NAME)
|
|
360 |
next_url = check_next_url(self.request, request.GET.get(REDIRECT_FIELD_NAME))
|
349 |
361 |
idp = self.get_idp(request)
|
350 |
362 |
if idp is None:
|
351 |
363 |
return HttpResponseBadRequest('no idp found')
|
... | ... | |
387 |
399 |
</samlp:Extensions>''' % eo_next_url
|
388 |
400 |
)
|
389 |
401 |
self.set_next_url(next_url)
|
|
402 |
self.add_login_hints(idp, authn_request, request=request, next_url=next_url)
|
390 |
403 |
login.buildAuthnRequestMsg()
|
391 |
404 |
except lasso.Error as e:
|
392 |
405 |
return HttpResponseBadRequest('error initializing the authentication request: %r' % e)
|
... | ... | |
394 |
407 |
self.log.debug('to url %r', login.msgUrl)
|
395 |
408 |
return HttpResponseRedirect(login.msgUrl)
|
396 |
409 |
|
|
410 |
def add_extension_node(self, authn_request, node):
|
|
411 |
'''Factorize adding an XML node to the samlp:Extensions node'''
|
|
412 |
if not authn_request.extensions:
|
|
413 |
authn_request.extensions = lasso.Samlp2Extensions()
|
|
414 |
assert hasattr(authn_request.extensions, 'any'), 'extension nodes need lasso > 2.5.1'
|
|
415 |
serialized = ET.tostring(node, 'utf-8')
|
|
416 |
# tostring return bytes in PY3, but lasso needs str
|
|
417 |
if six.PY3:
|
|
418 |
serialized = serialized.decode('utf-8')
|
|
419 |
extension_content = authn_request.extensions.any or ()
|
|
420 |
extension_content += (serialized,)
|
|
421 |
authn_request.extensions.any = extension_content
|
|
422 |
|
|
423 |
def is_in_backoffice(self, request, next_url):
|
|
424 |
path = utils.get_local_path(request, next_url)
|
|
425 |
return path.startswith(('/admin/', '/manage/', '/manager/'))
|
|
426 |
|
|
427 |
def add_login_hints(self, idp, authn_request, request, next_url=None):
|
|
428 |
login_hints = utils.get_setting(idp, 'LOGIN_HINTS', [])
|
|
429 |
hints = []
|
|
430 |
for login_hint in login_hints:
|
|
431 |
if login_hint == 'backoffice':
|
|
432 |
if self.is_in_backoffice(request, next_url):
|
|
433 |
hints.append('backoffice')
|
|
434 |
if login_hint == 'always_backoffice':
|
|
435 |
hints.append('backoffice')
|
|
436 |
|
|
437 |
for hint in hints:
|
|
438 |
node = ET.Element(LOGIN_HINT)
|
|
439 |
node.text = hint
|
|
440 |
self.add_extension_node(authn_request, node)
|
|
441 |
|
|
442 |
|
397 |
443 |
# we need fine control of transactions to prevent double user creations
|
398 |
444 |
login = transaction.non_atomic_requests(csrf_exempt(LoginView.as_view()))
|
399 |
445 |
|