diff --git a/gateway/pom.xml b/gateway/pom.xml
index 5300af6f..d20c9fe8 100644
--- a/gateway/pom.xml
+++ b/gateway/pom.xml
@@ -125,6 +125,12 @@
json
20230618
+
+ org.testcontainers
+ rabbitmq
+ 1.19.3
+ test
+
org.georchestra
georchestra-testcontainers
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
index e341325b..48d86430 100644
--- a/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AbstractAccountsManager.java
+++ b/gateway/src/main/java/org/georchestra/gateway/accounts/admin/AbstractAccountsManager.java
@@ -27,11 +27,12 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
+import org.springframework.context.ApplicationEventPublisher;
@RequiredArgsConstructor
public abstract class AbstractAccountsManager implements AccountManager {
- private final @NonNull Consumer eventPublisher;
+ private final @NonNull ApplicationEventPublisher eventPublisher;
protected final ReadWriteLock lock = new ReentrantReadWriteLock();
@@ -64,7 +65,7 @@ GeorchestraUser createIfMissing(GeorchestraUser mapped) {
createInternal(mapped);
existing = findInternal(mapped).orElseThrow(() -> new IllegalStateException(
"User " + mapped.getUsername() + " not found right after creation"));
- eventPublisher.accept(new AccountCreated(existing));
+ eventPublisher.publishEvent(new AccountCreated(existing));
}
return existing;
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
index 35f8393f..787e1f8e 100644
--- 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
@@ -22,7 +22,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
@@ -37,12 +36,12 @@
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.AbstractAccountsManager;;
import org.georchestra.gateway.accounts.admin.AccountManager;
import org.georchestra.security.api.UsersApi;
import org.georchestra.security.model.GeorchestraUser;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.ldap.NameNotFoundException;
import lombok.NonNull;
@@ -63,7 +62,7 @@ class LdapAccountsManager extends AbstractAccountsManager {
private final @NonNull OrgsDao orgsDao;
private final @NonNull UsersApi usersApi;
- public LdapAccountsManager(Consumer eventPublisher, AccountDao accountDao, RoleDao roleDao,
+ public LdapAccountsManager(ApplicationEventPublisher eventPublisher, AccountDao accountDao, RoleDao roleDao,
OrgsDao orgsDao, UsersApi usersApi) {
super(eventPublisher);
this.accountDao = accountDao;
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
index 4ac349ca..e78d7191 100644
--- 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
@@ -31,6 +31,7 @@
* distributed event through rabbitmq to the {@literal OAUTH2-ACCOUNT-CREATION}
* queue.
*/
+
public class RabbitmqAccountCreatedEventSender {
public static final String OAUTH2_ACCOUNT_CREATION = "OAUTH2-ACCOUNT-CREATION";
@@ -41,7 +42,7 @@ public RabbitmqAccountCreatedEventSender(AmqpTemplate eventTemplate) {
this.eventTemplate = eventTemplate;
}
- @EventListener(AccountCreated.class)
+ @EventListener
public void on(AccountCreated event) {
GeorchestraUser user = event.getUser();
final String oAuth2Provider = user.getOAuth2Provider();
diff --git a/gateway/src/test/java/org/georchestra/gateway/rabbitmq/SendMessageRabbitmqIT.java b/gateway/src/test/java/org/georchestra/gateway/rabbitmq/SendMessageRabbitmqIT.java
new file mode 100644
index 00000000..c27b4532
--- /dev/null
+++ b/gateway/src/test/java/org/georchestra/gateway/rabbitmq/SendMessageRabbitmqIT.java
@@ -0,0 +1,124 @@
+package org.georchestra.gateway.rabbitmq;
+
+import org.geonetwork.testcontainers.postgres.GeorchestraDatabaseContainer;
+import org.georchestra.ds.orgs.OrgsDao;
+import org.georchestra.ds.users.AccountDao;
+import org.georchestra.gateway.accounts.admin.AccountCreated;
+import org.georchestra.gateway.accounts.events.rabbitmq.RabbitmqAccountCreatedEventSender;
+import org.georchestra.gateway.app.GeorchestraGatewayApplication;
+import org.georchestra.security.model.GeorchestraUser;
+import org.georchestra.testcontainers.console.GeorchestraConsoleContainer;
+import org.georchestra.testcontainers.ldap.GeorchestraLdapContainer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.TestPropertySource;
+import org.testcontainers.Testcontainers;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.RabbitMQContainer;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+/**
+ * Integration tests for {@link RabbitmqAccountCreatedEventSender}.
+ */
+@SpringBootTest(classes = GeorchestraGatewayApplication.class)
+@ActiveProfiles("rabbitmq")
+@ExtendWith(OutputCaptureExtension.class)
+@TestPropertySource(properties = { "enableRabbitmqEvents=true", //
+ "georchestra.datadir=src/test/resources/test-datadir"//
+})
+public class SendMessageRabbitmqIT {
+
+ private @Autowired ApplicationEventPublisher eventPublisher;
+ private @Autowired ApplicationContext context;
+ private @Autowired RabbitmqAccountCreatedEventSender sender;
+ private @Autowired AccountDao accountDao;
+ private @Autowired OrgsDao orgsDao;
+ public static int rabbitmqPort = 5672;
+ public static int smtpPort = 25;
+
+ public static GeorchestraLdapContainer ldap = new GeorchestraLdapContainer();
+ public static GeorchestraDatabaseContainer db = new GeorchestraDatabaseContainer();
+ public static RabbitMQContainer rabbitmq = new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.12"))
+ .withExposedPorts(rabbitmqPort);
+ public static GenericContainer> smtp = new GenericContainer<>("camptocamp/smtp-sink:latest")
+ .withExposedPorts(smtpPort);
+ public static GeorchestraConsoleContainer console;
+
+ public static @BeforeAll void startUpContainers() {
+ db.start();
+ ldap.start();
+ smtp.start();
+ rabbitmq.start();
+
+ Testcontainers.exposeHostPorts(ldap.getMappedLdapPort(), db.getMappedDatabasePort(),
+ rabbitmq.getMappedPort(rabbitmqPort), smtp.getMappedPort(smtpPort));
+ System.setProperty("georchestra.gateway.security.events.rabbitmq.host", "localhost");
+ System.setProperty("georchestra.gateway.security.events.rabbitmq.port",
+ String.valueOf(rabbitmq.getMappedPort(rabbitmqPort)));
+
+ console = new GeorchestraConsoleContainer()//
+ .withCopyFileToContainer(MountableFile.forClasspathResource("test-datadir"), "/etc/georchestra")//
+ .withEnv("enableRabbitmqEvents", "true").withEnv("pgsqlHost", "host.testcontainers.internal")//
+ .withEnv("pgsqlPort", String.valueOf(db.getMappedDatabasePort()))//
+ .withEnv("ldapHost", "host.testcontainers.internal")//
+ .withEnv("ldapPort", String.valueOf(ldap.getMappedLdapPort()))//
+ .withEnv("rabbitmqHost", "host.testcontainers.internal")//
+ .withEnv("rabbitmqPort", String.valueOf(rabbitmq.getMappedPort(rabbitmqPort)))//
+ .withEnv("rabbitmqUser", "guest")//
+ .withEnv("rabbitmqPassword", "guest")//
+ .withEnv("smtpHost", "host.testcontainers.internal")//
+ .withEnv("smtpPort", String.valueOf(smtp.getMappedPort(smtpPort)))//
+ .withLogToStdOut();
+
+ console.start();
+ System.setProperty("georchestra.console.url",
+ String.format("http://localhost:%d", console.getMappedConsolePort()));
+ }
+
+ public static @AfterAll void shutDownContainers() {
+ console.stop();
+ ldap.stop();
+ db.stop();
+ smtp.stop();
+ }
+
+ public @Test void testReceivingMessageFromConsole(CapturedOutput output) throws Exception {
+ assertNotNull(sender);
+ GeorchestraUser user = new GeorchestraUser();
+ user.setId(UUID.randomUUID().toString());
+ user.setLastUpdated("anystringwoulddo");
+ user.setUsername("testadmin");
+ user.setEmail("testadmin@georchestra.org");
+ user.setFirstName("John");
+ user.setLastName("Doe");
+ user.setRoles(Arrays.asList("ADMINISTRATOR", "GN_ADMIN"));
+ user.setTelephoneNumber("341444111");
+ user.setTitle("developer");
+ user.setNotes("user notes");
+ user.setPostalAddress("123 java street");
+ user.setOrganization("PSC");
+ user.setOAuth2Provider("testProvider");
+ user.setOAuth2Uid("123");
+ eventPublisher.publishEvent(new AccountCreated(user));
+ await().atMost(30, TimeUnit.SECONDS).until(() -> {
+ return output.getOut().contains(
+ "new OAuth2 account creation notification for testadmin@georchestra.org has been received by console");
+ });
+ }
+}
diff --git a/gateway/src/test/resources/application-rabbitmq.yml b/gateway/src/test/resources/application-rabbitmq.yml
new file mode 100644
index 00000000..97a81554
--- /dev/null
+++ b/gateway/src/test/resources/application-rabbitmq.yml
@@ -0,0 +1,65 @@
+georchestra:
+ gateway:
+ default-headers:
+ # Default security headers to append to proxied requests
+ proxy: true
+ username: true
+ roles: true
+ org: true
+ orgname: true
+ global-access-rules:
+ - intercept-url:
+ - "/**"
+ - "/proxy/?url=*"
+ anonymous: true
+ security:
+ createNonExistingUsersInLDAP: true
+ oauth2.enabled: false
+ header-authentication:
+ enabled: true
+ ldap:
+ default:
+ enabled: true
+ extended: true
+ url: ldap://${ldapHost}:${ldapPort}/
+ baseDn: dc=georchestra,dc=org
+ adminDn: cn=admin,dc=georchestra,dc=org
+ adminPassword: secret
+ users:
+ rdn: ou=users
+ searchFilter: (uid={0})
+ pendingUsersSearchBaseDN: ou=pendingusers
+ protectedUsers: geoserver_privileged_user
+ roles:
+ rdn: ou=roles
+ searchFilter: (member={0})
+ protectedRoles: ADMINISTRATOR, EXTRACTORAPP, GN_.*, ORGADMIN, REFERENT, USER, SUPERUSER
+ orgs:
+ rdn: ou=orgs
+ orgTypes: Association,Company,NGO,Individual,Other
+ pendingOrgSearchBaseDN: ou=pendingorgs
+ events:
+ rabbitmq:
+ # Note usually enableRabbitmqEvents, rabbitmqHost, etc. come from georchestra's default.properties
+ enabled: true
+ host: ${rabbitmqHost}
+ port: ${rabbitmqPort}
+ user: guest
+ password: guest
+spring:
+ main:
+ web-application-type: reactive
+ banner-mode: off
+ application.name: gateway-service
+ cloud:
+ gateway:
+ enabled: true
+ default-filters:
+ - SecureHeaders
+ - TokenRelay
+ - RemoveSecurityHeaders
+ # AddSecHeaders appends sec-* headers to proxied requests based on the
+ # georchestra.gateway.default-headers and georchestra.gateway.servies..headers config properties
+ - AddSecHeaders
+ httpclient.wiretap: true
+ httpserver.wiretap: false
diff --git a/gateway/src/test/resources/test-datadir/console/console.properties b/gateway/src/test/resources/test-datadir/console/console.properties
new file mode 100644
index 00000000..df94ae8f
--- /dev/null
+++ b/gateway/src/test/resources/test-datadir/console/console.properties
@@ -0,0 +1,186 @@
+# set by GeorchestraDatabaseContainer as a System property,
+# so bad it looks like console doesn't resolve ${} placeholders
+#pgsqlHost=${jdbc.host}
+# set by GeorchestraDatabaseContainer as a System property
+#pgsqlPort=${jdbc.port}
+pgsqlDatabase=georchestra
+pgsqlUser=georchestra
+pgsqlPassword=georchestra
+
+# set by GeorchestraDatabaseContainer as a System property
+#ldapHost=localhost
+# set by GeorchestraDatabaseContainer as a System property
+#ldapPort=389
+
+#ldap.pool.testOnBorrow=true
+#ldap.pool.maxActive=8
+#ldap.pool.minIdle=1
+#ldap.pool.maxIdle=8
+#ldap.pool.maxTotal=-1
+#ldap.pool.maxWait=-1
+
+#ldapBaseDn=
+#ldapAdminDn=
+#ldapAdminPassword=
+#ldapUsersRdn=
+#ldapRolesRdn=
+#ldapOrgsRdn=
+#smtpHost=
+#smtpPort=
+
+# Org type values is used to populate the drop down list from /console/account/new
+# default: Association,Company,NGO,Individual,Other
+orgTypeValues=Association,Company,NGO,Individual,Other
+# Areas map configuration
+# This map appears on the /console/account/new page, when the user checks the "my org does not exist" checkbox.
+# Currently the map is configured with the EPSG:4326 SRS.
+# Center of map
+AreaMapCenter=9.3707, 42.0753
+
+# Zoom of map
+AreaMapZoom=7
+
+# AreasUrl is the URL of a static geojson file in the current folder, which
+# provides the basic geometries used to build up organization's areas.
+# Also accepts an URL, which can be a static file or a WFS request.
+# MUST provide a GeoJSON FeatureCollection with the EPSG:4326 SRS.
+# example "dynamic" AreasUrl=https://my.server.org/geoserver/ows?SERVICE=WFS&REQUEST=GetFeature&typeName=gadm:gadm_for_countries&outputFormat=json&srs=EPSG:4326&cql_filter=ISO='FRA' or ISO='BEL'
+AreasUrl=cities.geojson
+
+# The following properties are used to configure the map widget behavior:
+# AreasKey is the key stored in the org LDAP record to uniquely identify a feature.
+AreasKey=INSEE_COM
+
+# AreasValue is the feature "nice name" which appears in the widget list once selected, and in the search result as well.
+AreasValue=NOM_COM_M
+
+# AreasGroup is the feature property which is used to group together areas.
+# eg: if the GeoJSON file represents regions, then AreasGroup might be the property with the "state name".
+# CAUTION: AreasGroup **has to** be a string, not a numeric !
+AreasGroup=INSEE_DEP
+
+# LDAP organizational units
+
+# Pending users
+# default: ou=pendingusers
+#pendingUserSearchBaseDN=ou=pendingusers
+
+# Pending organizations
+# default: ou=pendingorgs
+#pendingOrgSearchBaseDN=ou=pendingorgs
+
+
+# PostgreSQL database connection parameters
+
+# Minimum connections pool size
+# default: 2
+#dataSource.minPoolSize = 2
+
+# Maximum connections pool size
+# default: 10
+#dataSource.maxPoolSize = 10
+
+# Acquire connection timeout (in ms for c3p0)
+# default: 1000
+#dataSource.timeout = 1000
+
+# Max time unused connections are kept idle in the pool. Unit is seconds for c3p0.
+# default: 60
+#dataSource.maxIdleTime=60
+
+# Email-related properties
+
+# Send emails in HTML format
+# default: false
+#emailHtml=false
+
+# Reply-To field in sent emails
+# default: ${administratorEmail}
+#replyTo=${administratorEmail}
+
+# From field in sent emails
+# default: ${administratorEmail}
+#from=${administratorEmail}
+
+# Subject of email when your account has been created
+# default: [${instanceName}] Your account has been created
+#subject.account.created=[${instanceName}] Your account has been created
+
+# Subject of email when your account creation is waiting for validation
+# default: [${instanceName}] Your new account is waiting for validation
+#subject.account.in.process=[${instanceName}] Your new account is waiting for validation
+
+# Subject of email for moderator at account creation
+# default: [${instanceName}] New account waiting for validation
+#subject.requires.moderation=[${instanceName}] New account waiting for validation
+
+# Subject of email for password change
+# default: [${instanceName}] Update your password
+#subject.change.password=[${instanceName}] Update your password
+
+# Subject of email for login change
+# default: [${instanceName}] New login for your account
+#subject.account.uid.renamed=[${instanceName}] New login for your account
+
+# Subject of email when a new account has been created
+# default: [${instanceName}] New account created
+#subject.new.account.notification=[${instanceName}] New account created
+
+# Encoding of the email templates
+# This "é" char should display nicely in a ISO 8859-1 configured editor
+# default: UTF-8
+#templateEncoding=UTF-8
+
+# Warn a user if their login has been modified
+# default: true
+#warnUserIfUidModified=true
+
+
+# Email proxy configuration
+# Basically, this webapp can send emails on behalf of LDAP users.
+# The service endpoint is available at /console/emailProxy
+# Usage is restricted to users having the EMAILPROXY role by default,
+# cf https://github.com/georchestra/datadir/blob/master/security-proxy/security-mappings.xml
+# see https://github.com/georchestra/georchestra/pull/1572 for more information.
+# The following restrictions have been implemented to prevent spammers.
+
+# From field in sent emails
+# default: ${administratorEmail}
+#emailProxyFromAddress=${administratorEmail}
+
+# Maximum number of recipients
+# default: 10
+#emailProxyMaxRecipient=10
+
+# Maximum email body size
+# default: 10000
+#emailProxyMaxBodySize=10000
+
+# Maximum email subject size
+# 200
+#emailProxyMaxSubjectSize=200
+
+# Comma-separated list of allowed recipients of emails
+# For example: psc@georchestra.org, postmaster@georchestra.org, listmaster@georchestra.org
+# default: ${administratorEmail}
+#emailProxyRecipientWhitelist=${administratorEmail}
+
+# Activates SASL
+# if set to true, the console will leave the possibility to the administrator
+# to set a user to cascade the authentication to another system.
+# See https://github.com/georchestra/georchestra/blob/master/docs/tutorials/sasl.md#remote-adldap-authentication-with-sasl
+# for more info on how to configure your OpenLDAP to cascade authentication to another LDAP-aware system.
+# default: false
+#saslEnabled=false
+
+# name of the remote SASL server
+# This option is purely informative, and give hints to the administrator on which server the authentication will take place
+# in case of the previous option is activated.
+# As all the SASL configuration is made outside of geOrchestra, setting this property won't have influence on the
+# server which will be actually queried for authentication.
+# default: null
+#saslServer=null
+
+# Activates or disable GDPR-related endpoints
+# default: true
+#gdpr.allowAccountDeletion=true
diff --git a/gateway/src/test/resources/test-datadir/console/templates/new-oauth2-account-notification-template.txt b/gateway/src/test/resources/test-datadir/console/templates/new-oauth2-account-notification-template.txt
new file mode 100644
index 00000000..0977a949
--- /dev/null
+++ b/gateway/src/test/resources/test-datadir/console/templates/new-oauth2-account-notification-template.txt
@@ -0,0 +1,15 @@
+Dear admin,
+
+A new OAuth 2 user signed up on {instanceName} !
+
+Name: {name}
+Email: {email}
+User ID: {uid}
+Organization: {org}
+Identity Provider: {providerName}
+ID received from provider: {providerId}
+
+Manage account on {publicUrl}/console/manager/users/{uid}/infos.
+
+---
+Sent by {instanceName} ({publicUrl}/)
diff --git a/gateway/src/test/resources/test-datadir/default.properties b/gateway/src/test/resources/test-datadir/default.properties
index 4db60287..141180ce 100644
--- a/gateway/src/test/resources/test-datadir/default.properties
+++ b/gateway/src/test/resources/test-datadir/default.properties
@@ -26,3 +26,12 @@ ldapOrgsRdn=ou=orgs
### SMTP properties
smtpHost=smtp
smtpPort=25
+# rabbitmq server domain name
+rabbitmqHost=localhost
+# rabbitmq user
+rabbitmqUser=georchestra
+# rabbitmq password
+rabbitmqPassword=georchestra
+# rabbitmq port
+rabbitmqPort=5672
+