diff --git a/docs/authzn.adoc b/docs/authzn.adoc index 05f13042..b0f03fb2 100644 --- a/docs/authzn.adoc +++ b/docs/authzn.adoc @@ -276,6 +276,19 @@ sec-roles: ROLE_ORG_6007280321;ROLE_GDI_PLANER;ROLE_GDI_EDITOR;ROLE_USER sec-org: 6007280321 ``` +== External authentication + +Whenever an external authentication is used (OAuth2 or external IDP), a new attribute is added to Header, named : +``` +sec-external-authentication +``` +which is set to "true" in this case. + +This allows the proxified webapps to adapt their behaviour consequently: +as an example, it does not make sense to display a password update form in the geOrchestra +console if the user is logged in via a third party identity provider. + +Having the flag passed in the HTTP headers allows to enable or disable such a functionality. + == Automatically creating users in a geOrchestra LDAP As in the <>, it is possible diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/CreateAccountUserCustomizer.java b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/CreateAccountUserCustomizer.java index bfb80c0c..240a8491 100644 --- a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/CreateAccountUserCustomizer.java +++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/CreateAccountUserCustomizer.java @@ -84,10 +84,10 @@ public class CreateAccountUserCustomizer implements GeorchestraUserCustomizerExt } else { user = accounts.getOrCreate(mappedUser); } + user.setIsExternalAuth(true); loggedInUsers.put(auth, user); return user; } return mappedUser; } - } diff --git a/gateway/src/main/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributor.java b/gateway/src/main/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributor.java index 7bcd6b6e..9541efd5 100644 --- a/gateway/src/main/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributor.java +++ b/gateway/src/main/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributor.java @@ -29,6 +29,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.web.server.ServerWebExchange; +import static org.georchestra.commons.security.SecurityHeaders.*; + /** * Contributes user-related {@literal sec-*} request headers. * @@ -43,26 +45,28 @@ public class GeorchestraUserHeadersContributor extends HeaderContributor { .map(GeorchestraTargetConfig::headers)// .ifPresent(mappings -> { Optional user = GeorchestraUsers.resolve(exchange); - add(headers, "sec-userid", mappings.getUserid(), user.map(GeorchestraUser::getId)); - add(headers, "sec-username", mappings.getUsername(), user.map(GeorchestraUser::getUsername)); - add(headers, "sec-org", mappings.getOrg(), user.map(GeorchestraUser::getOrganization)); - add(headers, "sec-email", mappings.getEmail(), user.map(GeorchestraUser::getEmail)); - add(headers, "sec-firstname", mappings.getFirstname(), user.map(GeorchestraUser::getFirstName)); - add(headers, "sec-lastname", mappings.getLastname(), user.map(GeorchestraUser::getLastName)); - add(headers, "sec-tel", mappings.getTel(), user.map(GeorchestraUser::getTelephoneNumber)); + add(headers, SEC_USERID, mappings.getUserid(), user.map(GeorchestraUser::getId)); + add(headers, SEC_USERNAME, mappings.getUsername(), user.map(GeorchestraUser::getUsername)); + add(headers, SEC_ORG, mappings.getOrg(), user.map(GeorchestraUser::getOrganization)); + add(headers, SEC_EMAIL, mappings.getEmail(), user.map(GeorchestraUser::getEmail)); + add(headers, SEC_FIRSTNAME, mappings.getFirstname(), user.map(GeorchestraUser::getFirstName)); + add(headers, SEC_LASTNAME, mappings.getLastname(), user.map(GeorchestraUser::getLastName)); + add(headers, SEC_TEL, mappings.getTel(), user.map(GeorchestraUser::getTelephoneNumber)); List roles = user.map(GeorchestraUser::getRoles).orElse(List.of()); - add(headers, "sec-roles", mappings.getRoles(), roles); + add(headers, SEC_ROLES, mappings.getRoles(), roles); - add(headers, "sec-lastupdated", mappings.getLastUpdated(), + add(headers, SEC_LASTUPDATED, mappings.getLastUpdated(), user.map(GeorchestraUser::getLastUpdated)); - add(headers, "sec-address", mappings.getAddress(), user.map(GeorchestraUser::getPostalAddress)); - add(headers, "sec-title", mappings.getTitle(), user.map(GeorchestraUser::getTitle)); - add(headers, "sec-notes", mappings.getNotes(), user.map(GeorchestraUser::getNotes)); - add(headers, "sec-ldap-remaining-days", Optional + add(headers, SEC_ADDRESS, mappings.getAddress(), user.map(GeorchestraUser::getPostalAddress)); + add(headers, SEC_TITLE, mappings.getTitle(), user.map(GeorchestraUser::getTitle)); + add(headers, SEC_NOTES, mappings.getNotes(), user.map(GeorchestraUser::getNotes)); + add(headers, SEC_LDAP_REMAINING_DAYS, Optional .of(user.isPresent() && user.get().getLdapWarn() != null && user.get().getLdapWarn()), user.map(GeorchestraUser::getLdapRemainingDays)); + add(headers, SEC_EXTERNAL_AUTHENTICATION, Optional.of(user.isPresent()), + String.valueOf(user.isPresent() && user.get().getIsExternalAuth())); }); }; } diff --git a/gateway/src/test/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributorTest.java b/gateway/src/test/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributorTest.java index 0b25fd9e..6f61e917 100644 --- a/gateway/src/test/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributorTest.java +++ b/gateway/src/test/java/org/georchestra/gateway/filter/headers/providers/GeorchestraUserHeadersContributorTest.java @@ -19,6 +19,7 @@ package org.georchestra.gateway.filter.headers.providers; +import static org.georchestra.commons.security.SecurityHeaders.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -115,18 +116,19 @@ void testContributesHeadersFromUser() { HttpHeaders target = new HttpHeaders(); contributor.accept(target); - assertEquals(List.of(user.getId()), target.get("sec-userid")); - assertEquals(List.of(user.getUsername()), target.get("sec-username")); - assertEquals(List.of(user.getFirstName()), target.get("sec-firstname")); - assertEquals(List.of(user.getLastName()), target.get("sec-lastname")); - assertEquals(List.of(user.getOrganization()), target.get("sec-org")); - assertEquals(List.of(user.getEmail()), target.get("sec-email")); - assertEquals(List.of(user.getTelephoneNumber()), target.get("sec-tel")); - assertEquals(List.of(user.getPostalAddress()), target.get("sec-address")); - assertEquals(List.of(user.getTitle()), target.get("sec-title")); - assertEquals(List.of(user.getNotes()), target.get("sec-notes")); + assertEquals(List.of(user.getId()), target.get(SEC_USERID)); + assertEquals(List.of(user.getUsername()), target.get(SEC_USERNAME)); + assertEquals(List.of(user.getFirstName()), target.get(SEC_FIRSTNAME)); + assertEquals(List.of(user.getLastName()), target.get(SEC_LASTNAME)); + assertEquals(List.of(user.getOrganization()), target.get(SEC_ORG)); + assertEquals(List.of(user.getEmail()), target.get(SEC_EMAIL)); + assertEquals(List.of(user.getTelephoneNumber()), target.get(SEC_TEL)); + assertEquals(List.of(user.getPostalAddress()), target.get(SEC_ADDRESS)); + assertEquals(List.of(user.getTitle()), target.get(SEC_TITLE)); + assertEquals(List.of(user.getNotes()), target.get(SEC_NOTES)); + assertEquals(List.of(String.valueOf(user.getIsExternalAuth())), target.get(SEC_EXTERNAL_AUTHENTICATION)); String roles = user.getRoles().stream().collect(Collectors.joining(";")); - assertEquals(List.of(roles), target.get("sec-roles")); + assertEquals(List.of(roles), target.get(SEC_ROLES)); } } diff --git a/gateway/src/test/java/org/georchestra/gateway/security/preauth/PreauthGatewaySecurityCustomizerIT.java b/gateway/src/test/java/org/georchestra/gateway/security/preauth/PreauthGatewaySecurityCustomizerIT.java index 182632eb..38c80843 100644 --- a/gateway/src/test/java/org/georchestra/gateway/security/preauth/PreauthGatewaySecurityCustomizerIT.java +++ b/gateway/src/test/java/org/georchestra/gateway/security/preauth/PreauthGatewaySecurityCustomizerIT.java @@ -30,8 +30,9 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.client.WireMock.ok; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.georchestra.commons.security.SecurityHeaders.SEC_EXTERNAL_AUTHENTICATION; +import static org.georchestra.commons.security.SecurityHeaders.SEC_ROLES; +import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(classes = GeorchestraGatewayApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @AutoConfigureWebTestClient(timeout = "PT20S") @@ -93,7 +94,27 @@ static void registerPgProperties(DynamicPropertyRegistry registry) { assertTrue(req.getHeaders().keys().stream().filter(h -> h.startsWith("preauth-")) .collect(Collectors.toList()).isEmpty()); // but still the regular sec-* ones - assertFalse(req.getHeader("sec-roles").isEmpty()); + assertFalse(req.getHeader(SEC_ROLES).isEmpty()); }); } + + public @Test void testProxifiedRequestWithExternalAuthenticationHeaderAttribute() { + mockService.stubFor(get(urlMatching("/test"))// + .willReturn(ok())); + + testClient.get().uri("/test").headers(h -> { // + h.set("sec-georchestra-preauthenticated", "true"); // + h.set("preauth-username", "testadmin"); // + h.set("preauth-email", "testadmin@example.org"); // + h.set("preauth-firstname", "Test"); // + h.set("preauth-lastname", "Admin"); // + h.set("preauth-org", "PSC"); // + }).exchange().expectStatus().is2xxSuccessful(); + + List requests = mockService.findAll(getRequestedFor(urlEqualTo("/test"))); + requests.forEach(req -> { + assertFalse(req.getHeader(SEC_EXTERNAL_AUTHENTICATION).isEmpty()); + }); + + } } \ No newline at end of file