Skip to content

Commit f3e61e4

Browse files
committed
Replaced synchronization with AtomicReference for handling authentication
1 parent b9cfae4 commit f3e61e4

2 files changed

Lines changed: 101 additions & 77 deletions

File tree

src/main/com/mongodb/DB.java

Lines changed: 96 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818

1919
package com.mongodb;
2020

21+
import com.mongodb.DBApiLayer.Result;
22+
import com.mongodb.util.Util;
23+
import org.bson.BSONObject;
24+
2125
import java.io.ByteArrayOutputStream;
2226
import java.io.IOException;
2327
import java.util.ArrayList;
@@ -27,10 +31,7 @@
2731
import java.util.LinkedHashSet;
2832
import java.util.List;
2933
import java.util.Set;
30-
31-
import com.mongodb.DBApiLayer.Result;
32-
import com.mongodb.util.Util;
33-
import org.bson.BSONObject;
34+
import java.util.concurrent.atomic.AtomicReference;
3435

3536
/**
3637
* an abstract class that represents a logical database on a server
@@ -533,97 +534,62 @@ public void dropDatabase(){
533534
* @return true if authenticated, false otherwise
534535
* @dochub authenticate
535536
*/
536-
public synchronized boolean isAuthenticated() {
537-
return ( _username != null );
537+
public boolean isAuthenticated() {
538+
return authorizationCredentialsReference.get() != null;
538539
}
539540

540541
/**
541542
* Authenticates to db with the given name and password
542543
*
543544
* @param username name of user for this database
544-
* @param passwd password of user for this database
545+
* @param password password of user for this database
545546
* @return true if authenticated, false otherwise
546547
* @throws MongoException
547548
* @dochub authenticate
548549
*/
549-
public synchronized boolean authenticate(String username, char[] passwd ){
550-
551-
if ( username == null || passwd == null )
552-
throw new NullPointerException( "username can't be null" );
550+
public boolean authenticate(String username, char[] password ){
553551

554-
if ( _username != null )
555-
throw new IllegalStateException( "can't call authenticate twice on the same DBObject" );
552+
if (authorizationCredentialsReference.get() != null) {
553+
throw new IllegalStateException("can't authenticate twice on the same database");
554+
}
556555

557-
String hash = _hash( username , passwd );
558-
CommandResult res = _doauth( username , hash.getBytes() );
559-
if ( !res.ok())
556+
AuthorizationCredentials newCredentials = new AuthorizationCredentials(username, password);
557+
CommandResult res = newCredentials.authorize();
558+
if (!res.ok())
560559
return false;
561-
_username = username;
562-
_authhash = hash.getBytes();
560+
561+
boolean wasNull = authorizationCredentialsReference.compareAndSet(null, newCredentials);
562+
if (!wasNull) {
563+
throw new IllegalStateException("can't authenticate twice on the same database");
564+
}
563565
return true;
564566
}
565567

566568
/**
567569
* Authenticates to db with the given name and password
568570
*
569571
* @param username name of user for this database
570-
* @param passwd password of user for this database
572+
* @param password password of user for this database
571573
* @return the CommandResult from authenticate command
572574
* @throws MongoException if authentication failed due to invalid user/pass, or other exceptions like I/O
573575
* @dochub authenticate
574576
*/
575-
public synchronized CommandResult authenticateCommand(String username, char[] passwd ){
576-
577-
if ( username == null || passwd == null )
578-
throw new NullPointerException( "username can't be null" );
577+
public synchronized CommandResult authenticateCommand(String username, char[] password ){
579578

580-
if ( _username != null )
581-
throw new IllegalStateException( "can't call authenticate twice on the same DBObject" );
579+
if (authorizationCredentialsReference.get() != null) {
580+
throw new IllegalStateException( "can't authenticate twice on the same database" );
581+
}
582582

583-
String hash = _hash( username , passwd );
584-
CommandResult res = _doauth( username , hash.getBytes() );
583+
AuthorizationCredentials newCredentials = new AuthorizationCredentials(username, password);
584+
CommandResult res = newCredentials.authorize();
585585
res.throwOnError();
586-
_username = username;
587-
_authhash = hash.getBytes();
586+
boolean wasNull = authorizationCredentialsReference.compareAndSet(null, newCredentials);
587+
if (!wasNull) {
588+
throw new IllegalStateException("can't authenticate twice on the same database");
589+
}
588590
return res;
589591
}
590592

591-
/*
592-
boolean reauth(){
593-
if ( _username == null || _authhash == null )
594-
throw new IllegalStateException( "no auth info!" );
595-
return _doauth( _username , _authhash );
596-
}
597-
*/
598-
599-
synchronized DBObject _authCommand( String nonce ){
600-
if ( _username == null || _authhash == null )
601-
throw new IllegalStateException( "no auth info!" );
602-
603-
return _authCommand( nonce , _username , _authhash );
604-
}
605-
606-
static DBObject _authCommand( String nonce , String username , byte[] hash ){
607-
String key = nonce + username + new String( hash );
608-
609-
BasicDBObject cmd = new BasicDBObject();
610-
611-
cmd.put("authenticate", 1);
612-
cmd.put("user", username);
613-
cmd.put("nonce", nonce);
614-
cmd.put("key", Util.hexMD5(key.getBytes()));
615-
616-
return cmd;
617-
}
618-
619-
private CommandResult _doauth( String username , byte[] hash ) {
620-
CommandResult res = command(new BasicDBObject("getnonce", 1));
621-
res.throwOnError();
622-
623-
DBObject cmd = _authCommand( res.getString( "nonce" ) , username , hash );
624-
return command(cmd);
625-
}
626-
627593
/**
628594
* Adds a new user for this db
629595
* @param username
@@ -739,7 +705,7 @@ public DB getSisterDB( String name ){
739705
* Makes it possible to execute "read" queries on a slave node
740706
*
741707
* @deprecated Replaced with ReadPreference.SECONDARY
742-
* @see com.mongodb.ReadPreference.SECONDARY
708+
* @see com.mongodb.ReadPreference#SECONDARY
743709
*/
744710
@Deprecated
745711
public void slaveOk(){
@@ -779,9 +745,9 @@ public int getOptions(){
779745

780746
public abstract void cleanCursors( boolean force );
781747

782-
synchronized String getUsername() {
783-
return _username;
784-
}
748+
AuthorizationCredentials getAuthorizationCredentials() {
749+
return authorizationCredentialsReference.get();
750+
}
785751

786752
final Mongo _mongo;
787753
final String _name;
@@ -791,7 +757,66 @@ synchronized String getUsername() {
791757
private com.mongodb.ReadPreference _readPref;
792758
final Bytes.OptionHolder _options;
793759

794-
private String _username;
795-
private byte[] _authhash = null;
760+
private AtomicReference<AuthorizationCredentials> authorizationCredentialsReference =
761+
new AtomicReference<AuthorizationCredentials>();
762+
763+
/**
764+
* Encapsulate everything relating to authorization of a user on a database
765+
*/
766+
class AuthorizationCredentials {
767+
private final String userName;
768+
private final byte[] authHash;
796769

770+
private AuthorizationCredentials(final String userName, final char[] password) {
771+
if (userName == null) {
772+
throw new IllegalArgumentException("userName can not be null");
773+
}
774+
if (password == null) {
775+
throw new IllegalArgumentException("password can not be null");
776+
}
777+
this.userName = userName;
778+
this.authHash = createHash(userName, password);
779+
}
780+
781+
CommandResult authorize() {
782+
CommandResult res = command(getNonceCommand());
783+
res.throwOnError();
784+
785+
return command(getAuthCommand(res.getString("nonce")));
786+
}
787+
788+
DBObject getAuthCommand( String nonce ){
789+
String key = nonce + userName + new String( authHash );
790+
791+
BasicDBObject cmd = new BasicDBObject();
792+
793+
cmd.put("authenticate", 1);
794+
cmd.put("user", userName);
795+
cmd.put("nonce", nonce);
796+
cmd.put("key", Util.hexMD5(key.getBytes()));
797+
798+
return cmd;
799+
}
800+
801+
BasicDBObject getNonceCommand() {
802+
return new BasicDBObject("getnonce", 1);
803+
}
804+
805+
private byte[] createHash( String userName , char[] password ){
806+
ByteArrayOutputStream bout = new ByteArrayOutputStream( userName.length() + 20 + password.length );
807+
try {
808+
bout.write(userName.getBytes());
809+
bout.write( ":mongo:".getBytes() );
810+
for (final char ch : password) {
811+
if (ch >= 128)
812+
throw new IllegalArgumentException("can't handle non-ascii passwords yet");
813+
bout.write((byte) ch);
814+
}
815+
}
816+
catch ( IOException ioe ){
817+
throw new RuntimeException( "impossible" , ioe );
818+
}
819+
return Util.hexMD5( bout.toByteArray() ).getBytes();
820+
}
821+
}
797822
}

src/main/com/mongodb/DBPort.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ protected void close(){
294294
}
295295

296296
void checkAuth( DB db ) throws IOException {
297-
if ( db.getUsername() == null ){
297+
DB.AuthorizationCredentials credentials = db.getAuthorizationCredentials();
298+
if ( credentials == null ){
298299
if ( db._name.equals( "admin" ) )
299300
return;
300301
checkAuth( db._mongo.getDB( "admin" ) );
@@ -303,14 +304,12 @@ void checkAuth( DB db ) throws IOException {
303304
if ( _authed.containsKey( db ) )
304305
return;
305306

306-
CommandResult res = runCommand( db , new BasicDBObject( "getnonce" , 1 ) );
307+
CommandResult res = runCommand( db , credentials.getNonceCommand() );
307308
res.throwOnError();
308309

309-
DBObject temp = db._authCommand( res.getString( "nonce" ) );
310-
311-
res = runCommand( db , temp );
312-
310+
res = runCommand( db , credentials.getAuthCommand( res.getString( "nonce" ) ) );
313311
res.throwOnError();
312+
314313
_authed.put( db , true );
315314
}
316315

0 commit comments

Comments
 (0)