From 902ee20cd38741545a68c2d0c72790147df93d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Sat, 16 Mar 2024 10:49:08 +0100 Subject: [PATCH] Support multiple installations, provide JWT via environment --- .gitignore | 1 - README.md | 28 ++++++--- .../multisite/AntragsgruenInstallation.java | 12 ++++ .../AntragsgruenInstallationProvider.java | 62 +++++++++++++++++++ .../AntragsgruenJwtDecoder.java | 33 +++------- .../InstallationNotFoundException.java | 7 +++ .../live/multisite/package-info.java | 6 ++ .../WebsocketChannelInterceptor.java | 41 +++++++++--- src/main/resources/application-test.yml | 6 +- src/main/resources/application.yml | 1 - src/main/resources/resource-config.json | 3 - .../antragsgruen/live/SpeechAdminTests.java | 4 +- .../de/antragsgruen/live/SpeechUserTests.java | 10 +-- .../live/utils/StompTestConnection.java | 7 ++- src/test/resources/jwt-test-public.key | 14 ----- 15 files changed, 162 insertions(+), 73 deletions(-) create mode 100644 src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallation.java create mode 100644 src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallationProvider.java rename src/main/java/de/antragsgruen/live/{websocket => multisite}/AntragsgruenJwtDecoder.java (53%) create mode 100644 src/main/java/de/antragsgruen/live/multisite/InstallationNotFoundException.java create mode 100644 src/main/java/de/antragsgruen/live/multisite/package-info.java delete mode 100644 src/test/resources/jwt-test-public.key diff --git a/.gitignore b/.gitignore index 9dfaf39..e128394 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ -src/main/resources/public.key src/main/resources/static/stomp.umd.min.js node_modules/ diff --git a/README.md b/README.md index c18858c..973c122 100644 --- a/README.md +++ b/README.md @@ -21,22 +21,30 @@ Users are connecting to the Live Server via Websocket/STOMP when using an intera - When subscribing to the speech admin topic, the SPEECH_ADMIN role is checked in the JWT. - SECURITY DISCLAIMER: the expiry date of the token is currently only checked when connecting. As long as the session is open, no expiry mechanism is in place, so revoking a user's access only has effect once that user reconnects. +## ID Mapping + +The IDs referred to by this service can be found at the following places in the central Antragsgrün system: + +- Installation ID: This is the ID specified as `live.installationId` in `config.json`. One Installation ID can hold either a single- or multi-site installation. +- Site: The subdomain used by a multi-site installation (field `subdomain` in the `site` database table). For single-site installations, this is typically `std`. +- Consultation: The URL path component identifying the specific consultation within a site (field `urlPath` in the `consultation` database table). +- User ID: The numerical ID of the user (field `id` in the `user` database table). ## RabbitMQ Setup The central Antragsgrün system publishes all its messages to one central exchange (by default: `antragsgruen-exchange`). Messages to all subdomains and consultations within a subdomain are published through that exchange, but are classified by a routing key pattern. The following routing key patterns are fixed, while its associated queues can be configured: -- `user.[site].[consultation].[userid]`, e.g. `user.stdparteitag.std-parteitag.1` contains messages directed to one particular user, by default being bound to the queue `antragsgruen-user-queue` and using the [MQUserEvent](src/main/java/de/antragsgruen/live/rabbitmq/dto/MQUserEvent.java)-DTO for deserialization. -- `speech.[site].[consultation]`, e.g. `speech.stdparteitag.std-parteitag` contains messages updating a speech queue, by default being bound to the queue `antragsgruen-speech-queue` and using the [MQSpeechQueue](src/main/java/de/antragsgruen/live/rabbitmq/dto/MQSpeechQueue.java)-DTO for deserialization. All users in the consultation receive this event, but in a personalized version. +- `user.[installationid].[site].[consultation].[userid]`, e.g. `user.localdev.stdparteitag.std-parteitag.1` contains messages directed to one particular user, by default being bound to the queue `antragsgruen-user-queue` and using the [MQUserEvent](src/main/java/de/antragsgruen/live/rabbitmq/dto/MQUserEvent.java)-DTO for deserialization. +- `speech.[installationid].[site].[consultation]`, e.g. `speech.localdev.stdparteitag.std-parteitag` contains messages updating a speech queue, by default being bound to the queue `antragsgruen-speech-queue` and using the [MQSpeechQueue](src/main/java/de/antragsgruen/live/rabbitmq/dto/MQSpeechQueue.java)-DTO for deserialization. All users in the consultation receive this event, but in a personalized version. In case messages cannot be processed by this live server, they are rejected and, through the `antragsgruen-exchange-dead`, end up in the dead letter queues `antragsgruen-queue-speech-dead` and `antragsgruen-queue-user-dead`. ## Exposed Websocket STOMP Topics -- `/user/[subdomain]/[consultation]/[userid]/speech` -- `/admin/[subdomain]/[consultation]/[userid]/speech` -- `/topic/[subdomain]/[consultation]/[...]` (currently not used) +- `/user/[installationid]/[subdomain]/[consultation]/[userid]/speech` +- `/admin/[installationid]/[subdomain]/[consultation]/[userid]/speech` +- `/topic/[installationid]/[subdomain]/[consultation]/[...]` (currently not used) ## Installing, Running, Configuration @@ -44,7 +52,7 @@ In case messages cannot be processed by this live server, they are rejected and, ### Prerequisites Before building the app, two steps have to be manually performed: -- Creating a public/private RSA key for the JWT signing. This app only needs the public key, located at `src/main/resources/public.key`. If you are just testing, the keys from the test suite can be used (`cp src/test/resources/jwt-test-public.key src/main/resources/public.key`). +- Creating a public/private RSA key for the JWT signing. This app only needs the public key, passed into the application along with the installation ID as an environment variable. If you are just testing, the keys from the test suite can be used. - Installing Stomp.JS. This can be done by calling `npm ci`. After this step, the file `src/main/resources/static/stomp.umd.min.js` should exist. ### Running @@ -59,7 +67,7 @@ Hint: this is only meant for local development. On production, you want to secur ### Configuration via Environment Variables -The following aspects can be configured through environment variables, expecially valuable when deploying it via docker (compose): +The following aspects can be configured through environment variables, especially valuable when deploying it via docker (compose): | Environment Variable Name | Default Value | Explanation | | ------------------------- | ---------------- | ------------------------------------------------------------ | @@ -98,7 +106,7 @@ Compiling and running: ### Running with Docker (JRE) -A dummy docker-compose.yml is provided that builds and runs the application. Note that the the file `src/main/resources/public.key` mentioned in "Prerequisites" still needs to be created before building the docker images. +A dummy docker-compose.yml is provided that builds and runs the application. ```shell docker compose -f docker-compose.jdk.yml build @@ -107,10 +115,10 @@ docker compose -f docker-compose.jdk.yml up ## Testing -### Running spotbugs +### Running spotbugs && checkstyle ```shell -./mvnw compile && ./mvnw spotbugs:check +./mvnw compile && ./mvnw spotbugs:check && ./mvnw checkstyle:check ``` ### Running the integration tests diff --git a/src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallation.java b/src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallation.java new file mode 100644 index 0000000..1eec522 --- /dev/null +++ b/src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallation.java @@ -0,0 +1,12 @@ +package de.antragsgruen.live.multisite; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class AntragsgruenInstallation { + private final String installationId; + + private final AntragsgruenJwtDecoder jwtDecoder; +} diff --git a/src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallationProvider.java b/src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallationProvider.java new file mode 100644 index 0000000..8803d7d --- /dev/null +++ b/src/main/java/de/antragsgruen/live/multisite/AntragsgruenInstallationProvider.java @@ -0,0 +1,62 @@ +package de.antragsgruen.live.multisite; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.HashMap; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AntragsgruenInstallationProvider { + private final Environment environment; + private final HashMap installations = new HashMap<>(); + + public AntragsgruenInstallation getInstallation(String installationId) throws InstallationNotFoundException { + if (!installations.containsKey(installationId)) { + throw new InstallationNotFoundException("Invalid installation id: " + installationId); + } + + return installations.get(installationId); + } + + @PostConstruct + public void onInit() { + String installationId; + String publicKey; + int count = 0; + do { + installationId = environment.getProperty("antragsgruen.installations." + count + ".id"); + publicKey = environment.getProperty("antragsgruen.installations." + count + ".public-key"); + + if (installationId == null && publicKey == null) { + return; + } + if (installationId == null || publicKey == null || installationId.isEmpty() || publicKey.isEmpty()) { + throw new RuntimeException("ANTRAGSGRUEN_INSTALLATION_" + count + "_* is inconsistently filled"); + } + + try { + this.installations.put(installationId, AntragsgruenInstallationProvider.createInstallation(installationId, publicKey)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + log.info("Found installation ID: " + installationId); + + count++; + } while (true); + } + + private static AntragsgruenInstallation createInstallation(String installationId, String publicKey) + throws NoSuchAlgorithmException, InvalidKeySpecException { + return new AntragsgruenInstallation( + installationId, + AntragsgruenJwtDecoder.create(publicKey) + ); + } +} diff --git a/src/main/java/de/antragsgruen/live/websocket/AntragsgruenJwtDecoder.java b/src/main/java/de/antragsgruen/live/multisite/AntragsgruenJwtDecoder.java similarity index 53% rename from src/main/java/de/antragsgruen/live/websocket/AntragsgruenJwtDecoder.java rename to src/main/java/de/antragsgruen/live/multisite/AntragsgruenJwtDecoder.java index 194b98a..5b24454 100644 --- a/src/main/java/de/antragsgruen/live/websocket/AntragsgruenJwtDecoder.java +++ b/src/main/java/de/antragsgruen/live/multisite/AntragsgruenJwtDecoder.java @@ -1,20 +1,12 @@ -package de.antragsgruen.live.websocket; +package de.antragsgruen.live.multisite; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; import org.apache.tomcat.util.codec.binary.Base64; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.stereotype.Service; -import org.springframework.util.StreamUtils; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPublicKey; @@ -22,19 +14,12 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; -@Service -@Slf4j +@RequiredArgsConstructor public class AntragsgruenJwtDecoder { - @Value("classpath:${antragsgruen.jwt.key.public}") - private Resource publicKeyFilename; + private final JwtDecoder jwtDecoder; - private JwtDecoder jwtDecoder; - - @PostConstruct - public void loadPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { - InputStream is = this.publicKeyFilename.getInputStream(); - String publicKeyString = StreamUtils - .copyToString(is, StandardCharsets.UTF_8) + public static AntragsgruenJwtDecoder create(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException { + String publicKeyString = publicKey .replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PUBLIC KEY-----", ""); @@ -42,9 +27,11 @@ public void loadPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecExcep byte[] keyBytes = Base64.decodeBase64(publicKeyString); EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); - RSAPublicKey publicKey = (RSAPublicKey) kf.generatePublic(keySpec); + RSAPublicKey rsaPublicKey = (RSAPublicKey) kf.generatePublic(keySpec); - this.jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey).build(); + return new AntragsgruenJwtDecoder( + NimbusJwtDecoder.withPublicKey(rsaPublicKey).build() + ); } public JwtAuthenticationToken getJwtAuthToken(String token) { diff --git a/src/main/java/de/antragsgruen/live/multisite/InstallationNotFoundException.java b/src/main/java/de/antragsgruen/live/multisite/InstallationNotFoundException.java new file mode 100644 index 0000000..f8a39af --- /dev/null +++ b/src/main/java/de/antragsgruen/live/multisite/InstallationNotFoundException.java @@ -0,0 +1,7 @@ +package de.antragsgruen.live.multisite; + +public class InstallationNotFoundException extends Exception { + public InstallationNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/de/antragsgruen/live/multisite/package-info.java b/src/main/java/de/antragsgruen/live/multisite/package-info.java new file mode 100644 index 0000000..d369b9e --- /dev/null +++ b/src/main/java/de/antragsgruen/live/multisite/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package de.antragsgruen.live.multisite; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/src/main/java/de/antragsgruen/live/websocket/WebsocketChannelInterceptor.java b/src/main/java/de/antragsgruen/live/websocket/WebsocketChannelInterceptor.java index 4958909..1f5361f 100644 --- a/src/main/java/de/antragsgruen/live/websocket/WebsocketChannelInterceptor.java +++ b/src/main/java/de/antragsgruen/live/websocket/WebsocketChannelInterceptor.java @@ -1,5 +1,7 @@ package de.antragsgruen.live.websocket; +import de.antragsgruen.live.multisite.AntragsgruenInstallation; +import de.antragsgruen.live.multisite.AntragsgruenInstallationProvider; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,8 +22,8 @@ @RequiredArgsConstructor @Slf4j public final class WebsocketChannelInterceptor implements ChannelInterceptor { - @NonNull private AntragsgruenJwtDecoder jwtDecoder; - @NonNull private TopicPermissionChecker topicPermissionChecker; + @NonNull private final TopicPermissionChecker topicPermissionChecker; + @NonNull private final AntragsgruenInstallationProvider installationProvider; @Override public Message preSend(Message message, MessageChannel channel) throws MessagingException { @@ -48,18 +50,37 @@ public Message postReceive(Message message, MessageChannel channel) { return message; } - private void onConnect(Message message, StompHeaderAccessor headerAccessor) throws MessagingException { + private AntragsgruenInstallation onConnectGetInstallation(StompHeaderAccessor headerAccessor) throws Exception { + List jwtHeaders = headerAccessor.getNativeHeader("installation"); + jwtHeaders = Optional.ofNullable(jwtHeaders).orElse(new ArrayList<>()); + + for (String head: jwtHeaders) { + return this.installationProvider.getInstallation(head); + } + + throw new Exception("No installation header found"); + } + + private JwtAuthenticationToken onConnectGetAuthenticatedToken(AntragsgruenInstallation installation, StompHeaderAccessor headerAccessor) throws Exception { List jwtHeaders = headerAccessor.getNativeHeader("jwt"); jwtHeaders = Optional.ofNullable(jwtHeaders).orElse(new ArrayList<>()); for (String head: jwtHeaders) { - try { - JwtAuthenticationToken token = this.jwtDecoder.getJwtAuthToken(head); - headerAccessor.setUser(token); - log.info("Connected Websocket: " + token.getName()); - } catch (Exception e) { - throw new MessagingException("Could not authenticate JWT: " + e.getMessage()); - } + return installation.getJwtDecoder().getJwtAuthToken(head); + } + + throw new Exception("No jwt header found"); + } + + private void onConnect(Message message, StompHeaderAccessor headerAccessor) throws MessagingException { + try { + AntragsgruenInstallation installation = this.onConnectGetInstallation(headerAccessor); + JwtAuthenticationToken token = this.onConnectGetAuthenticatedToken(installation, headerAccessor); + headerAccessor.setUser(token); + log.info("Connected Websocket: " + token.getName()); + } catch (Exception e) { + log.warn("Could not authenticate JWT: " + e.getMessage()); + throw new MessagingException("Could not authenticate JWT: " + e.getMessage()); } } diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 929adb8..ce0cdf9 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -1,7 +1,6 @@ # For testing, a bundled JWT key set is used antragsgruen: jwt.key: - public: jwt-test-public.key private: jwt-test-private.key rabbitmq: exchange: @@ -12,6 +11,11 @@ antragsgruen: user_dead: ${RABBITMQ_QUEUE_SPEECH:antragsgruen-queue-test-user-dead} speech: ${RABBITMQ_QUEUE_SPEECH:antragsgruen-queue-test-speech} speech_dead: ${RABBITMQ_QUEUE_SPEECH:antragsgruen-queue-test-speech-dead} + installations: + - + id: test + public-key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArGCspSkkS4juW8XfmCVbkv+CRd2MUmEPJIqwWZd33E27FIRMDOqHp44hyxFTQoUy/pCUgVSgAtfuj1Cz1Vzj3dQWKjbWPKIzjiU7tZ/rok+PjJQsYV7m3wCizOk/2UNTnn9v/6aXmLZTetEEEFxHvVDF63/KJLu0abwW4PjATSNC4lAO0oGgPXbwkovIUkP3Vdj+kAhN72UC+lYafhAWY8gwpEPA0cdBBIl15JQZ/9gdoWbsmOMv8/6hv/kMHKWtJ52HHnMLshXEOKU7H0gunyh7MiJ0THeuJnlrQAYVyl4Lho2sS4ZWIKaDWmKDujyMfSI6CujBy9koR4KuXMp+PHemOWmMSAiirCZXf7t5r98fuPmSrZLp6A8L0612LEIjcrLQWqECn/NZ9823/0lMDBiJdyJBcNBe/QUSXEc2bTf/1vScbCBKcNTmlJykgZv00B0et01rscpThWcN4xneceUNXVla1X53bYXBG/8ZgiBzq7KqsTo285RuTG+lC82UPtove+R7wh4zHR4/gc8kvsHDkiAgCJr0FjQZyGcrDpEA2w8dTgHOkIOeGTh8r9AHJKdStyKQJy524RDtvopa7v8iibOM7FMCfUiJCKcGk0thdFqQ5tMEqk6KAVT57eYCBGYphpXqzsfUDRVVxR4pdw3UzeS6rfB6FE903c8lOncCAwEAAQ== + #logging: # level: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dce3a48..65d7995 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,7 +8,6 @@ spring: # The JWT public key's location relative to src/main/resources. antragsgruen: - jwt.key.public: public.key ws: origins: ${ANTRAGSGRUEN_WS_ORIGINS:http://localhost} heartbeat: 10000 # milliseconds. nginx proxy has a default timeout of 60s, so 10s for heartbeat should be safe. diff --git a/src/main/resources/resource-config.json b/src/main/resources/resource-config.json index 9a77c40..1cd8241 100644 --- a/src/main/resources/resource-config.json +++ b/src/main/resources/resource-config.json @@ -5,9 +5,6 @@ }, { "pattern": "^stomp\\.umd\\.min\\.js$" - }, - { - "pattern": "^public\\.key$" } ] } \ No newline at end of file diff --git a/src/test/java/de/antragsgruen/live/SpeechAdminTests.java b/src/test/java/de/antragsgruen/live/SpeechAdminTests.java index 2f0e5c8..182e3d7 100644 --- a/src/test/java/de/antragsgruen/live/SpeechAdminTests.java +++ b/src/test/java/de/antragsgruen/live/SpeechAdminTests.java @@ -31,7 +31,7 @@ class SpeechAdminTests { public void tryToConnectWithoutAdminRole() { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "login-1", getRoles("WRONG_ROLE")); + stompConnection.connectAndWait("test", "site", "con", "login-1", getRoles("WRONG_ROLE")); FutureTask onError = stompConnection.subscribeAndExpectError("/admin/site/con/login-1/speech"); try { String message = onError.get(5, TimeUnit.SECONDS); @@ -45,7 +45,7 @@ public void tryToConnectWithoutAdminRole() { public void sendAndConvertRabbitMQMessage_speech1() throws IOException { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "login-1", getRoles("ROLE_SPEECH_ADMIN")); + stompConnection.connectAndWait("test", "site", "con", "login-1", getRoles("ROLE_SPEECH_ADMIN")); stompConnection.subscribe("/admin/site/con/login-1/speech"); testHelper.sendFileContentToRabbitMQ("sendAndConvertRabbitMQMessage_speech1_in.json", "speech.site.con"); diff --git a/src/test/java/de/antragsgruen/live/SpeechUserTests.java b/src/test/java/de/antragsgruen/live/SpeechUserTests.java index 6c06043..223e482 100644 --- a/src/test/java/de/antragsgruen/live/SpeechUserTests.java +++ b/src/test/java/de/antragsgruen/live/SpeechUserTests.java @@ -29,7 +29,7 @@ class SpeechUserTests { public void sendAndConvertRabbitMQMessage_speech1() throws IOException { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "login-1", null); + stompConnection.connectAndWait("test", "site", "con", "login-1", null); stompConnection.subscribe("/user/site/con/login-1/speech"); testHelper.sendFileContentToRabbitMQ("sendAndConvertRabbitMQMessage_speech1_in.json", "speech.site.con"); @@ -40,7 +40,7 @@ public void sendAndConvertRabbitMQMessage_speech1() throws IOException { public void sendAndConvertRabbitMQMessage_speech2() throws IOException { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "anonymous-qVnRU4NFICsBGtnWfi0dzGgWcKGlQoiN", null); + stompConnection.connectAndWait("test", "site", "con", "anonymous-qVnRU4NFICsBGtnWfi0dzGgWcKGlQoiN", null); stompConnection.subscribe("/user/site/con/anonymous-qVnRU4NFICsBGtnWfi0dzGgWcKGlQoiN/speech"); testHelper.sendFileContentToRabbitMQ("sendAndConvertRabbitMQMessage_speech2_in.json", "speech.site.con"); @@ -51,7 +51,7 @@ public void sendAndConvertRabbitMQMessage_speech2() throws IOException { public void sendAndConvertRabbitMQMessage_speech3() throws IOException { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "login-1", null); + stompConnection.connectAndWait("test", "site", "con", "login-1", null); stompConnection.subscribe("/user/site/con/login-1/speech"); testHelper.sendFileContentToRabbitMQ("sendAndConvertRabbitMQMessage_speech3_in.json", "speech.site.con"); @@ -62,7 +62,7 @@ public void sendAndConvertRabbitMQMessage_speech3() throws IOException { public void tryToConnectToIncorrectTopic1() { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "login-1", null); + stompConnection.connectAndWait("test", "site", "con", "login-1", null); FutureTask onError = stompConnection.subscribeAndExpectError("/user/site/othercon/login-1/speech"); try { String message = onError.get(5, TimeUnit.SECONDS); @@ -76,7 +76,7 @@ public void tryToConnectToIncorrectTopic1() { public void tryToConnectToIncorrectTopic2() { StompTestConnection stompConnection = testHelper.getStompConnection(port); - stompConnection.connectAndWait("site", "con", "login-2", null); + stompConnection.connectAndWait("test", "site", "con", "login-2", null); FutureTask onError = stompConnection.subscribeAndExpectError("/user/site/con/login-1/speech"); try { String message = onError.get(5, TimeUnit.SECONDS); diff --git a/src/test/java/de/antragsgruen/live/utils/StompTestConnection.java b/src/test/java/de/antragsgruen/live/utils/StompTestConnection.java index 3af8ccc..b4032a7 100644 --- a/src/test/java/de/antragsgruen/live/utils/StompTestConnection.java +++ b/src/test/java/de/antragsgruen/live/utils/StompTestConnection.java @@ -105,7 +105,7 @@ private String generateJwt(String site, String consultation, String userId, @Nul return signedJwt.serialize(); } - public FutureTask connect(String site, String consultation, String userId, @Nullable List roles) { + public FutureTask connect(String installation, String site, String consultation, String userId, @Nullable List roles) { WebSocketClient webSocketClient = new StandardWebSocketClient(); stompClient = new WebSocketStompClient(webSocketClient); stompClient.setMessageConverter(new MappingJackson2MessageConverter()); @@ -115,6 +115,7 @@ public FutureTask connect(String site, String consultation, String StompHeaders headers = new StompHeaders(); headers.set("jwt", generateJwt(site, consultation, userId, roles)); + headers.set("installation", installation); StompTestSessionHandler sessionHandler = new StompTestSessionHandler(); stompClient.connect(url, handshakeHeaders, headers, sessionHandler); @@ -123,9 +124,9 @@ public FutureTask connect(String site, String consultation, String return sessionHandler.onConnect(); } - public void connectAndWait(String site, String consultation, String userId, @Nullable List roles) { + public void connectAndWait(String installation, String site, String consultation, String userId, @Nullable List roles) { try { - this.stompSession = this.connect(site, consultation, userId, roles).get(5, TimeUnit.SECONDS); + this.stompSession = this.connect(installation, site, consultation, userId, roles).get(5, TimeUnit.SECONDS); } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } catch (TimeoutException e) { diff --git a/src/test/resources/jwt-test-public.key b/src/test/resources/jwt-test-public.key deleted file mode 100644 index 215bb30..0000000 --- a/src/test/resources/jwt-test-public.key +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArGCspSkkS4juW8XfmCVb -kv+CRd2MUmEPJIqwWZd33E27FIRMDOqHp44hyxFTQoUy/pCUgVSgAtfuj1Cz1Vzj -3dQWKjbWPKIzjiU7tZ/rok+PjJQsYV7m3wCizOk/2UNTnn9v/6aXmLZTetEEEFxH -vVDF63/KJLu0abwW4PjATSNC4lAO0oGgPXbwkovIUkP3Vdj+kAhN72UC+lYafhAW -Y8gwpEPA0cdBBIl15JQZ/9gdoWbsmOMv8/6hv/kMHKWtJ52HHnMLshXEOKU7H0gu -nyh7MiJ0THeuJnlrQAYVyl4Lho2sS4ZWIKaDWmKDujyMfSI6CujBy9koR4KuXMp+ -PHemOWmMSAiirCZXf7t5r98fuPmSrZLp6A8L0612LEIjcrLQWqECn/NZ9823/0lM -DBiJdyJBcNBe/QUSXEc2bTf/1vScbCBKcNTmlJykgZv00B0et01rscpThWcN4xne -ceUNXVla1X53bYXBG/8ZgiBzq7KqsTo285RuTG+lC82UPtove+R7wh4zHR4/gc8k -vsHDkiAgCJr0FjQZyGcrDpEA2w8dTgHOkIOeGTh8r9AHJKdStyKQJy524RDtvopa -7v8iibOM7FMCfUiJCKcGk0thdFqQ5tMEqk6KAVT57eYCBGYphpXqzsfUDRVVxR4p -dw3UzeS6rfB6FE903c8lOncCAwEAAQ== ------END PUBLIC KEY-----