summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2017-12-04 13:52:18 -0500
committerSara Golemon <sara.golemon@mongodb.com>2017-12-05 19:07:59 -0500
commita34fa65325dafc01857a4525d0d8b2f26b485965 (patch)
tree33b77aab43c0ae285ac97468e1c715dc721bfc7a
parent3d7be48d3b09db2c7ac723043b10c014430e85ed (diff)
downloadmongo-a34fa65325dafc01857a4525d0d8b2f26b485965.tar.gz
SERVER-6898 Enable listDatabases for all users
-rw-r--r--jstests/auth/lib/commands_lib.js12
-rw-r--r--jstests/auth/list_databases.js54
-rw-r--r--src/mongo/db/commands/list_databases.cpp48
-rw-r--r--src/mongo/s/commands/cluster_list_databases_cmd.cpp50
4 files changed, 123 insertions, 41 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 092856a4170..f79c527b2cf 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -3978,17 +3978,7 @@ var authCommandsLib = {
testcases: [
{
runOnDb: adminDbName,
- roles: {
- readAnyDatabase: 1,
- readWriteAnyDatabase: 1,
- dbAdminAnyDatabase: 1,
- userAdminAnyDatabase: 1,
- clusterMonitor: 1,
- clusterAdmin: 1,
- backup: 1,
- root: 1,
- __system: 1
- },
+ roles: roles_all,
privileges: [{resource: {cluster: true}, actions: ["listDatabases"]}]
},
{runOnDb: firstDbName, roles: {}},
diff --git a/jstests/auth/list_databases.js b/jstests/auth/list_databases.js
new file mode 100644
index 00000000000..5146328243d
--- /dev/null
+++ b/jstests/auth/list_databases.js
@@ -0,0 +1,54 @@
+// Auth tests for the listDatabases command.
+
+(function() {
+ 'use strict';
+
+ const mongod = MongoRunner.runMongod({auth: ""});
+ const admin = mongod.getDB('admin');
+
+ admin.createUser({user: 'admin', pwd: 'pass', roles: jsTest.adminUserRoles});
+ assert(admin.auth('admin', 'pass'));
+
+ // Establish db0..db7
+ for (let i = 0; i < 8; ++i) {
+ mongod.getDB('db' + i).foo.insert({bar: "baz"});
+ }
+
+ // Make db0, db2, db4, db6 readable to user1 abd user3.
+ // Make db0, db1, db2, db3 read/writable to user 2 and user3.
+ function makeRole(perm, dbNum) {
+ return {role: perm, db: ("db" + dbNum)};
+ }
+ const readEven = [0, 2, 4, 6].map(function(i) {
+ return makeRole("read", i);
+ });
+ const readWriteLow = [0, 1, 2, 3].map(function(i) {
+ return makeRole("readWrite", i);
+ });
+ admin.createUser({user: 'user1', pwd: 'pass', roles: readEven});
+ admin.createUser({user: 'user2', pwd: 'pass', roles: readWriteLow});
+ admin.createUser({user: 'user3', pwd: 'pass', roles: readEven.concat(readWriteLow)});
+ admin.logout();
+
+ [{user: "user1", dbs: ["db0", "db2", "db4", "db6"]},
+ {user: "user2", dbs: ["db0", "db1", "db2", "db3"]},
+ {user: "user3", dbs: ["db0", "db1", "db2", "db3", "db4", "db6"]},
+ {
+ user: "admin",
+ dbs: ["admin", "db0", "db1", "db2", "db3", "db4", "db5", "db6", "db7", "local"]
+ },
+ ].forEach(function(test) {
+ admin.auth(test.user, 'pass');
+ const dbs = assert.commandWorked(admin.runCommand({listDatabases: 1}));
+ assert.eq(dbs.databases
+ .map(function(db) {
+ return db.name;
+ })
+ .sort(),
+ test.dbs,
+ test.user + " permissions");
+ admin.logout();
+ });
+
+ MongoRunner.stopMongod(mongod);
+})();
diff --git a/src/mongo/db/commands/list_databases.cpp b/src/mongo/db/commands/list_databases.cpp
index fb719e1bda6..15d1139df8b 100644
--- a/src/mongo/db/commands/list_databases.cpp
+++ b/src/mongo/db/commands/list_databases.cpp
@@ -28,6 +28,7 @@
* it in the license file.
*/
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/database.h"
#include "mongo/db/catalog/database_catalog_entry.h"
#include "mongo/db/catalog/database_holder.h"
@@ -56,28 +57,32 @@ intmax_t dbSize(const string& database);
class CmdListDatabases : public BasicCommand {
public:
- virtual bool slaveOk() const {
+ bool slaveOk() const final {
return false;
}
- virtual bool slaveOverrideOk() const {
+ bool slaveOverrideOk() const final {
return true;
}
- virtual bool adminOnly() const {
+ bool adminOnly() const final {
return true;
}
- virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
+ bool supportsWriteConcern(const BSONObj& cmd) const final {
return false;
}
- virtual void help(stringstream& help) const {
+ void help(stringstream& help) const final {
help << "{ listDatabases:1, [filter: <filterObject>] [, nameOnly: true ] }\n"
"list databases on this server";
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::listDatabases);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+
+ /* listDatabases is always authorized,
+ * however the results returned will be redacted
+ * based on read privileges if auth is enabled
+ * and the current user does not have listDatabases permisison.
+ */
+ Status checkAuthForCommand(Client* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) final {
+ return Status::OK();
}
CmdListDatabases() : BasicCommand("listDatabases") {}
@@ -85,7 +90,7 @@ public:
bool run(OperationContext* opCtx,
const string& dbname,
const BSONObj& jsobj,
- BSONObjBuilder& result) {
+ BSONObjBuilder& result) final {
// Parse the filter.
std::unique_ptr<MatchExpression> filter;
if (auto filterElt = jsobj[kFilterField]) {
@@ -118,12 +123,25 @@ public:
vector<BSONObj> dbInfos;
- bool filterNameOnly = filter &&
+ // If we have ActionType::listDatabases,
+ // then we don't need to test each record in the output.
+ // Otherwise, we'll test the database names as we enumerate them.
+ const auto as = AuthorizationSession::get(opCtx->getClient());
+ const bool checkAuth = as &&
+ !as->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
+ ActionType::listDatabases);
+
+ const bool filterNameOnly = filter &&
filter->getCategory() == MatchExpression::MatchCategory::kLeaf &&
filter->path() == kNameField;
intmax_t totalSize = 0;
- for (vector<string>::iterator i = dbNames.begin(); i != dbNames.end(); ++i) {
- const string& dbname = *i;
+ for (const auto& dbname : dbNames) {
+ if (checkAuth && as &&
+ !as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname),
+ ActionType::find)) {
+ // We don't have listDatabases on the cluser or find on this database.
+ continue;
+ }
BSONObjBuilder b;
b.append("name", dbname);
diff --git a/src/mongo/s/commands/cluster_list_databases_cmd.cpp b/src/mongo/s/commands/cluster_list_databases_cmd.cpp
index 84f2cdc2ed5..cc61afaa7e6 100644
--- a/src/mongo/s/commands/cluster_list_databases_cmd.cpp
+++ b/src/mongo/s/commands/cluster_list_databases_cmd.cpp
@@ -35,6 +35,7 @@
#include "mongo/bson/util/bson_extract.h"
#include "mongo/client/read_preference.h"
#include "mongo/client/remote_command_targeter.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
#include "mongo/s/catalog/sharding_catalog_client.h"
#include "mongo/s/client/shard.h"
@@ -55,39 +56,43 @@ class ListDatabasesCmd : public BasicCommand {
public:
ListDatabasesCmd() : BasicCommand("listDatabases", "listdatabases") {}
- virtual bool slaveOk() const {
+ bool slaveOk() const final {
return true;
}
- virtual bool slaveOverrideOk() const {
+ bool slaveOverrideOk() const final {
return true;
}
- virtual bool adminOnly() const {
+ bool adminOnly() const final {
return true;
}
- virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
+ bool supportsWriteConcern(const BSONObj& cmd) const final {
return false;
}
- virtual void help(std::stringstream& help) const {
+ void help(std::stringstream& help) const final {
help << "list databases in a cluster";
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::listDatabases);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ /* listDatabases is always authorized,
+ * however the results returned will be redacted
+ * based on read privileges if auth is enabled
+ * and the current user does not have listDatabases permisison.
+ */
+ Status checkAuthForCommand(Client* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) final {
+ return Status::OK();
}
- virtual bool run(OperationContext* opCtx,
- const std::string& dbname_unused,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) {
+
+ bool run(OperationContext* opCtx,
+ const std::string& dbname_unused,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) final {
const bool nameOnly = cmdObj["nameOnly"].trueValue();
map<string, long long> sizes;
@@ -151,6 +156,14 @@ public:
}
}
+ // If we have ActionType::listDatabases,
+ // then we don't need to test each record in the output.
+ // Otherwise, we'll test the database names as we enumerate them.
+ const auto as = AuthorizationSession::get(opCtx->getClient());
+ const bool checkAuth = as &&
+ !as->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
+ ActionType::listDatabases);
+
// Now that we have aggregated results for all the shards, convert to a response,
// and compute total sizes.
long long totalSize = 0;
@@ -164,6 +177,13 @@ public:
continue;
}
+ if (checkAuth && as &&
+ !as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(name),
+ ActionType::find)) {
+ // We don't have listDatabases on the cluser or find on this database.
+ continue;
+ }
+
long long size = i->second;
BSONObjBuilder temp;