From 8de54bba4bfcf83dd83b75fb2f66138466f1e685 Mon Sep 17 00:00:00 2001 From: marwanehcine Date: Wed, 15 Nov 2023 00:19:00 +0100 Subject: [PATCH] fix rabbitmq event sending process after code refactor --- gateway/pom.xml | 6 + .../admin/AbstractAccountsManager.java | 5 +- .../admin/ldap/LdapAccountsManager.java | 7 +- .../RabbitmqAccountCreatedEventSender.java | 3 +- .../rabbitmq/SendMessageRabbitmqIT.java | 124 ++++++++++++ .../test/resources/application-rabbitmq.yml | 65 ++++++ .../test-datadir/console/console.properties | 186 ++++++++++++++++++ ...w-oauth2-account-notification-template.txt | 15 ++ .../resources/test-datadir/default.properties | 9 + 9 files changed, 413 insertions(+), 7 deletions(-) create mode 100644 gateway/src/test/java/org/georchestra/gateway/rabbitmq/SendMessageRabbitmqIT.java create mode 100644 gateway/src/test/resources/application-rabbitmq.yml create mode 100644 gateway/src/test/resources/test-datadir/console/console.properties create mode 100644 gateway/src/test/resources/test-datadir/console/templates/new-oauth2-account-notification-template.txt 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 +