3232from saml2 .s_utils import UnknownSystemEntity
3333from saml2 .sigver import split_len
3434from saml2 .validate import valid_instance
35- from saml2 .time_util import valid , instant , add_duration , before , str_to_time
35+ from saml2 .time_util import valid
36+ from saml2 .time_util import instant
37+ from saml2 .time_util import add_duration
38+ from saml2 .time_util import before
39+ from saml2 .time_util import str_to_time
3640from saml2 .validate import NotValid
3741from saml2 .sigver import security_context
3842from saml2 .extension .mdattr import NAMESPACE as NS_MDATTR
7276ENTITY_CATEGORY_SUPPORT = "http://macedir.org/entity-category-support"
7377ASSURANCE_CERTIFICATION = "urn:oasis:names:tc:SAML:attribute:assurance-certification"
7478
79+ SAML_METADATA_CONTENT_TYPE = "application/samlmetadata+xml"
80+ DEFAULT_FRESHNESS_PERIOD = "P0Y0M0DT12H0M0S"
81+
82+
83+
7584REQ2SRV = {
7685 # IDP
7786 "authn_request" : "single_sign_on_service" ,
@@ -664,22 +673,21 @@ def signed(self):
664673 def parse_and_check_signature (self , txt ):
665674 self .parse (txt )
666675
667- if self .cert :
668- if not self .signed ():
669- return True
670-
671- node_name = self .node_name \
672- or "%s:%s" % (md .EntitiesDescriptor .c_namespace ,
673- md .EntitiesDescriptor .c_tag )
676+ if not self .cert :
677+ return True
674678
675- if self .security .verify_signature (
676- txt , node_name = node_name , cert_file = self .cert ):
677- return True
678- else :
679- return False
680- else :
679+ if not self .signed ():
681680 return True
682681
682+ fallback_name = "{ns}:{tag}" .format (
683+ ns = md .EntitiesDescriptor .c_namespace , tag = md .EntitiesDescriptor .c_tag
684+ )
685+ node_name = self .node_name or fallback_name
686+
687+ return self .security .verify_signature (
688+ txt , node_name = node_name , cert_file = self .cert
689+ )
690+
683691
684692class MetaDataFile (InMemoryMetaData ):
685693 """
@@ -805,9 +813,6 @@ def load(self, *args, **kwargs):
805813 self .entity [key ] = item
806814
807815
808- SAML_METADATA_CONTENT_TYPE = 'application/samlmetadata+xml'
809-
810-
811816class MetaDataMDX (InMemoryMetaData ):
812817 """
813818 Uses the MDQ protocol to fetch entity information.
@@ -817,8 +822,9 @@ class MetaDataMDX(InMemoryMetaData):
817822
818823 @staticmethod
819824 def sha1_entity_transform (entity_id ):
820- return "{{sha1}}{}" .format (
821- hashlib .sha1 (entity_id .encode ("utf-8" )).hexdigest ())
825+ entity_id_sha1 = hashlib .sha1 (entity_id .encode ("utf-8" )).hexdigest ()
826+ transform = "{{sha1}}{digest}" .format (digest = entity_id_sha1 )
827+ return transform
822828
823829 def __init__ (self , url = None , security = None , cert = None ,
824830 entity_transform = None , freshness_period = None , ** kwargs ):
@@ -847,53 +853,61 @@ def __init__(self, url=None, security=None, cert=None,
847853
848854 self .cert = cert
849855 self .security = security
850- self .freshness_period = freshness_period
851- if freshness_period :
852- self .expiration_date = {}
856+ self .freshness_period = freshness_period or DEFAULT_FRESHNESS_PERIOD
857+ self .expiration_date = {}
853858
854859 # We assume that the MDQ server will return a single entity
855860 # described by a single <EntityDescriptor> element. The protocol
856861 # does allow multiple entities to be returned in an
857862 # <EntitiesDescriptor> element but we will not currently support
858863 # that use case since it is unlikely to be leveraged for most
859864 # flows.
860- self .node_name = "%s:%s" % (md .EntityDescriptor .c_namespace ,
861- md .EntityDescriptor .c_tag )
865+ self .node_name = "{ns}:{tag}" .format (
866+ ns = md .EntityDescriptor .c_namespace , tag = md .EntityDescriptor .c_tag
867+ )
862868
863869 def load (self , * args , ** kwargs ):
864870 # Do nothing
865871 pass
866872
867- def fetch_metadata (self , item ):
868- mdx_url = "%s/entities/%s" % (self .url , self .entity_transform (item ))
869- response = requests .get (mdx_url , headers = {
870- 'Accept' : SAML_METADATA_CONTENT_TYPE })
871- if response .status_code == 200 :
872- _txt = response .content
873- if self .parse_and_check_signature (_txt ):
874- if self .freshness_period :
875- curr_time = str_to_time (instant ())
876- self .expiration_date [item ] = add_duration (
877- curr_time , self .freshness_period )
878- return self .entity [item ]
879- else :
880- logger .info ("Response status: %s" , response .status_code )
881- raise KeyError
873+ def _fetch_metadata (self , item ):
874+ mdx_url = "{url}/entities/{id}" .format (
875+ url = self .url , id = self .entity_transform (item )
876+ )
882877
883- def _is_fresh (self , item ):
884- return self .freshness_period and before (self .expiration_date [item ])
878+ response = requests .get (mdx_url , headers = {"Accept" : SAML_METADATA_CONTENT_TYPE })
879+ if response .status_code != 200 :
880+ error_msg = "Fething {item}: Got response status {status}" .format (
881+ item = item , status = response .status_code
882+ )
883+ logger .info (error_msg )
884+ raise KeyError (error_msg )
885+
886+ _txt = response .content
887+ if not self .parse_and_check_signature (_txt ):
888+ error_msg = "Fething {item}: invalid signature" .format (
889+ item = item , status = response .status_code
890+ )
891+ logger .info (error_msg )
892+ raise KeyError (error_msg )
893+
894+ curr_time = str_to_time (instant ())
895+ self .expiration_date [item ] = add_duration (curr_time , self .freshness_period )
896+ return self .entity [item ]
897+
898+ def _is_metadata_fresh (self , item ):
899+ return before (self .expiration_date [item ])
885900
886901 def __getitem__ (self , item ):
887- if item in self .entity :
888- if self ._is_fresh (item ):
889- entity = self .entity [item ]
890- else :
891- logger .info ("Metadata for {} have expired, refreshing "
892- "metadata" .format (item ))
893- self .entity .pop (item )
894- entity = self .fetch_metadata (item )
902+ if item not in self .entity :
903+ entity = self ._fetch_metadata (item )
904+ elif not self ._is_metadata_fresh (item ):
905+ msg = "Metadata for {} have expired; refreshing metadata" .format (item )
906+ logger .info (msg )
907+ old_entity = self .entity .pop (item )
908+ entity = self ._fetch_metadata (item )
895909 else :
896- entity = self .fetch_metadata ( item )
910+ entity = self .entity [ item ]
897911 return entity
898912
899913 def single_sign_on_service (self , entity_id , binding = None , typ = "idpsso" ):
0 commit comments