Skip to content

Commit 385b951

Browse files
committed
Merge pull request #266 from jusabatier/preserve_uid_patch
Allow to disable uid transformation for an IDP
1 parent c106512 commit 385b951

5 files changed

Lines changed: 119 additions & 10 deletions

File tree

datadir/gateway/security.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ georchestra:
77
# from datadir
88
moderatedSignup: ${moderatedSignup:false}
99
createNonExistingUsersInLDAP: false
10-
# events:
11-
# accountcreated:
12-
# # Set this url to console's endpoint to be able to receive an email when a new user logs in
13-
# # for the first time with an IDP and is created in LDAP.
14-
# url: "http://console:8080/console/internal/events/accountcreated"
10+
# You can disable UID transformation for an IDP by its identifier
11+
# This lead to not prefixing or sanitize the UID from this IDP
12+
#disableUidTransformation: 'icu'
13+
#events:
14+
# accountcreated:
15+
# # Set this url to console's endpoint to be able to receive an email when a new user logs in
16+
# # for the first time with an IDP and is created in LDAP.
17+
# url: "http://console:8080/console/internal/events/accountcreated"
1518
oauth2:
1619
# if enabled, make sure to have at least one OAuth2 client
1720
# set up at spring.security.oauth2.client below

gateway/src/main/java/org/georchestra/gateway/security/GeorchestraGatewaySecurityConfigProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ public class GeorchestraGatewaySecurityConfigProperties implements Validator {
7070
*/
7171
private boolean createNonExistingUsersInLDAP = true;
7272

73+
/**
74+
* Identifier of the Identity Provider for which UID transformation is disabled.
75+
* When this value matches an IDP's id, the username from that provider is used
76+
* as-is (no prefixing and no sanitization). If empty or not matching, UID
77+
* transformation is applied normally.
78+
*/
79+
private String disableUidTransformation = "";
80+
7381
/**
7482
* Default organization assigned to users when no specific organization is set.
7583
*/

gateway/src/main/java/org/georchestra/gateway/security/oauth2/OAuth2Configuration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,10 @@ OAuth2UserMapper oAuth2GeorchestraUserUserMapper() {
198198
*/
199199
@Bean
200200
OpenIdConnectUserMapper openIdConnectGeorchestraUserUserMapper(
201-
OpenIdConnectCustomClaimsConfigProperties nonStandardClaimsConfig) {
201+
OpenIdConnectCustomClaimsConfigProperties nonStandardClaimsConfig,
202+
GeorchestraGatewaySecurityConfigProperties securityConfigProperties) {
202203

203-
return new OpenIdConnectUserMapper(nonStandardClaimsConfig);
204+
return new OpenIdConnectUserMapper(nonStandardClaimsConfig, securityConfigProperties);
204205
}
205206

206207
/**

gateway/src/main/java/org/georchestra/gateway/security/oauth2/OpenIdConnectUserMapper.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
public class OpenIdConnectUserMapper extends OAuth2UserMapper {
131131

132132
private final @NonNull OpenIdConnectCustomClaimsConfigProperties nonStandardClaimsConfig;
133+
private final @NonNull GeorchestraGatewaySecurityConfigProperties securityConfigProperties;
133134

134135
/**
135136
* Filters authentication tokens to process only {@link OidcUser}-based
@@ -180,8 +181,13 @@ public class OpenIdConnectUserMapper extends OAuth2UserMapper {
180181
if (customProviderClaims.isPresent()) {
181182
applyProviderNonStandardClaims(customProviderClaims.get(), oidcUser.getClaims(), user);
182183
}
183-
user.setUsername((token.getAuthorizedClientRegistrationId() + "_" + user.getUsername())
184-
.replaceAll("[^a-zA-Z0-9-_]", "_").toLowerCase());
184+
185+
boolean disableTransformation = clientId.equals(securityConfigProperties.getDisableUidTransformation());
186+
187+
if (!disableTransformation) {
188+
user.setUsername((clientId + "_" + user.getUsername()).replaceAll("[^a-zA-Z0-9-_]", "_").toLowerCase());
189+
}
190+
185191
} catch (Exception e) {
186192
log.error("Error mapping non-standard OIDC claims for authenticated user", e);
187193
throw new IllegalStateException(e);

gateway/src/test/java/org/georchestra/gateway/security/oauth2/OpenIdConnectUserMapperTest.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@
2828
import java.util.Date;
2929
import java.util.List;
3030
import java.util.Map;
31+
import java.util.Optional;
3132

3233
import io.jsonwebtoken.Jwts;
34+
import org.georchestra.gateway.security.GeorchestraGatewaySecurityConfigProperties;
3335
import org.georchestra.security.model.GeorchestraUser;
3436
import org.junit.jupiter.api.BeforeEach;
3537
import org.junit.jupiter.api.Test;
38+
import org.springframework.security.core.authority.AuthorityUtils;
39+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
3640
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3741
import org.springframework.security.oauth2.core.AuthorizationGrantType;
3842
import org.springframework.security.oauth2.core.oidc.AddressStandardClaim;
43+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
3944
import org.springframework.security.oauth2.core.oidc.StandardClaimAccessor;
4045

4146
import com.nimbusds.oauth2.sdk.ParseException;
@@ -52,6 +57,7 @@ class OpenIdConnectUserMapperTest {
5257

5358
OpenIdConnectUserMapper mapper;
5459
OpenIdConnectCustomClaimsConfigProperties nonStandardClaimsConfig;
60+
GeorchestraGatewaySecurityConfigProperties securityConfigProperties;
5561
ExtendedOAuth2ClientProperties properties;
5662

5763
/**
@@ -60,7 +66,8 @@ class OpenIdConnectUserMapperTest {
6066
@BeforeEach
6167
void setUp() throws Exception {
6268
nonStandardClaimsConfig = new OpenIdConnectCustomClaimsConfigProperties();
63-
mapper = new OpenIdConnectUserMapper(nonStandardClaimsConfig);
69+
securityConfigProperties = new GeorchestraGatewaySecurityConfigProperties();
70+
mapper = new OpenIdConnectUserMapper(nonStandardClaimsConfig, securityConfigProperties);
6471
}
6572

6673
@Test
@@ -328,6 +335,90 @@ public void customProviderValuesMapper() {
328335
assertThat(georchestraUser.getFirstName()).isEqualTo("given_name");
329336
}
330337

338+
@Test
339+
void map_shouldTransformUsernameWhenDisableUidTransformationIsEmpty() {
340+
OpenIdConnectUserMapper mapper = newMapper("");
341+
342+
OAuth2AuthenticationToken token = mock(OAuth2AuthenticationToken.class);
343+
OidcUser oidcUser = mock(OidcUser.class);
344+
345+
when(token.getPrincipal()).thenReturn(oidcUser);
346+
when(token.getAuthorizedClientRegistrationId()).thenReturn("google");
347+
when(token.getAuthorities()).thenReturn(AuthorityUtils.NO_AUTHORITIES);
348+
349+
when(oidcUser.getSubject()).thenReturn("b7f3dd13-f9cc-4573-8482-b4fccf8e1977");
350+
when(oidcUser.getPreferredUsername()).thenReturn("John.Doe@test.com");
351+
when(oidcUser.getGivenName()).thenReturn("John");
352+
when(oidcUser.getFamilyName()).thenReturn("Doe");
353+
when(oidcUser.getEmail()).thenReturn("jdoe@test.com");
354+
when(oidcUser.getPhoneNumber()).thenReturn("+123");
355+
when(oidcUser.getAddress()).thenReturn(null);
356+
when(oidcUser.getClaims()).thenReturn(Map.of());
357+
358+
Optional<GeorchestraUser> result = mapper.map(token);
359+
360+
assertThat(result).isPresent();
361+
assertThat(result.orElseThrow().getUsername()).isEqualTo("google_john_doe_test_com");
362+
}
363+
364+
@Test
365+
void map_shouldTransformUsernameWhenDisableUidTransformationDoesNotMatchProvider() {
366+
OpenIdConnectUserMapper mapper = newMapper("github");
367+
368+
OAuth2AuthenticationToken token = mock(OAuth2AuthenticationToken.class);
369+
OidcUser oidcUser = mock(OidcUser.class);
370+
371+
when(token.getPrincipal()).thenReturn(oidcUser);
372+
when(token.getAuthorizedClientRegistrationId()).thenReturn("google");
373+
when(token.getAuthorities()).thenReturn(AuthorityUtils.NO_AUTHORITIES);
374+
375+
when(oidcUser.getSubject()).thenReturn("b7f3dd13-f9cc-4573-8482-b4fccf8e1977");
376+
when(oidcUser.getPreferredUsername()).thenReturn("John.Doe@test.com");
377+
when(oidcUser.getGivenName()).thenReturn("John");
378+
when(oidcUser.getFamilyName()).thenReturn("Doe");
379+
when(oidcUser.getEmail()).thenReturn("jdoe@test.com");
380+
when(oidcUser.getPhoneNumber()).thenReturn("+123");
381+
when(oidcUser.getAddress()).thenReturn(null);
382+
when(oidcUser.getClaims()).thenReturn(Map.of());
383+
384+
Optional<GeorchestraUser> result = mapper.map(token);
385+
386+
assertThat(result).isPresent();
387+
assertThat(result.orElseThrow().getUsername()).isEqualTo("google_john_doe_test_com");
388+
}
389+
390+
@Test
391+
void map_shouldNotTransformUsernameWhenDisableUidTransformationMatchesProvider() {
392+
OpenIdConnectUserMapper mapper = newMapper("google");
393+
394+
OAuth2AuthenticationToken token = mock(OAuth2AuthenticationToken.class);
395+
OidcUser oidcUser = mock(OidcUser.class);
396+
397+
when(token.getPrincipal()).thenReturn(oidcUser);
398+
when(token.getAuthorizedClientRegistrationId()).thenReturn("google");
399+
when(token.getAuthorities()).thenReturn(AuthorityUtils.NO_AUTHORITIES);
400+
401+
when(oidcUser.getSubject()).thenReturn("b7f3dd13-f9cc-4573-8482-b4fccf8e1977");
402+
when(oidcUser.getPreferredUsername()).thenReturn("John.Doe@test.com");
403+
when(oidcUser.getGivenName()).thenReturn("John");
404+
when(oidcUser.getFamilyName()).thenReturn("Doe");
405+
when(oidcUser.getEmail()).thenReturn("jdoe@test.com");
406+
when(oidcUser.getPhoneNumber()).thenReturn("+123");
407+
when(oidcUser.getAddress()).thenReturn(null);
408+
when(oidcUser.getClaims()).thenReturn(Map.of());
409+
410+
Optional<GeorchestraUser> result = mapper.map(token);
411+
412+
assertThat(result).isPresent();
413+
assertThat(result.orElseThrow().getUsername()).isEqualTo("John.Doe@test.com");
414+
}
415+
416+
private OpenIdConnectUserMapper newMapper(String disableUidTransformation) {
417+
GeorchestraGatewaySecurityConfigProperties securityConfigProperties = new GeorchestraGatewaySecurityConfigProperties();
418+
securityConfigProperties.setDisableUidTransformation(disableUidTransformation);
419+
return new OpenIdConnectUserMapper(nonStandardClaimsConfig, securityConfigProperties);
420+
}
421+
331422
private Map<String, Object> sampleClaims() throws ParseException {
332423
String json = SAMPLE_CLAIMS;
333424
return sampleClaims(json);

0 commit comments

Comments
 (0)