Skip to content

Commit

Permalink
Inform admins when new oauth2 account is created using spring rabbitm…
Browse files Browse the repository at this point in the history
…q events
  • Loading branch information
marwanehcine committed Sep 13, 2023
1 parent 90507cb commit c56c0f0
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 5 deletions.
15 changes: 15 additions & 0 deletions gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
<dependency>
<!-- Annotation processor that generates metadata about classes annotated with @ConfigurationProperties. -->
<!-- This metadata is used by IDEs to provide auto-completion and documentation for the properties when editing application.properties
Expand Down Expand Up @@ -110,6 +118,13 @@
<artifactId>ldaptive-netscape</artifactId>
<version>1.0</version>
</dependency>
<!-- org.json dependency added to send and receive data
over rabbitmq event as json object stringified.-->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230618</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String> synReceivedMessageUid = Collections.synchronizedSet(new HashSet<String>());

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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class GatewaySecurityConfiguration {
* {@link ServerHttpSecurity#build build} the {@link SecurityWebFilterChain}.
*/

@Autowired
@Autowired(required = false)
ServerLogoutSuccessHandler oidcLogoutSuccessHandler;

@Bean
Expand All @@ -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<ServerHttpSecurityCustomizer> sortedCustomizers(List<ServerHttpSecurityCustomizer> customizers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public class LdapConfigProperties implements Validator {

private boolean createNonExistingUsersInLDAP = true;

private boolean enableRabbitmqEvents = true;

@Valid
private Map<String, Server> ldap = Map.of();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<OAuth2AuthenticationToken> tokenFilter() {
Expand All @@ -165,6 +169,7 @@ public class OpenIdConnectUserMapper extends OAuth2UserMapper {
}

protected @Override Optional<GeorchestraUser> map(OAuth2AuthenticationToken token) {

if (config.isCreateNonExistingUsersInLDAP()) {
String oAuth2ProviderId = String.format("%s;%s", token.getAuthorizedClientRegistrationId(),
token.getName());
Expand All @@ -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);
}
}

Expand Down
3 changes: 2 additions & 1 deletion gateway/src/main/resources/META-INF/spring.factories
Original file line number Diff line number Diff line change
Expand Up @@ -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
org.georchestra.gateway.autoconfigure.app.RoutePredicateFactoriesAutoConfiguration,\
org.georchestra.gateway.events.RabbitmqEventsAutoConfiguration
1 change: 1 addition & 0 deletions gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions gateway/src/main/resources/rabbit-listener-context.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
<rabbit:connection-factory id="connectionFactory" host="${rabbitmqHost}" username="${rabbitmqUser}" password="${rabbitmqPassword}" />
<rabbit:admin connection-factory="connectionFactory" />
<!-- Create OAuth2Queue queue -->
<rabbit:queue id="OAuth2ReplyQueue" />
<!-- create OAuth2Exchange and bind OAuth2Queue with routing-gateway to the OAUTH2-EXCHANGE-->
<rabbit:topic-exchange id="OAuth2Exchange" name="OAUTH2-EXCHANGE-GATEWAY">
<rabbit:bindings>
<rabbit:binding queue="OAuth2ReplyQueue" pattern="routing-console"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- instantiate eventsListener -->
<bean id="eventsListener" class="org.georchestra.gateway.events.RabbitmqEventsListener" >
</bean>
<!-- glue the listener and OAuth2Queue to the container-->
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener ref="eventsListener" queues="OAuth2ReplyQueue" /></rabbit:listener-container>
</beans>
11 changes: 11 additions & 0 deletions gateway/src/main/resources/rabbit-sender-context.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
<!-- obtain admin rights to create the an exchange -->
<rabbit:admin connection-factory="connectionFactory" />

<!-- create a bean which can send message to OAUTH2-EXCHANGE for the program to call -->
<rabbit:template id="eventTemplate" connection-factory="connectionFactory" exchange="OAUTH2-EXCHANGE"/>
</beans>

0 comments on commit c56c0f0

Please sign in to comment.