11 from Cookie import SimpleCookie
12 from hashlib import sha1
13 from urlparse import parse_qs
15 from saml2 import BINDING_HTTP_ARTIFACT
16 from saml2 import BINDING_URI
17 from saml2 import BINDING_PAOS
18 from saml2 import BINDING_SOAP
19 from saml2 import BINDING_HTTP_REDIRECT
20 from saml2 import BINDING_HTTP_POST
21 from saml2 import server
22 from saml2 import time_util
23 from saml2.authn import is_equal
25 from saml2.authn_context import AuthnBroker
26 from saml2.authn_context import PASSWORD
27 from saml2.authn_context import UNSPECIFIED
28 from saml2.authn_context import authn_context_class_ref
29 from saml2.httputil import Response
30 from saml2.httputil import NotFound
31 from saml2.httputil import geturl
32 from saml2.httputil import get_post
33 from saml2.httputil import Redirect
34 from saml2.httputil import Unauthorized
35 from saml2.httputil import BadRequest
36 from saml2.httputil import ServiceError
37 from saml2.ident import Unknown
38 from saml2.metadata import create_metadata_string
39 from saml2.profile import ecp
40 from saml2.s_utils import rndstr
41 from saml2.s_utils import exception_trace
42 from saml2.s_utils import UnknownPrincipal
43 from saml2.s_utils import UnsupportedBinding
44 from saml2.s_utils import PolicyError
45 from saml2.sigver import verify_redirect_signature
46 from saml2.sigver import encrypt_cert_from_item
48 from idp_user import USERS
49 from idp_user import EXTRA
50 from mako.lookup import TemplateLookup
52 logger = logging.getLogger("saml2.idp")
53 logger.setLevel(logging.WARNING)
62 def _expiration(timeout, tformat="%a, %d-%b-%Y %H:%M:%S GMT"):
70 return time_util.instant(tformat)
71 elif timeout == "dawn":
72 return time.strftime(tformat, time.gmtime(0))
74 # validity time should match lifetime of assertions
75 return time_util.in_a_while(minutes=timeout, format=tformat)
78 # -----------------------------------------------------------------------------
81 def dict2list_of_tuples(d):
82 return [(k, v) for k, v in d.items()]
85 # -----------------------------------------------------------------------------
88 class Service(object):
89 def __init__(self, environ, start_response, user=None):
90 self.environ = environ
91 logger.debug("ENVIRON: %s" % environ)
92 self.start_response = start_response
95 def unpack_redirect(self):
96 if "QUERY_STRING" in self.environ:
97 _qs = self.environ["QUERY_STRING"]
98 return dict([(k, v[0]) for k, v in parse_qs(_qs).items()])
102 def unpack_post(self):
103 _dict = parse_qs(get_post(self.environ))
104 logger.debug("unpack_post:: %s" % _dict)
106 return dict([(k, v[0]) for k, v in _dict.items()])
110 def unpack_soap(self):
112 query = get_post(self.environ)
113 return {"SAMLRequest": query, "RelayState": ""}
117 def unpack_either(self):
118 if self.environ["REQUEST_METHOD"] == "GET":
119 _dict = self.unpack_redirect()
120 elif self.environ["REQUEST_METHOD"] == "POST":
121 _dict = self.unpack_post()
124 logger.debug("_dict: %s" % _dict)
127 def operation(self, saml_msg, binding):
128 logger.debug("_operation: %s" % saml_msg)
129 if not (saml_msg and 'SAMLRequest' in saml_msg):
130 resp = BadRequest('Error parsing request or no request')
131 return resp(self.environ, self.start_response)
133 # saml_msg may also contain Signature and SigAlg
134 if "Signature" in saml_msg:
135 kwargs = {"signature": saml_msg["signature"],
136 "sigalg": saml_msg["SigAlg"]}
140 _encrypt_cert = encrypt_cert_from_item(
141 saml_msg["req_info"].message)
142 return self.do(saml_msg["SAMLRequest"], binding,
143 saml_msg["RelayState"],
144 encrypt_cert=_encrypt_cert, **kwargs)
146 # Can live with no relay state
147 return self.do(saml_msg["SAMLRequest"], binding,
148 saml_msg["RelayState"], **kwargs)
150 def artifact_operation(self, saml_msg):
152 resp = BadRequest("Missing query")
153 return resp(self.environ, self.start_response)
155 # exchange artifact for request
156 request = IDP.artifact2message(saml_msg["SAMLart"], "spsso")
158 return self.do(request, BINDING_HTTP_ARTIFACT,
159 saml_msg["RelayState"])
161 return self.do(request, BINDING_HTTP_ARTIFACT)
163 def response(self, binding, http_args):
165 if binding == BINDING_HTTP_ARTIFACT:
167 elif http_args["data"]:
168 resp = Response(http_args["data"], headers=http_args["headers"])
170 for header in http_args["headers"]:
171 if header[0] == "Location":
172 resp = Redirect(header[1])
175 resp = ServiceError("Don't know how to return response")
177 return resp(self.environ, self.start_response)
179 def do(self, query, binding, relay_state="", encrypt_cert=None):
183 """ Expects a HTTP-redirect request """
185 _dict = self.unpack_redirect()
186 return self.operation(_dict, BINDING_HTTP_REDIRECT)
189 """ Expects a HTTP-POST request """
191 _dict = self.unpack_post()
192 return self.operation(_dict, BINDING_HTTP_POST)
195 # Can be either by HTTP_Redirect or HTTP_POST
196 _dict = self.unpack_either()
197 return self.artifact_operation(_dict)
201 Single log out using HTTP_SOAP binding
203 logger.debug("- SOAP -")
204 _dict = self.unpack_soap()
205 logger.debug("_dict: %s" % _dict)
206 return self.operation(_dict, BINDING_SOAP)
209 _dict = self.unpack_either()
210 return self.operation(_dict, BINDING_SOAP)
212 def not_authn(self, key, requested_authn_context):
213 ruri = geturl(self.environ, query=False)
215 kwargs = dict(authn_context=requested_authn_context, key=key, redirect_uri=ruri)
216 # Clear cookie, if it already exists
217 kaka = delete_cookie(self.environ, "idpauthn")
219 kwargs["headers"] = [kaka]
220 return do_authentication(self.environ, self.start_response, **kwargs)
222 # -----------------------------------------------------------------------------
224 REPOZE_ID_EQUIVALENT = "uid"
225 FORM_SPEC = """<form name="myform" method="post" action="%s">
226 <input type="hidden" name="SAMLResponse" value="%s" />
227 <input type="hidden" name="RelayState" value="%s" />
230 # -----------------------------------------------------------------------------
231 # === Single log in ====
232 # -----------------------------------------------------------------------------
235 class AuthenticationNeeded(Exception):
236 def __init__(self, authn_context=None, *args, **kwargs):
237 Exception.__init__(*args, **kwargs)
238 self.authn_context = authn_context
242 def __init__(self, environ, start_response, user=None):
243 Service.__init__(self, environ, start_response, user)
245 self.response_bindings = None
247 self.binding_out = None
248 self.destination = None
252 def verify_request(self, query, binding):
254 :param query: The SAML query, transport encoded
255 :param binding: Which binding the query came in over
259 logger.info("Missing QUERY")
260 resp = Unauthorized('Unknown user')
261 return resp_args, resp(self.environ, self.start_response)
263 if not self.req_info:
264 self.req_info = IDP.parse_authn_request(query, binding)
266 logger.info("parsed OK")
267 _authn_req = self.req_info.message
268 logger.debug("%s" % _authn_req)
271 self.binding_out, self.destination = IDP.pick_binding(
272 "assertion_consumer_service",
273 bindings=self.response_bindings,
274 entity_id=_authn_req.issuer.text, request=_authn_req)
275 except Exception as err:
276 logger.error("Couldn't find receiver endpoint: %s" % err)
279 logger.debug("Binding: %s, destination: %s" % (self.binding_out,
284 resp_args = IDP.response_args(_authn_req)
286 except UnknownPrincipal as excp:
287 _resp = IDP.create_error_response(_authn_req.id,
288 self.destination, excp)
289 except UnsupportedBinding as excp:
290 _resp = IDP.create_error_response(_authn_req.id,
291 self.destination, excp)
293 return resp_args, _resp
295 def do(self, query, binding_in, relay_state="", encrypt_cert=None):
298 :param query: The request
299 :param binding_in: Which binding was used when receiving the query
300 :param relay_state: The relay state provided by the SP
301 :param encrypt_cert: Cert to use for encryption
305 resp_args, _resp = self.verify_request(query, binding_in)
306 except UnknownPrincipal as excp:
307 logger.error("UnknownPrincipal: %s" % (excp,))
308 resp = ServiceError("UnknownPrincipal: %s" % (excp,))
309 return resp(self.environ, self.start_response)
310 except UnsupportedBinding as excp:
311 logger.error("UnsupportedBinding: %s" % (excp,))
312 resp = ServiceError("UnsupportedBinding: %s" % (excp,))
313 return resp(self.environ, self.start_response)
316 identity = USERS[self.user].copy()
317 # identity["eduPersonTargetedID"] = get_eptid(IDP, query, session)
318 logger.info("Identity: %s" % (identity,))
320 if REPOZE_ID_EQUIVALENT:
321 identity[REPOZE_ID_EQUIVALENT] = self.user
324 metod = self.environ["idp.authn"]
328 resp_args["authn"] = metod
330 _resp = IDP.create_authn_response(
331 identity, userid=self.user,
332 encrypt_cert=encrypt_cert,
334 except Exception as excp:
335 logging.error(exception_trace(excp))
336 resp = ServiceError("Exception: %s" % (excp,))
337 return resp(self.environ, self.start_response)
339 logger.info("AuthNResponse: %s" % _resp)
340 if self.op_type == "ecp":
341 kwargs = {"soap_headers": [
343 assertion_consumer_service_url=self.destination)]}
347 http_args = IDP.apply_binding(self.binding_out,
348 "%s" % _resp, self.destination,
349 relay_state, response=True, **kwargs)
351 logger.debug("HTTPargs: %s" % http_args)
352 return self.response(self.binding_out, http_args)
355 def _store_request(saml_msg):
356 logger.debug("_store_request: %s" % saml_msg)
357 key = sha1(saml_msg["SAMLRequest"]).hexdigest()
358 # store the AuthnRequest
359 IDP.ticket[key] = saml_msg
363 """ This is the HTTP-redirect endpoint """
365 logger.info("--- In SSO Redirect ---")
366 saml_msg = self.unpack_redirect()
369 _key = saml_msg["key"]
370 saml_msg = IDP.ticket[_key]
371 self.req_info = saml_msg["req_info"]
375 self.req_info = IDP.parse_authn_request(saml_msg["SAMLRequest"],
376 BINDING_HTTP_REDIRECT)
378 resp = BadRequest("Message signature verification failure")
379 return resp(self.environ, self.start_response)
381 _req = self.req_info.message
383 if "SigAlg" in saml_msg and "Signature" in saml_msg:
385 issuer = _req.issuer.text
386 _certs = IDP.metadata.certs(issuer, "any", "signing")
389 if verify_redirect_signature(saml_msg, cert):
393 resp = BadRequest("Message signature verification failure")
394 return resp(self.environ, self.start_response)
397 if _req.force_authn is not None and \
398 _req.force_authn.lower() == 'true':
399 saml_msg["req_info"] = self.req_info
400 key = self._store_request(saml_msg)
401 return self.not_authn(key, _req.requested_authn_context)
403 return self.operation(saml_msg, BINDING_HTTP_REDIRECT)
405 saml_msg["req_info"] = self.req_info
406 key = self._store_request(saml_msg)
407 return self.not_authn(key, _req.requested_authn_context)
409 return self.operation(saml_msg, BINDING_HTTP_REDIRECT)
413 The HTTP-Post endpoint
415 logger.info("--- In SSO POST ---")
416 saml_msg = self.unpack_either()
419 _key = saml_msg["key"]
420 saml_msg = IDP.ticket[_key]
421 self.req_info = saml_msg["req_info"]
424 self.req_info = IDP.parse_authn_request(
425 saml_msg["SAMLRequest"], BINDING_HTTP_POST)
426 _req = self.req_info.message
428 if _req.force_authn is not None and \
429 _req.force_authn.lower() == 'true':
430 saml_msg["req_info"] = self.req_info
431 key = self._store_request(saml_msg)
432 return self.not_authn(key, _req.requested_authn_context)
434 return self.operation(saml_msg, BINDING_HTTP_POST)
436 saml_msg["req_info"] = self.req_info
437 key = self._store_request(saml_msg)
438 return self.not_authn(key, _req.requested_authn_context)
440 return self.operation(saml_msg, BINDING_HTTP_POST)
442 # def artifact(self):
443 # # Can be either by HTTP_Redirect or HTTP_POST
444 # _req = self._store_request(self.unpack_either())
445 # if isinstance(_req, basestring):
446 # return self.not_authn(_req)
447 # return self.artifact_operation(_req)
451 logger.info("--- ECP SSO ---")
455 authz_info = self.environ["HTTP_AUTHORIZATION"]
456 if authz_info.startswith("Basic "):
458 _info = base64.b64decode(authz_info[6:])
460 resp = Unauthorized()
463 (user, passwd) = _info.split(":")
464 if is_equal(PASSWD[user], passwd):
465 resp = Unauthorized()
468 "idp.authn"] = AUTHN_BROKER.get_authn_by_accr(
471 resp = Unauthorized()
473 resp = Unauthorized()
475 resp = Unauthorized()
478 return resp(self.environ, self.start_response)
480 _dict = self.unpack_soap()
481 self.response_bindings = [BINDING_PAOS]
484 return self.operation(_dict, BINDING_SOAP)
487 # -----------------------------------------------------------------------------
488 # === Authentication ====
489 # -----------------------------------------------------------------------------
492 def do_authentication(environ, start_response, authn_context, key,
493 redirect_uri, headers=None):
495 Display the login form
497 logger.debug("Do authentication")
498 auth_info = AUTHN_BROKER.pick(authn_context)
501 method, reference = auth_info[0]
502 logger.debug("Authn chosen: %s (ref=%s)" % (method, reference))
503 return method(environ, start_response, reference, key, redirect_uri, headers)
505 resp = Unauthorized("No usable authentication method")
506 return resp(environ, start_response)
509 # -----------------------------------------------------------------------------
512 "daev0001": "qwerty",
513 "haho0032": "qwerty",
514 "roland": "dianakra",
519 def username_password_authn(environ, start_response, reference, key,
520 redirect_uri, headers=None):
522 Display the login form
524 logger.info("The login page")
526 kwargs = dict(mako_template="login.mako", template_lookup=LOOKUP)
528 kwargs["headers"] = headers
530 resp = Response(**kwargs)
537 "authn_reference": reference,
538 "redirect_uri": redirect_uri
540 logger.info("do_authentication argv: %s" % argv)
541 return resp(environ, start_response, **argv)
544 def verify_username_and_password(dic):
546 # verify username and password
547 if PASSWD[dic["login"][0]] == dic["password"][0]:
548 return True, dic["login"][0]
553 def do_verify(environ, start_response, _):
554 query = parse_qs(get_post(environ))
556 logger.debug("do_verify: %s" % query)
559 _ok, user = verify_username_and_password(query)
565 resp = Unauthorized("Unknown user or wrong password")
568 IDP.cache.uid2user[uid] = user
569 IDP.cache.user2uid[user] = uid
570 logger.debug("Register %s under '%s'" % (user, uid))
572 kaka = set_cookie("idpauthn", "/", uid, query["authn_reference"][0])
574 lox = "%s?id=%s&key=%s" % (query["redirect_uri"][0], uid,
576 logger.debug("Redirect => %s" % lox)
577 resp = Redirect(lox, headers=[kaka], content="text/html")
579 return resp(environ, start_response)
582 def not_found(environ, start_response):
583 """Called if no URL matches."""
585 return resp(environ, start_response)
588 # -----------------------------------------------------------------------------
589 # === Single log out ===
590 # -----------------------------------------------------------------------------
592 # def _subject_sp_info(req_info):
593 # # look for the subject
594 # subject = req_info.subject_id()
595 # subject = subject.text.strip()
596 # sp_entity_id = req_info.message.issuer.text.strip()
597 # return subject, sp_entity_id
600 def do(self, request, binding, relay_state="", encrypt_cert=None):
602 logger.info("--- Single Log Out Service ---")
604 logger.debug("req: '%s'" % request)
605 req_info = IDP.parse_logout_request(request, binding)
606 except Exception as exc:
607 logger.error("Bad request: %s" % exc)
608 resp = BadRequest("%s" % exc)
609 return resp(self.environ, self.start_response)
611 msg = req_info.message
613 lid = IDP.ident.find_local_id(msg.name_id)
614 logger.info("local identifier: %s" % lid)
615 if lid in IDP.cache.user2uid:
616 uid = IDP.cache.user2uid[lid]
617 if uid in IDP.cache.uid2user:
618 del IDP.cache.uid2user[uid]
619 del IDP.cache.user2uid[lid]
620 # remove the authentication
622 IDP.session_db.remove_authn_statements(msg.name_id)
623 except KeyError as exc:
624 logger.error("Unknown session: %s" % exc)
625 resp = ServiceError("Unknown session: %s" % exc)
626 return resp(self.environ, self.start_response)
628 resp = IDP.create_logout_response(msg, [binding])
630 if binding == BINDING_SOAP:
634 binding, destination = IDP.pick_binding("single_logout_service",
640 hinfo = IDP.apply_binding(binding, "%s" % resp, destination,
641 relay_state, response=response)
642 except Exception as exc:
643 logger.error("ServiceError: %s" % exc)
644 resp = ServiceError("%s" % exc)
645 return resp(self.environ, self.start_response)
647 #_tlh = dict2list_of_tuples(hinfo["headers"])
648 delco = delete_cookie(self.environ, "idpauthn")
650 hinfo["headers"].append(delco)
651 logger.info("Header: %s" % (hinfo["headers"],))
653 if binding == BINDING_HTTP_REDIRECT:
654 for key, value in hinfo['headers']:
655 if key.lower() == 'location':
656 resp = Redirect(value, headers=hinfo["headers"])
657 return resp(self.environ, self.start_response)
659 resp = ServiceError('missing Location header')
660 return resp(self.environ, self.start_response)
662 resp = Response(hinfo["data"], headers=hinfo["headers"])
663 return resp(self.environ, self.start_response)
666 # ----------------------------------------------------------------------------
667 # Manage Name ID service
668 # ----------------------------------------------------------------------------
672 def do(self, query, binding, relay_state="", encrypt_cert=None):
673 logger.info("--- Manage Name ID Service ---")
674 req = IDP.parse_manage_name_id_request(query, binding)
675 request = req.message
677 # Do the necessary stuff
678 name_id = IDP.ident.handle_manage_name_id_request(
679 request.name_id, request.new_id, request.new_encrypted_id,
682 logger.debug("New NameID: %s" % name_id)
684 _resp = IDP.create_manage_name_id_response(request)
686 # It's using SOAP binding
687 hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % _resp, "",
688 relay_state, response=True)
690 resp = Response(hinfo["data"], headers=hinfo["headers"])
691 return resp(self.environ, self.start_response)
694 # ----------------------------------------------------------------------------
695 # === Assertion ID request ===
696 # ----------------------------------------------------------------------------
701 def do(self, aid, binding, relay_state="", encrypt_cert=None):
702 logger.info("--- Assertion ID Service ---")
705 assertion = IDP.create_assertion_id_request_response(aid)
708 return resp(self.environ, self.start_response)
710 hinfo = IDP.apply_binding(BINDING_URI, "%s" % assertion, response=True)
712 logger.debug("HINFO: %s" % hinfo)
713 resp = Response(hinfo["data"], headers=hinfo["headers"])
714 return resp(self.environ, self.start_response)
716 def operation(self, _dict, binding, **kwargs):
717 logger.debug("_operation: %s" % _dict)
718 if not _dict or "ID" not in _dict:
719 resp = BadRequest('Error parsing request or no request')
720 return resp(self.environ, self.start_response)
722 return self.do(_dict["ID"], binding, **kwargs)
725 # ----------------------------------------------------------------------------
726 # === Artifact resolve service ===
727 # ----------------------------------------------------------------------------
730 def do(self, request, binding, relay_state="", encrypt_cert=None):
731 _req = IDP.parse_artifact_resolve(request, binding)
733 msg = IDP.create_artifact_response(_req, _req.artifact.text)
735 hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % msg, "", "",
738 resp = Response(hinfo["data"], headers=hinfo["headers"])
739 return resp(self.environ, self.start_response)
742 # ----------------------------------------------------------------------------
743 # === Authn query service ===
744 # ----------------------------------------------------------------------------
749 def do(self, request, binding, relay_state="", encrypt_cert=None):
750 logger.info("--- Authn Query Service ---")
751 _req = IDP.parse_authn_query(request, binding)
752 _query = _req.message
754 msg = IDP.create_authn_query_response(_query.subject,
755 _query.requested_authn_context,
756 _query.session_index)
758 logger.debug("response: %s" % msg)
759 hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % msg, "", "",
762 resp = Response(hinfo["data"], headers=hinfo["headers"])
763 return resp(self.environ, self.start_response)
766 # ----------------------------------------------------------------------------
767 # === Attribute query service ===
768 # ----------------------------------------------------------------------------
773 def do(self, request, binding, relay_state="", encrypt_cert=None):
774 logger.info("--- Attribute Query Service ---")
776 _req = IDP.parse_attribute_query(request, binding)
777 _query = _req.message
779 name_id = _query.subject.name_id
781 logger.debug("Local uid: %s" % uid)
782 identity = EXTRA[uid]
784 # Comes in over SOAP so only need to construct the response
785 args = IDP.response_args(_query, [BINDING_SOAP])
786 msg = IDP.create_attribute_response(identity,
787 name_id=name_id, **args)
789 logger.debug("response: %s" % msg)
790 hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % msg, "", "",
793 resp = Response(hinfo["data"], headers=hinfo["headers"])
794 return resp(self.environ, self.start_response)
797 # ----------------------------------------------------------------------------
798 # Name ID Mapping service
799 # When an entity that shares an identifier for a principal with an identity
800 # provider wishes to obtain a name identifier for the same principal in a
801 # particular format or federation namespace, it can send a request to
802 # the identity provider using this protocol.
803 # ----------------------------------------------------------------------------
807 def do(self, query, binding, relay_state="", encrypt_cert=None):
808 req = IDP.parse_name_id_mapping_request(query, binding)
809 request = req.message
810 # Do the necessary stuff
812 name_id = IDP.ident.handle_name_id_mapping_request(
813 request.name_id, request.name_id_policy)
815 resp = BadRequest("Unknown entity")
816 return resp(self.environ, self.start_response)
818 resp = BadRequest("Unknown entity")
819 return resp(self.environ, self.start_response)
821 info = IDP.response_args(request)
822 _resp = IDP.create_name_id_mapping_response(name_id, **info)
825 hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % _resp, "", "",
828 resp = Response(hinfo["data"], headers=hinfo["headers"])
829 return resp(self.environ, self.start_response)
832 # ----------------------------------------------------------------------------
834 # ----------------------------------------------------------------------------
835 def info_from_cookie(kaka):
836 logger.debug("KAKA: %s" % kaka)
838 cookie_obj = SimpleCookie(kaka)
839 morsel = cookie_obj.get("idpauthn", None)
842 key, ref = base64.b64decode(morsel.value).split(":")
843 return IDP.cache.uid2user[key], ref
844 except (KeyError, TypeError):
847 logger.debug("No idpauthn cookie")
851 def delete_cookie(environ, name):
852 kaka = environ.get("HTTP_COOKIE", '')
853 logger.debug("delete KAKA: %s" % kaka)
855 cookie_obj = SimpleCookie(kaka)
856 morsel = cookie_obj.get(name, None)
857 cookie = SimpleCookie()
859 cookie[name]['path'] = "/"
860 logger.debug("Expire: %s" % morsel)
861 cookie[name]["expires"] = _expiration("dawn")
862 return tuple(cookie.output().split(": ", 1))
866 def set_cookie(name, _, *args):
867 cookie = SimpleCookie()
868 cookie[name] = base64.b64encode(":".join(args))
869 cookie[name]['path'] = "/"
870 cookie[name]["expires"] = _expiration(5) # 5 minutes from now
871 logger.debug("Cookie expires: %s" % cookie[name]["expires"])
872 return tuple(cookie.output().split(": ", 1))
874 # ----------------------------------------------------------------------------
876 # map urls to functions
879 (r'sso/post$', (SSO, "post")),
880 (r'sso/post/(.*)$', (SSO, "post")),
881 (r'sso/redirect$', (SSO, "redirect")),
882 (r'sso/redirect/(.*)$', (SSO, "redirect")),
883 (r'sso/art$', (SSO, "artifact")),
884 (r'sso/art/(.*)$', (SSO, "artifact")),
886 (r'slo/redirect$', (SLO, "redirect")),
887 (r'slo/redirect/(.*)$', (SLO, "redirect")),
888 (r'slo/post$', (SLO, "post")),
889 (r'slo/post/(.*)$', (SLO, "post")),
890 (r'slo/soap$', (SLO, "soap")),
891 (r'slo/soap/(.*)$', (SLO, "soap")),
893 (r'airs$', (AIDR, "uri")),
894 (r'ars$', (ARS, "soap")),
896 (r'mni/post$', (NMI, "post")),
897 (r'mni/post/(.*)$', (NMI, "post")),
898 (r'mni/redirect$', (NMI, "redirect")),
899 (r'mni/redirect/(.*)$', (NMI, "redirect")),
900 (r'mni/art$', (NMI, "artifact")),
901 (r'mni/art/(.*)$', (NMI, "artifact")),
902 (r'mni/soap$', (NMI, "soap")),
903 (r'mni/soap/(.*)$', (NMI, "soap")),
905 (r'nim$', (NIM, "soap")),
906 (r'nim/(.*)$', (NIM, "soap")),
908 (r'aqs$', (AQS, "soap")),
909 (r'attr$', (ATTR, "soap"))
913 #(r'login?(.*)$', do_authentication),
914 (r'verify?(.*)$', do_verify),
915 (r'sso/ecp$', (SSO, "ecp")),
918 # ----------------------------------------------------------------------------
921 def metadata(environ, start_response):
924 if path is None or len(path) == 0:
925 path = os.path.dirname(os.path.abspath(__file__))
928 metadata = create_metadata_string(path + args.config, IDP.config,
929 args.valid, args.cert, args.keyfile,
930 args.id, args.name, args.sign)
931 start_response('200 OK', [('Content-Type', "text/xml")])
933 except Exception as ex:
934 logger.error("An error occured while creating metadata:" + ex.message)
935 return not_found(environ, start_response)
938 def staticfile(environ, start_response):
941 if path is None or len(path) == 0:
942 path = os.path.dirname(os.path.abspath(__file__))
945 path += environ.get('PATH_INFO', '').lstrip('/')
946 path = os.path.realpath(path)
947 if not path.startswith(args.path):
948 resp = Unauthorized()
949 return resp(environ, start_response)
950 start_response('200 OK', [('Content-Type', "text/xml")])
951 return open(path, 'r').read()
952 except Exception as ex:
953 logger.error("An error occured while creating metadata:" + ex.message)
954 return not_found(environ, start_response)
957 def application(environ, start_response):
959 The main WSGI application. Dispatch the current request to
960 the functions from above and store the regular expression
961 captures in the WSGI environment as `myapp.url_args` so that
962 the functions from above can access the url placeholders.
964 If nothing matches, call the `not_found` function.
966 :param environ: The HTTP application environment
967 :param start_response: The application to run when the handling of the
969 :return: The response as a list of lines
972 path = environ.get('PATH_INFO', '').lstrip('/')
974 if path == "metadata":
975 return metadata(environ, start_response)
977 kaka = environ.get("HTTP_COOKIE", None)
978 logger.info("<application> PATH: %s" % path)
981 logger.info("= KAKA =")
982 user, authn_ref = info_from_cookie(kaka)
984 environ["idp.authn"] = AUTHN_BROKER[authn_ref]
987 query = parse_qs(environ["QUERY_STRING"])
988 logger.debug("QUERY: %s" % query)
989 user = IDP.cache.uid2user[query["id"][0]]
993 url_patterns = AUTHN_URLS
995 logger.info("-- No USER --")
996 # insert NON_AUTHN_URLS first in case there is no user
997 url_patterns = NON_AUTHN_URLS + url_patterns
999 for regex, callback in url_patterns:
1000 match = re.search(regex, path)
1001 if match is not None:
1003 environ['myapp.url_args'] = match.groups()[0]
1005 environ['myapp.url_args'] = path
1007 logger.debug("Callback: %s" % (callback,))
1008 if isinstance(callback, tuple):
1009 cls = callback[0](environ, start_response, user)
1010 func = getattr(cls, callback[1])
1012 return callback(environ, start_response, user)
1014 if re.search(r'static/.*', path) is not None:
1015 return staticfile(environ, start_response)
1016 return not_found(environ, start_response)
1018 # ----------------------------------------------------------------------------
1020 if __name__ == '__main__':
1021 from wsgiref.simple_server import make_server
1023 parser = argparse.ArgumentParser()
1024 parser.add_argument('-p', dest='path', help='Path to configuration file.')
1025 parser.add_argument('-v', dest='valid',
1026 help="How long, in days, the metadata is valid from "
1027 "the time of creation")
1028 parser.add_argument('-c', dest='cert', help='certificate')
1029 parser.add_argument('-i', dest='id',
1030 help="The ID of the entities descriptor")
1031 parser.add_argument('-k', dest='keyfile',
1032 help="A file with a key to sign the metadata with")
1033 parser.add_argument('-n', dest='name')
1034 parser.add_argument('-s', dest='sign', action='store_true',
1035 help="sign the metadata")
1036 parser.add_argument('-m', dest='mako_root', default="./")
1037 parser.add_argument(dest="config")
1038 args = parser.parse_args()
1040 AUTHN_BROKER = AuthnBroker()
1041 AUTHN_BROKER.add(authn_context_class_ref(PASSWORD),
1042 username_password_authn, 10,
1043 "http://%s" % socket.gethostname())
1044 AUTHN_BROKER.add(authn_context_class_ref(UNSPECIFIED),
1045 "", 0, "http://%s" % socket.gethostname())
1046 CONFIG = importlib.import_module(args.config)
1047 IDP = server.Server(args.config, cache=Cache())
1050 _rot = args.mako_root
1051 LOOKUP = TemplateLookup(directories=[_rot + 'templates', _rot + 'htdocs'],
1052 module_directory=_rot + 'modules',
1053 input_encoding='utf-8', output_encoding='utf-8')
1058 SRV = make_server(HOST, PORT, application)
1059 print "IdP listening on %s:%s" % (HOST, PORT)