def login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http Redirect protocol binding. """ logger.debug('Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if not request.user.is_anonymous(): try: redirect_authenticated_user = settings.SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN except AttributeError: redirect_authenticated_user = True if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render_to_response(authorization_error_template, {'came_from': came_from}, context_instance=RequestContext(request)) selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render_to_response(wayf_template, {'available_idps': idps.items(), 'came_from': came_from}, context_instance=RequestContext(request)) client = Saml2Client(conf) try: (session_id, result) = client.prepare_for_authenticate(entityid=selected_idp, relay_state=came_from, binding=BINDING_HTTP_REDIRECT) except TypeError, e: logger.error('Unable to know which IdP to use') return HttpResponse(unicode(e))
def login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html', post_binding_form_template='djangosaml2/post_binding_form.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http Redirect protocol binding. * post_binding_form_template - path to a template containing HTML form with hidden input elements, used to send the SAML message data when HTTP POST binding is being used. You can customize this template to include custom branding and/or text explaining the automatic redirection process. Please see the example template in templates/djangosaml2/example_post_binding_form.html If set to None or nonexistent template, default form from the saml2 library will be rendered. """ logger.debug('Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not is_safe_url(url=came_from, host=request.get_host()): came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if not request.user.is_anonymous(): try: redirect_authenticated_user = settings.SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN except AttributeError: redirect_authenticated_user = True if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render(request, authorization_error_template, { 'came_from': came_from, }) selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render(request, wayf_template, { 'available_idps': idps.items(), '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 binding = BINDING_HTTP_POST if getattr(conf, '_sp_authn_requests_signed', False) else BINDING_HTTP_REDIRECT client = Saml2Client(conf) 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(unicode(e)) logger.debug('Saving the session_id in the OutstandingQueries cache') oq_cache = OutstandingQueriesCache(request.session) oq_cache.set(session_id, came_from) logger.debug('Redirecting user to the IdP via %s binding.', binding.split(':')[-1]) if binding == BINDING_HTTP_REDIRECT: return HttpResponseRedirect(get_location(result)) elif binding == BINDING_HTTP_POST: 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']) else: raise NotImplementedError('Unsupported binding: %s', binding)
def login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html', post_binding_form_template='djangosaml2/post_binding_form.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http Redirect protocol binding. * post_binding_form_template - path to a template containing HTML form with hidden input elements, used to send the SAML message data when HTTP POST binding is being used. You can customize this template to include custom branding and/or text explaining the automatic redirection process. Please see the example template in templates/djangosaml2/example_post_binding_form.html If set to None or nonexistent template, default form from the saml2 library will be rendered. """ logger.debug('Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if not request.user.is_anonymous(): try: redirect_authenticated_user = settings.SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN except AttributeError: redirect_authenticated_user = True if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render_to_response(authorization_error_template, { 'came_from': came_from, }, context_instance=RequestContext(request)) selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render_to_response(wayf_template, { 'available_idps': idps.items(), 'came_from': came_from, }, context_instance=RequestContext(request)) # 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 binding = BINDING_HTTP_POST if getattr(conf, '_sp_authn_requests_signed', False) else BINDING_HTTP_REDIRECT client = Saml2Client(conf) 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(unicode(e)) logger.debug('Saving the session_id in the OutstandingQueries cache') oq_cache = OutstandingQueriesCache(request.session) oq_cache.set(session_id, came_from) logger.debug('Redirecting user to the IdP via %s binding.', binding.split(':')[-1]) if binding == BINDING_HTTP_REDIRECT: return HttpResponseRedirect(get_location(result)) elif binding == BINDING_HTTP_POST: if not post_binding_form_template: return HttpResponse(result['data']) try: params = get_hidden_form_inputs(result['data'][3]) return render_to_response(post_binding_form_template, { 'target_url': result['url'], 'params': params, }, context_instance=RequestContext(request)) except TemplateDoesNotExist: return HttpResponse(result['data']) else: raise NotImplementedError('Unsupported binding: %s', binding)
def lookup_idp_from_ref(self, ref): conf = get_config(None, self.request) idps = available_idps(conf) return [k for k, v in idps.items() if v == ref][0]
def spid_login( request, config_loader_path=None, wayf_template="wayf.html", authorization_error_template="djangosaml2/auth_error.html", ): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http POST protocol binding. """ logger.debug("SPID Login process started") next_url = request.GET.get("next", settings.LOGIN_REDIRECT_URL) if not next_url: logger.warning("The next parameter exists but is empty") next_url = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not validate_referral_url(request, next_url): next_url = settings.LOGIN_REDIRECT_URL if request.user.is_authenticated: redirect_authenticated_user = getattr( settings, "SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN", True) if redirect_authenticated_user: return HttpResponseRedirect(next_url) else: # pragma: no cover logger.debug("User is already logged in") return render(request, authorization_error_template, {"came_from": next_url}) # this works only if request came from wayf selected_idp = request.GET.get("idp", None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug("A discovery process is needed") return render( request, wayf_template, { "available_idps": idps.items(), "next_url": next_url }, ) else: # otherwise is the first one _msg = "Unable to know which IdP to use" try: selected_idp = selected_idp or list(idps.keys())[0] except TypeError as e: # pragma: no cover logger.error(f"{_msg}: {e}") return HttpResponseNotFound(_msg) except IndexError as e: # pragma: no cover logger.error(f"{_msg}: {e}") return HttpResponseNotFound(_msg) # ensure our selected binding is supported by the IDP logger.debug( f"Trying binding {SAML2_DEFAULT_BINDING} for IDP {selected_idp}") supported_bindings = get_idp_sso_supported_bindings(selected_idp, config=conf) if not supported_bindings: _msg = "IdP Metadata not found or not valid" return HttpResponseNotFound(_msg) if SAML2_DEFAULT_BINDING not in supported_bindings: _msg = (f"Requested: {SAML2_DEFAULT_BINDING} but the selected " f"IDP [{selected_idp}] doesn't support " f"{BINDING_HTTP_POST} or {BINDING_HTTP_REDIRECT}. " f"Check if IdP Metadata is correctly loaded and updated.") logger.error(_msg) raise UnsupportedBinding(_msg) # SPID things here try: login_response = spid_sp_authn_request(conf, selected_idp, next_url) except UnknownSystemEntity as e: # pragma: no cover _msg = f"Unknown IDP Entity ID: {selected_idp}" logger.error(f"{_msg}: {e}") return HttpResponseNotFound(_msg) session_id = login_response["session_id"] http_response = login_response["http_response"] # success, so save the session ID and return our response logger.debug( f"Saving session-id {session_id} in the OutstandingQueries cache") oq_cache = OutstandingQueriesCache(request.saml_session) oq_cache.set(session_id, next_url) if SAML2_DEFAULT_BINDING == saml2.BINDING_HTTP_POST: return HttpResponse(http_response["data"]) elif SAML2_DEFAULT_BINDING == saml2.BINDING_HTTP_REDIRECT: headers = dict(login_response["http_response"]["headers"]) return HttpResponseRedirect(headers["Location"])
def login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http Redirect protocol binding. """ logger.debug('Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if not request.user.is_anonymous(): try: redirect_authenticated_user = settings.SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN except AttributeError: redirect_authenticated_user = True if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render_to_response(authorization_error_template, { 'came_from': came_from, }, context_instance=RequestContext(request)) selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render_to_response(wayf_template, { 'available_idps': idps.items(), 'came_from': came_from, }, context_instance=RequestContext(request)) client = Saml2Client(conf) try: (session_id, result) = client.prepare_for_authenticate( entityid=selected_idp, relay_state=came_from, binding=BINDING_HTTP_REDIRECT, ) except TypeError, e: logger.error('Unable to know which IdP to use') return HttpResponse(unicode(e))
def login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html', post_binding_form_template='djangosaml2/post_binding_form.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http Redirect protocol binding. * post_binding_form_template - path to a template containing HTML form with hidden input elements, used to send the SAML message data when HTTP POST binding is being used. You can customize this template to include custom branding and/or text explaining the automatic redirection process. Please see the example template in templates/djangosaml2/example_post_binding_form.html If set to None or nonexistent template, default form from the saml2 library will be rendered. """ logger.debug('Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not is_safe_url(url=came_from, host=request.get_host()): came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if not request.user.is_anonymous(): try: redirect_authenticated_user = settings.SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN except AttributeError: redirect_authenticated_user = True if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render(request, authorization_error_template, { 'came_from': came_from, }) selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render(request, wayf_template, { 'available_idps': idps.items(), 'came_from': came_from, }) # 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 logger.debug('Redirecting user to the IdP via %s binding.', binding) if binding == BINDING_HTTP_REDIRECT: try: # do not sign the xml itself, instead us the sigalg to # generate the signature as a URL param sigalg = SIG_RSA_SHA1 if sign_requests 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: 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( binary_type(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
def login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html', post_binding_form_template='djangosaml2/post_binding_form.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http Redirect protocol binding. * post_binding_form_template - path to a template containing HTML form with hidden input elements, used to send the SAML message data when HTTP POST binding is being used. You can customize this template to include custom branding and/or text explaining the automatic redirection process. Please see the example template in templates/djangosaml2/example_post_binding_form.html If set to None or nonexistent template, default form from the saml2 library will be rendered. """ logger.debug('Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not is_safe_url_compat(url=came_from, allowed_hosts={request.get_host()}): came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if callable_bool(request.user.is_authenticated): redirect_authenticated_user = getattr(settings, 'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN', True) if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render(request, authorization_error_template, { 'came_from': came_from, }) selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render(request, wayf_template, { 'available_idps': idps.items(), 'came_from': came_from, }) # 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 logger.debug('Redirecting user to the IdP via %s binding.', binding) if binding == BINDING_HTTP_REDIRECT: try: # do not sign the xml itself, instead use the sigalg to # generate the signature as a URL param sig_alg_option_map = {'sha1': SIG_RSA_SHA1, 'sha256': SIG_RSA_SHA256} sig_alg_option = getattr(conf, '_sp_authn_requests_signed_alg', 'sha1') sigalg = sig_alg_option_map[sig_alg_option] if sign_requests else None nsprefix = get_namespace_prefixes() session_id, result = client.prepare_for_authenticate( entityid=selected_idp, relay_state=came_from, binding=binding, sign=False, sigalg=sigalg, nsprefix=nsprefix) 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: if post_binding_form_template: # get request XML to build our own html based on the template 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) try: if PY3: saml_request = base64.b64encode(binary_type(request_xml, 'UTF-8')) else: saml_request = base64.b64encode(binary_type(request_xml)) http_response = render(request, post_binding_form_template, { 'target_url': location, 'params': { 'SAMLRequest': saml_request, 'RelayState': came_from, }, }) except TemplateDoesNotExist: pass if not http_response: # use the html provided by pysaml2 if no template was specified or it didn't exist 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']) 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
def spid_login(request, config_loader_path=None, wayf_template='djangosaml2/wayf.html', authorization_error_template='djangosaml2/auth_error.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http POST protocol binding. """ logger.debug('SPID Login process started') came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not came_from: logger.warning('The next parameter exists but is empty') came_from = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not is_safe_url_compat(url=came_from, allowed_hosts={request.get_host()}): came_from = settings.LOGIN_REDIRECT_URL # if the user is already authenticated that maybe because of two reasons: # A) He has this URL in two browser windows and in the other one he # has already initiated the authenticated session. # B) He comes from a view that (incorrectly) send him here because # he does not have enough permissions. That view should have shown # an authorization error in the first place. # We can only make one thing here and that is configurable with the # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting # is True (default value) we will redirect him to the came_from view. # Otherwise, we will show an (configurable) authorization error. if callable(request.user.is_authenticated): redirect_authenticated_user = getattr(settings, 'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN', True) if redirect_authenticated_user: return HttpResponseRedirect(came_from) else: logger.debug('User is already logged in') return render(request, authorization_error_template, { 'came_from': came_from}) # this works only if request came from wayf selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render(request, wayf_template, { 'available_idps': idps.items(), 'came_from': came_from}) else: # otherwise is the first one try: selected_idp = list(idps.keys())[0] except TypeError as e: logger.error('Unable to know which IdP to use') return HttpResponse(text_type(e)) # choose a binding to try first # sign_requests = getattr(conf, '_sp_authn_requests_signed', False) binding = BINDING_HTTP_POST 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 != BINDING_HTTP_POST: raise UnsupportedBinding('IDP %s does not support %s or %s', selected_idp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT) client = Saml2Client(conf) logger.debug('Redirecting user to the IdP via %s binding.', binding) # use the html provided by pysaml2 if no template was specified or it didn't exist # SPID want the fqdn of the IDP, not the SSO endpoint location_fixed = selected_idp location = client.sso_location(selected_idp, binding) # ...hope to see the SSO endpoint soon in spid-testenv2 authn_req = saml2.samlp.AuthnRequest() authn_req.destination = location_fixed # spid-testenv2 preleva l'attribute consumer service dalla authnRequest (anche se questo sta già nei metadati...) authn_req.attribute_consuming_service_index = "0" # import pdb; pdb.set_trace() issuer = saml2.saml.Issuer() issuer.name_qualifier = client.config.entityid issuer.text = client.config.entityid issuer.format = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity" authn_req.issuer = issuer # message id authn_req.id = saml2.s_utils.sid() authn_req.version = saml2.VERSION # "2.0" authn_req.issue_instant = saml2.time_util.instant() name_id_policy = saml2.samlp.NameIDPolicy() # del(name_id_policy.allow_create) name_id_policy.format = settings.SPID_NAMEID_FORMAT authn_req.name_id_policy = name_id_policy authn_context = requested_authn_context(class_ref=settings.SPID_AUTH_CONTEXT) authn_req.requested_authn_context = authn_context authn_req.protocol_binding = settings.SPID_DEFAULT_BINDING assertion_consumer_service_url = client.config._sp_endpoints['assertion_consumer_service'][0][0] authn_req.assertion_consumer_service_url = assertion_consumer_service_url #'http://sp1.testunical.it:8000/saml2/acs/' authn_req_signed = client.sign(authn_req, sign_prepare=False, sign_alg=settings.SPID_ENC_ALG, digest_alg=settings.SPID_DIG_ALG) session_id = authn_req.id _req_str = authn_req_signed logger.debug('AuthRequest to {}: {}'.format(selected_idp, (_req_str))) http_info = client.apply_binding(binding, _req_str, location, sign=True, sigalg=settings.SPID_ENC_ALG) # 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 HttpResponse(http_info['data'])
def spid_login(request, config_loader_path=None, wayf_template='wayf.html', authorization_error_template='djangosaml2/auth_error.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http POST protocol binding. """ logger.debug('SPID Login process started') next_url = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not next_url: logger.warning('The next parameter exists but is empty') next_url = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not validate_referral_url(request, next_url): next_url = settings.LOGIN_REDIRECT_URL if callable(request.user.is_authenticated): redirect_authenticated_user = getattr( settings, 'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN', True) if redirect_authenticated_user: return HttpResponseRedirect(next_url) else: logger.debug('User is already logged in') return render(request, authorization_error_template, {'came_from': next_url}) # this works only if request came from wayf selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render(request, wayf_template, { 'available_idps': idps.items(), 'next_url': next_url }) else: # otherwise is the first one try: selected_idp = selected_idp or list(idps.keys())[0] except TypeError as e: logger.error('Unable to know which IdP to use') return HttpResponse(text_type(e)) binding = BINDING_HTTP_POST logger.debug(f'Trying binding {binding} for IDP {selected_idp}') # ensure our selected binding is supported by the IDP supported_bindings = get_idp_sso_supported_bindings(selected_idp, config=conf) if binding != BINDING_HTTP_POST: raise UnsupportedBinding('IDP %s does not support %s or %s', selected_idp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT) # SPID things here login_response = spid_sp_authn_request(conf, selected_idp, binding, settings.SPID_NAMEID_FORMAT, settings.SPID_AUTH_CONTEXT, settings.SPID_SIG_ALG, settings.SPID_DIG_ALG, next_url) session_id = login_response['session_id'] http_response = login_response['http_response'] # success, so save the session ID and return our response logger.debug( f'Saving session-id {session_id} in the OutstandingQueries cache') oq_cache = OutstandingQueriesCache(request.saml_session) oq_cache.set(session_id, next_url) return HttpResponse(http_response['data'])
import requests import saml2 from saml2 import saml, BINDING_HTTP_POST from django.test.client import RequestFactory from djangosaml2.conf import get_config from djangosaml2.overrides import Saml2Client from djangosaml2.utils import available_idps # SP init ######### conf = get_config(None) client = Saml2Client(conf) # just needed to initialize the MetadataStore - it automatically fetches remote idp's metadata configured_idps = available_idps(conf) # Arguments needed to create an Attribute query ############################################### message_id = 'MSG_ID1' entityid = "http://idp1.testunical.it:9000/idp/aa/metadata" destination = "http://idp1.testunical.it:9000/aap" subject_id = "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" attributes = { ('urn:oasis:names:tc:SAML:attribute:pairwise-id', "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"): "*****@*****.**", ("fiscalCode", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"): "TIN-SDF7SD89F7SD98F", ("email", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", "email"): None, }
def render(self, context): conf = config_settings_loader() context[self.variable_name] = available_idps(conf) return ''
def is_valid_idp(value): remote_providers = available_idps(get_config()).keys() return value in remote_providers or models.IdentityProvider.objects.filter( url=value).exists()
def spid_login(request, config_loader_path=None, wayf_template='wayf.html', authorization_error_template='djangosaml2/auth_error.html'): """SAML Authorization Request initiator This view initiates the SAML2 Authorization handshake using the pysaml2 library to create the AuthnRequest. It uses the SAML 2.0 Http POST protocol binding. """ logger.debug('SPID Login process started') next_url = request.GET.get('next', settings.LOGIN_REDIRECT_URL) if not next_url: logger.warning('The next parameter exists but is empty') next_url = settings.LOGIN_REDIRECT_URL # Ensure the user-originating redirection url is safe. if not validate_referral_url(request, next_url): next_url = settings.LOGIN_REDIRECT_URL if request.user.is_authenticated: redirect_authenticated_user = getattr(settings, 'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN', True) if redirect_authenticated_user: return HttpResponseRedirect(next_url) else: # pragma: no cover logger.debug('User is already logged in') return render(request, authorization_error_template, { 'came_from': next_url}) # this works only if request came from wayf selected_idp = request.GET.get('idp', None) conf = get_config(config_loader_path, request) # is a embedded wayf needed? idps = available_idps(conf) if selected_idp is None and len(idps) > 1: logger.debug('A discovery process is needed') return render(request, wayf_template, { 'available_idps': idps.items(), 'next_url': next_url } ) else: # otherwise is the first one _msg = 'Unable to know which IdP to use' try: selected_idp = selected_idp or list(idps.keys())[0] except TypeError as e: # pragma: no cover logger.error(f'{_msg}: {e}') return HttpResponseError(_msg) except IndexError as e: # pragma: no cover logger.error(f'{_msg}: {e}') return HttpResponseNotFound(_msg) binding = settings.SPID_DEFAULT_BINDING logger.debug(f'Trying binding {binding} for IDP {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: _msg = ( f"Requested: {binding} but the selected " f"IDP [{selected_idp}] doesn't support " f"{BINDING_HTTP_POST} or {BINDING_HTTP_REDIRECT}. " f"Check if IdP Metadata is correctly loaded and updated." ) logger.error(_msg) raise UnsupportedBinding(_msg) # SPID things here try: login_response = spid_sp_authn_request(conf, selected_idp, binding, settings.SPID_NAMEID_FORMAT, settings.SPID_AUTH_CONTEXT, settings.SPID_SIG_ALG, settings.SPID_DIG_ALG, next_url ) except UnknownSystemEntity as e: # pragma: no cover _msg = f'Unknown IDP Entity ID: {selected_idp}' logger.error(f'{_msg}: {e}') return HttpResponseNotFound(_msg) session_id = login_response['session_id'] http_response = login_response['http_response'] # success, so save the session ID and return our response logger.debug(f'Saving session-id {session_id} in the OutstandingQueries cache') oq_cache = OutstandingQueriesCache(request.saml_session) oq_cache.set(session_id, next_url) if binding == saml2.BINDING_HTTP_POST: return HttpResponse(http_response['data']) elif binding == saml2.BINDING_HTTP_REDIRECT: headers = dict(login_response['http_response']['headers']) return HttpResponseRedirect(headers['Location'])
def idps_dropdown(): return render_to_string('reusable_blocks/idp_dropdowns.html', { "idps": available_idps(config_settings_loader()).items(), "base": settings.BASE_URL })