diff --git a/gateway/pom.xml b/gateway/pom.xml index 959bcbeb..1460d4e4 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -66,6 +66,14 @@ lombok true + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.amqp + spring-rabbit + + + org.json + json + 20230618 + 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 f215b719..4b08b342 100644 --- a/gateway/src/main/java/org/georchestra/gateway/app/GeorchestraGatewayApplication.java +++ b/gateway/src/main/java/org/georchestra/gateway/app/GeorchestraGatewayApplication.java @@ -42,7 +42,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.reactive.result.view.Rendering; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; diff --git a/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java new file mode 100644 index 00000000..5d423ff5 --- /dev/null +++ b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsAutoConfiguration.java @@ -0,0 +1,37 @@ +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.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" }) +@ConditionalOnExpression("${georchestra.gateway.security.enableRabbitmqEvents:true}") +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/RabbitmqEventsListener.java b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsListener.java new file mode 100644 index 00000000..bb2f5b39 --- /dev/null +++ b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsListener.java @@ -0,0 +1,31 @@ +package org.georchestra.gateway.events; + +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; + +@Slf4j(topic = "org.georchestra.gateway.events") +public class RabbitmqEventsListener implements MessageListener { + + public static final String OAUTH2_ACCOUNT_CREATION_RECEIVED = "OAUTH2-ACCOUNT-CREATION-RECEIVED"; + + private static Set synReceivedMessageUid = Collections.synchronizedSet(new HashSet()); + + public void onMessage(Message message) { + String messageBody = new String(message.getBody()); + JSONObject jsonObj = new JSONObject(messageBody); + String uid = jsonObj.getString("uid"); + String subject = jsonObj.getString("subject"); + if (subject.equals(OAUTH2_ACCOUNT_CREATION_RECEIVED) + && !synReceivedMessageUid.stream().anyMatch(s -> s.equals(uid))) { + String msg = jsonObj.getString("msg"); + synReceivedMessageUid.add(uid); + log.info(msg); + } + } +} \ 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 new file mode 100644 index 00000000..7e46a787 --- /dev/null +++ b/gateway/src/main/java/org/georchestra/gateway/events/RabbitmqEventsSender.java @@ -0,0 +1,35 @@ +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/security/GatewaySecurityConfiguration.java b/gateway/src/main/java/org/georchestra/gateway/security/GatewaySecurityConfiguration.java index e636f15f..14940e35 100644 --- a/gateway/src/main/java/org/georchestra/gateway/security/GatewaySecurityConfiguration.java +++ b/gateway/src/main/java/org/georchestra/gateway/security/GatewaySecurityConfiguration.java @@ -58,7 +58,7 @@ public class GatewaySecurityConfiguration { * {@link ServerHttpSecurity#build build} the {@link SecurityWebFilterChain}. */ - @Autowired + @Autowired(required = false) ServerLogoutSuccessHandler oidcLogoutSuccessHandler; @Bean @@ -82,8 +82,12 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, log.info("Security filter chain initialized"); - return http.formLogin().loginPage("/login").and().logout().logoutUrl("/logout") - .logoutSuccessHandler(oidcLogoutSuccessHandler).and().build(); + if (oidcLogoutSuccessHandler != null) { + return http.formLogin().loginPage("/login").and().logout().logoutUrl("/logout") + .logoutSuccessHandler(oidcLogoutSuccessHandler).and().build(); + } else { + return http.formLogin().loginPage("/login").and().logout().logoutUrl("/logout").and().build(); + } } private Stream sortedCustomizers(List customizers) { diff --git a/gateway/src/main/java/org/georchestra/gateway/security/ldap/LdapConfigProperties.java b/gateway/src/main/java/org/georchestra/gateway/security/ldap/LdapConfigProperties.java index 9c817ab0..a3159f0f 100644 --- a/gateway/src/main/java/org/georchestra/gateway/security/ldap/LdapConfigProperties.java +++ b/gateway/src/main/java/org/georchestra/gateway/security/ldap/LdapConfigProperties.java @@ -65,6 +65,8 @@ public class LdapConfigProperties implements Validator { private boolean createNonExistingUsersInLDAP = true; + private boolean enableRabbitmqEvents = true; + @Valid private Map ldap = Map.of(); diff --git a/gateway/src/main/java/org/georchestra/gateway/security/oauth2/OpenIdConnectUserMapper.java b/gateway/src/main/java/org/georchestra/gateway/security/oauth2/OpenIdConnectUserMapper.java index 8e1dc768..646fe2fb 100644 --- a/gateway/src/main/java/org/georchestra/gateway/security/oauth2/OpenIdConnectUserMapper.java +++ b/gateway/src/main/java/org/georchestra/gateway/security/oauth2/OpenIdConnectUserMapper.java @@ -35,6 +35,7 @@ import org.georchestra.ds.security.UserMapperImpl; import org.georchestra.ds.security.UsersApiImpl; import org.georchestra.ds.users.*; +import org.georchestra.gateway.events.RabbitmqEventsSender; import org.georchestra.gateway.security.ldap.LdapConfigProperties; import org.georchestra.security.model.GeorchestraUser; import org.slf4j.Logger; @@ -153,6 +154,9 @@ public class OpenIdConnectUserMapper extends OAuth2UserMapper { @Autowired(required = false) private RoleDao roleDao; + @Autowired(required = false) + private RabbitmqEventsSender eventsSender; + private final @NonNull OpenIdConnectCustomClaimsConfigProperties nonStandardClaimsConfig; protected @Override Predicate tokenFilter() { @@ -165,6 +169,7 @@ public class OpenIdConnectUserMapper extends OAuth2UserMapper { } protected @Override Optional map(OAuth2AuthenticationToken token) { + if (config.isCreateNonExistingUsersInLDAP()) { String oAuth2ProviderId = String.format("%s;%s", token.getAuthorizedClientRegistrationId(), token.getName()); @@ -190,12 +195,19 @@ public class OpenIdConnectUserMapper extends OAuth2UserMapper { accountDao.insert(newAccount); roleDao.addUser(Role.USER, newAccount); userOpt = usersApi.findByOAuth2ProviderId(oAuth2ProviderId); + if (config.isEnableRabbitmqEvents() && eventsSender != null) { + eventsSender.sendNewOAuthAccountMessage( + oidcUser.getGivenName() + " " + oidcUser.getFamilyName(), oidcUser.getEmail(), + token.getAuthorizedClientRegistrationId()); + } } catch (DuplicatedUidException e) { throw new IllegalStateException(e); } catch (DuplicatedEmailException e) { throw new IllegalStateException(e); } catch (DataServiceException e) { throw new IllegalStateException(e); + } catch (Exception e) { + throw new RuntimeException(e); } } diff --git a/gateway/src/main/resources/META-INF/spring.factories b/gateway/src/main/resources/META-INF/spring.factories index 95c56c86..156d9d89 100644 --- a/gateway/src/main/resources/META-INF/spring.factories +++ b/gateway/src/main/resources/META-INF/spring.factories @@ -4,4 +4,5 @@ org.georchestra.gateway.autoconfigure.security.WebSecurityAutoConfiguration,\ org.georchestra.gateway.autoconfigure.security.LdapSecurityAutoConfiguration,\ org.georchestra.gateway.autoconfigure.security.OAuth2SecurityAutoConfiguration,\ org.georchestra.gateway.autoconfigure.app.FiltersAutoConfiguration,\ -org.georchestra.gateway.autoconfigure.app.RoutePredicateFactoriesAutoConfiguration \ No newline at end of file +org.georchestra.gateway.autoconfigure.app.RoutePredicateFactoriesAutoConfiguration,\ +org.georchestra.gateway.events.RabbitmqEventsAutoConfiguration \ No newline at end of file diff --git a/gateway/src/main/resources/application.yml b/gateway/src/main/resources/application.yml index 38b8e450..f8b964f1 100644 --- a/gateway/src/main/resources/application.yml +++ b/gateway/src/main/resources/application.yml @@ -133,6 +133,7 @@ logging: '[org.georchestra.gateway.config.security.accessrules]': debug '[org.georchestra.gateway.security.ldap]': debug '[org.georchestra.gateway.security.oauth2]': debug + '[org.georchestra.gateway.events]': debug --- spring.config.activate.on-profile: dev diff --git a/gateway/src/main/resources/rabbit-listener-context.xml b/gateway/src/main/resources/rabbit-listener-context.xml new file mode 100644 index 00000000..78183e3b --- /dev/null +++ b/gateway/src/main/resources/rabbit-listener-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gateway/src/main/resources/rabbit-sender-context.xml b/gateway/src/main/resources/rabbit-sender-context.xml new file mode 100644 index 00000000..dc368c9d --- /dev/null +++ b/gateway/src/main/resources/rabbit-sender-context.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file