summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2020-10-27 17:00:08 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-10 22:43:55 +0000
commite73f614e223781e613453a795e99d1d494ec44b7 (patch)
tree061783da52e2df6aee73f111a35a2a4206ecf652
parent458b245815739fa15b9f18543e343444b6129c74 (diff)
downloadmongo-e73f614e223781e613453a795e99d1d494ec44b7.tar.gz
SERVER-51866 IDLify MergeAuthzCollections command
-rw-r--r--src/mongo/db/auth/user_management_commands_parser.cpp45
-rw-r--r--src/mongo/db/auth/user_management_commands_parser.h20
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp670
-rw-r--r--src/mongo/db/commands/user_management_commands.idl24
-rw-r--r--src/mongo/db/commands/user_management_commands_common.cpp51
-rw-r--r--src/mongo/db/commands/user_management_commands_common.h3
-rw-r--r--src/mongo/s/commands/cluster_user_management_commands.cpp50
7 files changed, 357 insertions, 506 deletions
diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp
index 22ac894c885..c7de4541041 100644
--- a/src/mongo/db/auth/user_management_commands_parser.cpp
+++ b/src/mongo/db/auth/user_management_commands_parser.cpp
@@ -186,50 +186,5 @@ Status parseAndValidatePrivilegeArray(const BSONArray& privileges,
return Status::OK();
}
-Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj,
- MergeAuthzCollectionsArgs* parsedArgs) {
- stdx::unordered_set<std::string> validFieldNames;
- validFieldNames.insert("_mergeAuthzCollections");
- validFieldNames.insert("tempUsersCollection");
- validFieldNames.insert("tempRolesCollection");
- validFieldNames.insert("db");
- validFieldNames.insert("drop");
-
- Status status = _checkNoExtraFields(cmdObj, "_mergeAuthzCollections", validFieldNames);
- if (!status.isOK()) {
- return status;
- }
-
- status = bsonExtractStringFieldWithDefault(
- cmdObj, "tempUsersCollection", "", &parsedArgs->usersCollName);
- if (!status.isOK()) {
- return status;
- }
-
- status = bsonExtractStringFieldWithDefault(
- cmdObj, "tempRolesCollection", "", &parsedArgs->rolesCollName);
- if (!status.isOK()) {
- return status;
- }
-
- status = bsonExtractStringField(cmdObj, "db", &parsedArgs->db);
- if (!status.isOK()) {
- if (status == ErrorCodes::NoSuchKey) {
- return Status(ErrorCodes::OutdatedClient,
- "Missing \"db\" field for _mergeAuthzCollections command. This is "
- "most likely due to running an outdated (pre-2.6.4) version of "
- "mongorestore.");
- }
- return status;
- }
-
- status = bsonExtractBooleanFieldWithDefault(cmdObj, "drop", false, &parsedArgs->drop);
- if (!status.isOK()) {
- return status;
- }
-
- return Status::OK();
-}
-
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h
index f5f4a49f27c..639c6cde630 100644
--- a/src/mongo/db/auth/user_management_commands_parser.h
+++ b/src/mongo/db/auth/user_management_commands_parser.h
@@ -70,25 +70,5 @@ Status parseUserNamesFromBSONArray(const BSONArray& usersArray,
StringData dbname,
std::vector<UserName>* parsedUserNames);
-struct MergeAuthzCollectionsArgs {
- std::string usersCollName;
- std::string rolesCollName;
- std::string db;
- bool drop;
-
- MergeAuthzCollectionsArgs() : drop(false) {}
-};
-
-/**
- * Takes a command object describing an invocation of the "_mergeAuthzCollections" command and
- * parses out the name of the temporary collections to use for user and role data, whether or
- * not to drop the existing users/roles, the database if this is a for a db-specific restore.
- * Returns ErrorCodes::OutdatedClient if the "db" field is missing, as that likely indicates
- * the command was sent by an outdated (pre 2.6.4) version of mongorestore.
- * Returns other codes indicating missing or incorrectly typed fields.
- */
-Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj,
- MergeAuthzCollectionsArgs* parsedArgs);
-
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index b0d55c65396..c19b3ab9efc 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -1991,407 +1991,351 @@ CmdUMCTyped<GetUserCacheGenerationCommand, GetUserCacheGenerationReply, UMCCache
* It either adds the users/roles to the existing ones or replaces the existing ones, depending
* on whether the "drop" argument is true or false.
*/
-class CmdMergeAuthzCollections : public BasicCommand {
+class CmdMergeAuthzCollections : public TypedCommand<CmdMergeAuthzCollections> {
public:
- CmdMergeAuthzCollections() : BasicCommand("_mergeAuthzCollections") {}
+ using Request = MergeAuthzCollectionsCommand;
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kNever;
- }
+ class Invocation final : public InvocationBase {
+ public:
+ using InvocationBase::InvocationBase;
+ void typedRun(OperationContext* opCtx);
- bool supportsWriteConcern(const BSONObj& cmd) const override {
- return true;
+ private:
+ bool supportsWriteConcern() const final {
+ return true;
+ }
+
+ void doCheckAuthorization(OperationContext* opCtx) const final {
+ auth::checkAuthForTypedCommand(opCtx->getClient(), request());
+ }
+
+ NamespaceString ns() const override {
+ return NamespaceString(request().getDbName(), "");
+ }
+ };
+
+ AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
+ return AllowedOnSecondary::kNever;
}
bool adminOnly() const {
return true;
}
+} cmdMergeAuthzCollections;
- std::string help() const override {
- return "Internal command used by mongorestore for updating user/role data";
- }
-
- Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) const override {
- return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj);
- }
-
- static UserName extractUserNameFromBSON(const BSONObj& userObj) {
- std::string name;
- std::string db;
- Status status =
- bsonExtractStringField(userObj, AuthorizationManager::USER_NAME_FIELD_NAME, &name);
- uassertStatusOK(status);
- status = bsonExtractStringField(userObj, AuthorizationManager::USER_DB_FIELD_NAME, &db);
- uassertStatusOK(status);
- return UserName(name, db);
- }
-
- static RoleName extractRoleNameFromBSON(const BSONObj& roleObj) {
- std::string name;
- std::string db;
- Status status =
- bsonExtractStringField(roleObj, AuthorizationManager::ROLE_NAME_FIELD_NAME, &name);
- uassertStatusOK(status);
- status = bsonExtractStringField(roleObj, AuthorizationManager::ROLE_DB_FIELD_NAME, &db);
- uassertStatusOK(status);
- return RoleName(name, db);
- }
-
- /**
- * Audits the fact that we are creating or updating the user described by userObj.
- */
- static void auditCreateOrUpdateUser(const BSONObj& userObj, bool create) {
- UserName userName = extractUserNameFromBSON(userObj);
- std::vector<RoleName> roles;
- uassertStatusOK(auth::parseRoleNamesFromBSONArray(
- BSONArray(userObj["roles"].Obj()), userName.getDB(), &roles));
- BSONObj customData;
- if (userObj.hasField("customData")) {
- customData = userObj["customData"].Obj();
- }
+UserName _extractUserNameFromBSON(const BSONObj& userObj) {
+ std::string name;
+ std::string db;
+ uassertStatusOK(
+ bsonExtractStringField(userObj, AuthorizationManager::USER_NAME_FIELD_NAME, &name));
+ uassertStatusOK(bsonExtractStringField(userObj, AuthorizationManager::USER_DB_FIELD_NAME, &db));
+ return UserName(name, db);
+}
- boost::optional<BSONArray> authenticationRestrictions;
- if (userObj.hasField("authenticationRestrictions")) {
- auto r = getRawAuthenticationRestrictions(
- BSONArray(userObj["authenticationRestrictions"].Obj()));
- uassertStatusOK(r);
- authenticationRestrictions = r.getValue();
- }
+RoleName _extractRoleNameFromBSON(const BSONObj& roleObj) {
+ std::string name;
+ std::string db;
+ uassertStatusOK(
+ bsonExtractStringField(roleObj, AuthorizationManager::ROLE_NAME_FIELD_NAME, &name));
+ uassertStatusOK(bsonExtractStringField(roleObj, AuthorizationManager::ROLE_DB_FIELD_NAME, &db));
+ return RoleName(name, db);
+}
- const bool hasPwd = userObj["credentials"].Obj().hasField("SCRAM-SHA-1") ||
- userObj["credentials"].Obj().hasField("SCRAM-SHA-256");
- if (create) {
- audit::logCreateUser(Client::getCurrent(),
- userName,
- hasPwd,
- userObj.hasField("customData") ? &customData : nullptr,
- roles,
- authenticationRestrictions);
- } else {
- audit::logUpdateUser(Client::getCurrent(),
- userName,
- hasPwd,
- userObj.hasField("customData") ? &customData : nullptr,
- &roles,
- authenticationRestrictions);
- }
+/**
+ * Audits the fact that we are creating or updating the user described by userObj.
+ */
+void _auditCreateOrUpdateUser(const BSONObj& userObj, bool create) {
+ UserName userName = _extractUserNameFromBSON(userObj);
+ std::vector<RoleName> roles;
+ uassertStatusOK(auth::parseRoleNamesFromBSONArray(
+ BSONArray(userObj["roles"].Obj()), userName.getDB(), &roles));
+ BSONObj customData;
+ if (userObj.hasField("customData")) {
+ customData = userObj["customData"].Obj();
+ }
+
+ boost::optional<BSONArray> authenticationRestrictions;
+ if (userObj.hasField("authenticationRestrictions")) {
+ auto r = getRawAuthenticationRestrictions(
+ BSONArray(userObj["authenticationRestrictions"].Obj()));
+ uassertStatusOK(r);
+ authenticationRestrictions = r.getValue();
+ }
+
+ const bool hasPwd = userObj["credentials"].Obj().hasField("SCRAM-SHA-1") ||
+ userObj["credentials"].Obj().hasField("SCRAM-SHA-256");
+ if (create) {
+ audit::logCreateUser(Client::getCurrent(),
+ userName,
+ hasPwd,
+ userObj.hasField("customData") ? &customData : nullptr,
+ roles,
+ authenticationRestrictions);
+ } else {
+ audit::logUpdateUser(Client::getCurrent(),
+ userName,
+ hasPwd,
+ userObj.hasField("customData") ? &customData : nullptr,
+ &roles,
+ authenticationRestrictions);
}
+}
- /**
- * Audits the fact that we are creating or updating the role described by roleObj.
- */
- static void auditCreateOrUpdateRole(const BSONObj& roleObj, bool create) {
- RoleName roleName = extractRoleNameFromBSON(roleObj);
- std::vector<RoleName> roles;
- std::vector<Privilege> privileges;
- uassertStatusOK(auth::parseRoleNamesFromBSONArray(
- BSONArray(roleObj["roles"].Obj()), roleName.getDB(), &roles));
- uassertStatusOK(auth::parseAndValidatePrivilegeArray(BSONArray(roleObj["privileges"].Obj()),
- &privileges));
-
- boost::optional<BSONArray> authenticationRestrictions;
- if (roleObj.hasField("authenticationRestrictions")) {
- auto r = getRawAuthenticationRestrictions(
- BSONArray(roleObj["authenticationRestrictions"].Obj()));
- uassertStatusOK(r);
- authenticationRestrictions = r.getValue();
- }
-
- if (create) {
- audit::logCreateRole(
- Client::getCurrent(), roleName, roles, privileges, authenticationRestrictions);
- } else {
- audit::logUpdateRole(
- Client::getCurrent(), roleName, &roles, &privileges, authenticationRestrictions);
- }
+/**
+ * Designed to be used as a callback to be called on every user object in the result
+ * set of a query over the tempUsersCollection provided to the command. For each user
+ * in the temp collection that is defined on the given db, adds that user to the actual
+ * admin.system.users collection.
+ * Also removes any users it encounters from the usersToDrop set.
+ */
+void _addUser(OperationContext* opCtx,
+ AuthorizationManager* authzManager,
+ StringData db,
+ bool update,
+ stdx::unordered_set<UserName>* usersToDrop,
+ const BSONObj& userObj) {
+ UserName userName = _extractUserNameFromBSON(userObj);
+ if (!db.empty() && userName.getDB() != db) {
+ return;
}
- /**
- * Designed to be used as a callback to be called on every user object in the result
- * set of a query over the tempUsersCollection provided to the command. For each user
- * in the temp collection that is defined on the given db, adds that user to the actual
- * admin.system.users collection.
- * Also removes any users it encounters from the usersToDrop set.
- */
- static void addUser(OperationContext* opCtx,
- AuthorizationManager* authzManager,
- StringData db,
- bool update,
- stdx::unordered_set<UserName>* usersToDrop,
- const BSONObj& userObj) {
- UserName userName = extractUserNameFromBSON(userObj);
- if (!db.empty() && userName.getDB() != db) {
- return;
- }
-
- if (update && usersToDrop->count(userName)) {
- auditCreateOrUpdateUser(userObj, false);
- Status status = updatePrivilegeDocument(opCtx, userName, userObj);
- if (!status.isOK()) {
- // Match the behavior of mongorestore to continue on failure
- LOGV2_WARNING(
- 20510,
- "Could not update user {user} in _mergeAuthzCollections command: {error}",
- "Could not update user during _mergeAuthzCollections command",
- "user"_attr = userName,
- "error"_attr = redact(status));
- }
- } else {
- auditCreateOrUpdateUser(userObj, true);
- Status status = insertPrivilegeDocument(opCtx, userObj);
- if (!status.isOK()) {
- // Match the behavior of mongorestore to continue on failure
- LOGV2_WARNING(
- 20511,
- "Could not insert user {user} in _mergeAuthzCollections command: {error}",
- "Could not insert user during _mergeAuthzCollections command",
- "user"_attr = userName,
- "error"_attr = redact(status));
- }
- }
- usersToDrop->erase(userName);
- }
-
- /**
- * Designed to be used as a callback to be called on every role object in the result
- * set of a query over the tempRolesCollection provided to the command. For each role
- * in the temp collection that is defined on the given db, adds that role to the actual
- * admin.system.roles collection.
- * Also removes any roles it encounters from the rolesToDrop set.
- */
- static void addRole(OperationContext* opCtx,
- AuthorizationManager* authzManager,
- StringData db,
- bool update,
- stdx::unordered_set<RoleName>* rolesToDrop,
- const BSONObj roleObj) {
- RoleName roleName = extractRoleNameFromBSON(roleObj);
- if (!db.empty() && roleName.getDB() != db) {
- return;
- }
-
- if (update && rolesToDrop->count(roleName)) {
- auditCreateOrUpdateRole(roleObj, false);
- Status status = updateRoleDocument(opCtx, roleName, roleObj);
- if (!status.isOK()) {
- // Match the behavior of mongorestore to continue on failure
- LOGV2_WARNING(
- 20512,
- "Could not update role {role} in _mergeAuthzCollections command: {error}",
- "Could not update role during _mergeAuthzCollections command",
- "role"_attr = roleName,
- "error"_attr = redact(status));
- }
- } else {
- auditCreateOrUpdateRole(roleObj, true);
- Status status = insertRoleDocument(opCtx, roleObj);
- if (!status.isOK()) {
- // Match the behavior of mongorestore to continue on failure
- LOGV2_WARNING(
- 20513,
- "Could not insert role {role} in _mergeAuthzCollections command: {error}",
- "Could not insert role during _mergeAuthzCollections command",
- "role"_attr = roleName,
- "error"_attr = redact(status));
- }
- }
- rolesToDrop->erase(roleName);
- }
-
- /**
- * Moves all user objects from usersCollName into admin.system.users. If drop is true,
- * removes any users that were in admin.system.users but not in usersCollName.
- */
- static Status processUsers(OperationContext* opCtx,
- AuthorizationManager* authzManager,
- StringData usersCollName,
- StringData db,
- bool drop) {
- // When the "drop" argument has been provided, we use this set to store the users
- // that are currently in the system, and remove from it as we encounter
- // same-named users in the collection we are restoring from. Once we've fully
- // moved over the temp users collection into its final location, we drop
- // any users that previously existed there but weren't in the temp collection.
- // This is so that we can completely replace the system.users
- // collection with the users from the temp collection, without removing all
- // users at the beginning and thus potentially locking ourselves out by having
- // no users in the whole system for a time.
- stdx::unordered_set<UserName> usersToDrop;
-
- if (drop) {
- // Create map of the users currently in the DB
- BSONObj query =
- db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db);
- BSONObj fields = BSON(AuthorizationManager::USER_NAME_FIELD_NAME
- << 1 << AuthorizationManager::USER_DB_FIELD_NAME << 1);
-
- Status status =
- queryAuthzDocument(opCtx,
- AuthorizationManager::usersCollectionNamespace,
- query,
- fields,
- [&](const BSONObj& userObj) {
- usersToDrop.insert(extractUserNameFromBSON(userObj));
- });
- if (!status.isOK()) {
- return status;
- }
+ if (update && usersToDrop->count(userName)) {
+ _auditCreateOrUpdateUser(userObj, false);
+ Status status = updatePrivilegeDocument(opCtx, userName, userObj);
+ if (!status.isOK()) {
+ // Match the behavior of mongorestore to continue on failure
+ LOGV2_WARNING(20510,
+ "Could not update user {user} in _mergeAuthzCollections command: {error}",
+ "Could not update user during _mergeAuthzCollections command",
+ "user"_attr = userName,
+ "error"_attr = redact(status));
}
-
- Status status = queryAuthzDocument(
- opCtx,
- NamespaceString(usersCollName),
- db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db),
- BSONObj(),
- [&](const BSONObj& userObj) {
- return addUser(opCtx, authzManager, db, drop, &usersToDrop, userObj);
- });
+ } else {
+ _auditCreateOrUpdateUser(userObj, true);
+ Status status = insertPrivilegeDocument(opCtx, userObj);
if (!status.isOK()) {
- return status;
+ // Match the behavior of mongorestore to continue on failure
+ LOGV2_WARNING(20511,
+ "Could not insert user {user} in _mergeAuthzCollections command: {error}",
+ "Could not insert user during _mergeAuthzCollections command",
+ "user"_attr = userName,
+ "error"_attr = redact(status));
}
+ }
+ usersToDrop->erase(userName);
+}
+
- if (drop) {
+/**
+ * Moves all user objects from usersCollName into admin.system.users. If drop is true,
+ * removes any users that were in admin.system.users but not in usersCollName.
+ */
+void _processUsers(OperationContext* opCtx,
+ AuthorizationManager* authzManager,
+ StringData usersCollName,
+ StringData db,
+ const bool drop) {
+ // When the "drop" argument has been provided, we use this set to store the users
+ // that are currently in the system, and remove from it as we encounter
+ // same-named users in the collection we are restoring from. Once we've fully
+ // moved over the temp users collection into its final location, we drop
+ // any users that previously existed there but weren't in the temp collection.
+ // This is so that we can completely replace the system.users
+ // collection with the users from the temp collection, without removing all
+ // users at the beginning and thus potentially locking ourselves out by having
+ // no users in the whole system for a time.
+ stdx::unordered_set<UserName> usersToDrop;
+
+ if (drop) {
+ // Create map of the users currently in the DB
+ BSONObj query =
+ db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db);
+ BSONObj fields = BSON(AuthorizationManager::USER_NAME_FIELD_NAME
+ << 1 << AuthorizationManager::USER_DB_FIELD_NAME << 1);
+
+ uassertStatusOK(queryAuthzDocument(opCtx,
+ AuthorizationManager::usersCollectionNamespace,
+ query,
+ fields,
+ [&](const BSONObj& userObj) {
+ usersToDrop.insert(
+ _extractUserNameFromBSON(userObj));
+ }));
+ }
+
+ uassertStatusOK(queryAuthzDocument(
+ opCtx,
+ NamespaceString(usersCollName),
+ db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db),
+ BSONObj(),
+ [&](const BSONObj& userObj) {
+ return _addUser(opCtx, authzManager, db, drop, &usersToDrop, userObj);
+ }));
+
+ if (drop) {
+ for (const auto& userName : usersToDrop) {
+ audit::logDropUser(Client::getCurrent(), userName);
std::int64_t numRemoved;
- for (const UserName& userName : usersToDrop) {
- audit::logDropUser(Client::getCurrent(), userName);
- status = removePrivilegeDocuments(opCtx,
- BSON(AuthorizationManager::USER_NAME_FIELD_NAME
- << userName.getUser().toString()
- << AuthorizationManager::USER_DB_FIELD_NAME
- << userName.getDB().toString()),
- &numRemoved);
- if (!status.isOK()) {
- return status;
- }
- dassert(numRemoved == 1);
- }
+ uassertStatusOK(removePrivilegeDocuments(opCtx, userName.toBSON(), &numRemoved));
+ dassert(numRemoved == 1);
}
+ }
+}
- return Status::OK();
+/**
+ * Audits the fact that we are creating or updating the role described by roleObj.
+ */
+void _auditCreateOrUpdateRole(const BSONObj& roleObj, bool create) {
+ RoleName roleName = _extractRoleNameFromBSON(roleObj);
+ std::vector<RoleName> roles;
+ std::vector<Privilege> privileges;
+ uassertStatusOK(auth::parseRoleNamesFromBSONArray(
+ BSONArray(roleObj["roles"].Obj()), roleName.getDB(), &roles));
+ uassertStatusOK(
+ auth::parseAndValidatePrivilegeArray(BSONArray(roleObj["privileges"].Obj()), &privileges));
+
+ boost::optional<BSONArray> authenticationRestrictions;
+ if (roleObj.hasField("authenticationRestrictions")) {
+ auto r = getRawAuthenticationRestrictions(
+ BSONArray(roleObj["authenticationRestrictions"].Obj()));
+ uassertStatusOK(r);
+ authenticationRestrictions = r.getValue();
+ }
+
+ if (create) {
+ audit::logCreateRole(
+ Client::getCurrent(), roleName, roles, privileges, authenticationRestrictions);
+ } else {
+ audit::logUpdateRole(
+ Client::getCurrent(), roleName, &roles, &privileges, authenticationRestrictions);
}
+}
- /**
- * Moves all user objects from usersCollName into admin.system.users. If drop is true,
- * removes any users that were in admin.system.users but not in usersCollName.
- */
- static Status processRoles(OperationContext* opCtx,
- AuthorizationManager* authzManager,
- StringData rolesCollName,
- StringData db,
- bool drop) {
- // When the "drop" argument has been provided, we use this set to store the roles
- // that are currently in the system, and remove from it as we encounter
- // same-named roles in the collection we are restoring from. Once we've fully
- // moved over the temp roles collection into its final location, we drop
- // any roles that previously existed there but weren't in the temp collection.
- // This is so that we can completely replace the system.roles
- // collection with the roles from the temp collection, without removing all
- // roles at the beginning and thus potentially locking ourselves out.
- stdx::unordered_set<RoleName> rolesToDrop;
-
- if (drop) {
- // Create map of the roles currently in the DB
- BSONObj query =
- db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db);
- BSONObj fields = BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
- << 1 << AuthorizationManager::ROLE_DB_FIELD_NAME << 1);
-
- Status status =
- queryAuthzDocument(opCtx,
- AuthorizationManager::rolesCollectionNamespace,
- query,
- fields,
- [&](const BSONObj& roleObj) {
- return rolesToDrop.insert(extractRoleNameFromBSON(roleObj));
- });
- if (!status.isOK()) {
- return status;
- }
- }
+/**
+ * Designed to be used as a callback to be called on every role object in the result
+ * set of a query over the tempRolesCollection provided to the command. For each role
+ * in the temp collection that is defined on the given db, adds that role to the actual
+ * admin.system.roles collection.
+ * Also removes any roles it encounters from the rolesToDrop set.
+ */
+void _addRole(OperationContext* opCtx,
+ AuthorizationManager* authzManager,
+ StringData db,
+ bool update,
+ stdx::unordered_set<RoleName>* rolesToDrop,
+ const BSONObj roleObj) {
+ RoleName roleName = _extractRoleNameFromBSON(roleObj);
+ if (!db.empty() && roleName.getDB() != db) {
+ return;
+ }
- Status status = queryAuthzDocument(
- opCtx,
- NamespaceString(rolesCollName),
- db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db),
- BSONObj(),
- [&](const BSONObj& roleObj) {
- return addRole(opCtx, authzManager, db, drop, &rolesToDrop, roleObj);
- });
+ if (update && rolesToDrop->count(roleName)) {
+ _auditCreateOrUpdateRole(roleObj, false);
+ Status status = updateRoleDocument(opCtx, roleName, roleObj);
if (!status.isOK()) {
- return status;
+ // Match the behavior of mongorestore to continue on failure
+ LOGV2_WARNING(20512,
+ "Could not update role {role} in _mergeAuthzCollections command: {error}",
+ "Could not update role during _mergeAuthzCollections command",
+ "role"_attr = roleName,
+ "error"_attr = redact(status));
+ }
+ } else {
+ _auditCreateOrUpdateRole(roleObj, true);
+ Status status = insertRoleDocument(opCtx, roleObj);
+ if (!status.isOK()) {
+ // Match the behavior of mongorestore to continue on failure
+ LOGV2_WARNING(20513,
+ "Could not insert role {role} in _mergeAuthzCollections command: {error}",
+ "Could not insert role during _mergeAuthzCollections command",
+ "role"_attr = roleName,
+ "error"_attr = redact(status));
}
+ }
+ rolesToDrop->erase(roleName);
+}
- if (drop) {
+/**
+ * Moves all user objects from usersCollName into admin.system.users. If drop is true,
+ * removes any users that were in admin.system.users but not in usersCollName.
+ */
+void _processRoles(OperationContext* opCtx,
+ AuthorizationManager* authzManager,
+ StringData rolesCollName,
+ StringData db,
+ const bool drop) {
+ // When the "drop" argument has been provided, we use this set to store the roles
+ // that are currently in the system, and remove from it as we encounter
+ // same-named roles in the collection we are restoring from. Once we've fully
+ // moved over the temp roles collection into its final location, we drop
+ // any roles that previously existed there but weren't in the temp collection.
+ // This is so that we can completely replace the system.roles
+ // collection with the roles from the temp collection, without removing all
+ // roles at the beginning and thus potentially locking ourselves out.
+ stdx::unordered_set<RoleName> rolesToDrop;
+
+ if (drop) {
+ // Create map of the roles currently in the DB
+ BSONObj query =
+ db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db);
+ BSONObj fields = BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
+ << 1 << AuthorizationManager::ROLE_DB_FIELD_NAME << 1);
+
+ uassertStatusOK(queryAuthzDocument(opCtx,
+ AuthorizationManager::rolesCollectionNamespace,
+ query,
+ fields,
+ [&](const BSONObj& roleObj) {
+ return rolesToDrop.insert(
+ _extractRoleNameFromBSON(roleObj));
+ }));
+ }
+
+ uassertStatusOK(queryAuthzDocument(
+ opCtx,
+ NamespaceString(rolesCollName),
+ db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db),
+ BSONObj(),
+ [&](const BSONObj& roleObj) {
+ return _addRole(opCtx, authzManager, db, drop, &rolesToDrop, roleObj);
+ }));
+
+ if (drop) {
+ for (const auto& roleName : rolesToDrop) {
+ audit::logDropRole(Client::getCurrent(), roleName);
std::int64_t numRemoved;
- for (stdx::unordered_set<RoleName>::iterator it = rolesToDrop.begin();
- it != rolesToDrop.end();
- ++it) {
- const RoleName& roleName = *it;
- audit::logDropRole(Client::getCurrent(), roleName);
- status = removeRoleDocuments(opCtx,
- BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
- << roleName.getRole().toString()
- << AuthorizationManager::ROLE_DB_FIELD_NAME
- << roleName.getDB().toString()),
- &numRemoved);
- if (!status.isOK()) {
- return status;
- }
- dassert(numRemoved == 1);
- }
+ uassertStatusOK(removeRoleDocuments(opCtx, roleName.toBSON(), &numRemoved));
+ dassert(numRemoved == 1);
}
-
- return Status::OK();
}
+}
- bool run(OperationContext* opCtx,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) override {
- auth::MergeAuthzCollectionsArgs args;
- Status status = auth::parseMergeAuthzCollectionsCommand(cmdObj, &args);
- uassertStatusOK(status);
-
- if (args.usersCollName.empty() && args.rolesCollName.empty()) {
- uasserted(ErrorCodes::BadValue,
- "Must provide at least one of \"tempUsersCollection\" and "
- "\"tempRolescollection\"");
- }
+void CmdMergeAuthzCollections::Invocation::typedRun(OperationContext* opCtx) {
+ const auto& cmd = request();
+ const auto tempUsersColl = cmd.getTempUsersCollection();
+ const auto tempRolesColl = cmd.getTempRolesCollection();
- ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
-
- auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager));
- // From here on, we always want to invalidate the user cache before returning.
- auto invalidateGuard = makeGuard([&] {
- try {
- authzManager->invalidateUserCache(opCtx);
- } catch (const DBException& e) {
- // Since this may be called after a uassert, we want to catch any uasserts
- // that come out of invalidating the user cache and explicitly append it to
- // the command response.
- CommandHelpers::appendCommandStatusNoThrow(result, e.toStatus());
- }
- });
+ uassert(ErrorCodes::BadValue,
+ "Must provide at least one of \"tempUsersCollection\" and \"tempRolescollection\"",
+ !tempUsersColl.empty() | !tempRolesColl.empty());
- if (!args.usersCollName.empty()) {
- Status status =
- processUsers(opCtx, authzManager, args.usersCollName, args.db, args.drop);
- uassertStatusOK(status);
- }
+ auto* svcCtx = opCtx->getClient()->getServiceContext();
+ auto* authzManager = AuthorizationManager::get(svcCtx);
+ auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager));
- if (!args.rolesCollName.empty()) {
- Status status =
- processRoles(opCtx, authzManager, args.rolesCollName, args.db, args.drop);
- uassertStatusOK(status);
- }
+ // From here on, we always want to invalidate the user cache before returning.
+ auto invalidateGuard = makeGuard([&] { authzManager->invalidateUserCache(opCtx); });
+ const auto db = cmd.getDb();
+ const bool drop = cmd.getDrop();
- return true;
+ if (!tempUsersColl.empty()) {
+ _processUsers(opCtx, authzManager, tempUsersColl, db, drop);
}
-} cmdMergeAuthzCollections;
+ if (!tempRolesColl.empty()) {
+ _processRoles(opCtx, authzManager, tempRolesColl, db, drop);
+ }
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/commands/user_management_commands.idl b/src/mongo/db/commands/user_management_commands.idl
index 8897b8b7da1..e8a3715bb8c 100644
--- a/src/mongo/db/commands/user_management_commands.idl
+++ b/src/mongo/db/commands/user_management_commands.idl
@@ -397,3 +397,27 @@ commands:
command_name: _getUserCacheGeneration
cpp_name: GetUserCacheGenerationCommand
strict: true
+
+ _mergeAuthzCollections:
+ description: "Internal command used by mongorestore for updating user/role data"
+ namespace: ignored
+ command_name: _mergeAuthzCollections
+ cpp_name: MergeAuthzCollectionsCommand
+ strict: true
+ fields:
+ tempUsersCollection:
+ description: "Temporary users collection name"
+ type: string
+ default: ''
+ tempRolesCollection:
+ description: "Temporary roles collection name"
+ type: string
+ default: ''
+ db:
+ description: "Database name"
+ type: string
+ drop:
+ description: "Drop temp collections when complete"
+ type: bool
+ default: false
+
diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp
index d352949342d..826d70d3eba 100644
--- a/src/mongo/db/commands/user_management_commands_common.cpp
+++ b/src/mongo/db/commands/user_management_commands_common.cpp
@@ -362,44 +362,37 @@ void checkAuthForTypedCommand(Client* client, const GetUserCacheGenerationComman
ActionType::internal));
}
-Status checkAuthForMergeAuthzCollectionsCommand(Client* client, const BSONObj& cmdObj) {
- auth::MergeAuthzCollectionsArgs args;
- Status status = auth::parseMergeAuthzCollectionsCommand(cmdObj, &args);
- if (!status.isOK()) {
- return status;
- }
+void checkAuthForTypedCommand(Client* client, const MergeAuthzCollectionsCommand& request) {
+ auto* as = AuthorizationSession::get(client);
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
ActionSet actions;
actions.addAction(ActionType::createUser);
actions.addAction(ActionType::createRole);
actions.addAction(ActionType::grantRole);
actions.addAction(ActionType::revokeRole);
- if (args.drop) {
+ if (request.getDrop()) {
actions.addAction(ActionType::dropUser);
actions.addAction(ActionType::dropRole);
}
- if (!authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(),
- actions)) {
- return Status(ErrorCodes::Unauthorized,
- "Not authorized to update user/role data using _mergeAuthzCollections"
- " command");
- }
- if (!args.usersCollName.empty() &&
- !authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(args.usersCollName)),
- ActionType::find)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream() << "Not authorized to read " << args.usersCollName);
- }
- if (!args.rolesCollName.empty() &&
- !authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(args.rolesCollName)),
- ActionType::find)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream() << "Not authorized to read " << args.rolesCollName);
- }
- return Status::OK();
+ uassert(ErrorCodes::Unauthorized,
+ "Not authorized to update user/role data using _mergeAuthzCollections a command",
+ as->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(), actions));
+
+ auto tempUsersColl = request.getTempUsersCollection();
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to read " << tempUsersColl,
+ tempUsersColl.empty() ||
+ as->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(tempUsersColl)),
+ ActionType::find));
+
+ auto tempRolesColl = request.getTempRolesCollection();
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to read " << tempRolesColl,
+ tempRolesColl.empty() ||
+ as->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(tempRolesColl)),
+ ActionType::find));
}
} // namespace auth
diff --git a/src/mongo/db/commands/user_management_commands_common.h b/src/mongo/db/commands/user_management_commands_common.h
index 1af4316c899..6b2d9e5c538 100644
--- a/src/mongo/db/commands/user_management_commands_common.h
+++ b/src/mongo/db/commands/user_management_commands_common.h
@@ -97,8 +97,7 @@ void checkAuthForTypedCommand(Client*, const UsersInfoCommand&);
void checkAuthForTypedCommand(Client*, const RolesInfoCommand&);
void checkAuthForTypedCommand(Client*, const InvalidateUserCacheCommand&);
void checkAuthForTypedCommand(Client*, const GetUserCacheGenerationCommand&);
-
-Status checkAuthForMergeAuthzCollectionsCommand(Client* client, const BSONObj& cmdObj);
+void checkAuthForTypedCommand(Client*, const MergeAuthzCollectionsCommand&);
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_user_management_commands.cpp b/src/mongo/s/commands/cluster_user_management_commands.cpp
index 843b31c80a4..075c118b4f8 100644
--- a/src/mongo/s/commands/cluster_user_management_commands.cpp
+++ b/src/mongo/s/commands/cluster_user_management_commands.cpp
@@ -305,56 +305,12 @@ public:
}
} cmdInvalidateUserCache;
-/**
- * This command is used only by mongorestore to handle restoring users/roles. We do this so
- * that mongorestore doesn't do direct inserts into the admin.system.users and
- * admin.system.roles, which would bypass the authzUpdateLock and allow multiple concurrent
- * modifications to users/roles. What mongorestore now does instead is it inserts all user/role
- * definitions it wants to restore into temporary collections, then this command moves those
- * user/role definitions into their proper place in admin.system.users and admin.system.roles.
- * It either adds the users/roles to the existing ones or replaces the existing ones, depending
- * on whether the "drop" argument is true or false.
- */
-class CmdMergeAuthzCollections : public BasicCommand {
+class CmdMergeAuthzCollections
+ : public CmdUMCPassthrough<MergeAuthzCollectionsCommand, void, UserCacheInvalidatorNOOP> {
public:
- CmdMergeAuthzCollections() : BasicCommand("_mergeAuthzCollections") {}
-
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kNever;
- }
-
- virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- std::string help() const override {
- return "Internal command used by mongorestore for updating user/role data";
- }
-
- virtual Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) const {
- return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj);
- }
-
- bool run(OperationContext* opCtx,
- const string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) {
- uassertStatusOK(Grid::get(opCtx)->catalogClient()->runUserManagementWriteCommand(
- opCtx,
- getName(),
- dbname,
- applyReadWriteConcern(
- opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
- &result));
+ bool adminOnly() const final {
return true;
}
-
} cmdMergeAuthzCollections;
} // namespace