diff --git a/datadir/gateway/security.yaml b/datadir/gateway/security.yaml
index 0a4e5a6e..39f54490 100644
--- a/datadir/gateway/security.yaml
+++ b/datadir/gateway/security.yaml
@@ -4,6 +4,15 @@
georchestra:
gateway:
security:
+ createNonExistingUsersInLDAP: false
+ events:
+ rabbitmq:
+ # Note usually enableRabbitmqEvents, rabbitmqHost, etc. come from georchestra's default.properties
+ enabled: ${enableRabbitmqEvents:false}
+ host: ${rabbitmqHost}
+ port: ${rabbitmqPort}
+ user: ${rabbitmqUser}
+ password: ${rabbitmqPassword}
oauth2:
# if enabled, make sure to have at least one OAuth2 client
# set up at spring.security.oauth2.client below
@@ -19,7 +28,7 @@ georchestra:
# Multiple LDAP data sources are supported. The first key defines a simple
# name for them. The `default` one here, disabled by default, is pre-configured
# to use Georchestra's default OpenLDAP database.
- # You should usually just enable it in the georchestra dataidr's gateway.yml
+ # You should usually just enable it in the georchestra datadir's gateway.yml
# with georchestra.gateway.security.ldap.default.enabled: true
default:
enabled: true
diff --git a/docker-compose.yml b/docker-compose.yml
index 1d29aae9..a8f4b112 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,12 +8,6 @@ volumes:
o: bind
device: $PWD/datadir
-secrets:
- slapd_password:
- file: ./datadir/secrets/slapd_password.txt
- geoserver_privileged_user_passwd:
- file: ./datadir/secrets/geoserver_privileged_user_passwd.txt
-
services:
database:
image: georchestra/database:latest
@@ -28,9 +22,6 @@ services:
ldap:
image: georchestra/ldap:latest
- secrets:
- - slapd_password
- - geoserver_privileged_user_passwd
environment:
- SLAPD_ORGANISATION=georchestra
- SLAPD_DOMAIN=georchestra.org
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AbstractAccountsManager.java b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AbstractAccountsManager.java
new file mode 100644
index 00000000..5e384f38
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AbstractAccountsManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.admin;
+
+import java.util.Optional;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+
+import org.georchestra.security.model.GeorchestraUser;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public abstract class AbstractAccountsManager implements AccountManager {
+
+ private final @NonNull Consumer eventPublisher;
+
+ protected final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+ @Override
+ public GeorchestraUser getOrCreate(@NonNull GeorchestraUser mappedUser) {
+ return find(mappedUser).orElseGet(() -> createIfMissing(mappedUser));
+ }
+
+ protected Optional find(GeorchestraUser mappedUser) {
+ lock.readLock().lock();
+ try {
+ return findInternal(mappedUser);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ protected Optional findInternal(GeorchestraUser mappedUser) {
+ if (null != mappedUser.getOAuth2ProviderId()) {
+ return findByOAuth2ProviderId(mappedUser.getOAuth2ProviderId());
+ }
+ return findByUsername(mappedUser.getUsername());
+ }
+
+ GeorchestraUser createIfMissing(GeorchestraUser mapped) {
+ lock.writeLock().lock();
+ try {
+ GeorchestraUser existing = findInternal(mapped).orElse(null);
+ if (null == existing) {
+ createInternal(mapped);
+ existing = findInternal(mapped).orElseThrow(() -> new IllegalStateException(
+ "User " + mapped.getUsername() + " not found right after creation"));
+ eventPublisher.accept(new AccountCreated(existing));
+ }
+ return existing;
+
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ protected abstract Optional findByOAuth2ProviderId(String oauth2ProviderId);
+
+ protected abstract Optional findByUsername(String username);
+
+ protected abstract void createInternal(GeorchestraUser mapped);
+
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountCreated.java b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountCreated.java
new file mode 100644
index 00000000..aa6775c1
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountCreated.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.admin;
+
+import org.georchestra.security.model.GeorchestraUser;
+
+import lombok.NonNull;
+import lombok.Value;
+
+/**
+ * Application event published when a new account was created
+ */
+@Value
+public class AccountCreated {
+
+ private @NonNull GeorchestraUser user;
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountManager.java b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountManager.java
new file mode 100644
index 00000000..d5b51975
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.admin;
+
+import org.georchestra.gateway.security.GeorchestraUserMapper;
+import org.georchestra.gateway.security.ResolveGeorchestraUserGlobalFilter;
+import org.georchestra.security.model.GeorchestraUser;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.security.core.Authentication;
+
+/**
+ * @see CreateAccountUserCustomizer
+ * @see ResolveGeorchestraUserGlobalFilter
+ */
+public interface AccountManager {
+
+ /**
+ * Finds the stored user that belongs to the {@code mappedUser} or creates it if
+ * it doesn't exist in the users repository.
+ *
+ * When a user is created, an {@link AccountCreated} event must be published to
+ * the {@link ApplicationEventPublisher}.
+ *
+ * @param mappedUser the user {@link ResolveGeorchestraUserGlobalFilter}
+ * resolved by calling
+ * {@link GeorchestraUserMapper#resolve(Authentication)}
+ * @return the stored version of the user, whether it existed or was created as
+ * the result of calling this method.
+ */
+ GeorchestraUser getOrCreate(GeorchestraUser mappedUser);
+
+}
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
new file mode 100644
index 00000000..c0279be4
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/CreateAccountUserCustomizer.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.admin;
+
+import java.util.Objects;
+
+import org.georchestra.gateway.security.GeorchestraUserCustomizerExtension;
+import org.georchestra.security.model.GeorchestraUser;
+import org.springframework.core.Ordered;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * {@link GeorchestraUserCustomizerExtension} that
+ * {@link AccountManager#getOrCreate creates an account} when authenticated
+ * through request headers (trusted proxy feature) or through OAuth2.
+ */
+@RequiredArgsConstructor
+public class CreateAccountUserCustomizer implements GeorchestraUserCustomizerExtension, Ordered {
+
+ private final @NonNull AccountManager accounts;
+
+ /**
+ * @return {@link Ordered#LOWEST_PRECEDENCE} so it runs after all other
+ * authentication customizations have been performed, such as setting
+ * additional roles from externalized configuration, etc.
+ */
+ public @Override int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+ /**
+ * @return the stored version (either existing or created as result of calling
+ * this method) of the user account, if the {@code Authentication}
+ * object is either an {@link OAuth2AuthenticationToken}
+ */
+ @Override
+ public @NonNull GeorchestraUser apply(@NonNull Authentication auth, @NonNull GeorchestraUser mappedUser) {
+ final boolean isOauth2 = auth instanceof OAuth2AuthenticationToken;
+ if (isOauth2) {
+ Objects.requireNonNull(mappedUser.getOAuth2ProviderId(), "GeorchestraUser.oAuth2ProviderId is null");
+ }
+ if (isOauth2) {
+ return accounts.getOrCreate(mappedUser);
+ }
+ return mappedUser;
+ }
+
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/GeorchestraLdapAccountManagementConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/GeorchestraLdapAccountManagementConfiguration.java
new file mode 100644
index 00000000..4072a549
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/GeorchestraLdapAccountManagementConfiguration.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.admin.ldap;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.georchestra.ds.orgs.OrgsDao;
+import org.georchestra.ds.orgs.OrgsDaoImpl;
+import org.georchestra.ds.roles.RoleDao;
+import org.georchestra.ds.roles.RoleDaoImpl;
+import org.georchestra.ds.roles.RoleProtected;
+import org.georchestra.ds.security.UserMapperImpl;
+import org.georchestra.ds.security.UsersApiImpl;
+import org.georchestra.ds.users.AccountDao;
+import org.georchestra.ds.users.AccountDaoImpl;
+import org.georchestra.ds.users.UserRule;
+import org.georchestra.gateway.accounts.admin.AccountManager;
+import org.georchestra.gateway.accounts.admin.CreateAccountUserCustomizer;
+import org.georchestra.gateway.security.ldap.LdapConfigProperties;
+import org.georchestra.gateway.security.ldap.extended.ExtendedLdapConfig;
+import org.georchestra.security.api.UsersApi;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.LdapContextSource;
+import org.springframework.ldap.pool.factory.PoolingContextSource;
+import org.springframework.ldap.pool.validation.DefaultDirContextValidator;
+
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(LdapConfigProperties.class)
+public class GeorchestraLdapAccountManagementConfiguration {
+
+ @Bean
+ AccountManager ldapAccountsManager(//
+ ApplicationEventPublisher eventPublisher, //
+ AccountDao accountDao, RoleDao roleDao, OrgsDao orgsDao) {
+
+ UsersApi usersApi = ldapUsersApi(accountDao, roleDao);
+ return new LdapAccountsManager(eventPublisher::publishEvent, accountDao, roleDao, orgsDao, usersApi);
+ }
+
+ @Bean
+ CreateAccountUserCustomizer createAccountUserCustomizer(AccountManager accountManager) {
+ return new CreateAccountUserCustomizer(accountManager);
+ }
+
+ private UsersApi ldapUsersApi(AccountDao accountDao, RoleDao roleDao) {
+ UserMapperImpl mapper = new UserMapperImpl();
+ mapper.setRoleDao(roleDao);
+ List protectedUsers = Collections.emptyList();
+ UserRule rule = new UserRule();
+ rule.setListOfprotectedUsers(protectedUsers.toArray(String[]::new));
+ UsersApiImpl usersApi = new UsersApiImpl();
+ usersApi.setAccountsDao(accountDao);
+ usersApi.setMapper(mapper);
+ usersApi.setUserRule(rule);
+ return usersApi;
+ }
+
+ @Bean
+ LdapContextSource singleContextSource(LdapConfigProperties config) {
+ ExtendedLdapConfig ldapConfig = config.extendedEnabled().get(0);
+ LdapContextSource singleContextSource = new LdapContextSource();
+ singleContextSource.setUrl(ldapConfig.getUrl());
+ singleContextSource.setBase(ldapConfig.getBaseDn());
+ singleContextSource.setUserDn(ldapConfig.getAdminDn().get());
+ singleContextSource.setPassword(ldapConfig.getAdminPassword().get());
+ return singleContextSource;
+ }
+
+ @Bean
+ PoolingContextSource contextSource(LdapContextSource singleContextSource) {
+ PoolingContextSource contextSource = new PoolingContextSource();
+ contextSource.setContextSource(singleContextSource);
+ contextSource.setDirContextValidator(new DefaultDirContextValidator());
+ contextSource.setTestOnBorrow(true);
+ contextSource.setMaxActive(8);
+ contextSource.setMinIdle(1);
+ contextSource.setMaxIdle(8);
+ contextSource.setMaxTotal(-1);
+ contextSource.setMaxWait(-1);
+ return contextSource;
+ }
+
+ @Bean
+ LdapTemplate ldapTemplate(PoolingContextSource contextSource) throws Exception {
+ LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
+ return ldapTemplate;
+ }
+
+ @Bean
+ RoleDao roleDao(LdapTemplate ldapTemplate, LdapConfigProperties config) {
+ RoleDaoImpl impl = new RoleDaoImpl();
+ impl.setLdapTemplate(ldapTemplate);
+ impl.setRoleSearchBaseDN(config.extendedEnabled().get(0).getRolesRdn());
+ return impl;
+ }
+
+ @Bean
+ OrgsDao orgsDao(LdapTemplate ldapTemplate, LdapConfigProperties config) {
+ OrgsDaoImpl impl = new OrgsDaoImpl();
+ impl.setLdapTemplate(ldapTemplate);
+ ExtendedLdapConfig ldapConfig = config.extendedEnabled().get(0);
+ impl.setBasePath(ldapConfig.getBaseDn());
+ impl.setOrgSearchBaseDN(ldapConfig.getOrgsRdn());
+ impl.setPendingOrgSearchBaseDN(ldapConfig.getPendingOrgsRdn());
+ return impl;
+ }
+
+ @Bean
+ AccountDao accountDao(LdapTemplate ldapTemplate, LdapConfigProperties config) throws Exception {
+ ExtendedLdapConfig ldapConfig = config.extendedEnabled().get(0);
+ String baseDn = ldapConfig.getBaseDn();
+ String userSearchBaseDN = ldapConfig.getUsersRdn();
+ String roleSearchBaseDN = ldapConfig.getRolesRdn();
+
+ // we don't need a configuration property for this,
+ // we don't allow pending users to log in. The LdapAuthenticationProvider won't
+ // even look them up.
+ final String pendingUsersSearchBaseDN = "ou=pendingusers";
+
+ AccountDaoImpl impl = new AccountDaoImpl(ldapTemplate);
+ impl.setBasePath(baseDn);
+ impl.setUserSearchBaseDN(userSearchBaseDN);
+ impl.setRoleSearchBaseDN(roleSearchBaseDN);
+ if (pendingUsersSearchBaseDN != null) {
+ impl.setPendingUserSearchBaseDN(pendingUsersSearchBaseDN);
+ }
+
+ String orgSearchBaseDN = ldapConfig.getOrgsRdn();
+ requireNonNull(orgSearchBaseDN);
+ impl.setOrgSearchBaseDN(orgSearchBaseDN);
+
+ final String pendingOrgSearchBaseDN = "ou=pendingorgs";
+ impl.setPendingOrgSearchBaseDN(pendingOrgSearchBaseDN);
+
+ impl.init();
+ return impl;
+ }
+
+ @Bean
+ RoleProtected roleProtected() {
+ RoleProtected roleProtected = new RoleProtected();
+ roleProtected.setListOfprotectedRoles(
+ new String[] { "ADMINISTRATOR", "GN_.*", "ORGADMIN", "REFERENT", "USER", "SUPERUSER" });
+ return roleProtected;
+ }
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/LdapAccountsManager.java b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/LdapAccountsManager.java
new file mode 100644
index 00000000..50a33c77
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/LdapAccountsManager.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.admin.ldap;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import org.georchestra.ds.DataServiceException;
+import org.georchestra.ds.DuplicatedCommonNameException;
+import org.georchestra.ds.orgs.Org;
+import org.georchestra.ds.orgs.OrgsDao;
+import org.georchestra.ds.roles.RoleDao;
+import org.georchestra.ds.roles.RoleFactory;
+import org.georchestra.ds.users.Account;
+import org.georchestra.ds.users.AccountDao;
+import org.georchestra.ds.users.AccountFactory;
+import org.georchestra.ds.users.DuplicatedEmailException;
+import org.georchestra.ds.users.DuplicatedUidException;
+import org.georchestra.gateway.accounts.admin.AbstractAccountsManager;
+import org.georchestra.gateway.accounts.admin.AccountCreated;
+import org.georchestra.gateway.accounts.admin.AccountManager;
+import org.georchestra.security.api.UsersApi;
+import org.georchestra.security.model.GeorchestraUser;
+import org.springframework.ldap.NameNotFoundException;
+
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * {@link AccountManager} that fetches and creates {@link GeorchestraUser}s from
+ * the Georchestra extended LDAP service provided by an {@link AccountDao} and
+ * {@link RoleDao}.
+ */
+@Slf4j(topic = "org.georchestra.gateway.accounts.admin.ldap")
+class LdapAccountsManager extends AbstractAccountsManager {
+
+ private final @NonNull AccountDao accountDao;
+ private final @NonNull RoleDao roleDao;
+
+ private final @NonNull OrgsDao orgsDao;
+ private final @NonNull UsersApi usersApi;
+
+ public LdapAccountsManager(Consumer eventPublisher, AccountDao accountDao, RoleDao roleDao,
+ OrgsDao orgsDao, UsersApi usersApi) {
+ super(eventPublisher);
+ this.accountDao = accountDao;
+ this.roleDao = roleDao;
+ this.orgsDao = orgsDao;
+ this.usersApi = usersApi;
+ }
+
+ @Override
+ protected Optional findByOAuth2ProviderId(@NonNull String oauth2ProviderId) {
+ return usersApi.findByOAuth2ProviderId(oauth2ProviderId).map(this::ensureRolesPrefixed);
+ }
+
+ @Override
+ protected Optional findByUsername(@NonNull String username) {
+ return usersApi.findByUsername(username).map(this::ensureRolesPrefixed);
+ }
+
+ private GeorchestraUser ensureRolesPrefixed(GeorchestraUser user) {
+ List roles = user.getRoles().stream().filter(Objects::nonNull)
+ .map(r -> r.startsWith("ROLE_") ? r : "ROLE_" + r).collect(Collectors.toList());
+ user.setRoles(roles);
+ return user;
+ }
+
+ @Override
+ protected void createInternal(GeorchestraUser mapped) {
+ Account newAccount = mapToAccountBrief(mapped);
+ try {
+ accountDao.insert(newAccount);
+ } catch (DataServiceException | DuplicatedUidException | DuplicatedEmailException accountError) {
+ throw new IllegalStateException(accountError);
+ }
+
+ ensureOrgExists(newAccount);
+
+ ensureRolesExist(mapped, newAccount);
+ }
+
+ private void ensureRolesExist(GeorchestraUser mapped, Account newAccount) {
+ try {// account created, add roles
+ if (!mapped.getRoles().contains("ROLE_USER")) {
+ roleDao.addUser("USER", newAccount);
+ }
+ for (String role : mapped.getRoles()) {
+ role = role.replaceFirst("^ROLE_", "");
+ ensureRoleExists(role);
+ roleDao.addUser(role, newAccount);
+ }
+ } catch (NameNotFoundException | DataServiceException roleError) {
+ try {// roll-back account
+ accountDao.delete(newAccount);
+ } catch (NameNotFoundException | DataServiceException rollbackError) {
+ log.warn("Error reverting user creation after roleDao update failure", rollbackError);
+ }
+ throw new IllegalStateException(roleError);
+ }
+ }
+
+ private void ensureRoleExists(String role) throws DataServiceException {
+ try {
+ roleDao.findByCommonName(role);
+ } catch (NameNotFoundException notFound) {
+ try {
+ roleDao.insert(RoleFactory.create(role, null, null));
+ } catch (DuplicatedCommonNameException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ private Account mapToAccountBrief(@NonNull GeorchestraUser preAuth) {
+ String username = preAuth.getUsername();
+ String email = preAuth.getEmail();
+ String firstName = preAuth.getFirstName();
+ String lastName = preAuth.getLastName();
+ String org = preAuth.getOrganization();
+ String password = null;
+ String phone = "";
+ String title = "";
+ String description = "";
+ final @javax.annotation.Nullable String oAuth2ProviderId = preAuth.getOAuth2ProviderId();
+
+ Account newAccount = AccountFactory.createBrief(username, password, firstName, lastName, email, phone, title,
+ description, oAuth2ProviderId);
+ newAccount.setPending(false);
+ newAccount.setOrg(org);
+ return newAccount;
+ }
+
+ private void ensureOrgExists(@NonNull Account newAccount) {
+ String orgId = newAccount.getOrg();
+ if (null == orgId)
+ return;
+ try { // account created, add org
+ Org org;
+ try {
+ org = orgsDao.findByCommonName(orgId);
+ // org already in the LDAP, add the newly
+ // created account to it
+ List currentMembers = org.getMembers();
+ currentMembers.add(newAccount.getUid());
+ org.setMembers(currentMembers);
+ orgsDao.update(org);
+ } catch (NameNotFoundException e) {
+ log.info("Org {} does not exist, trying to create it", orgId);
+ // org does not exist yet, create it
+ org = new Org();
+ org.setId(orgId);
+ org.setName(orgId);
+ org.setMembers(Arrays.asList(newAccount.getUid()));
+ orgsDao.insert(org);
+ }
+ } catch (Exception orgError) {
+ log.error("Error when trying to create / update the organisation {}, reverting the account creation", orgId,
+ orgError);
+ try {// roll-back account
+ accountDao.delete(newAccount);
+ } catch (NameNotFoundException | DataServiceException rollbackError) {
+ log.warn("Error reverting user creation after orgsDao update failure", rollbackError);
+ }
+ throw new IllegalStateException(orgError);
+ }
+ }
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqAccountCreatedEventSender.java b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqAccountCreatedEventSender.java
new file mode 100644
index 00000000..f64ae41f
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqAccountCreatedEventSender.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.events.rabbitmq;
+
+import java.util.UUID;
+
+import org.georchestra.gateway.accounts.admin.AccountCreated;
+import org.georchestra.security.model.GeorchestraUser;
+import org.json.JSONObject;
+import org.springframework.amqp.core.AmqpTemplate;
+import org.springframework.context.event.EventListener;
+
+/**
+ * Service bean that listens to {@link AccountCreated} events and publish a
+ * distributed event through rabbitmq to the {@literal OAUTH2-ACCOUNT-CREATION}
+ * queue.
+ */
+public class RabbitmqAccountCreatedEventSender {
+
+ public static final String OAUTH2_ACCOUNT_CREATION = "OAUTH2-ACCOUNT-CREATION";
+
+ private AmqpTemplate eventTemplate;
+
+ public RabbitmqAccountCreatedEventSender(AmqpTemplate eventTemplate) {
+ this.eventTemplate = eventTemplate;
+ }
+
+ @EventListener(AccountCreated.class)
+ public void on(AccountCreated event) {
+ GeorchestraUser user = event.getUser();
+ final String oAuth2ProviderId = user.getOAuth2ProviderId();
+ if (null != oAuth2ProviderId) {
+ String fullName = user.getFirstName() + " " + user.getLastName();
+ String email = user.getEmail();
+ String provider = oAuth2ProviderId;
+ sendNewOAuthAccountMessage(fullName, email, provider);
+ }
+ }
+
+ public void sendNewOAuthAccountMessage(String fullName, String email, String provider) {
+ // beans getting a reference to the sender
+ JSONObject jsonObj = new JSONObject();
+ jsonObj.put("uid", UUID.randomUUID());
+ jsonObj.put("subject", OAUTH2_ACCOUNT_CREATION);
+ jsonObj.put("username", fullName); // bean
+ jsonObj.put("email", email); // bean
+ jsonObj.put("provider", provider); // bean
+ eventTemplate.convertAndSend("routing-gateway", jsonObj.toString());// send
+ }
+}
\ No newline at end of file
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsConfiguration.java
new file mode 100644
index 00000000..1a035b0d
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsConfiguration.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.events.rabbitmq;
+
+import org.georchestra.gateway.accounts.admin.AccountCreated;
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.actuate.amqp.RabbitHealthIndicator;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportResource;
+
+/**
+ * {@link Configuration @Configuration} to enable sending events over rabbitmq *
+ *
+ * When an account is created in geOrchestra's LDAP in response to a
+ * pre-authenticated or OIDC successful authentication, an
+ * {@link AccountCreated} event will be catch up and sent over the wire.
+ *
+ * @see RabbitmqEventsConfigurationProperties
+ *
+ */
+@Configuration
+@EnableConfigurationProperties(RabbitmqEventsConfigurationProperties.class)
+@ImportResource({ "classpath:rabbit-listener-context.xml", "classpath:rabbit-sender-context.xml" })
+public class RabbitmqEventsConfiguration {
+
+ @Bean
+ RabbitmqAccountCreatedEventSender eventsSender(@Qualifier("eventTemplate") RabbitTemplate eventTemplate) {
+ return new RabbitmqAccountCreatedEventSender(eventTemplate);
+ }
+
+ @Bean
+ org.springframework.amqp.rabbit.connection.CachingConnectionFactory connectionFactory(
+ RabbitmqEventsConfigurationProperties config) {
+
+ com.rabbitmq.client.ConnectionFactory fac = new com.rabbitmq.client.ConnectionFactory();
+ fac.setHost(config.getHost());
+ fac.setPort(config.getPort());
+ fac.setUsername(config.getUser());
+ fac.setPassword(config.getPassword());
+
+ return new CachingConnectionFactory(fac);
+ }
+
+ @Bean
+ RabbitHealthIndicator rabbitHealthIndicator(@Qualifier("eventTemplate") RabbitTemplate eventTemplate) {
+ return new RabbitHealthIndicator(eventTemplate);
+ }
+
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsConfigurationProperties.java b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsConfigurationProperties.java
new file mode 100644
index 00000000..002549b8
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsConfigurationProperties.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.events.rabbitmq;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import lombok.Data;
+import lombok.Generated;
+
+/**
+ * Configuration properties to enable rabbit-mq event dispatching of accounts
+ * created
+ */
+@Data
+@Generated
+@Validated
+@ConfigurationProperties(prefix = RabbitmqEventsConfigurationProperties.PREFIX)
+public class RabbitmqEventsConfigurationProperties {
+
+ public static final String PREFIX = "georchestra.gateway.security.events.rabbitmq";
+ public static final String ENABLED = PREFIX + ".enabled";
+
+ /**
+ * Whether rabbit-mq events should be sent when an LDAP account was created upon
+ * a first successful login through OAuth2
+ */
+ private boolean enabled;
+ /**
+ * The rabbit-mq host name
+ */
+ private String host;
+ /**
+ * The rabbit-mq host port number
+ */
+ private int port;
+ /**
+ * The rabbit-mq authentication user
+ */
+ private String user;
+ /**
+ * The rabbit-mq authentication password
+ */
+ private String password;
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsListener.java b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsListener.java
similarity index 57%
rename from gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsListener.java
rename to gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsListener.java
index bb2f5b39..c34807d0 100644
--- a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsListener.java
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/events/rabbitmq/RabbitmqEventsListener.java
@@ -1,14 +1,34 @@
-package org.georchestra.gateway.events;
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.accounts.events.rabbitmq;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
-import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+//TODO: remove class as dead code?
@Slf4j(topic = "org.georchestra.gateway.events")
public class RabbitmqEventsListener implements MessageListener {
diff --git a/gateway/src/main/java/org/georchestra/gateway/app/GeorchestraGatewayApplication.java b/gateway/src/main/java/org/georchestra/gateway/app/GeorchestraGatewayApplication.java
index 4b08b342..4253b7e7 100644
--- a/gateway/src/main/java/org/georchestra/gateway/app/GeorchestraGatewayApplication.java
+++ b/gateway/src/main/java/org/georchestra/gateway/app/GeorchestraGatewayApplication.java
@@ -18,7 +18,15 @@
*/
package org.georchestra.gateway.app;
-import lombok.extern.slf4j.Slf4j;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.annotation.PostConstruct;
+
import org.georchestra.gateway.security.GeorchestraUserMapper;
import org.georchestra.gateway.security.ldap.LdapConfigProperties;
import org.georchestra.security.model.GeorchestraUser;
@@ -43,12 +51,9 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-import javax.annotation.PostConstruct;
-import java.io.File;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
+import lombok.extern.slf4j.Slf4j;
+import reactor.core.publisher.Mono;
@Controller
@Slf4j
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/ConditionalOnCreateLdapAccounts.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/ConditionalOnCreateLdapAccounts.java
new file mode 100644
index 00000000..50d87c39
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/ConditionalOnCreateLdapAccounts.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+
+package org.georchestra.gateway.autoconfigure.accounts;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ *
+ * @see ConditionalOnDefaultGeorchestraLdapEnabled
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ConditionalOnDefaultGeorchestraLdapEnabled
+@ConditionalOnProperty(name = "georchestra.gateway.security.createNonExistingUsersInLDAP", havingValue = "true", matchIfMissing = false)
+public @interface ConditionalOnCreateLdapAccounts {
+
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/ConditionalOnDefaultGeorchestraLdapEnabled.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/ConditionalOnDefaultGeorchestraLdapEnabled.java
new file mode 100644
index 00000000..dc0a7759
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/ConditionalOnDefaultGeorchestraLdapEnabled.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+
+package org.georchestra.gateway.autoconfigure.accounts;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.georchestra.gateway.autoconfigure.security.ConditionalOnLdapEnabled;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ *
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ConditionalOnLdapEnabled
+@ConditionalOnProperty(name = "georchestra.gateway.security.ldap.default.enabled", havingValue = "true", matchIfMissing = false)
+public @interface ConditionalOnDefaultGeorchestraLdapEnabled {
+
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/GeorchestraLdapAccountsCreationAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/GeorchestraLdapAccountsCreationAutoConfiguration.java
new file mode 100644
index 00000000..39210892
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/GeorchestraLdapAccountsCreationAutoConfiguration.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.autoconfigure.accounts;
+
+import org.georchestra.gateway.accounts.admin.ldap.GeorchestraLdapAccountManagementConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * {@link AutoConfiguration @AutoConfiguration}
+ *
+ * @see ConditionalOnCreateLdapAccounts
+ * @see GeorchestraLdapAccountManagementConfiguration
+ */
+@AutoConfiguration
+@ConditionalOnCreateLdapAccounts
+@Import(GeorchestraLdapAccountManagementConfiguration.class)
+public class GeorchestraLdapAccountsCreationAutoConfiguration {
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/RabbitmqEventsAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/RabbitmqEventsAutoConfiguration.java
new file mode 100644
index 00000000..332e305d
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/accounts/RabbitmqEventsAutoConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.autoconfigure.accounts;
+
+import org.georchestra.gateway.accounts.admin.AccountCreated;
+import org.georchestra.gateway.accounts.events.rabbitmq.RabbitmqEventsConfiguration;
+import org.georchestra.gateway.accounts.events.rabbitmq.RabbitmqEventsConfigurationProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Import;
+
+/**
+ * {@link AutoConfiguration @AutoConfiguration} to enable sending events over
+ * rabbitmq when it is enabled through
+ * {@literal georchestra.gateway.security.events.rabbitmq = true}.
+ *
+ * When an account is created in geOrchestra's LDAP in response to a
+ * pre-authenticated or OIDC successful authentication, an
+ * {@link AccountCreated} event will be catch up and sent over the wire.
+ *
+ *
+ * @see ConditionalOnCreateLdapAccounts
+ * @see RabbitmqEventsConfiguration
+ */
+@AutoConfiguration
+@ConditionalOnCreateLdapAccounts
+@ConditionalOnProperty(name = RabbitmqEventsConfigurationProperties.ENABLED, havingValue = "true", matchIfMissing = false)
+@Import(RabbitmqEventsConfiguration.class)
+public class RabbitmqEventsAutoConfiguration {
+
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/FiltersAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/FiltersAutoConfiguration.java
index 68c76389..4c2d19b4 100644
--- a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/FiltersAutoConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/FiltersAutoConfiguration.java
@@ -25,18 +25,15 @@
import org.geoserver.cloud.gateway.filter.RouteProfileGatewayFilterFactory;
import org.geoserver.cloud.gateway.filter.StripBasePathGatewayFilterFactory;
import org.geoserver.cloud.gateway.predicate.RegExpQueryRoutePredicateFactory;
-import org.springframework.boot.actuate.health.Health;
-import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-@Configuration(proxyBeanMethods = false)
+@AutoConfiguration
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@Import(HeaderFiltersConfiguration.class)
@EnableConfigurationProperties(GatewayConfigProperties.class)
@@ -67,9 +64,4 @@ public class FiltersAutoConfiguration {
public @Bean StripBasePathGatewayFilterFactory stripBasePathGatewayFilterFactory() {
return new StripBasePathGatewayFilterFactory();
}
-
- @ConditionalOnProperty(name = "enableRabbitmqEvents", havingValue = "false", matchIfMissing = true)
- public @Bean HealthIndicator rabbitHealthIndicator() {
- return () -> Health.up().withDetail("version", "mock").build();
- }
}
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/RoutePredicateFactoriesAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/RoutePredicateFactoriesAutoConfiguration.java
index 18a70bf2..d97bf371 100644
--- a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/RoutePredicateFactoriesAutoConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/app/RoutePredicateFactoriesAutoConfiguration.java
@@ -19,10 +19,10 @@
package org.georchestra.gateway.autoconfigure.app;
import org.georchestra.gateway.handler.predicate.QueryParamRoutePredicateFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-@Configuration(proxyBeanMethods = false)
+@AutoConfiguration
public class RoutePredicateFactoriesAutoConfiguration {
public @Bean QueryParamRoutePredicateFactory queryParamRoutePredicateFactory() {
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/AtLeastOneLdapDatasourceEnabledCondition.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/AtLeastOneLdapDatasourceEnabledCondition.java
index d0034e32..2739b41e 100644
--- a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/AtLeastOneLdapDatasourceEnabledCondition.java
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/AtLeastOneLdapDatasourceEnabledCondition.java
@@ -28,10 +28,12 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.georchestra.gateway.security.ldap.LdapConfigProperties;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.ItemsBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
+import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
@@ -42,7 +44,11 @@
import com.google.common.collect.Streams;
/**
- *
+ * {@link Condition} that matches if at least one LDAP config is enabled from
+ * the externalized config properties
+ * {@code georchestra.gateway.security.ldap..enabled}
+ *
+ * @see LdapConfigProperties
*/
class AtLeastOneLdapDatasourceEnabledCondition extends SpringBootCondition {
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/LdapSecurityAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/LdapSecurityAutoConfiguration.java
index e1e0bade..783c0975 100644
--- a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/LdapSecurityAutoConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/LdapSecurityAutoConfiguration.java
@@ -20,11 +20,9 @@
import javax.annotation.PostConstruct;
-import org.georchestra.gateway.security.ldap.LdapSecurityConfiguration;
-import org.georchestra.gateway.security.ldap.basic.BasicLdapAuthenticationConfiguration;
-import org.georchestra.gateway.security.ldap.extended.ExtendedLdapAuthenticationConfiguration;
+import org.georchestra.gateway.security.ldap.LdapAuthenticationConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import lombok.extern.slf4j.Slf4j;
@@ -32,14 +30,11 @@
/**
* {@link EnableAutoConfiguration AutoConfiguration} to set up LDAP security
*
- * @see LdapSecurityConfiguration
- * @see BasicLdapAuthenticationConfiguration
- * @see ExtendedLdapAuthenticationConfiguration
- * @see ActiveDirectoryAuthenticationConfiguration
+ * @see LdapAuthenticationConfiguration
*/
-@Configuration(proxyBeanMethods = false)
+@AutoConfiguration
@ConditionalOnLdapEnabled
-@Import(LdapSecurityConfiguration.class)
+@Import(LdapAuthenticationConfiguration.class)
@Slf4j(topic = "org.georchestra.gateway.autoconfigure.security")
public class LdapSecurityAutoConfiguration {
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/OAuth2SecurityAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/OAuth2SecurityAutoConfiguration.java
index 8c625439..671b5e6b 100644
--- a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/OAuth2SecurityAutoConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/OAuth2SecurityAutoConfiguration.java
@@ -21,13 +21,14 @@
import javax.annotation.PostConstruct;
import org.georchestra.gateway.security.oauth2.OAuth2Configuration;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import lombok.extern.slf4j.Slf4j;
-@Configuration(proxyBeanMethods = false)
+@AutoConfiguration
@Slf4j(topic = "org.georchestra.gateway.autoconfigure.security")
@Import({ OAuth2SecurityAutoConfiguration.Enabled.class, OAuth2SecurityAutoConfiguration.Disabled.class })
public class OAuth2SecurityAutoConfiguration {
diff --git a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/WebSecurityAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/WebSecurityAutoConfiguration.java
index 8dfa0330..5107c626 100644
--- a/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/WebSecurityAutoConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/autoconfigure/security/WebSecurityAutoConfiguration.java
@@ -20,11 +20,11 @@
import org.georchestra.gateway.security.GatewaySecurityConfiguration;
import org.georchestra.gateway.security.accessrules.AccessRulesConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-@Configuration(proxyBeanMethods = false)
+@AutoConfiguration
@ConditionalOnDefaultWebSecurity
@Import({ GatewaySecurityConfiguration.class, AccessRulesConfiguration.class })
public class WebSecurityAutoConfiguration {
diff --git a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java
index 536b879a..e69de29b 100644
--- a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java
@@ -1,38 +0,0 @@
-package org.georchestra.gateway.events;
-
-import org.springframework.amqp.core.AmqpTemplate;
-import org.springframework.amqp.rabbit.connection.ConnectionFactory;
-import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
-import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
-import org.springframework.boot.autoconfigure.AutoConfigureAfter;
-import org.springframework.amqp.core.Queue;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
-import org.springframework.context.annotation.*;
-
-@Profile("!test && !it")
-@Configuration(proxyBeanMethods = false)
-@AutoConfigureAfter(GatewayAutoConfiguration.class)
-@ImportResource({ "classpath:rabbit-listener-context.xml", "classpath:rabbit-sender-context.xml" })
-@ConditionalOnProperty(name = "enableRabbitmqEvents", havingValue = "true", matchIfMissing = false)
-public class RabbitmqEventsAutoConfiguration {
-
- @Bean
- @DependsOn({ "eventTemplate" })
- public RabbitmqEventsSender eventsSender(AmqpTemplate eventTemplate) {
- return new RabbitmqEventsSender(eventTemplate);
- }
-
- Queue OAuth2ReplyQueue() {
- return new Queue("OAuth2ReplyQueue", false);
- }
-
- MessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
- SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
- simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
- simpleMessageListenerContainer.setQueues(OAuth2ReplyQueue());
- simpleMessageListenerContainer.setMessageListener(new RabbitmqEventsListener());
- return simpleMessageListenerContainer;
- }
-}
\ No newline at end of file
diff --git a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsSender.java b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsSender.java
deleted file mode 100644
index 7e46a787..00000000
--- a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsSender.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.georchestra.gateway.events;
-
-import org.json.JSONObject;
-import org.springframework.amqp.core.AmqpTemplate;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationContext;
-
-import java.util.UUID;
-
-public class RabbitmqEventsSender {
-
- public static final String OAUTH2_ACCOUNT_CREATION = "OAUTH2-ACCOUNT-CREATION";
-
- @Autowired
- private ApplicationContext applicationContext;
-
- private AmqpTemplate eventTemplate;
-
- public RabbitmqEventsSender(AmqpTemplate eventTemplate) {
- this.eventTemplate = eventTemplate;
- }
-
- public void sendNewOAuthAccountMessage(String username, String email, String provider) throws Exception {
- // beans
- // getting a reference to
- // the sender
- JSONObject jsonObj = new JSONObject();
- jsonObj.put("uid", UUID.randomUUID());
- jsonObj.put("subject", OAUTH2_ACCOUNT_CREATION);
- jsonObj.put("username", username); // bean
- jsonObj.put("email", email); // bean
- jsonObj.put("provider", provider); // bean
- eventTemplate.convertAndSend("routing-gateway", jsonObj.toString());// send
- }
-}
\ No newline at end of file
diff --git a/gateway/src/main/java/org/georchestra/gateway/filter/headers/CookieAffinityGatewayFilterFactory.java b/gateway/src/main/java/org/georchestra/gateway/filter/headers/CookieAffinityGatewayFilterFactory.java
index e4bc4dcc..1073ea8d 100644
--- a/gateway/src/main/java/org/georchestra/gateway/filter/headers/CookieAffinityGatewayFilterFactory.java
+++ b/gateway/src/main/java/org/georchestra/gateway/filter/headers/CookieAffinityGatewayFilterFactory.java
@@ -1,8 +1,7 @@
package org.georchestra.gateway.filter.headers;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import lombok.Setter;
+import javax.validation.constraints.NotEmpty;
+
import org.georchestra.gateway.filter.global.ResolveTargetGlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
@@ -11,9 +10,11 @@
import org.springframework.http.ResponseCookie;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-import javax.validation.constraints.NotEmpty;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import reactor.core.publisher.Mono;
public class CookieAffinityGatewayFilterFactory
extends AbstractGatewayFilterFactory {
diff --git a/gateway/src/main/java/org/georchestra/gateway/filter/headers/HeaderFiltersConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/filter/headers/HeaderFiltersConfiguration.java
index a5e8070a..1e62361c 100644
--- a/gateway/src/main/java/org/georchestra/gateway/filter/headers/HeaderFiltersConfiguration.java
+++ b/gateway/src/main/java/org/georchestra/gateway/filter/headers/HeaderFiltersConfiguration.java
@@ -26,12 +26,14 @@
import org.georchestra.gateway.filter.headers.providers.JsonPayloadHeadersContributor;
import org.georchestra.gateway.filter.headers.providers.SecProxyHeaderContributor;
import org.georchestra.gateway.model.GatewayConfigProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(GatewayConfigProperties.class)
public class HeaderFiltersConfiguration {
/**
@@ -44,33 +46,40 @@ public class HeaderFiltersConfiguration {
* @see #userSecurityHeadersProvider()
* @see #organizationSecurityHeadersProvider()
*/
- public @Bean AddSecHeadersGatewayFilterFactory addSecHeadersGatewayFilterFactory(
- List providers) {
+
+ @Bean
+ AddSecHeadersGatewayFilterFactory addSecHeadersGatewayFilterFactory(List providers) {
return new AddSecHeadersGatewayFilterFactory(providers);
}
- public @Bean CookieAffinityGatewayFilterFactory cookieAffinityGatewayFilterFactory() {
+ @Bean
+ CookieAffinityGatewayFilterFactory cookieAffinityGatewayFilterFactory() {
return new CookieAffinityGatewayFilterFactory();
}
- public @Bean ProxyGatewayFilterFactory proxyGatewayFilterFactory() {
+ @Bean
+ ProxyGatewayFilterFactory proxyGatewayFilterFactory() {
return new ProxyGatewayFilterFactory();
}
- public @Bean GeorchestraUserHeadersContributor userSecurityHeadersProvider() {
+ @Bean
+ GeorchestraUserHeadersContributor userSecurityHeadersProvider() {
return new GeorchestraUserHeadersContributor();
}
- public @Bean SecProxyHeaderContributor secProxyHeaderProvider(GatewayConfigProperties configProps) {
+ @Bean
+ SecProxyHeaderContributor secProxyHeaderProvider(GatewayConfigProperties configProps) {
BooleanSupplier secProxyEnabledSupplier = () -> configProps.getDefaultHeaders().getProxy().orElse(false);
return new SecProxyHeaderContributor(secProxyEnabledSupplier);
}
- public @Bean GeorchestraOrganizationHeadersContributor organizationSecurityHeadersProvider() {
+ @Bean
+ GeorchestraOrganizationHeadersContributor organizationSecurityHeadersProvider() {
return new GeorchestraOrganizationHeadersContributor();
}
- public @Bean JsonPayloadHeadersContributor jsonPayloadHeadersContributor() {
+ @Bean
+ JsonPayloadHeadersContributor jsonPayloadHeadersContributor() {
return new JsonPayloadHeadersContributor();
}
@@ -78,7 +87,8 @@ public class HeaderFiltersConfiguration {
* General purpose {@link GatewayFilterFactory} to remove incoming HTTP request
* headers based on a Java regular expression
*/
- public @Bean RemoveHeadersGatewayFilterFactory removeHeadersGatewayFilterFactory() {
+ @Bean
+ RemoveHeadersGatewayFilterFactory removeHeadersGatewayFilterFactory() {
return new RemoveHeadersGatewayFilterFactory();
}
@@ -86,7 +96,8 @@ public class HeaderFiltersConfiguration {
* {@link GatewayFilterFactory} to remove incoming HTTP {@literal sec-*} HTTP
* request headers to prevent impersonation from outside
*/
- public @Bean RemoveSecurityHeadersGatewayFilterFactory removeSecurityHeadersGatewayFilterFactory() {
+ @Bean
+ RemoveSecurityHeadersGatewayFilterFactory removeSecurityHeadersGatewayFilterFactory() {
return new RemoveSecurityHeadersGatewayFilterFactory();
}
}
diff --git a/gateway/src/main/java/org/georchestra/gateway/filter/headers/ProxyGatewayFilterFactory.java b/gateway/src/main/java/org/georchestra/gateway/filter/headers/ProxyGatewayFilterFactory.java
index 8b44dc00..43f1f06e 100644
--- a/gateway/src/main/java/org/georchestra/gateway/filter/headers/ProxyGatewayFilterFactory.java
+++ b/gateway/src/main/java/org/georchestra/gateway/filter/headers/ProxyGatewayFilterFactory.java
@@ -1,14 +1,14 @@
package org.georchestra.gateway.filter.headers;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
-import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
+import org.springframework.http.server.reactive.ServerHttpRequest;
public class ProxyGatewayFilterFactory extends AbstractGatewayFilterFactory