Skip to content

Commit 67f9912

Browse files
committed
Add OAuth 2 credentials provider and refresh service
WIP. (cherry picked from commit 4251459) Conflicts: src/test/java/com/rabbitmq/client/test/ClientTests.java
1 parent b41d379 commit 67f9912

15 files changed

+1234
-12
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<awaitility.version>3.1.6</awaitility.version>
6464
<mockito.version>3.0.0</mockito.version>
6565
<assertj.version>3.12.2</assertj.version>
66+
<jetty.version>9.4.19.v20190610</jetty.version>
6667

6768
<maven.javadoc.plugin.version>3.0.1</maven.javadoc.plugin.version>
6869
<maven.release.plugin.version>2.5.3</maven.release.plugin.version>
@@ -744,6 +745,12 @@
744745
<version>1.3</version>
745746
<scope>test</scope>
746747
</dependency>
748+
<dependency>
749+
<groupId>org.eclipse.jetty</groupId>
750+
<artifactId>jetty-servlet</artifactId>
751+
<version>${jetty.version}</version>
752+
<scope>test</scope>
753+
</dependency>
747754
</dependencies>
748755

749756
<build>

src/main/java/com/rabbitmq/client/ConnectionFactory.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,7 @@
1515

1616
package com.rabbitmq.client;
1717

18-
import com.rabbitmq.client.impl.AMQConnection;
19-
import com.rabbitmq.client.impl.ConnectionParams;
20-
import com.rabbitmq.client.impl.CredentialsProvider;
21-
import com.rabbitmq.client.impl.DefaultCredentialsProvider;
22-
import com.rabbitmq.client.impl.DefaultExceptionHandler;
23-
import com.rabbitmq.client.impl.ErrorOnWriteListener;
24-
import com.rabbitmq.client.impl.FrameHandler;
25-
import com.rabbitmq.client.impl.FrameHandlerFactory;
26-
import com.rabbitmq.client.impl.SocketFrameHandlerFactory;
18+
import com.rabbitmq.client.impl.*;
2719
import com.rabbitmq.client.impl.nio.NioParams;
2820
import com.rabbitmq.client.impl.nio.SocketChannelFrameHandlerFactory;
2921
import com.rabbitmq.client.impl.recovery.AutorecoveringConnection;
@@ -209,6 +201,8 @@ public class ConnectionFactory implements Cloneable {
209201
*/
210202
private TrafficListener trafficListener = TrafficListener.NO_OP;
211203

204+
private CredentialsRefreshService credentialsRefreshService;
205+
212206
/** @return the default host to use for connections */
213207
public String getHost() {
214208
return host;
@@ -854,6 +848,10 @@ public MetricsCollector getMetricsCollector() {
854848
return metricsCollector;
855849
}
856850

851+
public void setCredentialsRefreshService(CredentialsRefreshService credentialsRefreshService) {
852+
this.credentialsRefreshService = credentialsRefreshService;
853+
}
854+
857855
protected synchronized FrameHandlerFactory createFrameHandlerFactory() throws IOException {
858856
if(nio) {
859857
if(this.frameHandlerFactory == null) {
@@ -1161,6 +1159,7 @@ public ConnectionParams params(ExecutorService consumerWorkServiceExecutor) {
11611159
result.setConnectionRecoveryTriggeringCondition(connectionRecoveryTriggeringCondition);
11621160
result.setTopologyRecoveryRetryHandler(topologyRecoveryRetryHandler);
11631161
result.setTrafficListener(trafficListener);
1162+
result.setCredentialsRefreshService(credentialsRefreshService);
11641163
return result;
11651164
}
11661165

src/main/java/com/rabbitmq/client/impl/AMQConnection.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public static Map<String, Object> defaultClientProperties() {
142142
private final int channelRpcTimeout;
143143
private final boolean channelShouldCheckRpcResponseType;
144144
private final TrafficListener trafficListener;
145+
private final CredentialsRefreshService credentialsRefreshService;
145146

146147
/* State modified after start - all volatile */
147148

@@ -239,6 +240,9 @@ public AMQConnection(ConnectionParams params, FrameHandler frameHandler, Metrics
239240
this.channelShouldCheckRpcResponseType = params.channelShouldCheckRpcResponseType();
240241

241242
this.trafficListener = params.getTrafficListener() == null ? TrafficListener.NO_OP : params.getTrafficListener();
243+
244+
this.credentialsRefreshService = params.getCredentialsRefreshService();
245+
242246
this._channel0 = new AMQChannel(this, 0) {
243247
@Override public boolean processAsync(Command c) throws IOException {
244248
return getConnection().processControlCommand(c);
@@ -336,6 +340,15 @@ public void start()
336340

337341
String username = credentialsProvider.getUsername();
338342
String password = credentialsProvider.getPassword();
343+
344+
if (credentialsProvider.getExpiration() != null) {
345+
if (this.credentialsRefreshService.needRefresh(credentialsProvider.getExpiration())) {
346+
credentialsProvider.refresh();
347+
username = credentialsProvider.getUsername();
348+
password = credentialsProvider.getPassword();
349+
}
350+
}
351+
339352
LongString challenge = null;
340353
LongString response = sm.handleChallenge(null, username, password);
341354

@@ -413,6 +426,26 @@ public void start()
413426
throw AMQChannel.wrap(sse);
414427
}
415428

429+
if (this.credentialsProvider.getExpiration() != null) {
430+
String registrationId = this.credentialsRefreshService.register(credentialsProvider, () -> {
431+
// return false if connection is closed, so refresh service can get rid of this registration
432+
if (!isOpen()) {
433+
return false;
434+
}
435+
if (this._inConnectionNegotiation) {
436+
// this should not happen
437+
return true;
438+
}
439+
String refreshedPassword = credentialsProvider.getPassword();
440+
441+
// TODO send password to server with update-secret extension, using channel 0
442+
443+
return true;
444+
});
445+
446+
addShutdownListener(sse -> this.credentialsRefreshService.unregister(this.credentialsProvider, registrationId));
447+
}
448+
416449
// We can now respond to errors having finished tailoring the connection
417450
this._inConnectionNegotiation = false;
418451
}

src/main/java/com/rabbitmq/client/impl/ConnectionParams.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public class ConnectionParams {
6060

6161
private TrafficListener trafficListener;
6262

63+
private CredentialsRefreshService credentialsRefreshService;
64+
6365
public ConnectionParams() {}
6466

6567
public CredentialsProvider getCredentialsProvider() {
@@ -277,4 +279,12 @@ public void setTrafficListener(TrafficListener trafficListener) {
277279
public TrafficListener getTrafficListener() {
278280
return trafficListener;
279281
}
282+
283+
public void setCredentialsRefreshService(CredentialsRefreshService credentialsRefreshService) {
284+
this.credentialsRefreshService = credentialsRefreshService;
285+
}
286+
287+
public CredentialsRefreshService getCredentialsRefreshService() {
288+
return credentialsRefreshService;
289+
}
280290
}
Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,67 @@
1+
// Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
// info@rabbitmq.com.
15+
116
package com.rabbitmq.client.impl;
217

18+
import java.util.Date;
19+
320
/**
421
* Provider interface for establishing credentials for connecting to the broker. Especially useful
5-
* for situations where credentials might change before a recovery takes place or where it is
22+
* for situations where credentials might expire or change before a recovery takes place or where it is
623
* convenient to plug in an outside custom implementation.
724
*
8-
* @since 4.5.0
25+
* @see CredentialsRefreshService
26+
* @since 5.2.0
927
*/
1028
public interface CredentialsProvider {
1129

30+
/**
31+
* Username to use for authentication
32+
*
33+
* @return username
34+
*/
1235
String getUsername();
1336

37+
/**
38+
* Password/secret/token to use for authentication
39+
*
40+
* @return password/secret/token
41+
*/
1442
String getPassword();
1543

44+
/**
45+
* The expiration date of the credentials, if any.
46+
* <p>
47+
* If credentials do not expire, must return null. Default
48+
* behavior is to return null, assuming credentials never
49+
* expire.
50+
*
51+
* @return credentials expiration date
52+
*/
53+
default Date getExpiration() {
54+
// no expiration by default
55+
return null;
56+
}
57+
58+
/**
59+
* Instructs the provider to refresh or renew credentials.
60+
* <p>
61+
* Default behavior is no-op.
62+
*/
63+
default void refresh() {
64+
// no need to refresh anything by default
65+
}
66+
1667
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2019 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
// info@rabbitmq.com.
15+
16+
package com.rabbitmq.client.impl;
17+
18+
import java.util.Date;
19+
import java.util.concurrent.Callable;
20+
21+
/**
22+
* Provider interface to refresh credentials when appropriate
23+
* and perform an operation once the credentials have been
24+
* renewed. In the context of RabbitMQ, the operation consists
25+
* in calling the <code>update.secret</code> AMQP extension
26+
* to provide new valid credentials before the current ones
27+
* expire.
28+
* <p>
29+
* New connections are registered and implementations must perform
30+
* credentials renewal when appropriate. Implementations
31+
* must call a registered callback once credentials are renewed.
32+
*
33+
* @see CredentialsProvider
34+
* @see DefaultCredentialsRefreshService
35+
*/
36+
public interface CredentialsRefreshService {
37+
38+
/**
39+
* Register a new entity that needs credentials renewal.
40+
* <p>
41+
* The registered callback must return true if the action was
42+
* performed correctly, throw an exception if something goes wrong,
43+
* and return false if it became stale and wants to be unregistered.
44+
* <p>
45+
* Implementations are free to automatically unregister an entity whose
46+
* callback has failed a given number of times.
47+
*
48+
* @param credentialsProvider the credentials provider
49+
* @param refreshAction the action to perform after credentials renewal
50+
* @return a tracking ID for the registration
51+
*/
52+
String register(CredentialsProvider credentialsProvider, Callable<Boolean> refreshAction);
53+
54+
/**
55+
* Unregister the entity with the given registration ID.
56+
* <p>
57+
* Its state is cleaned up and its registered callback will not be
58+
* called again.
59+
*
60+
* @param credentialsProvider the credentials provider
61+
* @param registrationId the registration ID
62+
*/
63+
void unregister(CredentialsProvider credentialsProvider, String registrationId);
64+
65+
/**
66+
* Provide a hint about whether credentials should be renewed.
67+
*
68+
* @param expiration
69+
* @return true if credentials should be renewed, false otherwise
70+
*/
71+
boolean needRefresh(Date expiration);
72+
73+
}

src/main/java/com/rabbitmq/client/impl/DefaultCredentialsProvider.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
// Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
// info@rabbitmq.com.
15+
116
package com.rabbitmq.client.impl;
217

318
/**

0 commit comments

Comments
 (0)