Skip to content
20 changes: 20 additions & 0 deletions djangosaml2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from django.conf import settings
from saml2.s_utils import UnknownSystemEntity


def get_custom_setting(name, default=None):
Expand All @@ -33,6 +34,25 @@ def available_idps(config, langpref=None):
return dict([(idp, config.metadata.name(idp, langpref)) for idp in idps])


def get_idp_sso_supported_bindings(idp_entity_id=None, config=None):
"""Returns the list of bindings supported by an IDP
This is not clear in the pysaml2 code, so wrapping it in a util"""
if config is None:
# avoid circular import
from djangosaml2.conf import get_config
config = get_config()
# load metadata store from config
meta = getattr(config, 'metadata', {})
# if idp is None, assume only one exists so just use that
if idp_entity_id is None:
# .keys() returns dict_keys in python3.5+
idp_entity_id = list(available_idps(config).keys()).pop()
try:
return meta.service(idp_entity_id, 'idpsso_descriptor', 'single_sign_on_service').keys()
except UnknownSystemEntity:
return []


def get_location(http_info):
"""Extract the redirect URL from a pysaml2 http_info object"""
assert 'headers' in http_info
Expand Down
28 changes: 25 additions & 3 deletions djangosaml2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ def csrf_exempt(view_func):
from saml2.metadata import entity_descriptor
from saml2.ident import code, decode
from saml2.sigver import MissingKey
from saml2.s_utils import UnsupportedBinding
from saml2.response import StatusError
from saml2.xmldsig import SIG_RSA_SHA1 # support for this is required by spec

from djangosaml2.cache import IdentityCache, OutstandingQueriesCache
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
from djangosaml2.utils import get_custom_setting, available_idps, get_location, get_idp_sso_supported_bindings


logger = logging.getLogger('djangosaml2')
Expand Down Expand Up @@ -138,7 +139,28 @@ def login(request,
'came_from': came_from,
})

binding = BINDING_HTTP_POST if getattr(conf, '_sp_authn_requests_signed', False) else BINDING_HTTP_REDIRECT
# 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)
if binding not in supported_bindings:
logger.debug('Binding %s not in IDP %s supported bindings: %s',
binding, selected_idp, supported_bindings)
if binding == BINDING_HTTP_POST:
logger.warning('IDP %s does not support %s, trying %s',
selected_idp, binding, BINDING_HTTP_REDIRECT)
binding = 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 %s does not support %s or %s',
selected_idp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT)

client = Saml2Client(conf)
http_response = None
Expand Down Expand Up @@ -187,7 +209,7 @@ def login(request,
},
})
else:
raise NotImplementedError('Unsupported binding: %s', binding)
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')
Expand Down