Skip to content

Commit 023a7b6

Browse files
author
Simon MacMullen
committed
Introduce our own notion of a SASL mechanism and use that. JDKSaslConfig acts as a bridge to the JDK impl if you want to use that.
1 parent 84f3c6d commit 023a7b6

12 files changed

+337
-253
lines changed

src/com/rabbitmq/client/ConnectionFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public class ConnectionFactory implements Cloneable {
8989
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
9090
private Map<String, Object> _clientProperties = AMQConnection.defaultClientProperties();
9191
private SocketFactory factory = SocketFactory.getDefault();
92-
private SaslConfig saslConfig = new DefaultSaslConfig(this);
92+
private SaslConfig saslConfig = DefaultSaslConfig.PLAIN;
9393

9494
/**
9595
* Instantiate a ConnectionFactory with a default set of parameters.

src/com/rabbitmq/client/DefaultSaslConfig.java

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,56 +16,40 @@
1616

1717
package com.rabbitmq.client;
1818

19-
import javax.security.auth.callback.CallbackHandler;
20-
import javax.security.sasl.Sasl;
21-
import javax.security.sasl.SaslClient;
22-
import javax.security.sasl.SaslException;
19+
import com.rabbitmq.client.impl.ExternalMechanism;
20+
import com.rabbitmq.client.impl.PlainMechanism;
21+
2322
import java.util.Arrays;
2423
import java.util.HashSet;
25-
import java.util.List;
2624
import java.util.Set;
2725

2826
/**
29-
* Default implementation of SaslConfig that uses the standard Java
30-
* algorithm for selecting a sasl client.
31-
* @see com.rabbitmq.client.ConnectionFactory
27+
* Default SASL configuration. Uses one of our built-in mechanisms.
3228
*/
3329
public class DefaultSaslConfig implements SaslConfig {
34-
public static final String[] DEFAULT_PREFERRED_MECHANISMS = new String[]{"PLAIN"};
30+
private final String mechanism;
3531

36-
private final ConnectionFactory factory;
37-
private final List<String> mechanisms;
38-
private final CallbackHandler callbackHandler;
32+
public static final DefaultSaslConfig PLAIN = new DefaultSaslConfig("PLAIN");
33+
public static final DefaultSaslConfig EXTERNAL = new DefaultSaslConfig("EXTERNAL");
3934

4035
/**
41-
* Create a DefaultSaslConfig which only wants to use PLAIN.
36+
* Create a DefaultSaslConfig with an explicit mechanism to use.
4237
*
43-
* @param factory - the ConnectionFactory to use to obtain username, password and host
38+
* @param mechanism - a SASL mechanism to use
4439
*/
45-
public DefaultSaslConfig(ConnectionFactory factory) {
46-
this(factory, DEFAULT_PREFERRED_MECHANISMS);
40+
private DefaultSaslConfig(String mechanism) {
41+
this.mechanism = mechanism;
4742
}
4843

49-
/**
50-
* Create a DefaultSaslConfig with a list of mechanisms to use.
51-
*
52-
* @param factory - the ConnectionFactory to use to obtain username, password and host
53-
* @param mechanisms - a list of SASL mechanisms to use (in descending order of preference)
54-
*/
55-
public DefaultSaslConfig(ConnectionFactory factory, String[] mechanisms) {
56-
this.factory = factory;
57-
callbackHandler = new UsernamePasswordCallbackHandler(factory);
58-
this.mechanisms = Arrays.asList(mechanisms);
59-
}
60-
61-
public SaslClient getSaslClient(String[] serverMechanisms) throws SaslException {
44+
public SaslMechanism getSaslMechanism(String[] serverMechanisms) {
6245
Set<String> server = new HashSet<String>(Arrays.asList(serverMechanisms));
6346

64-
for (String mechanism: mechanisms) {
65-
if (server.contains(mechanism)) {
66-
SaslClient saslClient = Sasl.createSaslClient(new String[]{mechanism},
67-
null, "AMQP", factory.getHost(), null, callbackHandler);
68-
if (saslClient != null) return saslClient;
47+
if (server.contains(mechanism)) {
48+
if (mechanism.equals("PLAIN")) {
49+
return new PlainMechanism();
50+
}
51+
else if (mechanism.equals("EXTERNAL")) {
52+
return new ExternalMechanism();
6953
}
7054
}
7155
return null;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// The contents of this file are subject to the Mozilla Public License
2+
// Version 1.1 (the "License"); you may not use this file except in
3+
// compliance with the License. You may obtain a copy of the License
4+
// at http://www.mozilla.org/MPL/
5+
//
6+
// Software distributed under the License is distributed on an "AS IS"
7+
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
8+
// the License for the specific language governing rights and
9+
// limitations under the License.
10+
//
11+
// The Original Code is RabbitMQ.
12+
//
13+
// The Initial Developer of the Original Code is VMware, Inc.
14+
// Copyright (c) 2007-2011 VMware, Inc. All rights reserved.
15+
//
16+
17+
package com.rabbitmq.client;
18+
19+
import com.rabbitmq.client.impl.LongString;
20+
import com.rabbitmq.client.impl.LongStringHelper;
21+
22+
import javax.security.auth.callback.Callback;
23+
import javax.security.auth.callback.CallbackHandler;
24+
import javax.security.auth.callback.NameCallback;
25+
import javax.security.auth.callback.PasswordCallback;
26+
import javax.security.auth.callback.UnsupportedCallbackException;
27+
import javax.security.sasl.Sasl;
28+
import javax.security.sasl.SaslClient;
29+
import javax.security.sasl.SaslException;
30+
import java.io.IOException;
31+
import java.util.Arrays;
32+
import java.util.HashSet;
33+
import java.util.List;
34+
import java.util.Set;
35+
36+
/**
37+
* Implementation of SaslConfig that uses the JDK SASL implementation. This is
38+
* not the default since it does not work on Java 1.4, Android or IBM's JDK.
39+
* @see com.rabbitmq.client.ConnectionFactory
40+
*/
41+
public class JDKSaslConfig implements SaslConfig {
42+
public static final String[] DEFAULT_PREFERRED_MECHANISMS = new String[]{"PLAIN"};
43+
44+
private final ConnectionFactory factory;
45+
private final List<String> mechanisms;
46+
private final CallbackHandler callbackHandler;
47+
48+
/**
49+
* Create a JDKSaslConfig which only wants to use PLAIN.
50+
*
51+
* @param factory - the ConnectionFactory to use to obtain username, password and host
52+
*/
53+
public JDKSaslConfig(ConnectionFactory factory) {
54+
this(factory, DEFAULT_PREFERRED_MECHANISMS);
55+
}
56+
57+
/**
58+
* Create a JDKSaslConfig with a list of mechanisms to use.
59+
*
60+
* @param factory - the ConnectionFactory to use to obtain username, password and host
61+
* @param mechanisms - a list of SASL mechanisms to use (in descending order of preference)
62+
*/
63+
public JDKSaslConfig(ConnectionFactory factory, String[] mechanisms) {
64+
this.factory = factory;
65+
callbackHandler = new UsernamePasswordCallbackHandler(factory);
66+
this.mechanisms = Arrays.asList(mechanisms);
67+
}
68+
69+
public SaslMechanism getSaslMechanism(String[] serverMechanisms) {
70+
Set<String> server = new HashSet<String>(Arrays.asList(serverMechanisms));
71+
72+
for (String mechanism: mechanisms) {
73+
if (server.contains(mechanism)) {
74+
try {
75+
SaslClient saslClient = Sasl.createSaslClient(new String[]{mechanism},
76+
null, "AMQP", factory.getHost(), null, callbackHandler);
77+
if (saslClient != null) return new JDKSaslMechanism(saslClient);
78+
} catch (SaslException e) {
79+
throw new RuntimeException(e);
80+
}
81+
}
82+
}
83+
return null;
84+
}
85+
86+
private class JDKSaslMechanism implements SaslMechanism {
87+
private SaslClient client;
88+
89+
public JDKSaslMechanism(SaslClient client) {
90+
this.client = client;
91+
}
92+
93+
public String getName() {
94+
return client.getMechanismName();
95+
}
96+
97+
public LongString handleChallenge(LongString challenge, ConnectionFactory factory) {
98+
try {
99+
return LongStringHelper.asLongString(client.evaluateChallenge(challenge.getBytes()));
100+
} catch (SaslException e) {
101+
throw new RuntimeException(e);
102+
}
103+
}
104+
}
105+
106+
private class UsernamePasswordCallbackHandler implements CallbackHandler {
107+
private ConnectionFactory factory;
108+
public UsernamePasswordCallbackHandler(ConnectionFactory factory) {
109+
this.factory = factory;
110+
}
111+
112+
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
113+
for (Callback callback: callbacks) {
114+
if (callback instanceof NameCallback) {
115+
NameCallback nc = (NameCallback)callback;
116+
nc.setName(factory.getUsername());
117+
118+
} else if (callback instanceof PasswordCallback) {
119+
PasswordCallback pc = (PasswordCallback)callback;
120+
pc.setPassword(factory.getPassword().toCharArray());
121+
122+
} else {
123+
throw new UnsupportedCallbackException
124+
(callback, "Unrecognized Callback");
125+
}
126+
}
127+
}
128+
}
129+
130+
}

src/com/rabbitmq/client/SaslConfig.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616

1717
package com.rabbitmq.client;
1818

19-
import javax.security.sasl.SaslClient;
20-
import javax.security.sasl.SaslException;
21-
2219
/**
2320
* This interface represents a hook to allow you to control how exactly
2421
* a sasl client is selected during authentication.
2522
* @see com.rabbitmq.client.ConnectionFactory
2623
*/
2724
public interface SaslConfig {
28-
SaslClient getSaslClient(String[] mechanisms) throws SaslException;
25+
SaslMechanism getSaslMechanism(String[] mechanisms);
2926
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// The contents of this file are subject to the Mozilla Public License
2+
// Version 1.1 (the "License"); you may not use this file except in
3+
// compliance with the License. You may obtain a copy of the License
4+
// at http://www.mozilla.org/MPL/
5+
//
6+
// Software distributed under the License is distributed on an "AS IS"
7+
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
8+
// the License for the specific language governing rights and
9+
// limitations under the License.
10+
//
11+
// The Original Code is RabbitMQ.
12+
//
13+
// The Initial Developer of the Original Code is VMware, Inc.
14+
// Copyright (c) 2007-2011 VMware, Inc. All rights reserved.
15+
//
16+
17+
package com.rabbitmq.client;
18+
19+
import com.rabbitmq.client.impl.LongString;
20+
21+
import java.io.IOException;
22+
23+
/**
24+
* Our own view of a SASL authentication mechanism, introduced to remove a
25+
* dependency on javax.security.sasl.
26+
*/
27+
public interface SaslMechanism {
28+
/**
29+
* The name of this mechanism (e.g. PLAIN)
30+
* @return
31+
*/
32+
String getName();
33+
34+
/**
35+
* Handle one round of challenge-response
36+
* @param challenge the challenge this round, or null on first round.
37+
* @param factory for reference to e.g. username and password.
38+
* @return response
39+
* @throws IOException
40+
*/
41+
LongString handleChallenge(LongString challenge, ConnectionFactory factory);
42+
}

src/com/rabbitmq/client/UsernamePasswordCallbackHandler.java

Lines changed: 0 additions & 48 deletions
This file was deleted.

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

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,11 @@
3434
import com.rabbitmq.client.MissedHeartbeatException;
3535
import com.rabbitmq.client.PossibleAuthenticationFailureException;
3636
import com.rabbitmq.client.ProtocolVersionMismatchException;
37+
import com.rabbitmq.client.SaslMechanism;
3738
import com.rabbitmq.client.ShutdownSignalException;
3839
import com.rabbitmq.utility.BlockingCell;
3940
import com.rabbitmq.utility.Utility;
4041

41-
import javax.security.sasl.SaslClient;
42-
4342
/**
4443
* Concrete class representing and managing an AMQP connection to a broker.
4544
* <p>
@@ -267,20 +266,20 @@ public void start()
267266
}
268267

269268
String[] mechanisms = connStart.getMechanisms().toString().split(" ");
270-
SaslClient sc = _factory.getSaslConfig().getSaslClient(mechanisms);
271-
if (sc == null) {
269+
SaslMechanism sm = _factory.getSaslConfig().getSaslMechanism(mechanisms);
270+
if (sm == null) {
272271
throw new IOException("No compatible authentication mechanism found - " +
273272
"server offered [" + connStart.getMechanisms() + "]");
274273
}
275274

276275
LongString challenge = null;
277-
LongString response = LongStringHelper.asLongString(
278-
sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null);
276+
LongString response = sm.handleChallenge(LongStringHelper.asLongString(""), _factory);
277+
279278
AMQP.Connection.Tune connTune = null;
280279
do {
281280
Method method = (challenge == null)
282281
? new AMQImpl.Connection.StartOk(_clientProperties,
283-
sc.getMechanismName(),
282+
sm.getName(),
284283
response, "en_US")
285284
: new AMQImpl.Connection.SecureOk(response);
286285

@@ -290,20 +289,13 @@ public void start()
290289
connTune = (AMQP.Connection.Tune) serverResponse;
291290
} else {
292291
challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
293-
response = LongStringHelper.asLongString(sc.evaluateChallenge(challenge.getBytes()));
292+
response = sm.handleChallenge(challenge, _factory);
294293
}
295294
} catch (ShutdownSignalException e) {
296295
throw new PossibleAuthenticationFailureException(e);
297296
}
298297
} while (connTune == null);
299298

300-
sc.dispose();
301-
302-
if (!sc.isComplete()) {
303-
throw new RuntimeException(sc.getMechanismName() +
304-
" did not complete, server thought it did");
305-
}
306-
307299
int channelMax =
308300
negotiatedMaxValue(_factory.getRequestedChannelMax(),
309301
connTune.getChannelMax());

0 commit comments

Comments
 (0)