From e73f614e223781e613453a795e99d1d494ec44b7 Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Tue, 27 Oct 2020 17:00:08 +0000 Subject: SERVER-51866 IDLify MergeAuthzCollections command --- .../db/auth/user_management_commands_parser.cpp | 45 -- .../db/auth/user_management_commands_parser.h | 20 - src/mongo/db/commands/user_management_commands.cpp | 670 ++++++++++----------- src/mongo/db/commands/user_management_commands.idl | 24 + .../commands/user_management_commands_common.cpp | 51 +- .../db/commands/user_management_commands_common.h | 3 +- .../commands/cluster_user_management_commands.cpp | 50 +- 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 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* 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 { 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 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 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 roles; + uassertStatusOK(auth::parseRoleNamesFromBSONArray( + BSONArray(userObj["roles"].Obj()), userName.getDB(), &roles)); + BSONObj customData; + if (userObj.hasField("customData")) { + customData = userObj["customData"].Obj(); + } + + boost::optional 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 roles; - std::vector privileges; - uassertStatusOK(auth::parseRoleNamesFromBSONArray( - BSONArray(roleObj["roles"].Obj()), roleName.getDB(), &roles)); - uassertStatusOK(auth::parseAndValidatePrivilegeArray(BSONArray(roleObj["privileges"].Obj()), - &privileges)); - - boost::optional 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* 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* 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* 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 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 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 roles; + std::vector privileges; + uassertStatusOK(auth::parseRoleNamesFromBSONArray( + BSONArray(roleObj["roles"].Obj()), roleName.getDB(), &roles)); + uassertStatusOK( + auth::parseAndValidatePrivilegeArray(BSONArray(roleObj["privileges"].Obj()), &privileges)); + + boost::optional 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 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* 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 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::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 { 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 -- cgit v1.2.1