diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba047b..621fb4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +- map SAML entity IDs to SIRET (#34) - include ACR claim in ID token only if requested (#32) ## [1.0.8] - 2025-09-10 diff --git a/docker-compose.yml b/docker-compose.yml index ec1f90f..0a447ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,7 +66,7 @@ services: #OIDC_PROVIDER: https://oidc2fer-staging.beta.numerique.gouv.fr OIDC_CLIENT_ID: oidc-test-client OIDC_CLIENT_SECRET: oidc-test-secret - OIDC_SCOPES: openid,uid,given_name,usual_name,email + OIDC_SCOPES: openid,uid,given_name,usual_name,email,siret extra_hosts: - "oidc2fer.127.0.0.1.nip.io:host-gateway" diff --git a/src/helm/env.d/dev/values.oidc2fer.yaml.gotmpl b/src/helm/env.d/dev/values.oidc2fer.yaml.gotmpl index 7301ebe..2d38b2c 100644 --- a/src/helm/env.d/dev/values.oidc2fer.yaml.gotmpl +++ b/src/helm/env.d/dev/values.oidc2fer.yaml.gotmpl @@ -36,6 +36,10 @@ satosa: "token_endpoint_auth_method": "client_secret_post" } } + SIRET_MAP: |- + { + "https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010" + } ingress: enabled: true diff --git a/src/helm/env.d/outscale-production/values.oidc2fer.yaml.gotmpl b/src/helm/env.d/outscale-production/values.oidc2fer.yaml.gotmpl index e006f66..6659212 100644 --- a/src/helm/env.d/outscale-production/values.oidc2fer.yaml.gotmpl +++ b/src/helm/env.d/outscale-production/values.oidc2fer.yaml.gotmpl @@ -24,6 +24,94 @@ satosa: OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } } + # As provided by RENATER on 2025-09-19 + SIRET_MAP: |- + { + "https://shibboleth.grenoble-inp.fr/idp/shibboleth": "19381912500017", + "https://identites.ensea.fr/idp/shibboleth": "19951376300011", + "https://idp.ent.dauphine.fr/idp/shibboleth": "19754692200018", + "https://idp.univ-lyon2.fr/idp/shibboleth": "19691775100014", + "https://shibboleth.insa-rouen.fr/idp/shibboleth": "19760165100023", + "https://idp1.univ-fcomte.fr/idp/shibboleth": "19251215000363", + "https://idp.univ-tours.fr/idp/shibboleth": "19370800500478", + "https://apps.univ-lr.fr/idp/shibboleth": "19170032700015", + "https://shib.mines-albi.fr/idp/shibboleth": "19811200500022", + "https://federation.upf.pf/idp/shibboleth": "19987001500013", + "https://federation.utbm.fr/idp/shibboleth": "19900356700013", + "urn:mace:cru.fr:federation:univ-paris1.fr": "19751717000019", + "https://idp.sciencespobordeaux.fr/idp/shibboleth": "19330192600039", + "https://vip.espci.fr/saml2/idp/metadata.php": "20000068500012", + "urn:mace:cru.fr:federation:univ-nantes.fr": "13002974700016", + "https://shibboleth.univ-corse.fr/idp/shibboleth": "19202664900264", + "https://srv-fii.insa-toulouse.fr/idp/shibboleth": "19310152400018", + "urn:mace:cru.fr:federation:univ-rouen.fr": "19761904200017", + "https://ident-shib.ensc-rennes.fr/idp/shibboleth": "19350077400016", + "urn:mace:cru.fr:federation:unilim.fr": "19870669900321", + "https://idp.inha.fr/idp/shibboleth": "19754688000018", + "https://federation.unimes.fr/idp/shibboleth": "13000375900029", + "urn:mace:cru.fr:federation:univ-rennes1.fr": "13003051300019", + "urn:mace:cru.fr:federation:univ-ubs.fr": "19561718800600", + "https://idp.univ-orleans.fr/idp/shibboleth": "19450855200016", + "https://shibboleth.univ-savoie.fr/idp/shibboleth": "19730858800015", + "https://idp.cirad.fr/idp/shibboleth": "33159627000016", + "https://sso.ird.fr/idp/shibboleth": "18000602500159", + "https://idp.ensma.fr/idp/shibboleth": "19860073600021", + "https://idp.renater.fr/idp/shibboleth": "18008947600055", + "https://idp.inp-toulouse.fr/idp/shibboleth": "19311381800127", + "https://idp.unistra.fr/idp/shibboleth": "13000545700010", + "https://idp1.agroparistech.fr/idp/shibboleth": "13000285000134", + "https://janus.cnrs.fr/idp": "18008901303720", + "https://federation.umontpellier.fr/idp/shibboleth": "13002979600013", + "https://idp.sciencespo-lyon.fr/idp/shibboleth": "19690173000024", + "https://shibboleth.univ-grenoble-alpes.fr/idp/shibboleth": "13002608100013", + "https://idp.bnu.fr/idp/shibboleth": "18004406700015", + "https://ruhnu.univ-tlse2.fr/idp/shibboleth": "19311383400017", + "https://idp2.amue.fr/idp/shibboleth": "18004312700083", + "https://idpv3.univ-amu.fr/idp/shibboleth": "13001533200013", + "https://idp-ng.univ-st-etienne.fr/idp/shibboleth": "93850168100010", + "https://identities.univ-jfc.fr/idp/prod": "19811201300018", + "https://idp3.univ-lorraine.fr/idp/shibboleth": "13001550600012", + "https://idp.univ-lemans.fr/idp/shibboleth": "19720916600010", + "https://idp.univ-lille.fr/idp/shibboleth": "13002975400012", + "https://idp.univ-tln.fr/idp/shibboleth": "19830766200017", + "https://idp.uca.fr/idp/shibboleth": "13002806100013", + "https://shibboleth3.utt.fr/idp/shibboleth": "19101060200032", + "https://idp3.ut-capitole.fr/idp/shibboleth": "19311382600013", + "https://idp.imt-atlantique.fr/idp/shibboleth": "18009202500121", + "https://shib.univ-lehavre.fr/idp/shibboleth": "19762762300097", + "https://idp2.univ-paris8.fr/idp/shibboleth": "19931827000014", + "https://shib2.unc.nc/idp/shibboleth": "13000322100012", + "https://upnidp2.parisnanterre.fr/idp/shibboleth": "19921204400010", + "https://idp.ensfea.fr/idp/shibboleth": "19310143300012", + "https://idp.univ-eiffel.fr/idp/shibboleth": "13002612300013", + "https://idp.insa-rennes.fr/idp/shibboleth": "19350097200016", + "https://idp.univ-tlse3.fr/idp/shibboleth": "93827139200012", + "https://idp.universite-paris-saclay.fr/idp": "13002602400054", + "https://shibboleth.ens2m.fr/idp/shibboleth": "19250082500026", + "https://sso-ciation-saclay.centralesupelec.fr/idp/shibboleth": "13002076100016", + "https://federation.ens.psl.eu/idp/shibboleth": "19753459700012", + "https://authentification.inrae.fr/saml/metadata/idp": "18007003901803", + "https://idp.ensam.eu/idp/shibboleth": "19753472000010", + "https://idp1.ens-paris-saclay.fr/idp/shibboleth": "19940607500036", + "https://idp2.emse.fr/idp/shibboleth": "18009202500105", + "https://idp-septa.onisep.fr/idp/shibboleth": "18004302800653", + "https://papangue.vetagro-sup.fr/idp/shibboleth": "13000858400018", + "https://multipass.imt-nord-europe.fr/idp/shibboleth": "18009202500139", + "https://idp4.unicaen.fr/idp/shibboleth": "19141408500016", + "https://sso.bordeaux-inp.fr/idp/shibboleth": "13000635600013", + "https://auth.uttop.fr/saml/metadata": "19650048200019", + "https://idp.univ-cotedazur.fr/idp/shibboleth": "13002566100013", + "https://idp2.brgm.fr/idp/shibboleth": "58205614900120", + "https://idp.crous-reims.fr/idp/shibboleth": "18510200100012", + "https://idp.univ-avignon.fr/idp/shibboleth": "19840685200204", + "https://idp-genci.renater.fr/idp/shibboleth": "49468697500041", + "https://auth.sso.vet-alfort.fr/saml/metadata": "19940608300014", + "https://idp.univ-perp.fr/idp/shibboleth": "19660437500010", + "https://idp.institut-agro.fr/idp": "13000279300011", + "https://prod-idp.inria.fr": "18008904700013", + "https://idp.ec-nantes.fr/idp/shibboleth": "19440100600011" + } + ingress: enabled: true host: renater.agentconnect.gouv.fr diff --git a/src/helm/env.d/staging/values.oidc2fer.yaml.gotmpl b/src/helm/env.d/staging/values.oidc2fer.yaml.gotmpl index c8964af..c5821eb 100644 --- a/src/helm/env.d/staging/values.oidc2fer.yaml.gotmpl +++ b/src/helm/env.d/staging/values.oidc2fer.yaml.gotmpl @@ -24,6 +24,11 @@ satosa: OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } } + SIRET_MAP: |- + { + "https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010" + } + ingress: enabled: true host: oidc2fer-staging.beta.numerique.gouv.fr diff --git a/src/satosa/internal_attributes.yaml b/src/satosa/internal_attributes.yaml index 6a76b29..a3b6e7a 100644 --- a/src/satosa/internal_attributes.yaml +++ b/src/satosa/internal_attributes.yaml @@ -31,3 +31,6 @@ attributes: uid: openid: - uid + siret: + openid: + - siret diff --git a/src/satosa/oidc2fer/attribute_generators/__init__.py b/src/satosa/oidc2fer/attribute_generators/__init__.py new file mode 100644 index 0000000..73c690f --- /dev/null +++ b/src/satosa/oidc2fer/attribute_generators/__init__.py @@ -0,0 +1 @@ +from .entity_id_to_siret_mapper import EntityIdToSiretMapper diff --git a/src/satosa/oidc2fer/attribute_generators/entity_id_to_siret_mapper.py b/src/satosa/oidc2fer/attribute_generators/entity_id_to_siret_mapper.py new file mode 100644 index 0000000..0b05eac --- /dev/null +++ b/src/satosa/oidc2fer/attribute_generators/entity_id_to_siret_mapper.py @@ -0,0 +1,23 @@ +import json +import logging + +from satosa.micro_services.base import ResponseMicroService + +logger = logging.getLogger(__name__) + + +class EntityIdToSiretMapper(ResponseMicroService): + def __init__(self, config, *args, **kwargs): + super().__init__(*args, **kwargs) + self.attribute = config.get("attribute", "siret") + self.mapping = json.loads(config.get("mapping_json", "{}")) + + def process(self, context, data): + entity_id = data.auth_info.issuer + if entity_id in self.mapping: + siret = self.mapping[entity_id] + logger.info("Mapping entity ID %s to SIRET %s", entity_id, siret) + data.attributes[self.attribute] = siret + else: + logger.warning("No SIRET mapping found for entity ID %s", entity_id) + return super().process(context, data) diff --git a/src/satosa/plugins/frontends/openid_connect_frontend.yaml b/src/satosa/plugins/frontends/openid_connect_frontend.yaml index 9505bad..f14c718 100644 --- a/src/satosa/plugins/frontends/openid_connect_frontend.yaml +++ b/src/satosa/plugins/frontends/openid_connect_frontend.yaml @@ -43,6 +43,7 @@ config: - uid - given_name - usual_name + - siret # Set code/token lifetimes as short as possible authorization_code_lifetime: 60 @@ -56,3 +57,5 @@ config: - given_name usual_name: - usual_name + siret: + - siret diff --git a/src/satosa/plugins/microservices/siret_mapping.yaml b/src/satosa/plugins/microservices/siret_mapping.yaml new file mode 100644 index 0000000..ef00bfc --- /dev/null +++ b/src/satosa/plugins/microservices/siret_mapping.yaml @@ -0,0 +1,5 @@ +module: oidc2fer.attribute_generators.EntityIdToSiretMapper +name: EntityIdToSiretMapper +config: + attribute: siret + mapping_json: !ENV SIRET_MAP diff --git a/src/satosa/proxy_conf.yaml b/src/satosa/proxy_conf.yaml index c6bcca1..c8127bd 100644 --- a/src/satosa/proxy_conf.yaml +++ b/src/satosa/proxy_conf.yaml @@ -17,6 +17,7 @@ MICRO_SERVICES: - plugins/microservices/primary_identifier.yaml - plugins/microservices/static_attributes.yaml - plugins/microservices/attribute_processor.yaml + - plugins/microservices/siret_mapping.yaml LOGGING: # All the logging configuration is done in the Gunicorn config, this is just # here to avoid overwriting it with the default SATOSA config diff --git a/src/satosa/tests/oidc2fer/attribute_generators/test_entity_id_to_siret_mapper.py b/src/satosa/tests/oidc2fer/attribute_generators/test_entity_id_to_siret_mapper.py new file mode 100644 index 0000000..2065f86 --- /dev/null +++ b/src/satosa/tests/oidc2fer/attribute_generators/test_entity_id_to_siret_mapper.py @@ -0,0 +1,42 @@ +import json + +from satosa.context import Context +from satosa.internal import AuthenticationInformation, InternalData + +from oidc2fer.attribute_generators import EntityIdToSiretMapper + + +class TestEntityIdToSiretMapper: + def create_mapper(self): + mapper = EntityIdToSiretMapper( + config={ + "attribute": "siret", + "mapping_json": json.dumps( + { + "https://idp.example.fr": "12345678200010", + } + ), + }, + name="siret_mapper", + base_url="https://satosa.example.com", + ) + mapper.next = lambda ctx, data: data + return mapper + + def test_sets_siret_for_known_entityid(self): + mapper = self.create_mapper() + resp = InternalData( + auth_info=AuthenticationInformation(issuer="https://idp.example.fr") + ) + ctx = Context() + mapper.process(ctx, resp) + assert resp.attributes["siret"] == "12345678200010" + + def test_does_not_set_siret_for_unknown_entityid(self): + mapper = self.create_mapper() + resp = InternalData( + auth_info=AuthenticationInformation(issuer="https://unknown.example.fr") + ) + ctx = Context() + mapper.process(ctx, resp) + assert "siret" not in resp.attributes diff --git a/src/satosa/tests/test_e2e.py b/src/satosa/tests/test_e2e.py index cef255d..b208eff 100644 --- a/src/satosa/tests/test_e2e.py +++ b/src/satosa/tests/test_e2e.py @@ -49,6 +49,7 @@ def oidc_to_renater( "email": expected_email, "given_name": expected_given_name, "usual_name": expected_usual_name, + "siret": "12345678200010", }.items() <= userinfo.items() return id_token @@ -102,6 +103,7 @@ def pro_connect_to_renater( "email": expected_email, "given_name": expected_given_name, "usual_name": expected_usual_name, + "siret": "12345678200010", }.items() <= result.items() return result