Skip to content

Commit 031ee50

Browse files
committed
JAVA-691: Changed SaslAuthenticator so that it is able to re-authenticate if the connection is broken.
Improved thread safety of DBPort class with use of volatile and AtomicLong
1 parent e3d4cbb commit 031ee50

2 files changed

Lines changed: 94 additions & 69 deletions

File tree

src/main/com/mongodb/DBPort.java

Lines changed: 93 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.HashMap;
3939
import java.util.Map;
4040
import java.util.concurrent.ConcurrentHashMap;
41+
import java.util.concurrent.atomic.AtomicLong;
4142
import java.util.logging.Level;
4243
import java.util.logging.Logger;
4344

@@ -55,7 +56,6 @@ public class DBPort {
5556
static final boolean USE_NAGLE = false;
5657

5758
static final long CONN_RETRY_TIME_MS = 15000;
58-
private boolean globallyAuthed = false;
5959

6060
/**
6161
* creates a new DBPort
@@ -112,7 +112,7 @@ private synchronized Response go(OutMessage msg, DBCollection coll, boolean forc
112112
}
113113
}
114114

115-
_calls++;
115+
_calls.incrementAndGet();
116116

117117
if ( _socket == null )
118118
_open();
@@ -176,7 +176,7 @@ private CommandResult convertToCommandResult(DBObject cmd, Response res) {
176176
}
177177

178178
synchronized CommandResult tryGetLastError( DB db , long last, WriteConcern concern) throws IOException {
179-
if ( last != _calls )
179+
if ( last != _calls.get() )
180180
return null;
181181

182182
return getLastError(db, concern);
@@ -288,6 +288,9 @@ int getLocalPort() {
288288
* closes the underlying connection and streams
289289
*/
290290
protected void close(){
291+
if (_saslAuthenticator != null) {
292+
_saslAuthenticator.clear();
293+
}
291294
_authed.clear();
292295

293296
if ( _socket != null ){
@@ -307,14 +310,10 @@ protected void close(){
307310
void checkAuth(DB db) throws IOException {
308311
// TODO: add support for retry when credentials ticket times out
309312
if (db.getMongo().getCredentials() != null) {
310-
if (!globallyAuthed) {
311-
if (!db.getMongo().getCredentials().getMechanism().equals(MongoClientCredentials.GSSAPI_MECHANISM)) {
312-
throw new MongoException("Unsupported authentication mechanism: " + db.getMongo().getCredentials().getMechanism());
313-
}
314-
new GSSAPIAuthenticator(this, db.getMongo()).authenticate();
315-
globallyAuthed = true;
313+
if (_saslAuthenticator == null) {
314+
_saslAuthenticator = new GSSAPIAuthenticator(db.getMongo());
316315
}
317-
saslAcquirePrivilegeForDatabase(db);
316+
_saslAuthenticator.acquirePrivilegeForDatabase(db);
318317
}
319318
else {
320319
DB.AuthenticationCredentials credentials = db.getAuthenticationCredentials();
@@ -337,18 +336,6 @@ void checkAuth(DB db) throws IOException {
337336
}
338337
}
339338

340-
private void saslAcquirePrivilegeForDatabase(final DB db) throws IOException {
341-
BasicDBObject acquirePrivilegeCmd = new BasicDBObject("acquirePrivilege", 1).
342-
append("principal", db.getMongo().getCredentials().getUserName()).
343-
append("resource", db.getName());
344-
if (_saslAuthed.get(db) == null) {
345-
acquirePrivilegeCmd.append("actions", Arrays.asList("oldWrite"));
346-
CommandResult res = runCommand(db.getSisterDB("admin"), acquirePrivilegeCmd);
347-
res.throwOnError();
348-
_saslAuthed.put(db, true);
349-
}
350-
}
351-
352339
/**
353340
* Gets the pool that this port belongs to
354341
* @return
@@ -365,16 +352,17 @@ public DBPortPool getPool() {
365352
final Logger _logger;
366353
final DBDecoder _decoder;
367354

368-
private Socket _socket;
369-
private InputStream _in;
370-
private OutputStream _out;
355+
private volatile Socket _socket;
356+
private volatile InputStream _in;
357+
private volatile OutputStream _out;
371358

372-
private boolean _processingResponse;
359+
private volatile boolean _processingResponse;
373360

374-
private Map<DB,Boolean> _authed = new ConcurrentHashMap<DB, Boolean>( );
375-
private Map<DB,Boolean> _saslAuthed = new ConcurrentHashMap<DB, Boolean>( );
376-
int _lastThread;
377-
long _calls = 0;
361+
private volatile SaslAuthenticator _saslAuthenticator;
362+
private final Map<DB,Boolean> _authed = new ConcurrentHashMap<DB, Boolean>( );
363+
364+
volatile int _lastThread;
365+
final AtomicLong _calls = new AtomicLong();
378366
private volatile ActiveState _activeState;
379367

380368
class ActiveState {
@@ -388,21 +376,27 @@ class ActiveState {
388376
final String threadName;
389377
}
390378

391-
//TODO: add password support
392-
static class GSSAPIAuthenticator extends SaslAuthenticator {
379+
class GSSAPIAuthenticator extends SaslAuthenticator {
393380
public static final String GSSAPI_OID = "1.2.840.113554.1.2.2";
394381
public static final String GSSAPI_MECHANISM = "GSSAPI";
395382
public static final String MONGODB_PROTOCOL = "mongodb";
396383

397-
GSSAPIAuthenticator(DBPort port, final Mongo mongo) {
398-
super(port, mongo);
384+
GSSAPIAuthenticator(final Mongo mongo) {
385+
super(mongo);
386+
387+
if (!mongo.getCredentials().getMechanism().equals(MongoClientCredentials.GSSAPI_MECHANISM)) {
388+
throw new MongoException("Unsupported authentication mechanism: " + mongo.getCredentials().getMechanism());
389+
}
390+
}
391+
392+
@Override
393+
protected SaslClient createSaslClient() {
399394
try {
400395
Map<String, Object> props = new HashMap<String, Object>();
401-
// NOTE: I couldn't find this part documented anywhere
402396
props.put(Sasl.CREDENTIALS, getGSSCredential(mongo.getCredentials().getUserName()));
403397

404-
saslClient = Sasl.createSaslClient(new String[]{GSSAPI_MECHANISM}, mongo.getCredentials().getUserName(), MONGODB_PROTOCOL,
405-
port.serverAddress().getHost(), props, null);
398+
return Sasl.createSaslClient(new String[]{GSSAPI_MECHANISM}, mongo.getCredentials().getUserName(), MONGODB_PROTOCOL,
399+
serverAddress().getHost(), props, null);
406400
} catch (SaslException e) {
407401
throw new MongoException("Exception initializing SASL client", e);
408402
} catch (GSSException e) {
@@ -419,60 +413,91 @@ private GSSCredential getGSSCredential(String userName) throws GSSException {
419413
}
420414
}
421415

422-
static class SaslAuthenticator {
423-
final DBPort port;
424-
final Mongo mongo;
425-
Integer conversationId;
426-
SaslClient saslClient;
416+
abstract class SaslAuthenticator {
417+
protected final Mongo mongo;
418+
private final Map<DB, Boolean> authorizeDatabases = new ConcurrentHashMap<DB, Boolean>();
419+
private volatile boolean authenticated;
427420

428-
SaslAuthenticator(final DBPort port, final Mongo mongo) {
429-
this.port = port;
421+
SaslAuthenticator(final Mongo mongo) {
430422
this.mongo = mongo;
431423
}
432424

433-
void authenticate() throws SaslException {
434-
try {
435-
// Get optional initial response
436-
byte[] response = (saslClient.hasInitialResponse() ? saslClient.evaluateChallenge(new byte[0]) : null);
437-
CommandResult res = sendSaslStart(response);
438-
res.throwOnError();
425+
void clear() {
426+
authorizeDatabases.clear();
427+
authenticated = false;
428+
}
439429

440-
while (!saslClient.isComplete() && (res.get("done").equals(Boolean.FALSE))) {
441-
// Evaluate server challenge
442-
response = saslClient.evaluateChallenge((byte[]) res.get("payload"));
430+
void authenticate() {
431+
if (authenticated) {
432+
return;
433+
}
443434

444-
if (res.get("done").equals(Boolean.TRUE)) {
445-
// done; server doesn't expect any more SASL data
446-
if (response != null) {
447-
throw new MongoException("SASL protocol error: attempting to send response after completion");
435+
SaslClient saslClient = createSaslClient();
436+
try {
437+
// Get optional initial response
438+
byte[] response = (saslClient.hasInitialResponse() ? saslClient.evaluateChallenge(new byte[0]) : null);
439+
CommandResult res = sendSaslStart(response);
440+
res.throwOnError();
441+
442+
int conversationId = (Integer) res.get("conversationId");
443+
444+
while (!saslClient.isComplete() && (res.get("done").equals(Boolean.FALSE))) {
445+
// Evaluate server challenge
446+
response = saslClient.evaluateChallenge((byte[]) res.get("payload"));
447+
448+
if (res.get("done").equals(Boolean.TRUE)) {
449+
// done; server doesn't expect any more SASL data
450+
if (response != null) {
451+
throw new MongoException("SASL protocol error: attempting to send response after completion");
452+
}
453+
break;
454+
} else {
455+
res = sendSaslContinue(conversationId, response);
456+
res.throwOnError();
448457
}
449-
break;
450-
} else {
451-
res = sendSaslContinue(response);
452-
res.throwOnError();
453458
}
454-
}
459+
authenticated = true;
455460
} catch (IOException e) {
456461
// TODO: Fix exception message.
457-
throw new MongoException("", e);
462+
throw new MongoException("Exception authenticating with SASL", e);
463+
} finally {
464+
try {
465+
saslClient.dispose();
466+
} catch (SaslException e) {
467+
// ignore
468+
}
458469
}
459470
}
460471

472+
protected abstract SaslClient createSaslClient();
473+
461474
private CommandResult sendSaslStart(final byte[] outToken) throws IOException {
462475
// System.out.println("Sending saslStart with num bytes: " + outToken.length);
463476
DB adminDB = mongo.getDB("admin");
464477
DBObject cmd = new BasicDBObject("saslStart", 1).append("mechanism", "GSSAPI").append("payload", outToken);
465-
CommandResult res = port.runCommand(adminDB, cmd);
466-
conversationId = (Integer) res.get("conversationId");
467-
return res;
478+
return runCommand(adminDB, cmd);
468479
}
469480

470-
private CommandResult sendSaslContinue(final byte[] outToken) throws IOException {
481+
private CommandResult sendSaslContinue(final int conversationId, final byte[] outToken) throws IOException {
471482
// System.out.println("Sending saslContinue with num bytes: " + outToken.length);
472483
DB adminDB = mongo.getDB("admin");
473484
DBObject cmd = new BasicDBObject("saslContinue", 1).append("conversationId", conversationId).
474485
append("payload", outToken);
475-
return port.runCommand(adminDB, cmd);
486+
return runCommand(adminDB, cmd);
487+
}
488+
489+
public void acquirePrivilegeForDatabase(final DB db) throws IOException {
490+
authenticate();
491+
492+
if (authorizeDatabases.get(db) == null) {
493+
BasicDBObject acquirePrivilegeCmd = new BasicDBObject("acquirePrivilege", 1).
494+
append("principal", db.getMongo().getCredentials().getUserName()).
495+
append("resource", db.getName()).
496+
append("actions", Arrays.asList("oldWrite"));
497+
CommandResult res = runCommand(db.getSisterDB("admin"), acquirePrivilegeCmd);
498+
res.throwOnError();
499+
authorizeDatabases.put(db, true);
500+
}
476501
}
477502
}
478503

src/main/com/mongodb/WriteResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class WriteResult {
4141
WriteResult( DB db , DBPort p , WriteConcern concern ){
4242
_db = db;
4343
_port = p;
44-
_lastCall = p._calls;
44+
_lastCall = p._calls.get();
4545
_lastConcern = concern;
4646
_lazy = true;
4747
}

0 commit comments

Comments
 (0)