Skip to content

Commit 6ab0976

Browse files
committed
Add test with OAuth 2 and JWT token
1 parent a817d69 commit 6ab0976

File tree

5 files changed

+115
-10
lines changed

5 files changed

+115
-10
lines changed

ci/start-broker.sh

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.0}
3+
RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-pivotalrabbitmq/rabbitmq:amqp-token-renew}
44

55
wait_for_message() {
66
while ! docker logs "$1" | grep -q "$2";
@@ -17,7 +17,7 @@ cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls
1717
chmod o+r rabbitmq-configuration/tls/*
1818
chmod g+r rabbitmq-configuration/tls/*
1919

20-
echo "[rabbitmq_auth_mechanism_ssl]." >> rabbitmq-configuration/enabled_plugins
20+
echo "[rabbitmq_auth_mechanism_ssl,rabbitmq_auth_backend_oauth2]." >> rabbitmq-configuration/enabled_plugins
2121

2222
echo "loopback_users = none
2323
@@ -34,7 +34,24 @@ ssl_options.depth = 1
3434
3535
auth_mechanisms.1 = PLAIN
3636
auth_mechanisms.2 = ANONYMOUS
37-
auth_mechanisms.3 = EXTERNAL" >> rabbitmq-configuration/rabbitmq.conf
37+
auth_mechanisms.3 = EXTERNAL
38+
39+
auth_backends.1 = internal
40+
auth_backends.2 = rabbit_auth_backend_oauth2" >> rabbitmq-configuration/rabbitmq.conf
41+
42+
echo "[
43+
{rabbitmq_auth_backend_oauth2, [{key_config,
44+
[{signing_keys,
45+
#{<<\"token-key\">> =>
46+
{map,
47+
#{<<\"alg\">> => <<\"HS256\">>,
48+
<<\"k\">> => <<\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH\">>,
49+
<<\"kid\">> => <<\"token-key\">>,
50+
<<\"kty\">> => <<\"oct\">>,
51+
<<\"use\">> => <<\"sig\">>,
52+
<<\"value\">> => <<\"token-key\">>}}}}]},
53+
{resource_server_id,<<\"rabbitmq\">>}]}
54+
]." >> rabbitmq-configuration/advanced.config
3855

3956
echo "Running RabbitMQ ${RABBITMQ_IMAGE}"
4057

pom.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@
5151
<assertj.version>3.26.3</assertj.version>
5252
<mockito.version>5.14.2</mockito.version>
5353
<jqwik.version>1.9.1</jqwik.version>
54-
<amqp-client.version>5.20.0</amqp-client.version>
5554
<micrometer-tracing-test.version>1.3.5</micrometer-tracing-test.version>
5655
<micrometer-docs-generator.version>1.0.4</micrometer-docs-generator.version>
56+
<jose4j.version>0.9.6</jose4j.version>
5757
<maven.compiler.plugin.version>3.13.0</maven.compiler.plugin.version>
5858
<maven.dependency.plugin.version>3.8.1</maven.dependency.plugin.version>
5959
<maven-surefire-plugin.version>3.5.1</maven-surefire-plugin.version>
@@ -236,6 +236,14 @@
236236
<scope>provided</scope>
237237
</dependency>
238238

239+
<dependency>
240+
<groupId>org.bitbucket.b_c</groupId>
241+
<artifactId>jose4j</artifactId>
242+
<version>${jose4j.version}</version>
243+
<scope>test</scope>
244+
</dependency>
245+
246+
239247
</dependencies>
240248

241249
<dependencyManagement>

src/main/qpid/org/apache/qpid/protonj2/engine/sasl/client/PlainMechanism.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ public Symbol getName() {
4343

4444
@Override
4545
public boolean isApplicable(SaslCredentialsProvider credentials) {
46-
return credentials.username() != null && !credentials.username().isEmpty() &&
47-
credentials.password() != null && !credentials.password().isEmpty();
46+
return credentials.username() != null && credentials.password() != null;
4847
}
4948

5049
@Override

src/test/java/com/rabbitmq/client/amqp/impl/ManagementTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ void receiveLoopShouldStopAfterBeingIdle() {
9999
@ParameterizedTest
100100
@ValueSource(booleans = {true, false})
101101
@BrokerVersionAtLeast(RABBITMQ_4_1_0)
102-
void setToken(boolean isolateResources, TestInfo info) {
102+
void sessionShouldGetClosedAfterPermissionsChangedAndSetTokenCalled(
103+
boolean isolateResources, TestInfo info) {
103104
String username = "foo";
104105
String password = "bar";
105106
String vh = "/";
@@ -121,15 +122,18 @@ void setToken(boolean isolateResources, TestInfo info) {
121122
Sync publisherClosedSync = sync();
122123
Sync consumerClosedSync = sync();
123124
Publisher p =
124-
c.publisherBuilder().queue(q).listeners(closedListener(publisherClosedSync)).build();
125+
c.publisherBuilder()
126+
.queue(q)
127+
.listeners(closedOnSecurityExceptionListener(publisherClosedSync))
128+
.build();
125129
c.consumerBuilder()
126130
.queue(q)
127131
.messageHandler(
128132
(ctx, msg) -> {
129133
ctx.accept();
130134
consumeSync.down();
131135
})
132-
.listeners(closedListener(consumerClosedSync))
136+
.listeners(closedOnSecurityExceptionListener(consumerClosedSync))
133137
.build();
134138

135139
p.publish(p.message(), ctx -> {});
@@ -149,7 +153,7 @@ void setToken(boolean isolateResources, TestInfo info) {
149153
}
150154
}
151155

152-
private static Resource.StateListener closedListener(Sync sync) {
156+
private static Resource.StateListener closedOnSecurityExceptionListener(Sync sync) {
153157
return context -> {
154158
if (context.currentState() == Resource.State.CLOSED
155159
&& context.failureCause() instanceof AmqpException.AmqpSecurityException) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 2024 Broadcom. All Rights Reserved.
2+
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
// If you have any questions regarding licensing, please contact us at
17+
18+
package com.rabbitmq.client.amqp.impl;
19+
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
21+
22+
import com.rabbitmq.client.amqp.AmqpException;
23+
import com.rabbitmq.client.amqp.Connection;
24+
import com.rabbitmq.client.amqp.Environment;
25+
import java.time.Duration;
26+
import java.util.List;
27+
import org.jose4j.base64url.Base64;
28+
import org.jose4j.jws.AlgorithmIdentifiers;
29+
import org.jose4j.jws.JsonWebSignature;
30+
import org.jose4j.jwt.JwtClaims;
31+
import org.jose4j.jwt.NumericDate;
32+
import org.jose4j.keys.HmacKey;
33+
import org.jose4j.lang.JoseException;
34+
import org.junit.jupiter.api.Test;
35+
36+
@AmqpTestInfrastructure
37+
public class Oauth2Test {
38+
39+
private static final String BASE64_KEY = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH";
40+
41+
Environment environment;
42+
43+
@Test
44+
void expiredTokenShouldFail() throws Exception {
45+
String expiredToken = token(System.currentTimeMillis() - 1000);
46+
assertThatThrownBy(
47+
() -> environment.connectionBuilder().username("").password(expiredToken).build())
48+
.isInstanceOf(AmqpException.AmqpSecurityException.class);
49+
}
50+
51+
@Test
52+
void validTokenShouldSucceed() throws Exception {
53+
String validToken = token(System.currentTimeMillis() + Duration.ofMinutes(10).toMillis());
54+
try (Connection ignored =
55+
environment.connectionBuilder().username("").password(validToken).build()) {}
56+
}
57+
58+
private static String token(long expirationTime) throws JoseException {
59+
JwtClaims claims = new JwtClaims();
60+
claims.setIssuer("unit_test");
61+
claims.setAudience("rabbitmq");
62+
claims.setExpirationTime(NumericDate.fromMilliseconds(expirationTime));
63+
claims.setStringListClaim(
64+
"scope", List.of("rabbitmq.configure:*/*", "rabbitmq.write:*/*", "rabbitmq.read:*/*"));
65+
66+
JsonWebSignature signature = new JsonWebSignature();
67+
68+
byte[] key = Base64.decode(BASE64_KEY);
69+
HmacKey hmacKey = new HmacKey(key);
70+
signature.setKeyIdHeaderValue("token-key");
71+
signature.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
72+
signature.setKey(hmacKey);
73+
signature.setPayload(claims.toJson());
74+
75+
return signature.getCompactSerialization();
76+
}
77+
}

0 commit comments

Comments
 (0)