def handle_Response(self, rsp, binding, relay_state): """handle a positive response.""" rid, ok, fail = self._resolve_relay_state(relay_state) if rid != rsp.InResponseTo: raise SamlError("relay state and response to not match: %s" % rid) if binding == "redirect": raise SamlError( "We do not accept responses via redirect (as we do not yet support signatures for redirect" ) context = ResponseContext( rsp, binding, self.REQUEST, self._get_authority(), ) # check whether this is a success or failure if rsp.Status.StatusCode.Value != rsp.PREFIX + "Success": # this is a failure logger.error("failed SAML2 request: %s" % rsp.toxml()) if fail is None: raise SystemError("unsolicited SAML failure response") return self.REQUEST.response.redirect(fail) # success response for ass in rsp.Assertion: self._process_assertion(ass, context) return self.REQUEST.response.redirect(ok)
def handle_Response(self, rsp, binding, relay_state): """handle a positive response.""" rid, ok, fail = self._resolve_relay_state(relay_state) if rid != rsp.InResponseTo: raise SamlError("relay state and response to not match: %s" % rid) if binding == "redirect": raise SamlError( "We do not accept responses via redirect (as we do not yet support signatures for redirect" ) context = ResponseContext( rsp, binding, self.REQUEST, self._get_authority(), ) # check whether this is a success or failure if rsp.Status.StatusCode.Value != rsp.PREFIX + "Success": # this is a failure logger.error("failed SAML2 request: %s" % rsp.toxml()) if fail is None: raise SystemError("unsolicited SAML failure response") return self.REQUEST.response.redirect(fail) # success response for ass in rsp.Assertion: self._process_assertion(ass, context) # Do not send back to the login (infinity loop) and do not send the user # out of the portal pu_tool = getToolByName(self, "portal_url") purl = pu_tool() if '/login' in ok or not pu_tool.isURLInPortal(ok): ok = purl return self.REQUEST.response.redirect(ok)
def _process_assertion(self, ass, context): # ensure we know the issuer -- an exception results, if not eid = ass.Issuer.value() auth = self._get_authority() auth.metadata_by_id(eid) if not ass.verified_signature(): raise SamlError("assertion %s was not signed by the issuer" % ass.ID) if not ass.is_valid(context): raise SamlError("assertion %s is not valid" % ass.ID) if context.binding == "post": # check the `SubjectConfirmation` condition of the `Browser SSO profile` ok = False for sc in ass.Subject.SubjectConfirmation: scm = sc.Method if scm != "urn:oasis:names:tc:SAML:2.0:cm:bearer": # we do not understand the method -- hope, we find one we understand continue scd = sc.SubjectConfirmationData if scd is None: continue # not valid if scd.NotOnOrAfter is None or utcnow() >= as_utc( scd.NotOnOrAfter): continue # not valid if scd.Recipient != context.rsp.Destination: continue # not valid if scd.InResponseTo != context.rsp.InResponseTo: continue # not valid # XXX Jazkarta Note: NYU is on a private network, so we get an address # mismatch here. At the point at which we want to remove # client-specific customizations, we'll have to monkey patch this # method or find some other means of overriding this check: # if scd.Address and context.zrequest.getClientAddr() != scd.Address: # continue # not valid ok = True break if not ok: raise SamlError( "subject confirmation in assertion %s is invalid" % ass.ID) subject = ass.Subject.NameID for tag in ("Statement", "AuthnStatement", "AuthzDecisionStatement", "AttributeStatement"): for s in getattr(ass, tag): # process statement -- "AttributeError", if we do not support its type getattr(self, "_process_" + tag)(subject, s)
def resolve(self, auth, req, rsp=None): """update the instance to have defined *url* and *binding*. Note: There is probably no need for *rsp*. """ __traceback_info__ = self.__dict__ eid = self.eid if eid is None and req is not None: eid = self.eid = req.Issuer.value() rd = self.get_role_descriptor(auth) if self.url: # the url has been directly specified. # SAML2 demands to verify that the url indeed belongs to the authority # We try to ensure that be enforcing that the host in url # is identical to the host part of one of the service endpoints # used by this role. from dm.saml2.util import child_values from dm.saml2.pyxb.metadata import EndpointType from urlparse import urlsplit target_host = urlsplit(self.url)[1] ok = False for c in child_values(rd): if not hasattr(c, "__iter__"): continue # all endpoints are repeatable for ep in c: if not isinstance(ep, EndpointType): break # if the first value is not endpoint, none will be # should we consider `ResponseLocation` as well? if target_host == urlsplit(ep.Location)[1]: ok = True break if ok: break if not ok: raise SamlError("`%s` does not belong to authority `%s`" % (self.url, eid)) if self.binding is None: # The request has specified a serice url but no # protocol binding. Try post (the only binding # we currently support for authentication responses) # Alternatively, we may try the url to determine # the binding from metadata self.binding = HttpPostBinding else: # `endpoint` must have been specified epd = getattr(rd, self.endpoint) # we may have more than a single endpoint if self.binding: # retrict by the requested binding epd = [d for d in epd if d.Binding == self.binding] epi = self.endpoint_index if epi is not None: # this requires the endpoints to be indexed endpoints epd = [d for d in epd if d.index == epi] if len(epd) > 1: # we still have more than a single candidate - select on `isDefault` epd = [d for d in epd if getattr(d, "isDefault", True)] if not epd: raise SamlError("no appropriate endpoint found: " + str((eid, self.endpoint, self.binding, self.endpoint_index))) epd = epd[0] self.binding = epd.Binding self.url = rsp is not None and epd.ResponseLocation or epd.Location # determine whether we should sign the assertions # Note: SAML requires that assertions are signed in the # HTTP-Post binding. if not self.sign_ass and self.sign_ass_attr: self.sign_ass = getattr(rd, self.sign_ass_attr) self.sign_ass = self.sign_ass or self.binding == HttpPostBinding # determine whether we should sign the message if not self.sign_msg and self.sign_msg_attr: self.sign_msg = getattr(rd, self.sign_msg_attr)