def parse(self, resource: Resource, content: str) -> SAMLParserInfo: info = SAMLParserInfo(description='SAML Metadata', expiration_time='') t, expire_time_offset, exception = parse_saml_metadata( unicode_stream(content), base_url=resource.url, opts=resource.opts, validation_errors=info.validation_errors, ) if expire_time_offset is not None: now = utc_now() now = now.replace(microsecond=0) expire_time = now + expire_time_offset resource.expire_time = expire_time info.expiration_time = str(expire_time) if t is not None: resource.t = t resource.type = "application/samlmetadata+xml" for e in iter_entities(t): info.entities.append(e.get('entityID')) if exception is not None: resource.info.exception = exception return info
def __init__(self, scheduler=None) -> None: random.seed(self) self.rm = Resource(url=None, opts=ResourceOpts()) # root if scheduler is None: scheduler = make_default_scheduler() scheduler.start() self.scheduler = scheduler self.store = make_store_instance() self.icon_store = make_icon_store_instance() self.rm.add_watcher(self.store, scheduler=self.scheduler) if config.load_icons: self.rm.add_watcher(self.icon_store, scheduler=self.scheduler)
def _info(r: Resource) -> Mapping[str, Any]: nfo = r.info.to_dict() nfo['Valid'] = r.is_valid() nfo['Parser'] = r.last_parser if r.last_seen is not None: nfo['Last Seen'] = r.last_seen if len(r.children) > 0: nfo['Children'] = _infos(r.children) return nfo
def parse(self, resource: Resource, content: str) -> ParserInfo: info = ParserInfo(description='XRD links', expiration_time='never expires') t = parse_xml(unicode_stream(content)) relt = root(t) for xrd in t.iter("{%s}XRD" % NS['xrd']): for link in xrd.findall(".//{%s}Link[@rel='%s']" % (NS['xrd'], NS['md'])): link_href = link.get("href") certs = CertDict(link) fingerprints = list(certs.keys()) fp = None if len(fingerprints) > 0: fp = fingerprints[0] log.debug("XRD: {} verified by {}".format(link_href, fp)) child_opts = resource.opts.copy(update={'alias': None}) resource.add_child(link_href, child_opts) resource.last_seen = utc_now().replace(microsecond=0) resource.expire_time = None resource.never_expires = True return info
def parse(self, resource: Resource, content: str) -> ParserInfo: resource.children = deque() info = ParserInfo(description='Directory', expiration_time='never expires') n = 0 for fn in find_matching_files(content, self.extensions): child_opts = resource.opts.copy(update={'alias': None}) resource.add_child("file://" + fn, child_opts) n += 1 if n == 0: raise IOError("no entities found in {}".format(content)) resource.never_expires = True resource.expire_time = None resource.last_seen = utc_now().replace(microsecond=0) return info
def parse_resource(resource: Resource, content: str) -> Optional[ParserInfo]: for parser in _parsers: if parser.magic(content): resource.last_parser = parser return parser.parse(resource, content) return None
def difftool(): """ diff two saml metadata sources """ args = parse_options("samldiff", __doc__, 'hv', ['help', 'loglevel=', 'version']) log_args = {'level': config.loglevel} if config.logfile is not None: log_args['filename'] = config.logfile logging.basicConfig(**log_args) try: rm = Resource() rm.add(r1) rm.add(r2) store = MemoryStore() rm.reload(store=store) r1 = Resource(url=args[0], opts=ResourceOpts()) r2 = Resource(url=args[1], opts=ResourceOpts()) status = 0 if r1.t.get('Name') != r2.t.get('Name'): status += 1 print("Name differs: {} != {}".format(r1.t.get('Name'), r2.t.get('Name'))) d1 = diff(r1.t, r2.t) if d1: print("Only in {}".format(r1.url)) print("\n+".join(d1)) status += 2 d2 = diff(r2.t, r1.t) if d2: print("Only in {}".format(r2.url)) print("\n+".join(d2)) status += 4 s1 = dict() s2 = dict() for e1 in iter_entities(r1.t): s1[e1.get('entityID')] = e1 for e2 in iter_entities(r2.t): s2[e2.get('entityID')] = e2 formatter = DiffFormatter() for eid in set(s1.keys()).intersection(s2.keys()): d = diff_trees( s1[eid], s2[eid], formatter=formatter, diff_options=dict(uniqueattrs=[ "{urn:oasis:names:tc:SAML:2.0:metadata}entityID" ]), ) if d: status += 8 print(d) sys.exit(status) except Exception as ex: logging.debug(traceback.format_exc()) logging.error(ex) sys.exit(-1)
class MDRepository: """A class representing a set of SAML metadata and the resources from where this metadata was loaded.""" def __init__(self, scheduler=None) -> None: random.seed(self) self.rm = Resource(url=None, opts=ResourceOpts()) # root if scheduler is None: scheduler = make_default_scheduler() scheduler.start() self.scheduler = scheduler self.store = make_store_instance() self.icon_store = make_icon_store_instance() self.rm.add_watcher(self.store, scheduler=self.scheduler) if config.load_icons: self.rm.add_watcher(self.icon_store, scheduler=self.scheduler) def _lookup(self, member, store=None): if store is None: store = self.store if member is None: member = "entities" if is_text(member): if '!' in member: (src, xp) = member.split("!") if len(src) == 0: src = None return self.lookup(src, xp=xp, store=store) log.debug("calling store lookup %s" % member) return store.lookup(member) def lookup(self, member, xp=None, store=None): """ Lookup elements in the working metadata repository :param member: A selector (cf below) :type member: basestring :param xp: An optional xpath filter :type xp: basestring :param store: the store to operate on :return: An iterable of EntityDescriptor elements :rtype: etree.Element **Selector Syntax** - selector "+" selector - [sourceID] "!" xpath - attribute=value or {attribute}value - entityID - source (typically @Name from an EntitiesDescriptor set but could also be an alias) The first form results in the intersection of the results of doing a lookup on the selectors. The second form results in the EntityDescriptor elements from the source (defaults to all EntityDescriptors) that match the xpath expression. The attribute-value forms results in the EntityDescriptors that contain the specified entity attribute pair. If non of these forms apply, the lookup is done using either source ID (normally @Name from the EntitiesDescriptor) or the entityID of single EntityDescriptors. If member is a URI but isn't part of the metadata repository then it is fetched an treated as a list of (one per line) of selectors. If all else fails an empty list is returned. """ if store is None: store = self.store l = self._lookup(member, store=store) if hasattr(l, 'tag'): l = [l] elif hasattr(l, '__iter__'): l = list(l) if xp is None: return l else: log.debug("filtering %d entities using xpath %s" % (len(l), xp)) t = entitiesdescriptor(l, 'dummy', lookup_fn=self.lookup) if t is None: return [] l = root(t).xpath(xp, namespaces=NS, smart_strings=False) log.debug("got %d entities after filtering" % len(l)) return l
def test_cmp(self): r1 = Resource("https://mds.edugain.org", via=lambda x: x) r2 = Resource("https://mds.edugain.org") assert r1 == r2
def test_cmp(self): r1 = Resource("https://mds.edugain.org", ResourceOpts(via=[lambda x: x])) r2 = Resource("https://mds.edugain.org", ResourceOpts()) assert r1 == r2
def parse(self, resource: Resource, content: str) -> EidasMDParserInfo: info = EidasMDParserInfo(description='eIDAS MetadataServiceList', expiration_time='None') t = parse_xml(unicode_stream(content)) if config.xinclude: t.xinclude() relt = root(t) info.version = relt.get('Version', '0') info.issue_date = relt.get('IssueDate') info.next_update = relt.get('NextUpdate') if isinstance(info.next_update, str): resource.expire_time = iso2datetime(info.next_update) elif config.respect_cache_duration: duration = duration2timedelta(config.default_cache_duration) if not duration: # TODO: what is the right action here? raise ValueError( f'Invalid default cache duration: {config.default_cache_duration}' ) info.next_update = utc_now().replace(microsecond=0) + duration resource.expire_time = info.next_update info.expiration_time = 'None' if not resource.expire_time else resource.expire_time.isoformat( ) info.issuer_name = first_text(relt, "{%s}IssuerName" % NS['ser']) info.scheme_identifier = first_text(relt, "{%s}SchemeIdentifier" % NS['ser']) info.scheme_territory = first_text(relt, "{%s}SchemeTerritory" % NS['ser']) for mdl in relt.iter("{%s}MetadataList" % NS['ser']): for ml in mdl.iter("{%s}MetadataLocation" % NS['ser']): location = ml.get('Location') if location: certs = CertDict(ml) fingerprints = list(certs.keys()) fp = None if len(fingerprints) > 0: fp = fingerprints[0] ep = ml.find("{%s}Endpoint" % NS['ser']) if ep is not None and fp is not None: args = dict( country_code=mdl.get('Territory'), hide_from_discovery=strtobool( ep.get('HideFromDiscovery', 'false')), ) log.debug("MDSL[{}]: {} verified by {} for country {}". format(info.scheme_territory, location, fp, args.get('country_code'))) child_opts = resource.opts.copy(update={'alias': None}) child_opts.verify = fp r = resource.add_child(location, child_opts) # this is specific post-processing for MDSL files def _update_entities(_t, **kwargs): _country_code = kwargs.get('country_code') _hide_from_discovery = kwargs.get( 'hide_from_discovery') for e in iter_entities(_t): if _country_code: set_nodecountry(e, _country_code) if bool(_hide_from_discovery) and is_idp(e): set_entity_attributes( e, { ATTRS['entity-category']: 'http://refeds.org/category/hide-from-discovery' }) return _t r.add_via(Lambda(_update_entities, **args)) log.debug("Done parsing eIDAS MetadataServiceList") resource.last_seen = utc_now().replace(microsecond=0) resource.expire_time = None return info