summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/commands_builtin_roles.js8
-rw-r--r--jstests/auth/commands_user_defined_roles.js18
-rw-r--r--jstests/auth/lib/commands_lib.js93
-rw-r--r--src/mongo/db/auth/action_types.txt1
-rw-r--r--src/mongo/db/auth/authorization_session.cpp15
-rw-r--r--src/mongo/db/auth/authorization_session.h4
-rw-r--r--src/mongo/db/auth/authorization_session_test.cpp29
-rw-r--r--src/mongo/db/auth/role_graph_builtin_roles.cpp3
-rw-r--r--src/mongo/db/commands/count_cmd.cpp22
-rw-r--r--src/mongo/db/commands/find_cmd.cpp15
-rw-r--r--src/mongo/db/commands/list_indexes.cpp4
-rw-r--r--src/mongo/db/commands/parallel_collection_scan.cpp24
12 files changed, 208 insertions, 28 deletions
diff --git a/jstests/auth/commands_builtin_roles.js b/jstests/auth/commands_builtin_roles.js
index 6de64cc5495..14da86a505b 100644
--- a/jstests/auth/commands_builtin_roles.js
+++ b/jstests/auth/commands_builtin_roles.js
@@ -48,10 +48,14 @@ function testProperAuthorization(conn, t, testcase, r) {
var out = "";
var runOnDb = conn.getDB(testcase.runOnDb);
- authCommandsLib.setup(conn, t, runOnDb);
+ var state = authCommandsLib.setup(conn, t, runOnDb);
assert(r.db.auth("user|" + r.key, "password"));
authCommandsLib.authenticatedSetup(t, runOnDb);
- var res = runOnDb.runCommand(t.command);
+ var command = t.command;
+ if (typeof(command) === "function") {
+ command = t.command(state);
+ }
+ var res = runOnDb.runCommand(command);
if (testcase.roles[r.key]) {
if (res.ok == 0 && res.code == authErrCode) {
diff --git a/jstests/auth/commands_user_defined_roles.js b/jstests/auth/commands_user_defined_roles.js
index 6f5fd96b87b..3c8de27192f 100644
--- a/jstests/auth/commands_user_defined_roles.js
+++ b/jstests/auth/commands_user_defined_roles.js
@@ -23,7 +23,7 @@ function testProperAuthorization(conn, t, testcase, privileges) {
var firstDb = conn.getDB(firstDbName);
var adminDb = conn.getDB(adminDbName);
- authCommandsLib.setup(conn, t, runOnDb);
+ var state = authCommandsLib.setup(conn, t, runOnDb);
adminDb.auth("admin", "password");
assert.commandWorked(adminDb.runCommand({updateRole: testRole, privileges: privileges}));
@@ -32,7 +32,12 @@ function testProperAuthorization(conn, t, testcase, privileges) {
assert(adminDb.auth(testUser, "password"));
authCommandsLib.authenticatedSetup(t, runOnDb);
- var res = runOnDb.runCommand(t.command);
+
+ var command = t.command;
+ if (typeof(command) === "function") {
+ command = t.command(state);
+ }
+ var res = runOnDb.runCommand(command);
if (!testcase.expectFail && res.ok != 1 && res.code != commandNotSupportedCode) {
// don't error if the test failed with code commandNotSupported since
@@ -56,7 +61,7 @@ function testInsufficientPrivileges(conn, t, testcase, privileges) {
var firstDb = conn.getDB(firstDbName);
var adminDb = conn.getDB(adminDbName);
- authCommandsLib.setup(conn, t, runOnDb);
+ var state = authCommandsLib.setup(conn, t, runOnDb);
adminDb.auth("admin", "password");
assert.commandWorked(adminDb.runCommand({updateRole: testRole, privileges: privileges}));
@@ -65,7 +70,12 @@ function testInsufficientPrivileges(conn, t, testcase, privileges) {
assert(adminDb.auth(testUser, "password"));
authCommandsLib.authenticatedSetup(t, runOnDb);
- var res = runOnDb.runCommand(t.command);
+
+ var command = t.command;
+ if (typeof(command) === "function") {
+ command = t.command(state);
+ }
+ var res = runOnDb.runCommand(command);
if (res.ok == 1 || res.code != authErrCode) {
out = "expected authorization failure " + " but received " + tojson(res) +
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 7cde9677e96..279b0d7f41d 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -163,6 +163,8 @@ var roles_all = {
__system: 1
};
+load("jstests/libs/uuid_util.js");
+
var authCommandsLib = {
/************* TEST CASES ****************/
@@ -1731,6 +1733,31 @@ var authCommandsLib = {
]
},
{
+ testname: "countWithUUID",
+ command: function(state) {
+ return {count: state.uuid};
+ },
+ skipSharded: true,
+ setup: function(db) {
+ db.runCommand({create: "foo"});
+
+ return {uuid: getUUIDFromListCollections(db, db.foo.getName())};
+ },
+ teardown: function(db) {
+ db.foo.drop();
+ },
+ testcases: [
+ {
+ runOnDb: firstDbName,
+ roles: {__system: 1, root: 1, backup: 1},
+ privileges: [
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {cluster: true}, actions: ["useUUID"]}
+ ],
+ },
+ ]
+ },
+ {
testname: "_configsvrCommitChunkMerge",
command: {_configsvrCommitChunkMerge: "x.y"},
skipSharded: true,
@@ -2647,6 +2674,31 @@ var authCommandsLib = {
]
},
{
+ testname: "findWithUUID",
+ command: function(state) {
+ return {find: state.uuid};
+ },
+ skipSharded: true,
+ setup: function(db) {
+ db.runCommand({create: "foo"});
+
+ return {uuid: getUUIDFromListCollections(db, db.foo.getName())};
+ },
+ teardown: function(db) {
+ db.foo.drop();
+ },
+ testcases: [
+ {
+ runOnDb: firstDbName,
+ roles: {__system: 1, root: 1, backup: 1},
+ privileges: [
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {cluster: true}, actions: ["useUUID"]}
+ ],
+ },
+ ],
+ },
+ {
testname: "findAndModify",
command: {findAndModify: "x", query: {_id: "abc"}, update: {$inc: {n: 1}}},
setup: function(db) {
@@ -3341,6 +3393,42 @@ var authCommandsLib = {
]
},
{
+ testname: "listIndexesWithUUID",
+ command: function(state) {
+ return {listIndexes: state.uuid};
+ },
+ skipSharded: true,
+ setup: function(db) {
+ db.x.insert({_id: 5});
+ db.x.insert({_id: 6});
+
+ return {uuid: getUUIDFromListCollections(db, db.x.getName())};
+ },
+ teardown: function(db) {
+ db.x.drop();
+ },
+ testcases: [
+ {
+ runOnDb: firstDbName,
+ roles: {backup: 1, root: 1, __system: 1},
+ privileges: [
+ {resource: {db: firstDbName, collection: ""}, actions: ["listIndexes"]},
+ {resource: {cluster: true}, actions: ["useUUID"]}
+ ]
+ },
+ // Test legacy (pre 3.0) way of authorizing listIndexes.
+ {
+ runOnDb: firstDbName,
+ privileges: [
+ {resource: {db: firstDbName, collection: "system.indexes"}, actions: ["find"]},
+ {resource: {cluster: true}, actions: ["useUUID"]}
+
+ ]
+ }
+ ]
+ },
+
+ {
testname: "listShards",
command: {listShards: 1},
skipStandalone: true,
@@ -4511,10 +4599,13 @@ var authCommandsLib = {
var adminDb = conn.getDB(adminDbName);
if (t.setup) {
adminDb.auth("admin", "password");
- t.setup(runOnDb);
+ var state = t.setup(runOnDb);
runOnDb.getLastError();
adminDb.logout();
+ return state;
}
+
+ return {};
},
authenticatedSetup: function(t, runOnDb) {
diff --git a/src/mongo/db/auth/action_types.txt b/src/mongo/db/auth/action_types.txt
index 1f57ba6084b..88d777a4950 100644
--- a/src/mongo/db/auth/action_types.txt
+++ b/src/mongo/db/auth/action_types.txt
@@ -106,6 +106,7 @@
"top",
"touch",
"unlock",
+"useUUID",
"update",
"updateRole", # Not used for permissions checks, but to id the event in logs.
"updateUser", # Not used for permissions checks, but to id the event in logs.
diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp
index 023d5fc02a7..15ee3480a88 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -601,6 +601,21 @@ Status AuthorizationSession::checkAuthorizedToRevokePrivilege(const Privilege& p
return Status::OK();
}
+bool AuthorizationSession::isAuthorizedToParseNamespaceElement(const BSONElement& element) {
+ const bool isUUID = element.type() == BinData && element.binDataType() == BinDataType::newUUID;
+
+ uassert(ErrorCodes::InvalidNamespace,
+ "Failed to parse namespace element",
+ element.type() == String || isUUID);
+
+ if (isUUID) {
+ return isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
+ ActionType::useUUID);
+ }
+
+ return true;
+}
+
bool AuthorizationSession::isAuthorizedToCreateRole(
const struct auth::CreateOrUpdateRoleArgs& args) {
// A user is allowed to create a role under either of two conditions.
diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h
index 1f41180df18..a1e159c80de 100644
--- a/src/mongo/db/auth/authorization_session.h
+++ b/src/mongo/db/auth/authorization_session.h
@@ -227,6 +227,10 @@ public:
// Checks if this connection is using the localhost bypass
bool isUsingLocalhostBypass();
+ // Checks if this connection has the privileges necessary to parse a namespace from a
+ // given BSONElement.
+ bool isAuthorizedToParseNamespaceElement(const BSONElement& elem);
+
// Checks if this connection has the privileges necessary to create a new role
bool isAuthorizedToCreateRole(const auth::CreateOrUpdateRoleArgs& args);
diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp
index 308156c31ee..0ec66e94598 100644
--- a/src/mongo/db/auth/authorization_session_test.cpp
+++ b/src/mongo/db/auth/authorization_session_test.cpp
@@ -1278,5 +1278,34 @@ TEST_F(AuthorizationSessionTest, CanListCollectionsWithListCollectionsPrivilege)
ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testQuxNss.db()));
}
+TEST_F(AuthorizationSessionTest, CanUseUUIDNamespacesWithPrivilege) {
+ BSONObj stringObj = BSON("a"
+ << "string");
+ BSONObj uuidObj = BSON("a" << UUID::gen());
+ BSONObj invalidObj = BSON("a" << 12);
+
+ // Strings require no privileges
+ ASSERT_TRUE(authzSession->isAuthorizedToParseNamespaceElement(stringObj.firstElement()));
+
+ // UUIDs cannot be parsed with default privileges
+ ASSERT_FALSE(authzSession->isAuthorizedToParseNamespaceElement(uuidObj.firstElement()));
+
+ // Element must be either a string, or a UUID
+ ASSERT_THROWS_CODE(authzSession->isAuthorizedToParseNamespaceElement(invalidObj.firstElement()),
+ AssertionException,
+ ErrorCodes::InvalidNamespace);
+
+ // The useUUID privilege allows UUIDs to be parsed
+ authzSession->assumePrivilegesForDB(
+ Privilege(ResourcePattern::forClusterResource(), {ActionType::useUUID}));
+
+ ASSERT_TRUE(authzSession->isAuthorizedToParseNamespaceElement(stringObj.firstElement()));
+ ASSERT_TRUE(authzSession->isAuthorizedToParseNamespaceElement(uuidObj.firstElement()));
+ ASSERT_THROWS_CODE(authzSession->isAuthorizedToParseNamespaceElement(invalidObj.firstElement()),
+ AssertionException,
+ ErrorCodes::InvalidNamespace);
+}
+
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp
index 31e8e1234ec..94b14014549 100644
--- a/src/mongo/db/auth/role_graph_builtin_roles.cpp
+++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp
@@ -194,6 +194,7 @@ MONGO_INITIALIZER(AuthorizationBuiltinRoles)(InitializerContext* context) {
<< ActionType::replSetGetStatus // clusterManager gets this also
<< ActionType::serverStatus
<< ActionType::top
+ << ActionType::useUUID
<< ActionType::inprog
<< ActionType::shardingState;
@@ -499,7 +500,7 @@ void addQueryableBackupPrivileges(PrivilegeVector* privileges) {
ActionSet clusterActions;
clusterActions << ActionType::getParameter // To check authSchemaVersion
- << ActionType::listDatabases;
+ << ActionType::listDatabases << ActionType::useUUID;
Privilege::addPrivilegeToPrivilegeVector(
privileges, Privilege(ResourcePattern::forClusterResource(), clusterActions));
diff --git a/src/mongo/db/commands/count_cmd.cpp b/src/mongo/db/commands/count_cmd.cpp
index 9ce6c54675e..d70c9ce6c80 100644
--- a/src/mongo/db/commands/count_cmd.cpp
+++ b/src/mongo/db/commands/count_cmd.cpp
@@ -30,6 +30,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/client.h"
#include "mongo/db/commands.h"
#include "mongo/db/commands/run_aggregate.h"
@@ -91,12 +92,21 @@ public:
help << "count objects in collection";
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+
+ if (!authSession->isAuthorizedToParseNamespaceElement(cmdObj.firstElement())) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ const NamespaceString nss(parseNsOrUUID(opCtx, dbname, cmdObj));
+ if (!authSession->isAuthorizedForActionsOnNamespace(nss, ActionType::find)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ return Status::OK();
}
virtual Status explain(OperationContext* opCtx,
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index cbe0ce20d55..d3d9aafc096 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -123,12 +123,17 @@ public:
return false;
}
- Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) override {
- const NamespaceString nss(parseNs(dbname, cmdObj));
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+
+ if (!authSession->isAuthorizedToParseNamespaceElement(cmdObj.firstElement())) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ const NamespaceString nss(parseNsOrUUID(opCtx, dbname, cmdObj));
auto hasTerm = cmdObj.hasField(kTermField);
- return AuthorizationSession::get(client)->checkAuthForFind(nss, hasTerm);
+ return authSession->checkAuthForFind(nss, hasTerm);
}
Status explain(OperationContext* opCtx,
diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp
index 75d79099c39..d4e77888247 100644
--- a/src/mongo/db/commands/list_indexes.cpp
+++ b/src/mongo/db/commands/list_indexes.cpp
@@ -98,6 +98,10 @@ public:
const BSONObj& cmdObj) {
AuthorizationSession* authzSession = AuthorizationSession::get(client);
+ if (!authzSession->isAuthorizedToParseNamespaceElement(cmdObj.firstElement())) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
// Check for the listIndexes ActionType on the database, or find on system.indexes for pre
// 3.0 systems.
const NamespaceString ns(parseNsOrUUID(client->getOperationContext(), dbname, cmdObj));
diff --git a/src/mongo/db/commands/parallel_collection_scan.cpp b/src/mongo/db/commands/parallel_collection_scan.cpp
index 6b84996369c..533daa6ab94 100644
--- a/src/mongo/db/commands/parallel_collection_scan.cpp
+++ b/src/mongo/db/commands/parallel_collection_scan.cpp
@@ -74,15 +74,21 @@ public:
return ReadWriteType::kCommand;
}
- virtual Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- Privilege p(parseResourcePattern(dbname, cmdObj), actions);
- if (AuthorizationSession::get(client)->isAuthorizedForPrivilege(p))
- return Status::OK();
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+
+ if (!authSession->isAuthorizedToParseNamespaceElement(cmdObj.firstElement())) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ const NamespaceString ns(parseNsOrUUID(opCtx, dbname, cmdObj));
+ if (!authSession->isAuthorizedForActionsOnNamespace(ns, ActionType::find)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ return Status::OK();
}
virtual bool run(OperationContext* opCtx,