From f733b10a280d216636e9230768e46697b35783f7 Mon Sep 17 00:00:00 2001 From: enriquepablo Date: Wed, 27 Nov 2024 13:57:07 +0100 Subject: [PATCH 1/3] Do not crash when entity-selection-profile attribute has invalid JSON --- src/pyff/samlmd.py | 19 ++- .../metadata/test-sp-trustinfo-in-attr.xml | 112 ++++++++++++++++++ 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/pyff/samlmd.py b/src/pyff/samlmd.py index 9be47176..c6f445b3 100644 --- a/src/pyff/samlmd.py +++ b/src/pyff/samlmd.py @@ -1047,14 +1047,25 @@ def discojson_sp_attr(e): if b64_trustinfos is None: return None + entityID = e.get('entityID', None) + if entityID is None: + return None + sp = {} - sp['entityID'] = e.get('entityID', None) + sp['entityID'] = entityID sp['profiles'] = {} for b64_trustinfo in b64_trustinfos: - str_trustinfo = b64decode(b64_trustinfo.encode('ascii')) - trustinfo = json.loads(str_trustinfo.decode('utf8')) - sp['profiles'].update(trustinfo['profiles']) + try: + str_trustinfo = b64decode(b64_trustinfo.encode('ascii')) + trustinfo = json.loads(str_trustinfo.decode('utf8')) + sp['profiles'].update(trustinfo['profiles']) + + except Exception as e: + log.warning(f"Invalid entity-selection-profile attribute for {entityID}: {e}") + + if not sp['profiles']: + return None return sp diff --git a/src/pyff/test/data/metadata/test-sp-trustinfo-in-attr.xml b/src/pyff/test/data/metadata/test-sp-trustinfo-in-attr.xml index b1532fda..12e0d268 100644 --- a/src/pyff/test/data/metadata/test-sp-trustinfo-in-attr.xml +++ b/src/pyff/test/data/metadata/test-sp-trustinfo-in-attr.xml @@ -71,6 +71,118 @@ fMou5aW0mZ+QgJNKOrxY5vFxUq6pn3OiYbBu3m1C9ajbU/nx2evzt4+qUwTfHFb+ ZgXpOtmxRekFzVvGZ18BSPJKwAAqqZ11X7skT/NwEAhbgplVPv9WkDmDzqNvHqQJ nyRgD2ZqUPU9nEOjGy0gI07dciVcYZQ+CiZeSECIWgQwjDEBDuwMCVAZA6gfdz6C KJuN+RUSKPEcxPxle1MiB4MU0ei5X4xUbvLWKn9Ok7TOXg2BpnMAv6eON1wVo0Aa +D265cqy6Le/toVg= + + + + + + + + + + + + + + + ICOS Carbon Portal SAML service + ICOS Kolportalens SAML tjänst + + + + + + + ICOS Carbon Portal + ICOS Kolportalen + Carbon Portal + Kolportalen + https://www.icos-cp.eu/ + https://www.icos-cp.eu/ + + + Oleg + Mirzov + mailto:oleg.mirzov@nateko.lu.se + + + Alex + Vermeulen + mailto:alex.vermeulen@nateko.lu.se + + + + + + http://swamid.se/policy/mdrps + + + + + + + + + + + + + + + + + + + + http://www.geant.net/uri/dataprotection-code-of-conduct/v1 + + + invalidValueForAttribute + + + + + + + + Carbon Portal authentication service + Kolportalens autentiseringstjänst + Single Sign On for services of ICOS Carbon Portal. Maintained by the Carbon Portal team at Physical Geography department (nateko.lu.se). + Single Sign On tjänst för ICOS Kolportalen. Hanteras av Carbon Portal teamet på INES (nateko.lu.se). + https://cpauth.icos-cp.eu/saml/privacyStatement + https://www.icos-cp.eu/ + https://www.icos-cp.eu/ + https://cpauth.icos-cp.eu/saml/privacyStatement + + + + + cpauth.icos-cp.eu + + CN=cpauth.icos-cp.eu + MIIEJzCCAw+gAwIBAgIJANC3VWNs7fbTMA0GCSqGSIb3DQEBCwUAMIGpMQswCQYD +VQQGEwJTRTERMA8GA1UECAwIU2vDg8KlbmUxDTALBgNVBAcMBEx1bmQxGzAZBgNV +BAoMEklDT1MgQ2FyYm9uIFBvcnRhbDEfMB0GA1UECwwWQXV0aGVudGljYXRpb24g +U2VydmljZTEaMBgGA1UEAwwRY3BhdXRoLmljb3MtY3AuZXUxHjAcBgkqhkiG9w0B +CQEWD2luZm9AaWNvcy1jcC5ldTAeFw0xNTAyMDUxMjI0MzZaFw0yNTAyMDIxMjI0 +MzZaMIGpMQswCQYDVQQGEwJTRTERMA8GA1UECAwIU2vDg8KlbmUxDTALBgNVBAcM +BEx1bmQxGzAZBgNVBAoMEklDT1MgQ2FyYm9uIFBvcnRhbDEfMB0GA1UECwwWQXV0 +aGVudGljYXRpb24gU2VydmljZTEaMBgGA1UEAwwRY3BhdXRoLmljb3MtY3AuZXUx +HjAcBgkqhkiG9w0BCQEWD2luZm9AaWNvcy1jcC5ldTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAM2QN1jaZJeuPAH+4sVMZKk7vg4JIbUuTMKk0+KIAg5M +XiVsRiEUjY+LtIncrvA/kf2CIySI0WkbwZMjcDd03hNj4kLWhuyxfOCwDO6DsUbG +MbyI6HIYWXJp5ljfEEFgtMqT3dDtD5vwq8h4Zy20ukxOoIokKczrAvn4JjkMsj6Z +0CEAFBC29o4E8PWQbUBgvt6Z+2ao+RHMLD7nZVBx98Occ9KfnYnDDd9Oi1XFe009 +zaSbcqY2RpN8I9hcW/KQf3KnGW5xZ5dr4rhGklCkYr+h0W3xKu+hin8bk91t1Dkr +gaKl/N7M3Oof3k+7ZBlwaV97es5InWCeNgDxCGkBRNsCAwEAAaNQME4wHQYDVR0O +BBYEFDcD7MVudooGaNRYqXBYqQi3VzGxMB8GA1UdIwQYMBaAFDcD7MVudooGaNRY +qXBYqQi3VzGxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABS02eZS +weXGMJ2fEIy2JH0VhCbjuX/rz+8Hfh9LjzNb3QwKHuwP83yvPqRulV9FYmvOoK8T +fMou5aW0mZ+QgJNKOrxY5vFxUq6pn3OiYbBu3m1C9ajbU/nx2evzt4+qUwTfHFb+ +ZgXpOtmxRekFzVvGZ18BSPJKwAAqqZ11X7skT/NwEAhbgplVPv9WkDmDzqNvHqQJ +nyRgD2ZqUPU9nEOjGy0gI07dciVcYZQ+CiZeSECIWgQwjDEBDuwMCVAZA6gfdz6C +KJuN+RUSKPEcxPxle1MiB4MU0ei5X4xUbvLWKn9Ok7TOXg2BpnMAv6eON1wVo0Aa D265cqy6Le/toVg= From b39220353b3bd1b618b7fd500175665a525a65dc Mon Sep 17 00:00:00 2001 From: enriquepablo Date: Mon, 13 Jan 2025 11:51:36 +0100 Subject: [PATCH 2/3] warn when trust info has profiles with same name. Also allow extra metadata in trust info in entity attribute. --- src/pyff/samlmd.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/pyff/samlmd.py b/src/pyff/samlmd.py index c6f445b3..6593ee52 100644 --- a/src/pyff/samlmd.py +++ b/src/pyff/samlmd.py @@ -1048,18 +1048,27 @@ def discojson_sp_attr(e): return None entityID = e.get('entityID', None) - if entityID is None: - return None - sp = {} sp['entityID'] = entityID sp['profiles'] = {} + sp['extra_md'] = {} for b64_trustinfo in b64_trustinfos: try: str_trustinfo = b64decode(b64_trustinfo.encode('ascii')) trustinfo = json.loads(str_trustinfo.decode('utf8')) - sp['profiles'].update(trustinfo['profiles']) + for profile in trustinfo['profiles']: + if profile in sp['profiles']: + log.warning(f"SP Entity {entityID} has a duplicate trust profile {profile}") + else: + sp['profiles'][profile] = trustinfo['profiles'][profile] + + if 'extra_md' in trustinfo: + for extra_id in trustinfo['extra_md']: + if extra_id in sp['extra_md']: + log.warning(f"SP Entity {entityID} has a duplicate extra IdP metadata {extra_id}") + else: + sp['extra_md'][extra_id] = trustinfo['extra_md'][extra_id] except Exception as e: log.warning(f"Invalid entity-selection-profile attribute for {entityID}: {e}") From 0fb326d6043c1a3c6c2bb9a431cf4a98e600270f Mon Sep 17 00:00:00 2001 From: enriquepablo Date: Tue, 11 Feb 2025 18:22:57 +0100 Subject: [PATCH 3/3] select with duplicates from different sources --- src/pyff/builtins.py | 5 ++++- src/pyff/store.py | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/pyff/builtins.py b/src/pyff/builtins.py index 8bdd3a2d..e2f42591 100644 --- a/src/pyff/builtins.py +++ b/src/pyff/builtins.py @@ -832,7 +832,10 @@ def select(req: Plumbing.Request, *opts): if opts[0] == 'as' and len(opts) == 2: name = opts[1] - entities = resolve_entities(args, lookup_fn=req.md.store.select, dedup=dedup) + if dedup: + entities = resolve_entities(args, lookup_fn=req.md.store.select, dedup=dedup) + else: + entities = resolve_entities(args, lookup_fn=req.md.store.select_with_dups, dedup=dedup) if req.state.get('match', None): # TODO - allow this to be passed in via normal arguments diff --git a/src/pyff/store.py b/src/pyff/store.py index c4c31b33..9f748d17 100644 --- a/src/pyff/store.py +++ b/src/pyff/store.py @@ -4,6 +4,7 @@ import re import shutil import time +from collections import defaultdict from datetime import datetime, timedelta from io import BytesIO from threading import ThreadError @@ -807,6 +808,7 @@ def __init__(self, *args, **kwargs): self.md = dict() self.index = dict() self.entities = dict() + self.md_entities = defaultdict(dict) for hn in DINDEX: self.index.setdefault(hn, {}) @@ -885,13 +887,15 @@ def update(self, t, tid=None, etag=None, lazy=True): self.entities[relt.get('entityID')] = relt # TODO: merge? if tid is not None: self.md[tid] = [relt.get('entityID')] + self.md_entities[tid][relt.get('entityID')] = relt elif relt.tag == "{%s}EntitiesDescriptor" % NS['md']: if tid is None: tid = relt.get('Name') lst = [] for e in iter_entities(t): - self.update(e) + self.update(e, tid) lst.append(e.get('entityID')) + self.md_entities[tid][e.get('entityID')] = e self.md[tid] = lst def lookup(self, key): @@ -946,3 +950,9 @@ def _lookup(self, key): return lst return [] + + def select_with_dups(self, member): + if member in self.md_entities: + return self.md_entities[member].values() + + return []