Skip to content
Prev Previous commit
Merge remote-tracking branch 'upstream/master' into liquidpele-patch-2
Conflicts:
	djangosaml2/views.py
  • Loading branch information
Reece authored and Reece committed Apr 13, 2017
commit 9abd86fb58faa2a6ab5a2b84254130e15ee9c669
105 changes: 54 additions & 51 deletions djangosaml2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ def csrf_exempt(view_func):
from djangosaml2.cache import StateCache
from djangosaml2.conf import get_config
from djangosaml2.signals import post_authenticated
from djangosaml2.utils import get_custom_setting, available_idps, get_location, \
get_hidden_form_inputs, get_idp_sso_supported_bindings
from djangosaml2.utils import get_custom_setting, available_idps, get_location, get_idp_sso_supported_bindings


logger = logging.getLogger('djangosaml2')
Expand Down Expand Up @@ -140,25 +139,10 @@ def login(request,
'came_from': came_from,
})

# Choose binding (REDIRECT vs. POST).
# When authn_requests_signed is turned on, HTTP Redirect binding cannot be
# used the same way as without signatures; proper usage in this case involves
# stripping out the signature from SAML XML message and creating a new
# signature, following precise steps defined in the SAML2.0 standard.
#
# It is not feasible to implement this since we wouldn't be able to use an
# external (xmlsec1) library to handle the signatures - more (higher level)
# context is needed in order to create such signature (like the value of
# RelayState parameter).
#
# Therefore it is much easier to use the HTTP POST binding in this case, as
# it can relay the whole signed SAML message as is, without the need to
# manipulate the signature or the XML message itself.
#
# Read more in the official SAML2 specs (3.4.4.1):
# http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
# choose a binding to try first
sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
binding = BINDING_HTTP_POST if sign_requests else BINDING_HTTP_REDIRECT
logger.debug('Trying binding %s for IDP %s', binding, selected_idp)

# ensure our selected binding is supported by the IDP
supported_bindings = get_idp_sso_supported_bindings(selected_idp, config=conf)
Expand All @@ -169,51 +153,70 @@ def login(request,
logger.warning('IDP %s does not support %s, trying %s',
selected_idp, binding, BINDING_HTTP_REDIRECT)
binding = BINDING_HTTP_REDIRECT
if sign_requests:
sign_requests = False
logger.warning('sp_authn_requests_signed is True, but ignoring because pysaml2 does not support it for %s', BINDING_HTTP_REDIRECT)
else:
logger.warning('IDP %s does not support %s, trying %s',
selected_idp, binding, BINDING_HTTP_POST)
binding = BINDING_HTTP_POST
# if switched binding still not supported, give up
if binding not in supported_bindings:
raise UnsupportedBinding('IDP does not support %s or %s',
BINDING_HTTP_POST, BINDING_HTTP_REDIRECT)
raise UnsupportedBinding('IDP %s does not support %s or %s',
selected_idp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT)

client = Saml2Client(conf)
try:
# we use sign kwarg to override in case of redirect binding
# otherwise pysaml2 may sign the xml for redirect which is incorrect
(session_id, result) = client.prepare_for_authenticate(
entityid=selected_idp, relay_state=came_from,
binding=binding, sign=sign_requests,
)
except TypeError as e:
logger.error('Unable to know which IdP to use')
return HttpResponse(text_type(e))

logger.debug('Saving the session_id in the OutstandingQueries cache')
oq_cache = OutstandingQueriesCache(request.session)
oq_cache.set(session_id, came_from)
http_response = None

logger.debug('Redirecting user to the IdP via %s binding.', binding.split(':')[-1])
logger.debug('Redirecting user to the IdP via %s binding.', binding)
if binding == BINDING_HTTP_REDIRECT:
return HttpResponseRedirect(get_location(result))
try:
# do not sign the xml itself, instead us the sigalg to
# generate the signature as a URL param
sigalg = SIG_RSA_SHA1 if getattr(conf, '_sp_authn_requests_signed', False) else None
session_id, result = client.prepare_for_authenticate(
entityid=selected_idp, relay_state=came_from,
binding=binding, sign=False, sigalg=sigalg)
except TypeError as e:
logger.error('Unable to know which IdP to use')
return HttpResponse(text_type(e))
else:
http_response = HttpResponseRedirect(get_location(result))
elif binding == BINDING_HTTP_POST:
# use the html provided by pysaml2 if no template specified
if not post_binding_form_template:
return HttpResponse(result['data'])
try:
params = get_hidden_form_inputs(result['data'][3])
return render(request, post_binding_form_template, {
'target_url': result['url'],
'params': params,
})
except (DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden):
raise PermissionDenied
except TemplateDoesNotExist:
return HttpResponse(result['data'])
try:
session_id, result = client.prepare_for_authenticate(
entityid=selected_idp, relay_state=came_from,
binding=binding)
except TypeError as e:
logger.error('Unable to know which IdP to use')
return HttpResponse(text_type(e))
else:
http_response = HttpResponse(result['data'])
# get request XML to build our own html based on the template
else:
try:
location = client.sso_location(selected_idp, binding)
except TypeError as e:
logger.error('Unable to know which IdP to use')
return HttpResponse(text_type(e))
session_id, request_xml = client.create_authn_request(
location,
binding=binding)
http_response = render(request, post_binding_form_template, {
'target_url': location,
'params': {
'SAMLRequest': base64.b64encode(request_xml),
'RelayState': came_from,
},
})
else:
raise UnsupportedBinding('Unsupported binding: %s', binding)

# success, so save the session ID and return our response
logger.debug('Saving the session_id in the OutstandingQueries cache')
oq_cache = OutstandingQueriesCache(request.session)
oq_cache.set(session_id, came_from)
return http_response


@require_POST
@csrf_exempt
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.