From 7c0eee9e09059d742860871c84e03773f40cf32a Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 4 Nov 2013 13:29:26 +0100 Subject: [PATCH] DATAMONGO-789 - Support login via different authentication database. MongoDbUtils now supports to perform the authentication against a dedicated authenticationDatabase - if no authenticationDatabase is given explicitly then the given regular database will be used. The authentication database can be configured via the authentication-dbname attribute of the db-factory element in xml config or by overriding the getAuthenticationDatabaseName() method of AbstractMongoConfiguration. Original pull request: #92. --- .../config/AbstractMongoConfiguration.java | 20 +++--- .../mongodb/config/MongoDbFactoryParser.java | 13 ++-- .../data/mongodb/core/MongoAdmin.java | 15 ++++- .../data/mongodb/core/MongoDbUtils.java | 22 +++++-- .../mongodb/core/SimpleMongoDbFactory.java | 32 ++++++++-- .../data/mongodb/monitor/ServerInfo.java | 2 +- .../data/mongodb/config/spring-mongo-1.4.xsd | 7 +++ .../AbstractMongoConfigurationUnitTests.java | 8 +++ .../mongodb/config/MongoNamespaceTests.java | 18 ++++++ .../core/MongoDbUtilsIntegrationTests.java | 61 ++++++++++++++++--- .../core/SimpleMongoDbFactoryUnitTests.java | 10 +++ .../config/MongoNamespaceTests-context.xml | 8 +++ 12 files changed, 178 insertions(+), 38 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java index 51046e33f..3cf10c4f3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java @@ -49,6 +49,7 @@ import com.mongodb.Mongo; * * @author Mark Pollack * @author Oliver Gierke + * @author Thomas Darimont */ @Configuration public abstract class AbstractMongoConfiguration { @@ -60,6 +61,16 @@ public abstract class AbstractMongoConfiguration { */ protected abstract String getDatabaseName(); + /** + * Return the name of the authentication database to use. Defaults to {@literal null} and will turn into the value + * returned by {@link #getDatabaseName()} later on effectively. + * + * @return + */ + protected String getAuthenticationDatabaseName() { + return null; + } + /** * Return the {@link Mongo} instance to connect to. Annotate with {@link Bean} in case you want to expose a * {@link Mongo} instance to the {@link org.springframework.context.ApplicationContext}. @@ -91,14 +102,7 @@ public abstract class AbstractMongoConfiguration { */ @Bean public SimpleMongoDbFactory mongoDbFactory() throws Exception { - - UserCredentials credentials = getUserCredentials(); - - if (credentials == null) { - return new SimpleMongoDbFactory(mongo(), getDatabaseName()); - } else { - return new SimpleMongoDbFactory(mongo(), getDatabaseName(), credentials); - } + return new SimpleMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials(), getAuthenticationDatabaseName()); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java index 6e65a7c63..be6c1ff9e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 by the original author(s). + * Copyright 2011-2013 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import com.mongodb.MongoURI; * * @author Jon Brisbin * @author Oliver Gierke + * @author Thomas Darimont */ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser { @@ -70,6 +71,7 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser { String uri = element.getAttribute("uri"); String mongoRef = element.getAttribute("mongo-ref"); String dbname = element.getAttribute("dbname"); + BeanDefinition userCredentials = getUserCredentialsBeanDefinition(element, parserContext); // Common setup @@ -92,12 +94,9 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser { dbFactoryBuilder.addConstructorArgValue(registerMongoBeanDefinition(element, parserContext)); } - dbname = StringUtils.hasText(dbname) ? dbname : "db"; - dbFactoryBuilder.addConstructorArgValue(dbname); - - if (userCredentials != null) { - dbFactoryBuilder.addConstructorArgValue(userCredentials); - } + dbFactoryBuilder.addConstructorArgValue(StringUtils.hasText(dbname) ? dbname : "db"); + dbFactoryBuilder.addConstructorArgValue(userCredentials); + dbFactoryBuilder.addConstructorArgValue(element.getAttribute("authentication-dbname")); BeanDefinitionBuilder writeConcernPropertyEditorBuilder = getWriteConcernPropertyEditorBuilder(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java index 2406c7590..804ad5e59 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 the original author or authors. + * Copyright 2011-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import com.mongodb.Mongo; * Mongo server administration exposed via JMX annotations * * @author Mark Pollack + * @author Thomas Darimont */ @ManagedResource(description = "Mongo Admin Operations") public class MongoAdmin implements MongoAdminOperations { @@ -34,6 +35,7 @@ public class MongoAdmin implements MongoAdminOperations { private final Mongo mongo; private String username; private String password; + private String authenticationDatabaseName; public MongoAdmin(Mongo mongo) { Assert.notNull(mongo); @@ -82,7 +84,16 @@ public class MongoAdmin implements MongoAdminOperations { this.password = password; } + /** + * Sets the authenticationDatabaseName to use to authenticate with the Mongo database. + * + * @param authenticationDatabaseName The authenticationDatabaseName to use. + */ + public void setAuthenticationDatabaseName(String authenticationDatabaseName) { + this.authenticationDatabaseName = authenticationDatabaseName; + } + DB getDB(String databaseName) { - return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password)); + return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java index aaa653c48..11a46bda6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java @@ -33,6 +33,7 @@ import com.mongodb.Mongo; * @author Graeme Rocher * @author Oliver Gierke * @author Randy Watler + * @author Thomas Darimont * @since 1.0 */ public abstract class MongoDbUtils { @@ -54,7 +55,7 @@ public abstract class MongoDbUtils { * @return the {@link DB} connection */ public static DB getDB(Mongo mongo, String databaseName) { - return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true); + return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true, databaseName); } /** @@ -66,15 +67,22 @@ public abstract class MongoDbUtils { * @return the {@link DB} connection */ public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials) { + return getDB(mongo, databaseName, credentials, databaseName); + } + + public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials, + String authenticationDatabaseName) { Assert.notNull(mongo, "No Mongo instance specified!"); Assert.hasText(databaseName, "Database name must be given!"); Assert.notNull(credentials, "Credentials must not be null, use UserCredentials.NO_CREDENTIALS!"); + Assert.hasText(authenticationDatabaseName, "Authentication database name must not be null or empty!"); - return doGetDB(mongo, databaseName, credentials, true); + return doGetDB(mongo, databaseName, credentials, true, authenticationDatabaseName); } - private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate) { + private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate, + String authenticationDatabaseName) { DbHolder dbHolder = (DbHolder) TransactionSynchronizationManager.getResource(mongo); @@ -103,14 +111,16 @@ public abstract class MongoDbUtils { DB db = mongo.getDB(databaseName); boolean credentialsGiven = credentials.hasUsername() && credentials.hasPassword(); - synchronized (db) { + DB authDb = databaseName.equals(authenticationDatabaseName) ? db : mongo.getDB(authenticationDatabaseName); - if (credentialsGiven && !db.isAuthenticated()) { + synchronized (authDb) { + + if (credentialsGiven && !authDb.isAuthenticated()) { String username = credentials.getUsername(); String password = credentials.hasPassword() ? credentials.getPassword() : null; - if (!db.authenticate(username, password == null ? null : password.toCharArray())) { + if (!authDb.authenticate(username, password == null ? null : password.toCharArray())) { throw new CannotGetMongoDbConnectionException("Failed to authenticate to database [" + databaseName + "], " + credentials.toString(), databaseName, credentials); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java index 116676b5f..0a9d8e4b7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java @@ -23,6 +23,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.authentication.UserCredentials; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import com.mongodb.DB; import com.mongodb.Mongo; @@ -44,6 +45,8 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { private final boolean mongoInstanceCreated; private final UserCredentials credentials; private final PersistenceExceptionTranslator exceptionTranslator; + private final String authenticationDatabaseName; + private WriteConcern writeConcern; /** @@ -53,7 +56,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { * @param databaseName database name, not be {@literal null} or empty. */ public SimpleMongoDbFactory(Mongo mongo, String databaseName) { - this(mongo, databaseName, UserCredentials.NO_CREDENTIALS, false); + this(mongo, databaseName, null); } /** @@ -64,7 +67,20 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { * @param credentials username and password. */ public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials) { - this(mongo, databaseName, credentials, false); + this(mongo, databaseName, credentials, false, null); + } + + /** + * Create an instance of SimpleMongoDbFactory given the Mongo instance, database name, and username/password + * + * @param mongo Mongo instance, must not be {@literal null}. + * @param databaseName Database name, must not be {@literal null} or empty. + * @param credentials username and password. + * @param authenticationDatabaseName the database name to use for authentication + */ + public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials, + String authenticationDatabaseName) { + this(mongo, databaseName, credentials, false, authenticationDatabaseName); } /** @@ -77,11 +93,12 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { */ @SuppressWarnings("deprecation") public SimpleMongoDbFactory(MongoURI uri) throws MongoException, UnknownHostException { - this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), true); + this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), + true, uri.getDatabase()); } private SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials, - boolean mongoInstanceCreated) { + boolean mongoInstanceCreated, String authenticationDatabaseName) { Assert.notNull(mongo, "Mongo must not be null"); Assert.hasText(databaseName, "Database name must not be empty"); @@ -93,6 +110,11 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { this.mongoInstanceCreated = mongoInstanceCreated; this.credentials = credentials == null ? UserCredentials.NO_CREDENTIALS : credentials; this.exceptionTranslator = new MongoExceptionTranslator(); + this.authenticationDatabaseName = StringUtils.hasText(authenticationDatabaseName) ? authenticationDatabaseName + : databaseName; + + Assert.isTrue(this.authenticationDatabaseName.matches("[\\w-]+"), + "Authentication database name must only contain letters, numbers, underscores and dashes!"); } /** @@ -120,7 +142,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { Assert.hasText(dbName, "Database name must not be empty."); - DB db = MongoDbUtils.getDB(mongo, dbName, credentials); + DB db = MongoDbUtils.getDB(mongo, dbName, credentials, authenticationDatabaseName); if (writeConcern != null) { db.setWriteConcern(writeConcern); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/ServerInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/ServerInfo.java index 3381463d7..de2bb64dd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/ServerInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/ServerInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.4.xsd b/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.4.xsd index 27d9c86ad..12a864d3c 100644 --- a/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.4.xsd +++ b/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.4.xsd @@ -56,6 +56,13 @@ The name of the database to connect to. Default is 'db'. ]]> + + + + + () { - public Void doInDB(DB db) throws MongoException, DataAccessException { - db.addUser("admin", "admin".toCharArray()); - return null; - } - }); - factory = new ThreadPoolExecutorFactoryBean(); factory.setCorePoolSize(2); factory.setMaxPoolSize(10); @@ -96,6 +90,14 @@ public class MongoDbUtilsIntegrationTests { @Test public void authenticatesCorrectlyInMultithreadedEnvironment() throws Exception { + // Create sample user + template.execute(new DbCallback() { + public Void doInDB(DB db) throws MongoException, DataAccessException { + db.addUser("admin", "admin".toCharArray()); + return null; + } + }); + Callable callable = new Callable() { public Void call() throws Exception { @@ -122,4 +124,45 @@ public class MongoDbUtilsIntegrationTests { fail("Exception occurred!" + exception); } } + + /** + * @see DATAMONGO-789 + */ + @Test + public void authenticatesCorrectlyWithAuthenticationDB() throws Exception { + + // Create sample user + template.execute(new DbCallback() { + public Void doInDB(DB db) throws MongoException, DataAccessException { + db.getSisterDB("admin").addUser("admin", "admin".toCharArray()); + return null; + } + }); + + Callable callable = new Callable() { + public Void call() throws Exception { + + try { + DB db = MongoDbUtils.getDB(mongo, DATABASE_NAME, CREDENTIALS, AUTHENTICATION_DATABASE_NAME); + assertThat(db, is(notNullValue())); + } catch (Exception o_O) { + MongoDbUtilsIntegrationTests.this.exception = o_O; + } + + return null; + } + }; + + List> callables = new ArrayList>(); + + for (int i = 0; i < 10; i++) { + callables.add(callable); + } + + service.invokeAll(callables); + + if (exception != null) { + fail("Exception occurred!" + exception); + } + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java index b2c0c844d..89e176975 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java @@ -77,6 +77,16 @@ public class SimpleMongoDbFactoryUnitTests { assertThat(ReflectionTestUtils.getField(mongoDbFactory, "databaseName").toString(), is("myDatabase")); } + /** + * @see DATAMONGO-789 + */ + @Test + public void defaultsAuthenticationDatabaseToDatabase() { + + SimpleMongoDbFactory factory = new SimpleMongoDbFactory(mongo, "foo"); + assertThat(ReflectionTestUtils.getField(factory, "authenticationDatabaseName"), is((Object) "foo")); + } + private void rejectsDatabaseName(String databaseName) { try { diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceTests-context.xml index 4718bdc05..056c63812 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceTests-context.xml @@ -37,6 +37,14 @@ dbname="database" username="joe" password="secret"/> + +