Conversation
…DC user provisioning
… record for organization name resolution
…nd dependency management
There was a problem hiding this comment.
Pull request overview
Adds a new pluggable “organization name resolver” SPI and integrates a SIRENE (INSEE) implementation to resolve organization names (and short names) from identifiers (e.g., SIRET) during OIDC/LDAP account provisioning.
Changes:
- Introduces
georchestra-gateway-org-resolvers(SPI) andgeorchestra-gateway-sirene(SIRENE resolver + auto-configuration) modules. - Extends OIDC provider config and LDAP account management to optionally resolve/override organization names via a configurable resolver chain.
- Updates build/dependency wiring (parent, modules list, gateway deps, Makefile docker targets) and adds example configuration.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| pom.xml | Bumps Spring Cloud parent and adds new internal modules to dependency management. |
| modules/pom.xml | Registers org-resolvers and sirene modules in the build. |
| modules/org-resolvers/pom.xml | Adds new SPI module packaging. |
| modules/org-resolvers/src/main/java/.../OrganizationNameResolver.java | Defines the resolver SPI for organization name resolution. |
| modules/org-resolvers/src/main/java/.../ResolvedOrganization.java | Adds resolved org DTO (name + shortName). |
| modules/sirene/pom.xml | Adds new SIRENE integration module and dependencies. |
| modules/sirene/.../AutoConfiguration.imports | Registers SIRENE auto-configuration for Spring Boot. |
| modules/sirene/.../SireneApiConfigProperties.java | Adds config properties for SIRENE base URL/key and enablement. |
| modules/sirene/.../SireneAutoConfiguration.java | Wires RestClient + OrganizationNameResolver beans behind a property flag. |
| modules/sirene/.../SireneOrganizationNameResolver.java | Implements SIRENE API lookup + short-name derivation + caching. |
| gateway/pom.xml | Adds SPI dependency and optional SIRENE module dependency to the gateway. |
| gateway/src/main/java/.../OpenIdConnectCustomConfig.java | Adds provider-level flags and resolver-chain configuration accessors. |
| gateway/src/main/java/.../LdapAccountsManager.java | Uses resolver chain to set/update org names during org creation/linking. |
| gateway/src/main/java/.../GeorchestraLdapAccountManagementConfiguration.java | Injects optional OrganizationNameResolver into LDAP account manager. |
| gateway/src/test/java/.../LdapAccountsManagerTest.java | Adjusts tests for constructor signature + updated verification method signature. |
| datadir/gateway/security.yaml | Documents example SIRENE + OIDC resolver-chain configuration. |
| Makefile | Ensures new modules are built/installed before docker packaging. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private Optional<ResolvedOrganization> resolveOrgNameFromChain(String identifier, @NonNull String oAuth2Provider) { | ||
| List<String> resolvers = providersConfig.orgNameResolvers(oAuth2Provider); | ||
| if (resolvers.isEmpty()) { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| for (String resolverEntry : resolvers) { | ||
| Optional<ResolvedOrganization> result = tryResolver(resolverEntry, identifier); | ||
| if (result.isPresent()) { | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
| log.debug("No organization name resolver returned a result for identifier '{}' (provider: {})", identifier, | ||
| oAuth2Provider); | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| /** | ||
| * Tries a single resolver entry from the fallback chain. | ||
| */ | ||
| private Optional<ResolvedOrganization> tryResolver(String resolverEntry, String identifier) { | ||
| if ("sirene".equalsIgnoreCase(resolverEntry)) { | ||
| return orgNameResolver.flatMap(resolver -> resolver.resolve(identifier)); | ||
| } | ||
|
|
||
| if ("identifier".equalsIgnoreCase(resolverEntry)) { | ||
| if (StringUtils.isNotEmpty(identifier)) { | ||
| String shortName = identifier.length() > 32 ? identifier.substring(0, 32) : identifier; | ||
| return Optional.of(new ResolvedOrganization(identifier, shortName)); | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| if (resolverEntry.toLowerCase().startsWith("static:")) { | ||
| String staticValue = resolverEntry.substring("static:".length()).trim(); | ||
| if (!staticValue.isEmpty()) { | ||
| return Optional.of(new ResolvedOrganization(staticValue, staticValue)); | ||
| } | ||
| } | ||
|
|
||
| log.warn("Unknown organization name resolver type: '{}'", resolverEntry); | ||
| return Optional.empty(); |
There was a problem hiding this comment.
The new organization-name resolution chain (resolveOrgNameFromChain/tryResolver) and the overrideExistingOrgName behavior introduce non-trivial branching (provider config, resolver order, unknown resolver handling), but there are no tests covering these paths. Please add focused unit tests for at least: resolver ordering, static: parsing, identifier truncation, and override behavior when enabled/disabled.
…me resolver configuration
pmauduit
left a comment
There was a problem hiding this comment.
interesting so far, here are some early comments reading the diff
| private final @NonNull OpenIdConnectCustomConfig providersConfig; | ||
|
|
||
| /** Optional organization name resolver. */ | ||
| private final @NonNull Optional<OrganizationNameResolver> orgNameResolver; |
There was a problem hiding this comment.
@NonNull and Optional<T> ? Isn't it redundant ?
| OrgsDao orgsDao, DemultiplexingUsersApi demultiplexingUsersApi, | ||
| GeorchestraGatewaySecurityConfigProperties georchestraGatewaySecurityConfigProperties, | ||
| OpenIdConnectCustomConfig providersConfig) { | ||
| OpenIdConnectCustomConfig providersConfig, Optional<OrganizationNameResolver> orgNameResolver) { |
There was a problem hiding this comment.
I did not know that Spring was able to pass Optionals as dependencies, makes sense though.
| Org org = new Org(); | ||
| org.setId(orgId); | ||
| org.setOrgUniqueId(Optional.ofNullable(orgUniqueId).orElse("")); | ||
| org.setOrgType("Other"); |
There was a problem hiding this comment.
I am wondering if this should be configureable, and on the other hand, it could be up to the platform admin to manage them in the console once created, "other" sounding as a good enough default option.
| * to resolve organization names from national registries. | ||
| * </p> | ||
| */ | ||
| public interface OrganizationNameResolver { |
There was a problem hiding this comment.
I think that since the interface is mentioned in the georchestra spring configuration, maybe it should go into the core (under gateway/), not into a module ? and keep the implementation (sirene) into modules/
| * @param name the full organization name (e.g. "Ministère de | ||
| * l'Environnement") | ||
| * @param shortName a shortened/normalized version of the name (max 32 | ||
| * characters) |
There was a problem hiding this comment.
not sure if such a thing exist, but are there any annotation which could enforce this ? (like @MaxLength(32) or so ?). I guess we should be good with the current comment anyway, in term of documentation.
| } | ||
|
|
||
| /** | ||
| * Creates a {@link RestClient} pre-configured for the INSEE SIRENE API. |
There was a problem hiding this comment.
I remember the issues we had with Gabriel working on the gateway in an environment where the http clients had to go through a proxy. But not prio here, it can probably be revisited later on if needed.
| @SuppressWarnings("unchecked") | ||
| private Optional<ResolvedOrganization> doResolve(String siret) { | ||
| try { | ||
| log.info("Resolving organization name from SIRENE API for SIRET: {}", siret); |
There was a problem hiding this comment.
debug or trace instead of info ? (not sure though)
| } | ||
|
|
||
| String orgName = extractOrgName(uniteLegale); | ||
| if (orgName == null || orgName.isBlank()) { |
There was a problem hiding this comment.
I think you have a StringUtils.isEmpty(), either via apache commons, or via spring.
| } | ||
|
|
||
| String shortName = deriveShortName(orgName); | ||
| log.info("Resolved SIRET {} to organization: '{}' (short: '{}')", siret, orgName, shortName); |
There was a problem hiding this comment.
same, not sure if info is the right level, given the log trace being issued
No description provided.