def handle_logout_response(self, response, sign_alg=None, digest_alg=None): """ handles a Logout response :param response: A response.Response instance :return: 4-tuple of (session_id of the last sent logout request, response message, response headers and message) """ logger.info("state: %s", self.state) status = self.state[response.in_response_to] logger.info("status: %s", status) issuer = response.issuer() logger.info("issuer: %s", issuer) del self.state[response.in_response_to] if status["entity_ids"] == [issuer]: # done self.local_logout(decode(status["name_id"])) return 0, "200 Ok", [("Content-type", "text/html")], [] else: status["entity_ids"].remove(issuer) if "sign_alg" in status: sign_alg = status["sign_alg"] return self.do_logout( decode(status["name_id"]), status["entity_ids"], status["reason"], status["not_on_or_after"], status["sign"], sign_alg=sign_alg, digest_alg=digest_alg, )
def handle_logout_response(self, response, sign_alg=None, digest_alg=None): """ handles a Logout response :param response: A response.Response instance :return: 4-tuple of (session_id of the last sent logout request, response message, response headers and message) """ logger.info("state: %s", self.state) status = self.state[response.in_response_to] logger.info("status: %s", status) issuer = response.issuer() logger.info("issuer: %s", issuer) del self.state[response.in_response_to] if status["entity_ids"] == [issuer]: # done self.local_logout(decode(status["name_id"])) return 0, "200 Ok", [("Content-type", "text/html")], [] else: status["entity_ids"].remove(issuer) if "sign_alg" in status: sign_alg = status["sign_alg"] return self.do_logout(decode(status["name_id"]), status["entity_ids"], status["reason"], status["not_on_or_after"], status["sign"], sign_alg=sign_alg, digest_alg=digest_alg)
def signout(cls, request): """Logs the user out of the local system and redirects them to the SLO URL in the SAML Metadata""" name_id = decode(request.session['name_id']) logout(request) # from pysaml2/example/sp-wsgi/sp.py try: saml_client = _get_saml_client(utils.get_current_domain(request)) data = saml_client.global_logout(name_id) # following example flow control (with Django responses) at: # https://github.com/IdentityPython/pysaml2/blob/aed2ed41814b6b9f3d80121d42290ff0a2767cb2/example/sp-wsgi/sp.py#L742 for entity_id, logout_info in data.items(): if isinstance(logout_info, tuple): binding, http_info = logout_info if binding == entity.BINDING_HTTP_POST: body = "".join(http_info["data"]) return HttpResponse(body) elif binding == entity.BINDING_HTTP_REDIRECT: for key, value in http_info["headers"]: if key.lower() == "location": return HttpResponseRedirect(value) return _idp_error(ValueError("Missing Location Header")) else: return _idp_error(ValueError("Unknown Logout Binding: %s", binding)) else: # result from SOAP logout, should be OK pass # in pysaml2 example, this is found in `finish_logout` saml_client.local_logout(name_id) except LogoutError as e: cls.logger.warn("LogoutError: {}".format(e.args))
def global_logout(self, name_id, reason="", expire=None, sign=None): """ More or less a layer of indirection :-/ Bootstrapping the whole thing by finding all the IdPs that should be notified. :param name_id: The identifier of the subject that wants to be logged out. :param reason: Why the subject wants to log out :param expire: The latest the log out should happen. If this time has passed don't bother. :param sign: Whether the request should be signed or not. This also depends on what binding is used. :return: Depends on which binding is used: If the HTTP redirect binding then a HTTP redirect, if SOAP binding has been used the just the result of that conversation. """ if isinstance(name_id, basestring): name_id = decode(name_id) logger.info("logout request for: %s" % name_id) # find out which IdPs/AAs I should notify entity_ids = self.users.issuers_of_info(name_id) self.users.remove_person(name_id) return self.do_logout(name_id, entity_ids, reason, expire, sign)
def global_logout(self, name_id, reason="", expire=None, sign=None, sign_alg=None, digest_alg=None): """ More or less a layer of indirection :-/ Bootstrapping the whole thing by finding all the IdPs that should be notified. :param name_id: The identifier of the subject that wants to be logged out. :param reason: Why the subject wants to log out :param expire: The latest the log out should happen. If this time has passed don't bother. :param sign: Whether the request should be signed or not. This also depends on what binding is used. :return: Depends on which binding is used: If the HTTP redirect binding then a HTTP redirect, if SOAP binding has been used the just the result of that conversation. """ if isinstance(name_id, six.string_types): name_id = decode(name_id) logger.info("logout request for: %s", name_id) # find out which IdPs/AAs I should notify entity_ids = self.users.issuers_of_info(name_id) return self.do_logout(name_id, entity_ids, reason, expire, sign, sign_alg=sign_alg, digest_alg=digest_alg)
def authenticate(self, environ, identity=None): if identity: tktuser = identity.get('repoze.who.plugins.auth_tkt.userid', None) if tktuser and self.saml_client.is_logged_in(decode(tktuser)): return tktuser return identity.get('login', None) else: return None
def authenticate(self, environ, identity=None): if identity: if identity.get('user') and environ.get('s2repoze.sessioninfo') and identity.get('user') == environ.get('s2repoze.sessioninfo').get('ava'): return identity.get('login') tktuser = identity.get('repoze.who.plugins.auth_tkt.userid', None) if tktuser and self.saml_client.is_logged_in(decode(tktuser)): return tktuser return None else: return None
def get(self, name_id, entity_id, *args, **kwargs): info = super(IdentityCache, self).get(name_id, entity_id, *args, **kwargs) try: name_id = info['name_id'] except KeyError: pass else: info = dict(info) info['name_id'] = decode(name_id) return info
def _get_name_id(session): """ Get the SAML2 NameID of the currently logged in user. :param session: The current session object :return: NameID :rtype: saml2.saml.NameID | None """ try: return decode(session['_saml2_session_name_id']) except KeyError: return None
def authenticate(self, environ, identity=None): if identity: if identity.get('user') and environ.get( 's2repoze.sessioninfo') and identity.get( 'user') == environ.get('s2repoze.sessioninfo').get('ava'): return identity.get('login') tktuser = identity.get('repoze.who.plugins.auth_tkt.userid', None) if tktuser and self.saml_client.is_logged_in(decode(tktuser)): return tktuser return None else: return None
def authenticate(self, environ, identity=None): if identity: if (identity.get("user") and environ.get("s2repoze.sessioninfo") and identity.get("user") == environ.get("s2repoze.sessioninfo").get("ava")): return identity.get("login") tktuser = identity.get("repoze.who.plugins.auth_tkt.userid", None) if tktuser and self.saml_client.is_logged_in(decode(tktuser)): return tktuser return None else: return None
def authenticate(self, environ, identity=None): if identity: if ( identity.get("user") and environ.get("s2repoze.sessioninfo") and identity.get("user") == environ.get("s2repoze.sessioninfo").get("ava") ): return identity.get("login") tktuser = identity.get("repoze.who.plugins.auth_tkt.userid", None) if tktuser and self.saml_client.is_logged_in(decode(tktuser)): return tktuser return None else: return None
def add_metadata(self, environ, identity): """ Add information to the knowledge I have about the user """ name_id = identity["repoze.who.userid"] if isinstance(name_id, six.string_types): try: # Make sure that userids authenticated by another plugin # don't cause problems here. name_id = decode(name_id) except: pass _cli = self.saml_client logger.debug("[add_metadata] for %s", name_id) try: logger.debug("Issuers: %s", _cli.users.sources(name_id)) except KeyError: pass if "user" not in identity: identity["user"] = {} try: (ava, _) = _cli.users.get_identity(name_id) # now = time.gmtime() logger.debug("[add_metadata] adds: %s", ava) identity["user"].update(ava) except KeyError: pass if "pysaml2_vo_expanded" not in identity and _cli.vorg: # is this a Virtual Organization situation for vo in _cli.vorg.values(): try: if vo.do_aggregation(name_id): # Get the extended identity identity["user"] = _cli.users.get_identity(name_id)[0] # Only do this once, mark that the identity has been # expanded identity["pysaml2_vo_expanded"] = 1 except KeyError: logger.exception( "Failed to do attribute aggregation, " "missing common attribute" ) logger.debug("[add_metadata] returns: %s", dict(identity)) if not identity["user"]: # remove cookie and demand re-authentication pass
def signout(cls, request): """Logs the user out of the local system and redirects them to the REDIRECT URL in the SAML Metadata""" logout(request) saml_client = _get_saml_client(utils.get_current_domain(request)) if 'name_id' in request.session: name_id = decode(['name_id']) saml_client.local_logout(name_id) url = settings.SAML2_AUTH.get('LOGOUT_REDIRECT_URL') if url is None: # default to ADFS style logouts url = '{}/idpinitiatedsignon.aspx'.format( saml_client.sso_location()) return HttpResponseRedirect(url)
def get(self, name_id, entity_id, check_not_on_or_after=True): """ Get session information about a subject gotten from a specified IdP/AA. :param name_id: The subject identifier, a NameID instance :param entity_id: The identifier of the entity_id :param check_not_on_or_after: if True it will check if this subject is still valid or if it is too old. Otherwise it will not check this. True by default. :return: The session information """ cni = code(name_id) (timestamp, info) = self._db[cni][entity_id] if check_not_on_or_after and time_util.after(timestamp): raise ToOld("past %s" % str(timestamp)) if 'name_id' in info and isinstance(info['name_id'], six.string_types): info['name_id'] = decode(info['name_id']) return info or None
def clean_out_user(self, name_id): """ Remove all authentication statements that belongs to a user identified by a NameID instance :param name_id: NameID instance :return: The local identifier for the user """ lid = self.ident.find_local_id(name_id) logger.info("Clean out %s", lid) # remove the authentications try: for _nid in [decode(x) for x in self.ident.db[lid].split(" ")]: try: self.session_db.remove_authn_statements(_nid) except KeyError: pass except KeyError: pass return lid
def _get_subject_id(session): try: return decode(session['_saml2_subject_id']) except KeyError: return None
def challenge(self, environ, _status, _app_headers, _forget_headers): _cli = self.saml_client if 'REMOTE_USER' in environ: name_id = decode(environ["REMOTE_USER"]) _cli = self.saml_client path_info = environ['PATH_INFO'] if 'samlsp.logout' in environ: responses = _cli.global_logout(name_id) return self._handle_logout(responses) if 'samlsp.pending' in environ: response = environ['samlsp.pending'] if isinstance(response, HTTPRedirection): response.headers += _forget_headers return response #logger = environ.get('repoze.who.logger','') # Which page was accessed to get here came_from = construct_came_from(environ) environ["myapp.came_from"] = came_from logger.debug("[sp.challenge] RelayState >> '%s'" % came_from) # Am I part of a virtual organization or more than one ? try: vorg_name = environ["myapp.vo"] except KeyError: try: vorg_name = _cli.vorg._name except AttributeError: vorg_name = "" logger.info("[sp.challenge] VO: %s" % vorg_name) # If more than one idp and if none is selected, I have to do wayf (done, response) = self._pick_idp(environ, came_from) # Three cases: -1 something went wrong or Discovery service used # 0 I've got an IdP to send a request to # >0 ECP in progress logger.debug("_idp_pick returned: %s" % done) if done == -1: return response elif done > 0: self.outstanding_queries[done] = came_from return ECP_response(response) else: entity_id = response logger.info("[sp.challenge] entity_id: %s" % entity_id) # Do the AuthnRequest _binding = BINDING_HTTP_REDIRECT try: srvs = _cli.metadata.single_sign_on_service(entity_id, _binding) logger.debug("srvs: %s" % srvs) dest = srvs[0]["location"] logger.debug("destination: %s" % dest) extensions = None cert = None if _cli.config.generate_cert_func is not None: cert_str, req_key_str = _cli.config.generate_cert_func() cert = { "cert": cert_str, "key": req_key_str } spcertenc = SPCertEnc(x509_data=ds.X509Data( x509_certificate=ds.X509Certificate(text=cert_str))) extensions = Extensions(extension_elements=[ element_to_extension_element(spcertenc)]) if _cli.authn_requests_signed: _sid = saml2.s_utils.sid(_cli.seed) req_id, msg_str = _cli.create_authn_request( dest, vorg=vorg_name, sign=_cli.authn_requests_signed, message_id=_sid, extensions=extensions) _sid = req_id else: req_id, req = _cli.create_authn_request( dest, vorg=vorg_name, sign=False, extensions=extensions) msg_str = "%s" % req _sid = req_id if cert is not None: self.outstanding_certs[_sid] = cert ht_args = _cli.apply_binding(_binding, msg_str, destination=dest, relay_state=came_from) logger.debug("ht_args: %s" % ht_args) except Exception, exc: logger.exception(exc) raise Exception( "Failed to construct the AuthnRequest: %s" % exc) try: ret = _cli.config.getattr( "endpoints","sp")["discovery_response"][0][0] if (environ["PATH_INFO"]) in ret and ret.split( environ["PATH_INFO"])[1] == "": query = parse_qs(environ["QUERY_STRING"]) sid = query["sid"][0] came_from = self.outstanding_queries[sid] except: pass # remember the request self.outstanding_queries[_sid] = came_from if not ht_args["data"] and ht_args["headers"][0][0] == "Location": logger.debug('redirect to: %s' % ht_args["headers"][0][1]) return HTTPSeeOther(headers=ht_args["headers"]) else: return ht_args["data"]
def challenge(self, environ, _status, _app_headers, _forget_headers): _cli = self.saml_client if 'REMOTE_USER' in environ: name_id = decode(environ["REMOTE_USER"]) _cli = self.saml_client path_info = environ['PATH_INFO'] if 'samlsp.logout' in environ: responses = _cli.global_logout(name_id) return self._handle_logout(responses) if 'samlsp.pending' in environ: response = environ['samlsp.pending'] if isinstance(response, HTTPRedirection): response.headers += _forget_headers return response #logger = environ.get('repoze.who.logger','') # Which page was accessed to get here came_from = construct_came_from(environ) environ["myapp.came_from"] = came_from logger.debug("[sp.challenge] RelayState >> '%s'" % came_from) # Am I part of a virtual organization or more than one ? try: vorg_name = environ["myapp.vo"] except KeyError: try: vorg_name = _cli.vorg._name except AttributeError: vorg_name = "" logger.info("[sp.challenge] VO: %s" % vorg_name) # If more than one idp and if none is selected, I have to do wayf (done, response) = self._pick_idp(environ, came_from) # Three cases: -1 something went wrong or Discovery service used # 0 I've got an IdP to send a request to # >0 ECP in progress logger.debug("_idp_pick returned: %s" % done) if done == -1: return response elif done > 0: self.outstanding_queries[done] = came_from return ECP_response(response) else: entity_id = response logger.info("[sp.challenge] entity_id: %s" % entity_id) # Do the AuthnRequest _binding = BINDING_HTTP_REDIRECT try: srvs = _cli.metadata.single_sign_on_service(entity_id, _binding) logger.debug("srvs: %s" % srvs) dest = srvs[0]["location"] logger.debug("destination: %s" % dest) req = _cli.create_authn_request(dest, vorg=vorg_name) ht_args = _cli.apply_binding(_binding, "%s" % req, destination=dest, relay_state=came_from) _sid = req.id logger.debug("ht_args: %s" % ht_args) except Exception, exc: logger.exception(exc) raise Exception( "Failed to construct the AuthnRequest: %s" % exc) # remember the request self.outstanding_queries[_sid] = came_from if not ht_args["data"] and ht_args["headers"][0][0] == "Location": logger.debug('redirect to: %s' % ht_args["headers"][0][1]) return HTTPSeeOther(headers=ht_args["headers"]) else: return ht_args["data"]
def challenge(self, environ, _status, _app_headers, _forget_headers): _cli = self.saml_client if 'REMOTE_USER' in environ: name_id = decode(environ["REMOTE_USER"]) _cli = self.saml_client path_info = environ['PATH_INFO'] if 'samlsp.logout' in environ: responses = _cli.global_logout(name_id) return self._handle_logout(responses) if 'samlsp.pending' in environ: response = environ['samlsp.pending'] if isinstance(response, HTTPRedirection): response.headers += _forget_headers return response #logger = environ.get('repoze.who.logger','') # Which page was accessed to get here came_from = construct_came_from(environ) environ["myapp.came_from"] = came_from logger.debug("[sp.challenge] RelayState >> '%s'" % came_from) # Am I part of a virtual organization or more than one ? try: vorg_name = environ["myapp.vo"] except KeyError: try: vorg_name = _cli.vorg._name except AttributeError: vorg_name = "" logger.info("[sp.challenge] VO: %s" % vorg_name) # If more than one idp and if none is selected, I have to do wayf (done, response) = self._pick_idp(environ, came_from) # Three cases: -1 something went wrong or Discovery service used # 0 I've got an IdP to send a request to # >0 ECP in progress logger.debug("_idp_pick returned: %s" % done) if done == -1: return response elif done > 0: self.outstanding_queries[done] = came_from return ECP_response(response) else: entity_id = response logger.info("[sp.challenge] entity_id: %s" % entity_id) # Do the AuthnRequest _binding = BINDING_HTTP_REDIRECT try: srvs = _cli.metadata.single_sign_on_service(entity_id, _binding) logger.debug("srvs: %s" % srvs) dest = srvs[0]["location"] logger.debug("destination: %s" % dest) extensions = None cert = None if _cli.config.generate_cert_func is not None: cert_str, req_key_str = _cli.config.generate_cert_func() cert = { "cert": cert_str, "key": req_key_str } spcertenc = SPCertEnc(x509_data=ds.X509Data( x509_certificate=ds.X509Certificate(text=cert_str))) extensions = Extensions(extension_elements=[ element_to_extension_element(spcertenc)]) if _cli.authn_requests_signed: _sid = saml2.s_utils.sid(_cli.seed) req_id, msg_str = _cli.create_authn_request( dest, vorg=vorg_name, sign=_cli.authn_requests_signed, message_id=_sid, extensions=extensions) _sid = req_id else: req_id, req = _cli.create_authn_request( dest, vorg=vorg_name, sign=False, extensions=extensions) msg_str = "%s" % req _sid = req_id if cert is not None: self.outstanding_certs[_sid] = cert ht_args = _cli.apply_binding(_binding, msg_str, destination=dest, relay_state=came_from) logger.debug("ht_args: %s" % ht_args) except Exception, exc: logger.exception(exc) raise Exception( "Failed to construct the AuthnRequest: %s" % exc) try: ret = _cli.config.getattr( "endpoints", "sp")["discovery_response"][0][0] if (environ["PATH_INFO"]) in ret and ret.split( environ["PATH_INFO"])[1] == "": query = parse_qs(environ["QUERY_STRING"]) sid = query["sid"][0] came_from = self.outstanding_queries[sid] except: pass # remember the request self.outstanding_queries[_sid] = came_from if not ht_args["data"] and ht_args["headers"][0][0] == "Location": logger.debug('redirect to: %s' % ht_args["headers"][0][1]) return HTTPSeeOther(headers=ht_args["headers"]) else: return ht_args["data"]
def subjects(self): """ Return identifiers for all the subjects that are in the cache. :return: list of subject identifiers """ return [decode(c) for c in self._db.keys()]