summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2020-10-26 17:26:31 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-09 18:16:52 +0000
commit5e0d73d0d8e559e34203740af93b6ca03d573ea5 (patch)
tree3ddc4e4ddde8af5fb575ffe819d0bb4b3acee271
parentfa826f6a5b77eb059fe03d411276c3ee7eb303d5 (diff)
downloadmongo-5e0d73d0d8e559e34203740af93b6ca03d573ea5.tar.gz
SERVER-51864 IDLify usersInfo and rolesInfo commands
-rw-r--r--src/mongo/db/auth/authorization_manager.h12
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.cpp13
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.h9
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.h32
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_local.cpp80
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_local.h8
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.cpp33
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.h19
-rw-r--r--src/mongo/db/auth/privilege_format.h67
-rw-r--r--src/mongo/db/auth/umc_info_command_arg.h211
-rw-r--r--src/mongo/db/auth/user_management_commands_parser.cpp153
-rw-r--r--src/mongo/db/auth/user_management_commands_parser.h33
-rw-r--r--src/mongo/db/auth/user_management_commands_parser.idl61
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp360
-rw-r--r--src/mongo/db/commands/user_management_commands.idl88
-rw-r--r--src/mongo/db/commands/user_management_commands_common.cpp104
-rw-r--r--src/mongo/db/commands/user_management_commands_common.h10
-rw-r--r--src/mongo/embedded/embedded_auth_manager.cpp11
-rw-r--r--src/mongo/s/commands/cluster_user_management_commands.cpp114
19 files changed, 810 insertions, 608 deletions
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h
index bdc91d037ef..8b323da7f08 100644
--- a/src/mongo/db/auth/authorization_manager.h
+++ b/src/mongo/db/auth/authorization_manager.h
@@ -273,7 +273,15 @@ public:
const std::vector<RoleName>& roleName,
PrivilegeFormat privilegeFormat,
AuthenticationRestrictionsFormat,
- BSONObj* result) = 0;
+ std::vector<BSONObj>* result) = 0;
+
+ /**
+ * Delegates method call to the underlying AuthzManagerExternalState.
+ */
+ virtual Status getRolesAsUserFragment(OperationContext* opCtx,
+ const std::vector<RoleName>& roleName,
+ AuthenticationRestrictionsFormat,
+ BSONObj* result) = 0;
/**
* Delegates method call to the underlying AuthzManagerExternalState.
@@ -283,7 +291,7 @@ public:
PrivilegeFormat privilegeFormat,
AuthenticationRestrictionsFormat,
bool showBuiltinRoles,
- BSONArrayBuilder* result) = 0;
+ std::vector<BSONObj>* result) = 0;
/**
* Returns a Status or UserHandle for the given userName. If the user cache already has a
diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp
index 66084c34f87..50401c7809a 100644
--- a/src/mongo/db/auth/authorization_manager_impl.cpp
+++ b/src/mongo/db/auth/authorization_manager_impl.cpp
@@ -395,18 +395,27 @@ Status AuthorizationManagerImpl::getRolesDescription(OperationContext* opCtx,
const std::vector<RoleName>& roleName,
PrivilegeFormat privileges,
AuthenticationRestrictionsFormat restrictions,
- BSONObj* result) {
+ std::vector<BSONObj>* result) {
return _externalState->getRolesDescription(opCtx, roleName, privileges, restrictions, result);
}
+Status AuthorizationManagerImpl::getRolesAsUserFragment(
+ OperationContext* opCtx,
+ const std::vector<RoleName>& roleName,
+ AuthenticationRestrictionsFormat restrictions,
+ BSONObj* result) {
+ return _externalState->getRolesAsUserFragment(opCtx, roleName, restrictions, result);
+}
+
+
Status AuthorizationManagerImpl::getRoleDescriptionsForDB(
OperationContext* opCtx,
StringData dbname,
PrivilegeFormat privileges,
AuthenticationRestrictionsFormat restrictions,
bool showBuiltinRoles,
- BSONArrayBuilder* result) {
+ std::vector<BSONObj>* result) {
return _externalState->getRoleDescriptionsForDB(
opCtx, dbname, privileges, restrictions, showBuiltinRoles, result);
}
diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h
index 555d78b0869..c197f100dbd 100644
--- a/src/mongo/db/auth/authorization_manager_impl.h
+++ b/src/mongo/db/auth/authorization_manager_impl.h
@@ -83,14 +83,19 @@ public:
const std::vector<RoleName>& roleName,
PrivilegeFormat privilegeFormat,
AuthenticationRestrictionsFormat,
- BSONObj* result) override;
+ std::vector<BSONObj>* result) override;
+
+ Status getRolesAsUserFragment(OperationContext* opCtx,
+ const std::vector<RoleName>& roleName,
+ AuthenticationRestrictionsFormat,
+ BSONObj* result) override;
Status getRoleDescriptionsForDB(OperationContext* opCtx,
StringData dbname,
PrivilegeFormat privilegeFormat,
AuthenticationRestrictionsFormat,
bool showBuiltinRoles,
- BSONArrayBuilder* result) override;
+ std::vector<BSONObj>* result) override;
StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override;
StatusWith<UserHandle> acquireUserForSessionRefresh(OperationContext* opCtx,
diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h
index aff473d496f..b693d3e0622 100644
--- a/src/mongo/db/auth/authz_manager_external_state.h
+++ b/src/mongo/db/auth/authz_manager_external_state.h
@@ -124,23 +124,29 @@ public:
ResolveRoleOption option) = 0;
/**
- * Writes into "result" a document describing the named role is and returns Status::OK(). If
- * showPrivileges is kOmit or kShowPrivileges, the description includes the roles which the
- * named roles are a member of, including those memberships held implicitly through other roles
- * (indirect roles). If "showPrivileges" is kShowPrivileges, then the description documents
- * will also include a full list of the roles' privileges. If "showPrivileges" is
- * kShowAsUserFragment, then the description returned will take the form of a partial user
- * document, describing a hypothetical user which possesses the provided and implicit roles,
- * and all inherited privileges. In the event that some of this information is inconsistent,
- * the document will contain a "warnings" array, with std::string messages describing
- * inconsistencies.
+ * Fetches and returns objects representing named roles.
+ *
+ * Each BSONObj in the $result vector contains a full role description
+ * as retrieved from admin.system.roles plus inherited role/privilege
+ * information as appropriate.
*/
-
virtual Status getRolesDescription(OperationContext* opCtx,
const std::vector<RoleName>& roles,
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat,
- BSONObj* result) = 0;
+ std::vector<BSONObj>* result) = 0;
+
+ /**
+ * Fetches named roles and synthesizes them into a fragment of a user document.
+ *
+ * The document synthesized into $result looks like a complete user document
+ * representing the $roles specified and their subordinates, but without
+ * an actual user name or credentials.
+ */
+ virtual Status getRolesAsUserFragment(OperationContext* opCtx,
+ const std::vector<RoleName>& roles,
+ AuthenticationRestrictionsFormat,
+ BSONObj* result) = 0;
/**
* Writes into "result" documents describing the roles that are defined on the given
@@ -159,7 +165,7 @@ public:
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat,
bool showBuiltinRoles,
- BSONArrayBuilder* result) = 0;
+ std::vector<BSONObj>* result) = 0;
/**
* Returns true if there exists at least one privilege document in the system.
diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp
index 5f1bf3ef421..852cb44e57f 100644
--- a/src/mongo/db/auth/authz_manager_external_state_local.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp
@@ -484,36 +484,53 @@ StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles(
return ex.toStatus();
}
-Status AuthzManagerExternalStateLocal::getRolesDescription(
+Status AuthzManagerExternalStateLocal::getRolesAsUserFragment(
OperationContext* opCtx,
const std::vector<RoleName>& roleNames,
- PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat showRestrictions,
BSONObj* result) {
- auto option = makeResolveRoleOption(showPrivileges, showRestrictions);
+ auto option = makeResolveRoleOption(PrivilegeFormat::kShowAsUserFragment, showRestrictions);
- if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) {
- BSONObjBuilder fragment;
+ BSONObjBuilder fragment;
- BSONArrayBuilder rolesBuilder(fragment.subarrayStart("roles"));
- for (const auto& roleName : roleNames) {
- roleName.serializeToBSON(&rolesBuilder);
- }
- rolesBuilder.doneFast();
+ BSONArrayBuilder rolesBuilder(fragment.subarrayStart("roles"));
+ for (const auto& roleName : roleNames) {
+ roleName.serializeToBSON(&rolesBuilder);
+ }
+ rolesBuilder.doneFast();
+
+ auto swData = resolveRoles(opCtx, roleNames, option);
+ if (!swData.isOK()) {
+ return swData.getStatus();
+ }
+ auto data = std::move(swData.getValue());
+ data.roles->insert(roleNames.cbegin(), roleNames.cend());
+ serializeResolvedRoles(&fragment, data);
+
+ *result = fragment.obj();
+ return Status::OK();
+}
- auto swData = resolveRoles(opCtx, roleNames, option);
- if (!swData.isOK()) {
- return swData.getStatus();
+Status AuthzManagerExternalStateLocal::getRolesDescription(
+ OperationContext* opCtx,
+ const std::vector<RoleName>& roleNames,
+ PrivilegeFormat showPrivileges,
+ AuthenticationRestrictionsFormat showRestrictions,
+ std::vector<BSONObj>* result) {
+
+ if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) {
+ // Shouldn't be called this way, but cope if we are.
+ BSONObj fragment;
+ auto status = getRolesAsUserFragment(opCtx, roleNames, showRestrictions, &fragment);
+ if (status.isOK()) {
+ result->push_back(fragment);
}
- auto data = std::move(swData.getValue());
- data.roles->insert(roleNames.cbegin(), roleNames.cend());
- serializeResolvedRoles(&fragment, data);
- *result = fragment.obj();
- return Status::OK();
+ return status;
}
- BSONArrayBuilder rolesBuilder;
- for (const RoleName& role : roleNames) {
+ auto option = makeResolveRoleOption(showPrivileges, showRestrictions);
+
+ for (const auto& role : roleNames) {
try {
BSONObj roleDoc;
@@ -540,21 +557,19 @@ Status AuthzManagerExternalStateLocal::getRolesDescription(
} else {
auto status = findOne(
opCtx, AuthorizationManager::rolesCollectionNamespace, role.toBSON(), &roleDoc);
- if (!status.isOK()) {
- if (status.code() == ErrorCodes::NoMatchingDocument) {
- continue;
- }
- uassertStatusOK(status); // throws
+ if (status.code() == ErrorCodes::NoMatchingDocument) {
+ continue;
}
+ uassertStatusOK(status); // throws
}
- BSONObjBuilder roleBuilder(rolesBuilder.subobjStart());
+ BSONObjBuilder roleBuilder;
auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option, true);
auto data = uassertStatusOK(resolveRoles(opCtx, subRoles, option));
data.roles->insert(subRoles.cbegin(), subRoles.cend());
serializeResolvedRoles(&roleBuilder, data, roleDoc);
- roleBuilder.doneFast();
+ result->push_back(roleBuilder.obj());
} catch (const AssertionException& ex) {
return {ex.code(),
str::stream() << "Failed fetching role '" << role.getFullName()
@@ -562,7 +577,6 @@ Status AuthzManagerExternalStateLocal::getRolesDescription(
}
}
- *result = rolesBuilder.arr();
return Status::OK();
}
@@ -572,7 +586,7 @@ Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat showRestrictions,
bool showBuiltinRoles,
- BSONArrayBuilder* result) {
+ std::vector<BSONObj>* result) {
auto option = makeResolveRoleOption(showPrivileges, showRestrictions);
if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) {
@@ -582,7 +596,7 @@ Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(
if (showBuiltinRoles) {
for (const auto& roleName : auth::getBuiltinRoleNamesForDB(dbname)) {
- BSONObjBuilder roleBuilder(result->subobjStart());
+ BSONObjBuilder roleBuilder;
roleBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole());
roleBuilder.append(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB());
@@ -613,7 +627,7 @@ Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(
roleBuilder.append("inheritedAuthenticationRestrictions", BSONArray());
}
- roleBuilder.doneFast();
+ result->push_back(roleBuilder.obj());
}
}
@@ -623,14 +637,14 @@ Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(
BSONObj(),
[&](const BSONObj& roleDoc) {
try {
- BSONObjBuilder roleBuilder(result->subobjStart());
+ BSONObjBuilder roleBuilder;
auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option, true);
roleBuilder.append("isBuiltin", false);
auto data = uassertStatusOK(resolveRoles(opCtx, subRoles, option));
data.roles->insert(subRoles.cbegin(), subRoles.cend());
serializeResolvedRoles(&roleBuilder, data, roleDoc);
- roleBuilder.doneFast();
+ result->push_back(roleBuilder.obj());
return Status::OK();
} catch (const AssertionException& ex) {
return ex.toStatus();
diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h
index 2e489bf559f..68d24ebed79 100644
--- a/src/mongo/db/auth/authz_manager_external_state_local.h
+++ b/src/mongo/db/auth/authz_manager_external_state_local.h
@@ -70,13 +70,17 @@ public:
const std::vector<RoleName>& roles,
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat,
- BSONObj* result) override;
+ std::vector<BSONObj>* result) override;
+ Status getRolesAsUserFragment(OperationContext* opCtx,
+ const std::vector<RoleName>& roles,
+ AuthenticationRestrictionsFormat,
+ BSONObj* result) override;
Status getRoleDescriptionsForDB(OperationContext* opCtx,
StringData dbname,
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat,
bool showBuiltinRoles,
- BSONArrayBuilder* result) override;
+ std::vector<BSONObj>* result) override;
bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final;
diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp
index 80baf8a2525..b3f0483ecc1 100644
--- a/src/mongo/db/auth/authz_manager_external_state_s.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp
@@ -258,39 +258,6 @@ Status AuthzManagerExternalStateMongos::rolesExist(OperationContext* opCtx,
return ex.toStatus();
}
-using ResolvedRoleData = AuthzManagerExternalState::ResolvedRoleData;
-StatusWith<ResolvedRoleData> AuthzManagerExternalStateMongos::resolveRoles(
- OperationContext* opCtx, const std::vector<RoleName>& roleNames, ResolveRoleOption option) {
- // mongos never calls into resolveRoles().
- // That's done exclusively by mongod's User acquisition and user management commands.
- dassert(false);
- return {ErrorCodes::NotImplemented, "AuthzManagerExternalStateMongos::resolveRoles"};
-}
-
-Status AuthzManagerExternalStateMongos::getRolesDescription(
- OperationContext* opCtx,
- const std::vector<RoleName>& roles,
- PrivilegeFormat showPrivileges,
- AuthenticationRestrictionsFormat showRestrictions,
- BSONObj* result) {
- // mongos never calls into resolveRoles().
- // That's done exclusively by mongod's User acquisition and user management commands.
- dassert(false);
- return {ErrorCodes::NotImplemented, "AuthzManagerExternalStateMongos::resolveRoles"};
-}
-Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB(
- OperationContext* opCtx,
- StringData dbname,
- PrivilegeFormat showPrivileges,
- AuthenticationRestrictionsFormat showRestrictions,
- bool showBuiltinRoles,
- BSONArrayBuilder* result) {
- // mongos never calls into resolveRoles().
- // That's done exclusively by mongod's User acquisition and user management commands.
- dassert(false);
- return {ErrorCodes::NotImplemented, "AuthzManagerExternalStateMongos::resolveRoles"};
-}
-
bool AuthzManagerExternalStateMongos::hasAnyPrivilegeDocuments(OperationContext* opCtx) {
BSONObj usersInfoCmd = BSON("usersInfo" << 1);
BSONObjBuilder userBuilder;
diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h
index 2970f0b30e3..58547be92b2 100644
--- a/src/mongo/db/auth/authz_manager_external_state_s.h
+++ b/src/mongo/db/auth/authz_manager_external_state_s.h
@@ -61,18 +61,31 @@ public:
BSONObj* result) final;
StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx,
const std::vector<RoleName>& roleNames,
- ResolveRoleOption option) override;
+ ResolveRoleOption option) final {
+ return {ErrorCodes::NotImplemented, "AuthzMongos::resolveRoles"};
+ }
+
Status getRolesDescription(OperationContext* opCtx,
const std::vector<RoleName>& roles,
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat,
- BSONObj* result) final;
+ std::vector<BSONObj>* result) final {
+ return {ErrorCodes::NotImplemented, "AuthzMongos::getRolesDescription"};
+ }
+ Status getRolesAsUserFragment(OperationContext* opCtx,
+ const std::vector<RoleName>& roles,
+ AuthenticationRestrictionsFormat,
+ BSONObj* result) final {
+ return {ErrorCodes::NotImplemented, "AuthzMongos::getRolesAsUserFragment"};
+ }
Status getRoleDescriptionsForDB(OperationContext* opCtx,
StringData dbname,
PrivilegeFormat showPrivileges,
AuthenticationRestrictionsFormat,
bool showBuiltinRoles,
- BSONArrayBuilder* result) final;
+ std::vector<BSONObj>* result) final {
+ return {ErrorCodes::NotImplemented, "AuthzMongos::getRoleDescriptionsForDB"};
+ }
bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final;
};
diff --git a/src/mongo/db/auth/privilege_format.h b/src/mongo/db/auth/privilege_format.h
index 1b6ce493c59..e66d5010f38 100644
--- a/src/mongo/db/auth/privilege_format.h
+++ b/src/mongo/db/auth/privilege_format.h
@@ -29,8 +29,12 @@
#pragma once
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/bsonobjbuilder.h"
namespace mongo {
+
/**
* How user management functions should structure the BSON representation of privileges and roles.
*/
@@ -40,4 +44,67 @@ enum class PrivilegeFormat {
kShowAsUserFragment // Privileges and roles should all be collapsed together, and presented as
// a fragment of a user document.
};
+
+namespace auth {
+
+/**
+ * Proxy for PrivilegeFormat to parse into and out of IDL formats.
+ */
+class ParsedPrivilegeFormat {
+public:
+ static constexpr StringData kAsUserFragment = "asUserFragment"_sd;
+
+ static PrivilegeFormat fromBool(bool fmt) {
+ return fmt ? PrivilegeFormat::kShowSeparate : PrivilegeFormat::kOmit;
+ }
+
+ ParsedPrivilegeFormat() : _format(PrivilegeFormat::kOmit) {}
+ explicit ParsedPrivilegeFormat(bool fmt) : _format(fromBool(fmt)) {}
+ ParsedPrivilegeFormat(PrivilegeFormat fmt) : _format(fmt) {}
+ ParsedPrivilegeFormat& operator=(bool fmt) {
+ _format = fromBool(fmt);
+ return *this;
+ }
+
+ PrivilegeFormat operator*() const {
+ return _format;
+ }
+
+ static ParsedPrivilegeFormat parseFromBSON(const BSONElement& elem) {
+ if (elem.eoo()) {
+ return ParsedPrivilegeFormat();
+ }
+ if (elem.isNumber() || elem.isBoolean()) {
+ return ParsedPrivilegeFormat(elem.trueValue());
+ }
+ if ((elem.type() == String) && (elem.String() == kAsUserFragment)) {
+ return ParsedPrivilegeFormat(PrivilegeFormat::kShowAsUserFragment);
+ }
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "Failed to parse 'showPrivileges'. 'showPrivileges' should "
+ "either be a boolean or the string 'asUserFragment', given: "
+ << elem.toString());
+ }
+
+ void serializeToBSON(BSONArrayBuilder* bab) const {
+ if (_format == PrivilegeFormat::kShowAsUserFragment) {
+ bab->append(kAsUserFragment);
+ } else {
+ bab->append(_format == PrivilegeFormat::kShowSeparate);
+ }
+ }
+
+ void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const {
+ if (_format == PrivilegeFormat::kShowAsUserFragment) {
+ bob->append(fieldName, kAsUserFragment);
+ } else {
+ bob->append(fieldName, _format == PrivilegeFormat::kShowSeparate);
+ }
+ }
+
+private:
+ PrivilegeFormat _format;
+};
+
+} // namespace auth
} // namespace mongo
diff --git a/src/mongo/db/auth/umc_info_command_arg.h b/src/mongo/db/auth/umc_info_command_arg.h
new file mode 100644
index 00000000000..e97bade41ed
--- /dev/null
+++ b/src/mongo/db/auth/umc_info_command_arg.h
@@ -0,0 +1,211 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/auth/role_name.h"
+#include "mongo/db/auth/user_name.h"
+#include "mongo/stdx/variant.h"
+
+namespace mongo {
+namespace auth {
+
+/**
+ * Wraps the usersInfo and rolesInfo command args.
+ *
+ * These commands accept the following formats:
+ * {....sInfo: 1} // All users on the current DB.
+ * {usersInfo: {forAllDBs: 1}} // All users on all DBs. (usersInfo only)
+ *
+ * {....sInfo: 'alice'} // Specific user on current DB.
+ * {....sInfo: {db: 'test', user: 'alice'} // Specific user on specific DB.
+ * {....sInfo: [stringOrDoc]} // Set of users (using above two formats)
+ *
+ * Use isAllOnCurrentDB(), isAllForAllDBs(), and isExact() to determine format.
+ * Then use getElements(dbname) for isExact() form to get list of T names.
+ */
+template <typename T, bool enableForAllDBs>
+class UMCInfoCommandArg {
+public:
+ UMCInfoCommandArg() : UMCInfoCommandArg(AllOnCurrentDB{}) {}
+ static_assert(std::is_same<UserName, T>::value || std::is_same<RoleName, T>::value,
+ "UMCInfoCommandArg only valid with T = UserName | RoleName");
+
+ static UMCInfoCommandArg parseFromBSON(const BSONElement& elem) {
+ if (elem.numberInt() == 1) {
+ return UMCInfoCommandArg(AllOnCurrentDB{});
+ }
+ if (enableForAllDBs && (elem.type() == Object) && (elem.Obj()[kForAllDBs].trueValue())) {
+ return UMCInfoCommandArg(AllForAllDBs{});
+ }
+
+ if (elem.type() == Array) {
+ Multiple values;
+ for (const auto& v : elem.Obj()) {
+ values.push_back(parseNamedElement(v));
+ }
+ return UMCInfoCommandArg(std::move(values));
+ }
+
+ return UMCInfoCommandArg(parseNamedElement(elem));
+ }
+
+ void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const {
+ if (stdx::holds_alternative<AllOnCurrentDB>(_value)) {
+ bob->append(fieldName, 1);
+ } else if (stdx::holds_alternative<AllForAllDBs>(_value)) {
+ bob->append(fieldName, BSON(kForAllDBs << 1));
+ } else if (stdx::holds_alternative<Single>(_value)) {
+ serializeSingle(fieldName, bob, stdx::get<Single>(_value));
+ } else {
+ invariant(stdx::holds_alternative<Multiple>(_value));
+ const auto& elems = stdx::get<Multiple>(_value);
+ BSONArrayBuilder setBuilder(bob->subarrayStart(fieldName));
+ for (const auto& elem : elems) {
+ serializeSingle(&setBuilder, elem);
+ }
+ setBuilder.doneFast();
+ }
+ }
+
+ void serializeToBSON(BSONArrayBuilder* bob) const {
+ // Minimize code duplication by using object serialization path.
+ // In practice, we don't use this API, it only exists for IDL completeness.
+ BSONObjBuilder tmp;
+ serializeToBSON("", &tmp);
+ auto elem = tmp.obj();
+ bob->append(elem.firstElement());
+ }
+
+ /**
+ * {usersInfo: 1}
+ */
+ bool isAllOnCurrentDB() const {
+ return stdx::holds_alternative<AllOnCurrentDB>(_value);
+ }
+
+ /**
+ * {usersInfo: {forrAllDBs: 1}}
+ */
+ bool isAllForAllDBs() const {
+ return stdx::holds_alternative<AllForAllDBs>(_value);
+ }
+
+ /**
+ * {usersInfo: 'string' | {db,user|role} | [...] }
+ */
+ bool isExact() const {
+ return stdx::holds_alternative<Single>(_value) || stdx::holds_alternative<Multiple>(_value);
+ }
+
+ /**
+ * For isExact() commands, returns a set of T with unspecified DB names resolved with $dbname.
+ */
+ std::vector<T> getElements(StringData dbname) const {
+ if (!isExact()) {
+ dassert(false);
+ uasserted(ErrorCodes::InternalError, "Unable to get exact match for wildcard query");
+ }
+
+ if (stdx::holds_alternative<Single>(_value)) {
+ return {getElement(stdx::get<Single>(_value), dbname)};
+ } else {
+ invariant(stdx::holds_alternative<Multiple>(_value));
+ const auto& values = stdx::get<Multiple>(_value);
+ std::vector<T> ret;
+ std::transform(values.cbegin(),
+ values.cend(),
+ std::back_inserter(ret),
+ [dbname](const auto& value) { return getElement(value, dbname); });
+ return ret;
+ }
+ }
+
+private:
+ static constexpr StringData kForAllDBs = "forAllDBs"_sd;
+
+ struct AllOnCurrentDB {};
+ struct AllForAllDBs {};
+ using Single = stdx::variant<T, std::string>;
+ using Multiple = std::vector<Single>;
+
+ explicit UMCInfoCommandArg(AllOnCurrentDB opt) : _value(std::move(opt)) {}
+ explicit UMCInfoCommandArg(AllForAllDBs opt) : _value(std::move(opt)) {}
+ explicit UMCInfoCommandArg(Single value) : _value(std::move(value)) {}
+ explicit UMCInfoCommandArg(Multiple values) : _value(std::move(values)) {}
+
+ static Single parseNamedElement(const BSONElement& elem) {
+ if (elem.type() == String) {
+ return elem.String();
+ }
+ return T::parseFromBSON(elem);
+ }
+
+ static void serializeSingle(StringData fieldName, BSONObjBuilder* builder, Single elem) {
+ if (stdx::holds_alternative<T>(elem)) {
+ builder->append(fieldName, stdx::get<T>(elem).toBSON());
+ } else {
+ invariant(stdx::holds_alternative<std::string>(elem));
+ builder->append(fieldName, stdx::get<std::string>(elem));
+ }
+ }
+
+ static void serializeSingle(BSONArrayBuilder* builder, Single elem) {
+ if (stdx::holds_alternative<T>(elem)) {
+ builder->append(stdx::get<T>(elem).toBSON());
+ } else {
+ invariant(stdx::holds_alternative<std::string>(elem));
+ builder->append(stdx::get<std::string>(elem));
+ }
+ }
+
+ static T getElement(Single elem, StringData dbname) {
+ if (stdx::holds_alternative<T>(elem)) {
+ return stdx::get<T>(elem);
+ } else {
+ invariant(stdx::holds_alternative<std::string>(elem));
+ return T(stdx::get<std::string>(elem), dbname);
+ }
+ }
+
+ // Single is stored as a distinct type from Multiple
+ // to ensure that reserialization maintains the same level of nesting.
+ stdx::variant<AllOnCurrentDB, AllForAllDBs, Single, Multiple> _value;
+};
+
+using UsersInfoCommandArg = UMCInfoCommandArg<UserName, true>;
+using RolesInfoCommandArg = UMCInfoCommandArg<RoleName, false>;
+
+} // namespace auth
+} // namespace mongo
diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp
index e85a86cbfda..22ac894c885 100644
--- a/src/mongo/db/auth/user_management_commands_parser.cpp
+++ b/src/mongo/db/auth/user_management_commands_parser.cpp
@@ -144,159 +144,6 @@ Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray,
parsedRoleNames);
}
-Status parseUsersInfoCommand(const BSONObj& cmdObj, StringData dbname, UsersInfoArgs* parsedArgs) {
- stdx::unordered_set<std::string> validFieldNames;
- validFieldNames.insert("usersInfo");
- validFieldNames.insert("showAuthenticationRestrictions");
- validFieldNames.insert("showPrivileges");
- validFieldNames.insert("showCredentials");
- validFieldNames.insert("filter");
-
- Status status = _checkNoExtraFields(cmdObj, "usersInfo", validFieldNames);
- if (!status.isOK()) {
- return status;
- }
-
- if (cmdObj["usersInfo"].numberInt() == 1) {
- parsedArgs->target = UsersInfoArgs::Target::kDB;
- } else if (cmdObj["usersInfo"].type() == Object &&
- cmdObj["usersInfo"].Obj().getBoolField("forAllDBs")) {
- parsedArgs->target = UsersInfoArgs::Target::kGlobal;
- } else if (cmdObj["usersInfo"].type() == Array) {
- parsedArgs->target = UsersInfoArgs::Target::kExplicitUsers;
- status = parseUserNamesFromBSONArray(
- BSONArray(cmdObj["usersInfo"].Obj()), dbname, &parsedArgs->userNames);
- if (!status.isOK()) {
- return status;
- }
- std::sort(parsedArgs->userNames.begin(), parsedArgs->userNames.end());
- } else {
- parsedArgs->target = UsersInfoArgs::Target::kExplicitUsers;
- UserName name;
- status = _parseNameFromBSONElement(cmdObj["usersInfo"],
- dbname,
- AuthorizationManager::USER_NAME_FIELD_NAME,
- AuthorizationManager::USER_DB_FIELD_NAME,
- &name);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->userNames.push_back(name);
- }
-
- status = bsonExtractBooleanFieldWithDefault(
- cmdObj, "showPrivileges", false, &parsedArgs->showPrivileges);
- if (!status.isOK()) {
- return status;
- }
- status = bsonExtractBooleanFieldWithDefault(
- cmdObj, "showCredentials", false, &parsedArgs->showCredentials);
- if (!status.isOK()) {
- return status;
- }
-
- const auto showAuthenticationRestrictions = cmdObj["showAuthenticationRestrictions"];
- if (showAuthenticationRestrictions.eoo()) {
- parsedArgs->authenticationRestrictionsFormat = AuthenticationRestrictionsFormat::kOmit;
- } else {
- bool show;
- status = bsonExtractBooleanField(cmdObj, "showAuthenticationRestrictions", &show);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->authenticationRestrictionsFormat = show
- ? AuthenticationRestrictionsFormat::kShow
- : AuthenticationRestrictionsFormat::kOmit;
- }
-
-
- const auto filterObj = cmdObj["filter"];
- if (!filterObj.eoo()) {
- if (filterObj.type() != Object) {
- return Status(ErrorCodes::TypeMismatch, "filter must be an Object");
- }
- parsedArgs->filter = filterObj.Obj();
- }
-
- return Status::OK();
-}
-
-Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs) {
- stdx::unordered_set<std::string> validFieldNames;
- validFieldNames.insert("rolesInfo");
- validFieldNames.insert("showPrivileges");
- validFieldNames.insert("showAuthenticationRestrictions");
- validFieldNames.insert("showBuiltinRoles");
-
- Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames);
- if (!status.isOK()) {
- return status;
- }
-
- if (cmdObj["rolesInfo"].numberInt() == 1) {
- parsedArgs->allForDB = true;
- } else if (cmdObj["rolesInfo"].type() == Array) {
- status = parseRoleNamesFromBSONArray(
- BSONArray(cmdObj["rolesInfo"].Obj()), dbname, &parsedArgs->roleNames);
- if (!status.isOK()) {
- return status;
- }
- } else {
- RoleName name;
- status = _parseNameFromBSONElement(cmdObj["rolesInfo"],
- dbname,
- AuthorizationManager::ROLE_NAME_FIELD_NAME,
- AuthorizationManager::ROLE_DB_FIELD_NAME,
- &name);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->roleNames.push_back(name);
- }
-
- BSONElement showPrivileges = cmdObj["showPrivileges"];
- if (showPrivileges.eoo()) {
- parsedArgs->privilegeFormat = PrivilegeFormat::kOmit;
- } else if (showPrivileges.isNumber() || showPrivileges.isBoolean()) {
- parsedArgs->privilegeFormat =
- showPrivileges.trueValue() ? PrivilegeFormat::kShowSeparate : PrivilegeFormat::kOmit;
- } else if (showPrivileges.type() == BSONType::String &&
- showPrivileges.String() == "asUserFragment") {
- parsedArgs->privilegeFormat = PrivilegeFormat::kShowAsUserFragment;
- } else {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Failed to parse 'showPrivileges'. 'showPrivileges' should "
- "either be a boolean or the string 'asUserFragment', given: "
- << showPrivileges.toString());
- }
-
- const auto showAuthenticationRestrictions = cmdObj["showAuthenticationRestrictions"];
- if (showAuthenticationRestrictions.eoo()) {
- parsedArgs->authenticationRestrictionsFormat = AuthenticationRestrictionsFormat::kOmit;
- } else if (parsedArgs->privilegeFormat == PrivilegeFormat::kShowAsUserFragment) {
- return Status(
- ErrorCodes::UnsupportedFormat,
- "showAuthenticationRestrictions may not be used with showPrivileges='asUserFragment'");
- } else {
- bool show;
- status = bsonExtractBooleanField(cmdObj, "showAuthenticationRestrictions", &show);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->authenticationRestrictionsFormat = show
- ? AuthenticationRestrictionsFormat::kShow
- : AuthenticationRestrictionsFormat::kOmit;
- }
-
- status = bsonExtractBooleanFieldWithDefault(
- cmdObj, "showBuiltinRoles", false, &parsedArgs->showBuiltinRoles);
- if (!status.isOK()) {
- return status;
- }
-
- return Status::OK();
-}
-
/*
* Validates that the given privilege BSONArray is valid.
* If parsedPrivileges is not NULL, adds to it the privileges parsed out of the input BSONArray.
diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h
index b3f7073924f..f5f4a49f27c 100644
--- a/src/mongo/db/auth/user_management_commands_parser.h
+++ b/src/mongo/db/auth/user_management_commands_parser.h
@@ -45,39 +45,6 @@
namespace mongo {
namespace auth {
-struct UsersInfoArgs {
- enum class Target { kExplicitUsers, kDB, kGlobal };
-
- std::vector<UserName> userNames;
- Target target;
- bool showPrivileges = false;
- AuthenticationRestrictionsFormat authenticationRestrictionsFormat =
- AuthenticationRestrictionsFormat::kOmit;
- bool showCredentials = false;
- boost::optional<BSONObj> filter;
-};
-
-/**
- * Takes a command object describing an invocation of the "usersInfo" command and parses out
- * all the arguments into the "parsedArgs" output param.
- */
-Status parseUsersInfoCommand(const BSONObj& cmdObj, StringData dbname, UsersInfoArgs* parsedArgs);
-
-struct RolesInfoArgs {
- std::vector<RoleName> roleNames;
- bool allForDB = false;
- PrivilegeFormat privilegeFormat = PrivilegeFormat::kOmit;
- AuthenticationRestrictionsFormat authenticationRestrictionsFormat =
- AuthenticationRestrictionsFormat::kOmit;
- bool showBuiltinRoles = false;
-};
-
-/**
- * Takes a command object describing an invocation of the "rolesInfo" command and parses out
- * the arguments into the "parsedArgs" output param.
- */
-Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs);
-
/**
* Parses the privileges described in "privileges" into a vector of Privilege objects.
* Returns Status::OK() upon successfully parsing all the elements of "privileges".
diff --git a/src/mongo/db/auth/user_management_commands_parser.idl b/src/mongo/db/auth/user_management_commands_parser.idl
new file mode 100644
index 00000000000..10ffb919d29
--- /dev/null
+++ b/src/mongo/db/auth/user_management_commands_parser.idl
@@ -0,0 +1,61 @@
+# Copyright (C) 2020-present MongoDB, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the Server Side Public License, version 1,
+# as published by MongoDB, Inc.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Server Side Public License for more details.
+#
+# You should have received a copy of the Server Side Public License
+# along with this program. If not, see
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# As a special exception, the copyright holders give permission to link the
+# code of portions of this program with the OpenSSL library under certain
+# conditions as described in each individual source file and distribute
+# linked combinations including the program with the OpenSSL library. You
+# must comply with the Server Side Public License in all respects for
+# all of the code used other than as permitted herein. If you modify file(s)
+# with this exception, you may extend this exception to your version of the
+# file(s), but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version. If you delete this
+# exception statement from all source files in the program, then also delete
+# it in the license file.
+#
+global:
+ cpp_namespace: "mongo::auth"
+ cpp_includes:
+ - "mongo/db/auth/privilege_format.h"
+ - "mongo/db/auth/umc_info_command_arg.h"
+
+imports:
+ - "mongo/idl/basic_types.idl"
+ - "mongo/db/auth/auth_types.idl"
+
+types:
+ # Unifies the various invocations of {usersInfo:...}
+ UsersInfoCommandArg:
+ bson_serialization_type: any
+ description: "Argument to usersInfo command"
+ cpp_type: "UsersInfoCommandArg"
+ deserializer: "mongo::auth::UsersInfoCommandArg::parseFromBSON"
+ serializer: "mongo::auth::UsersInfoCommandArg::serializeToBSON"
+
+ # Unifies the various invocations of {rolesInfo:...}
+ RolesInfoCommandArg:
+ bson_serialization_type: any
+ description: "Argument to rolesInfo command"
+ cpp_type: "RolesInfoCommandArg"
+ deserializer: "mongo::auth::RolesInfoCommandArg::parseFromBSON"
+ serializer: "mongo::auth::RolesInfoCommandArg::serializeToBSON"
+
+ # PrivilegeFormat: true | false | "asUserFragment"
+ ParsedPrivilegeFormat:
+ bson_serialization_type: any
+ description: "PrivilegeFormat for rolesInfo command"
+ cpp_type: "ParsedPrivilegeFormat"
+ deserializer: "mongo::auth::ParsedPrivilegeFormat::parseFromBSON"
+ serializer: "mongo::auth::ParsedPrivilegeFormat::serializeToBSON"
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index bdc40e745cb..44edd0d4fd8 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -906,12 +906,24 @@ private:
TransactionState _state = TransactionState::kInit;
};
-template <typename RequestT, typename ReplyT>
-class CmdUMCTyped : public TypedCommand<CmdUMCTyped<RequestT, ReplyT>> {
+// Used by most UMC commands.
+struct UMCStdParams {
+ static constexpr bool supportsWriteConcern = true;
+ static constexpr auto allowedOnSecondary = BasicCommand::AllowedOnSecondary::kNever;
+};
+
+// Used by {usersInfo:...} and {rolesInfo:...}
+struct UMCInfoParams {
+ static constexpr bool supportsWriteConcern = false;
+ static constexpr auto allowedOnSecondary = BasicCommand::AllowedOnSecondary::kOptIn;
+};
+
+template <typename RequestT, typename ReplyT, typename Params = UMCStdParams>
+class CmdUMCTyped : public TypedCommand<CmdUMCTyped<RequestT, ReplyT, Params>> {
public:
using Request = RequestT;
using Reply = ReplyT;
- using TC = TypedCommand<CmdUMCTyped<RequestT, ReplyT>>;
+ using TC = TypedCommand<CmdUMCTyped<RequestT, ReplyT, Params>>;
class Invocation final : public TC::InvocationBase {
public:
@@ -922,7 +934,7 @@ public:
private:
bool supportsWriteConcern() const final {
- return true;
+ return Params::supportsWriteConcern;
}
void doCheckAuthorization(OperationContext* opCtx) const final {
@@ -935,7 +947,7 @@ public:
};
typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
- return TC::AllowedOnSecondary::kNever;
+ return Params::allowedOnSecondary;
}
};
@@ -1282,160 +1294,134 @@ void CmdUMCTyped<RevokeRolesFromUserCommand, void>::Invocation::typedRun(Operati
uassertStatusOK(status);
}
-class CmdUsersInfo : public BasicCommand {
-public:
- CmdUsersInfo() : BasicCommand("usersInfo") {}
-
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kOptIn;
- }
-
- bool supportsWriteConcern(const BSONObj& cmd) const override {
- return false;
- }
+CmdUMCTyped<UsersInfoCommand, UsersInfoReply, UMCInfoParams> cmdUsersInfo;
+template <>
+UsersInfoReply CmdUMCTyped<UsersInfoCommand, UsersInfoReply, UMCInfoParams>::Invocation::typedRun(
+ OperationContext* opCtx) {
+ const auto& cmd = request();
+ const auto& arg = cmd.getCommandParameter();
+ const auto& dbname = cmd.getDbName();
- std::string help() const override {
- return "Returns information about users.";
- }
+ auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
+ auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager));
- Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) const override {
- return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj);
- }
+ std::vector<BSONObj> users;
+ if (cmd.getShowPrivileges() || cmd.getShowAuthenticationRestrictions()) {
+ uassert(ErrorCodes::IllegalOperation,
+ "Privilege or restriction details require exact-match usersInfo queries",
+ !cmd.getFilter() && arg.isExact());
+
+ // If you want privileges or restrictions you need to call getUserDescription
+ // on each user.
+ for (const auto& userName : arg.getElements(dbname)) {
+ BSONObj userDetails;
+ auto status = authzManager->getUserDescription(opCtx, userName, &userDetails);
+ if (status.code() == ErrorCodes::UserNotFound) {
+ continue;
+ }
+ uassertStatusOK(status);
- bool run(OperationContext* opCtx,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) override {
- auth::UsersInfoArgs args;
- uassertStatusOK(auth::parseUsersInfoCommand(cmdObj, dbname, &args));
-
- auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
- auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager));
-
- BSONArrayBuilder usersArrayBuilder(result.subarrayStart("users"));
- if (args.showPrivileges ||
- (args.authenticationRestrictionsFormat == AuthenticationRestrictionsFormat::kShow)) {
- uassert(ErrorCodes::IllegalOperation,
- "Privilege or restriction details require exact-match usersInfo queries",
- !args.filter && (args.target == auth::UsersInfoArgs::Target::kExplicitUsers));
-
- // If you want privileges or restrictions you need to call getUserDescription
- // on each user.
- for (const auto& userName : args.userNames) {
- BSONObj userDetails;
- auto status = authzManager->getUserDescription(opCtx, userName, &userDetails);
- if (status.code() == ErrorCodes::UserNotFound) {
- continue;
- }
- uassertStatusOK(status);
-
- // getUserDescription always includes credentials and restrictions, which may need
- // to be stripped out
- BSONObjBuilder strippedUser(usersArrayBuilder.subobjStart());
- for (const BSONElement& e : userDetails) {
- if (e.fieldNameStringData() == "credentials") {
- BSONArrayBuilder mechanismNamesBuilder;
- BSONObj mechanismsObj = e.Obj();
- for (const BSONElement& mechanismElement : mechanismsObj) {
- mechanismNamesBuilder.append(mechanismElement.fieldNameStringData());
- }
- strippedUser.append("mechanisms", mechanismNamesBuilder.arr());
-
- if (!args.showCredentials) {
- continue;
- }
+ // getUserDescription always includes credentials and restrictions, which may need
+ // to be stripped out
+ BSONObjBuilder strippedUser;
+ for (const BSONElement& e : userDetails) {
+ if (e.fieldNameStringData() == "credentials") {
+ BSONArrayBuilder mechanismNamesBuilder;
+ BSONObj mechanismsObj = e.Obj();
+ for (const BSONElement& mechanismElement : mechanismsObj) {
+ mechanismNamesBuilder.append(mechanismElement.fieldNameStringData());
}
+ strippedUser.append("mechanisms", mechanismNamesBuilder.arr());
- if (e.fieldNameStringData() == "authenticationRestrictions" &&
- args.authenticationRestrictionsFormat ==
- AuthenticationRestrictionsFormat::kOmit) {
+ if (!cmd.getShowCredentials()) {
continue;
}
-
- strippedUser.append(e);
}
- strippedUser.doneFast();
- }
- } else {
- // If you don't need privileges, or authenticationRestrictions, you can just do a
- // regular query on system.users
- std::vector<BSONObj> pipeline;
-
- if (args.target == auth::UsersInfoArgs::Target::kGlobal) {
- // Leave the pipeline unconstrained, we want to return every user.
- } else if (args.target == auth::UsersInfoArgs::Target::kDB) {
- pipeline.push_back(
- BSON("$match" << BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname)));
- } else {
- BSONArrayBuilder usersMatchArray;
- for (size_t i = 0; i < args.userNames.size(); ++i) {
- usersMatchArray.append(BSON(AuthorizationManager::USER_NAME_FIELD_NAME
- << args.userNames[i].getUser()
- << AuthorizationManager::USER_DB_FIELD_NAME
- << args.userNames[i].getDB()));
+
+ if ((e.fieldNameStringData() == "authenticationRestrictions") &&
+ !cmd.getShowAuthenticationRestrictions()) {
+ continue;
}
- pipeline.push_back(BSON("$match" << BSON("$or" << usersMatchArray.arr())));
- }
- // Order results by user field then db field, matching how UserNames are ordered
- pipeline.push_back(BSON("$sort" << BSON("user" << 1 << "db" << 1)));
+ strippedUser.append(e);
+ }
+ users.push_back(strippedUser.obj());
+ }
+ } else {
+ // If you don't need privileges, or authenticationRestrictions, you can just do a
+ // regular query on system.users
+ std::vector<BSONObj> pipeline;
- // Rewrite the credentials object into an array of its fieldnames.
+ if (arg.isAllForAllDBs()) {
+ // Leave the pipeline unconstrained, we want to return every user.
+ } else if (arg.isAllOnCurrentDB()) {
pipeline.push_back(
- BSON("$addFields" << BSON("mechanisms"
- << BSON("$map" << BSON("input" << BSON("$objectToArray"
- << "$credentials")
- << "as"
- << "cred"
- << "in"
- << "$$cred.k")))));
-
- if (args.showCredentials) {
- // Authentication restrictions are only rendered in the single user case.
- pipeline.push_back(BSON("$unset"
- << "authenticationRestrictions"));
- } else {
- // Remove credentials as well, they're not required in the output
- pipeline.push_back(BSON("$unset" << BSON_ARRAY("authenticationRestrictions"
- << "credentials")));
+ BSON("$match" << BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname)));
+ } else {
+ invariant(arg.isExact());
+ BSONArrayBuilder usersMatchArray;
+ for (const auto& userName : arg.getElements(dbname)) {
+ usersMatchArray.append(userName.toBSON());
}
+ pipeline.push_back(BSON("$match" << BSON("$or" << usersMatchArray.arr())));
+ }
- // Handle a user specified filter.
- if (args.filter) {
- pipeline.push_back(BSON("$match" << *args.filter));
- }
+ // Order results by user field then db field, matching how UserNames are ordered
+ pipeline.push_back(BSON("$sort" << BSON("user" << 1 << "db" << 1)));
+
+ // Rewrite the credentials object into an array of its fieldnames.
+ pipeline.push_back(
+ BSON("$addFields" << BSON("mechanisms"
+ << BSON("$map" << BSON("input" << BSON("$objectToArray"
+ << "$credentials")
+ << "as"
+ << "cred"
+ << "in"
+ << "$$cred.k")))));
+
+ if (cmd.getShowCredentials()) {
+ // Authentication restrictions are only rendered in the single user case.
+ pipeline.push_back(BSON("$unset"
+ << "authenticationRestrictions"));
+ } else {
+ // Remove credentials as well, they're not required in the output
+ pipeline.push_back(BSON("$unset" << BSON_ARRAY("authenticationRestrictions"
+ << "credentials")));
+ }
- DBDirectClient client(opCtx);
-
- rpc::OpMsgReplyBuilder replyBuilder;
- AggregationRequest aggRequest(AuthorizationManager::usersCollectionNamespace,
- std::move(pipeline));
- // Impose no cursor privilege requirements, as cursor is drained internally
- uassertStatusOK(runAggregate(opCtx,
- AuthorizationManager::usersCollectionNamespace,
- aggRequest,
- aggRequest.serializeToCommandObj().toBson(),
- PrivilegeVector(),
- &replyBuilder));
- auto bodyBuilder = replyBuilder.getBodyBuilder();
- CommandHelpers::appendSimpleCommandStatus(bodyBuilder, true);
- bodyBuilder.doneFast();
- auto response = CursorResponse::parseFromBSONThrowing(replyBuilder.releaseBody());
- DBClientCursor cursor(
- &client, response.getNSS(), response.getCursorId(), 0, 0, response.releaseBatch());
-
- while (cursor.more()) {
- usersArrayBuilder.append(cursor.next());
- }
+ // Handle a user specified filter.
+ if (auto filter = cmd.getFilter()) {
+ pipeline.push_back(BSON("$match" << *filter));
}
- usersArrayBuilder.doneFast();
- return true;
+ DBDirectClient client(opCtx);
+
+ rpc::OpMsgReplyBuilder replyBuilder;
+ AggregationRequest aggRequest(AuthorizationManager::usersCollectionNamespace,
+ std::move(pipeline));
+ // Impose no cursor privilege requirements, as cursor is drained internally
+ uassertStatusOK(runAggregate(opCtx,
+ AuthorizationManager::usersCollectionNamespace,
+ aggRequest,
+ aggRequest.serializeToCommandObj().toBson(),
+ PrivilegeVector(),
+ &replyBuilder));
+ auto bodyBuilder = replyBuilder.getBodyBuilder();
+ CommandHelpers::appendSimpleCommandStatus(bodyBuilder, true);
+ bodyBuilder.doneFast();
+ auto response = CursorResponse::parseFromBSONThrowing(replyBuilder.releaseBody());
+ DBClientCursor cursor(
+ &client, response.getNSS(), response.getCursorId(), 0, 0, response.releaseBatch());
+
+ while (cursor.more()) {
+ users.push_back(cursor.next().getOwned());
+ }
}
-} cmdUsersInfo;
+ UsersInfoReply reply;
+ reply.setUsers(std::move(users));
+ return reply;
+}
CmdUMCTyped<CreateRoleCommand, void> cmdCreateRole;
template <>
@@ -1907,71 +1893,55 @@ CmdUMCTyped<DropAllRolesFromDatabaseCommand, DropAllRolesFromDatabaseReply>::Inv
* these roles. This format may change over time with changes to the auth
* schema.
*/
-class CmdRolesInfo : public BasicCommand {
-public:
- CmdRolesInfo() : BasicCommand("rolesInfo") {}
-
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kOptIn;
- }
-
- bool supportsWriteConcern(const BSONObj& cmd) const override {
- return false;
- }
+CmdUMCTyped<RolesInfoCommand, RolesInfoReply, UMCInfoParams> cmdRolesInfo;
+template <>
+RolesInfoReply CmdUMCTyped<RolesInfoCommand, RolesInfoReply, UMCInfoParams>::Invocation::typedRun(
+ OperationContext* opCtx) {
+ const auto& cmd = request();
+ const auto& arg = cmd.getCommandParameter();
+ const auto& dbname = cmd.getDbName();
- std::string help() const override {
- return "Returns information about roles.";
- }
+ auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
+ auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager));
- Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) const override {
- return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj);
- }
+ // Only usersInfo actually supports {forAllDBs: 1} mode.
+ invariant(!arg.isAllForAllDBs());
- bool run(OperationContext* opCtx,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) override {
- auth::RolesInfoArgs args;
- uassertStatusOK(auth::parseRolesInfoCommand(cmdObj, dbname, &args));
+ auto privFmt = *(cmd.getShowPrivileges());
+ auto restrictionFormat = cmd.getShowAuthenticationRestrictions()
+ ? AuthenticationRestrictionsFormat::kShow
+ : AuthenticationRestrictionsFormat::kOmit;
- AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
- auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager));
+ RolesInfoReply reply;
+ if (arg.isAllOnCurrentDB()) {
- if (args.allForDB) {
- if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) {
- uasserted(ErrorCodes::IllegalOperation,
- "Cannot get user fragment for all roles in a database");
- }
+ uassert(ErrorCodes::IllegalOperation,
+ "Cannot get user fragment for all roles in a database",
+ privFmt != PrivilegeFormat::kShowAsUserFragment);
- BSONArrayBuilder rolesBuilder(result.subarrayStart("roles"));
- uassertStatusOK(
- authzManager->getRoleDescriptionsForDB(opCtx,
- dbname,
- args.privilegeFormat,
- args.authenticationRestrictionsFormat,
- args.showBuiltinRoles,
- &rolesBuilder));
+ std::vector<BSONObj> roles;
+ uassertStatusOK(authzManager->getRoleDescriptionsForDB(
+ opCtx, dbname, privFmt, restrictionFormat, cmd.getShowBuiltinRoles(), &roles));
+ reply.setRoles(std::move(roles));
+ } else {
+ invariant(arg.isExact());
+ auto roleNames = arg.getElements(dbname);
+
+ if (privFmt == PrivilegeFormat::kShowAsUserFragment) {
+ BSONObj fragment;
+ uassertStatusOK(authzManager->getRolesAsUserFragment(
+ opCtx, roleNames, restrictionFormat, &fragment));
+ reply.setUserFragment(fragment);
} else {
- BSONObj roleDetails;
- uassertStatusOK(authzManager->getRolesDescription(opCtx,
- args.roleNames,
- args.privilegeFormat,
- args.authenticationRestrictionsFormat,
- &roleDetails));
-
- if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) {
- result.append("userFragment", roleDetails);
- } else {
- result.append("roles", BSONArray(roleDetails));
- }
+ std::vector<BSONObj> roles;
+ uassertStatusOK(authzManager->getRolesDescription(
+ opCtx, roleNames, privFmt, restrictionFormat, &roles));
+ reply.setRoles(std::move(roles));
}
-
- return true;
}
-} cmdRolesInfo;
+ return reply;
+}
class CmdInvalidateUserCache : public BasicCommand {
public:
diff --git a/src/mongo/db/commands/user_management_commands.idl b/src/mongo/db/commands/user_management_commands.idl
index 406b5924164..0dfbdd9fb17 100644
--- a/src/mongo/db/commands/user_management_commands.idl
+++ b/src/mongo/db/commands/user_management_commands.idl
@@ -32,6 +32,7 @@ imports:
- "mongo/idl/basic_types.idl"
- "mongo/db/auth/auth_types.idl"
- "mongo/db/auth/address_restriction.idl"
+ - "mongo/db/auth/user_management_commands_parser.idl"
server_parameters:
enforceUserClusterSeparation:
@@ -60,6 +61,27 @@ structs:
type: int
cpp_name: count
+ usersInfoReply:
+ description: "Reply from usersInfo command"
+ strict: false
+ fields:
+ users:
+ description: "Users descriptions"
+ type: array<object_owned>
+
+ rolesInfoReply:
+ description: "Reply from usersInfo command"
+ strict: false
+ fields:
+ roles:
+ description: "Users descriptions"
+ type: array<object_owned>
+ optional: true
+ userFragment:
+ description: "Roles as user document fragment"
+ type: object_owned
+ optional: true
+
UMCTransactionFailPoint:
description: Data for umcTransaction failpoint
fields:
@@ -90,7 +112,7 @@ commands:
type: array<RoleNameOrString>
digestPassword:
description: "True if the server should digest the password, false for pre-digested"
- type: bool
+ type: safeBool
default: true
writeConcern:
description: "The level of write concern for the creation operation"
@@ -127,7 +149,7 @@ commands:
optional: true
digestPassword:
description: "True if the server should digest the password, false for pre-digested"
- type: bool
+ type: safeBool
default: true
writeConcern:
description: "The level of write concern for the update operation"
@@ -291,3 +313,65 @@ commands:
namespace: ignored
cpp_name: DropAllRolesFromDatabaseCommand
strict: true
+
+ usersInfo:
+ description: "Returns information about users."
+ command_name: usersInfo
+ namespace: type
+ type: UsersInfoCommandArg
+ cpp_name: UsersInfoCommand
+ strict: true
+ fields:
+ showPrivileges:
+ description: >-
+ Set the field to true to show the user’s full set of privileges,
+ including expanded information for the inherited roles.
+ If viewing all users, you cannot specify this field.
+ type: safeBool
+ default: false
+ showCredentials:
+ description: >-
+ Set the field to true to display the user’s password hash.
+ type: safeBool
+ default: false
+ showAuthenticationRestrictions:
+ description: >-
+ Set the field to true to show the user’s authentication restrictions.
+ If viewing all users, you cannot specify this field.
+ type: safeBool
+ default: false
+ filter:
+ description: >-
+ A document that specifies $match stage conditions to return information
+ for users that match the filter conditions.
+ type: object
+ optional: true
+
+ rolesInfo:
+ description: "returns information about roles."
+ command_name: rolesInfo
+ namespace: type
+ type: RolesInfoCommandArg
+ cpp_name: RolesInfoCommand
+ strict: true
+ fields:
+ showPrivileges:
+ description: >-
+ Set the field to true to show the user’s full set of privileges,
+ including expanded information for the inherited roles.
+ If viewing all roles, you cannot specify this field.
+ type: ParsedPrivilegeFormat
+ default: false
+ showBuiltinRoles:
+ description: >-
+ When the rolesInfo field is set to 1, set showBuiltinRoles to true
+ to include built-in roles in the output.
+ Otherwise the output for rolesInfo: 1 displays only user-defined roles.
+ type: safeBool
+ default: false
+ showAuthenticationRestrictions:
+ description: >-
+ Set the field to true to show the user’s authentication restrictions.
+ If viewing all users, you cannot specify this field.
+ type: safeBool
+ 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 d10742585a1..06c4f2683ea 100644
--- a/src/mongo/db/commands/user_management_commands_common.cpp
+++ b/src/mongo/db/commands/user_management_commands_common.cpp
@@ -273,45 +273,35 @@ void checkAuthForTypedCommand(Client* client, const RevokeRolesFromRoleCommand&
uassertStatusOK(checkAuthorizedToRevokeRoles(as, rolesToRemove));
}
-Status checkAuthForUsersInfoCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
- auth::UsersInfoArgs args;
- Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args);
- if (!status.isOK()) {
- return status;
- }
+void checkAuthForTypedCommand(Client* client, const UsersInfoCommand& request) {
+ const auto& dbname = request.getDbName();
+ const auto& arg = request.getCommandParameter();
+ auto* as = AuthorizationSession::get(client);
- if (args.target == auth::UsersInfoArgs::Target::kDB) {
- if (!authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(dbname), ActionType::viewUser)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream()
- << "Not authorized to view users from the " << dbname << " database");
- }
- } else if (args.target == auth::UsersInfoArgs::Target::kGlobal) {
- if (!authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
- ActionType::viewUser)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream() << "Not authorized to view users from all"
- << " databases");
- }
+ if (arg.isAllOnCurrentDB()) {
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to view users from the " << dbname << " database",
+ as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname),
+ ActionType::viewUser));
+ } else if (arg.isAllForAllDBs()) {
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to view users from all databases",
+ as->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
+ ActionType::viewUser));
} else {
- for (size_t i = 0; i < args.userNames.size(); ++i) {
- if (authzSession->lookupUser(args.userNames[i])) {
- continue; // Can always view users you are logged in as
- }
- if (!authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(args.userNames[i].getDB()),
- ActionType::viewUser)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream() << "Not authorized to view users from the " << dbname
- << " database");
+ invariant(arg.isExact());
+ for (const auto& userName : arg.getElements(dbname)) {
+ if (as->lookupUser(userName)) {
+ // Can always view users you are logged in as.
+ continue;
}
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to view users from the " << dbname
+ << " database",
+ as->isAuthorizedForActionsOnResource(
+ ResourcePattern::forDatabaseName(userName.getDB()), ActionType::viewUser));
}
}
- return Status::OK();
}
void checkAuthForTypedCommand(Client* client, const RevokePrivilegesFromRoleCommand& request) {
@@ -328,40 +318,32 @@ void checkAuthForTypedCommand(Client* client, const DropAllRolesFromDatabaseComm
ActionType::dropRole));
}
-Status checkAuthForRolesInfoCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
- auth::RolesInfoArgs args;
- Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args);
- if (!status.isOK()) {
- return status;
- }
+void checkAuthForTypedCommand(Client* client, const RolesInfoCommand& request) {
+ const auto& dbname = request.getDbName();
+ const auto& arg = request.getCommandParameter();
+ auto* as = AuthorizationSession::get(client);
- if (args.allForDB) {
- if (!authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(dbname), ActionType::viewRole)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream()
- << "Not authorized to view roles from the " << dbname << " database");
- }
+ invariant(!arg.isAllForAllDBs());
+ if (arg.isAllOnCurrentDB()) {
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to view roles from the " << dbname << " database",
+ as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname),
+ ActionType::viewRole));
} else {
- for (size_t i = 0; i < args.roleNames.size(); ++i) {
- if (authzSession->isAuthenticatedAsUserWithRole(args.roleNames[i])) {
+ invariant(arg.isExact());
+ auto roles = arg.getElements(dbname);
+ for (const auto& role : roles) {
+ if (as->isAuthenticatedAsUserWithRole(role)) {
continue; // Can always see roles that you are a member of
}
- if (!authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(args.roleNames[i].getDB()),
- ActionType::viewRole)) {
- return Status(ErrorCodes::Unauthorized,
- str::stream() << "Not authorized to view roles from the "
- << args.roleNames[i].getDB() << " database");
- }
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to view roles from the " << role.getDB()
+ << " database",
+ as->isAuthorizedForActionsOnResource(
+ ResourcePattern::forDatabaseName(role.getDB()), ActionType::viewRole));
}
}
-
- return Status::OK();
}
Status checkAuthForInvalidateUserCacheCommand(Client* client) {
diff --git a/src/mongo/db/commands/user_management_commands_common.h b/src/mongo/db/commands/user_management_commands_common.h
index 7bc4caaffe3..3e162039d9f 100644
--- a/src/mongo/db/commands/user_management_commands_common.h
+++ b/src/mongo/db/commands/user_management_commands_common.h
@@ -93,14 +93,8 @@ void checkAuthForTypedCommand(Client*, const DropUserCommand&);
void checkAuthForTypedCommand(Client*, const DropRoleCommand&);
void checkAuthForTypedCommand(Client*, const RevokePrivilegesFromRoleCommand&);
void checkAuthForTypedCommand(Client*, const DropAllRolesFromDatabaseCommand&);
-
-Status checkAuthForUsersInfoCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj);
-
-Status checkAuthForRolesInfoCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj);
+void checkAuthForTypedCommand(Client*, const UsersInfoCommand&);
+void checkAuthForTypedCommand(Client*, const RolesInfoCommand&);
Status checkAuthForInvalidateUserCacheCommand(Client* client);
diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp
index fe3efac21a4..7eff45516c0 100644
--- a/src/mongo/embedded/embedded_auth_manager.cpp
+++ b/src/mongo/embedded/embedded_auth_manager.cpp
@@ -88,7 +88,14 @@ public:
const std::vector<RoleName>&,
PrivilegeFormat,
AuthenticationRestrictionsFormat,
- BSONObj*) override {
+ std::vector<BSONObj>*) override {
+ UASSERT_NOT_IMPLEMENTED;
+ }
+
+ Status getRolesAsUserFragment(OperationContext*,
+ const std::vector<RoleName>&,
+ AuthenticationRestrictionsFormat,
+ BSONObj*) override {
UASSERT_NOT_IMPLEMENTED;
}
@@ -97,7 +104,7 @@ public:
PrivilegeFormat,
AuthenticationRestrictionsFormat,
bool,
- BSONArrayBuilder*) override {
+ std::vector<BSONObj>*) override {
UASSERT_NOT_IMPLEMENTED;
}
diff --git a/src/mongo/s/commands/cluster_user_management_commands.cpp b/src/mongo/s/commands/cluster_user_management_commands.cpp
index c3d7d154cfb..bab53eef228 100644
--- a/src/mongo/s/commands/cluster_user_management_commands.cpp
+++ b/src/mongo/s/commands/cluster_user_management_commands.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/commands/user_management_commands_common.h"
#include "mongo/db/commands/user_management_commands_gen.h"
#include "mongo/db/jsobj.h"
+#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/rpc/write_concern_error_detail.h"
#include "mongo/s/catalog/type_shard.h"
#include "mongo/s/client/shard_registry.h"
@@ -196,43 +197,6 @@ CmdUMCPassthrough<DropAllUsersFromDatabaseCommand,
CmdUMCPassthrough<GrantRolesToUserCommand, void, UserCacheInvalidatorUser> cmdGrantRolesToUser;
CmdUMCPassthrough<RevokeRolesFromUserCommand, void, UserCacheInvalidatorUser>
cmdRevokeRolesFromUser;
-
-class CmdUsersInfo : public BasicCommand {
-public:
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kOptIn;
- }
-
- virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
- return false;
- }
-
- CmdUsersInfo() : BasicCommand("usersInfo") {}
-
- std::string help() const override {
- return "Returns information about users.";
- }
-
- virtual Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) const {
- return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* opCtx,
- const string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) {
- return Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand(
- opCtx,
- dbname,
- applyReadWriteConcern(
- opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
- &result);
- }
-
-} cmdUsersInfo;
-
CmdUMCPassthrough<CreateRoleCommand, void, UserCacheInvalidatorNOOP> cmdCreateRole;
CmdUMCPassthrough<UpdateRoleCommand, void, UserCacheInvalidatorAll> cmdUpdateRole;
CmdUMCPassthrough<GrantPrivilegesToRoleCommand, void, UserCacheInvalidatorAll>
@@ -247,41 +211,63 @@ CmdUMCPassthrough<DropAllRolesFromDatabaseCommand,
UserCacheInvalidatorAll>
cmdDropAllRolesFromDatabase;
-class CmdRolesInfo : public BasicCommand {
+/**
+ * usersInfo and rolesInfo are simpler read-only passthrough commands.
+ */
+template <typename RequestT, typename ReplyT>
+class CmdUMCInfo : public TypedCommand<CmdUMCInfo<RequestT, ReplyT>> {
public:
- CmdRolesInfo() : BasicCommand("rolesInfo") {}
+ using Request = RequestT;
+ using Reply = ReplyT;
+ using TC = TypedCommand<CmdUMCInfo<RequestT, ReplyT>>;
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kOptIn;
- }
+ class Invocation final : public TC::InvocationBase {
+ public:
+ using TC::InvocationBase::InvocationBase;
+ using TC::InvocationBase::request;
- virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
- return false;
- }
+ Reply typedRun(OperationContext* opCtx) {
+ const auto& cmd = request();
- std::string help() const override {
- return "Returns information about roles.";
- }
+ BSONObjBuilder builder;
+ const bool ok = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand(
+ opCtx,
+ cmd.getDbName().toString(),
+ applyReadWriteConcern(
+ opCtx,
+ this,
+ CommandHelpers::filterCommandRequestForPassthrough(cmd.toBSON({}))),
+ &builder);
- virtual Status checkAuthForCommand(Client* client,
- const std::string& dbname,
- const BSONObj& cmdObj) const {
- return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj);
- }
+ auto result = builder.obj();
+ if (!ok) {
+ uassertStatusOK(getStatusFromCommandResult(result));
+ }
- bool run(OperationContext* opCtx,
- const string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) {
- return Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand(
- opCtx,
- dbname,
- applyReadWriteConcern(
- opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
- &result);
+ return parseUMCReply<Request, Reply>(result);
+ }
+
+ private:
+ bool supportsWriteConcern() const final {
+ return false;
+ }
+
+ void doCheckAuthorization(OperationContext* opCtx) const final {
+ auth::checkAuthForTypedCommand(opCtx->getClient(), request());
+ }
+
+ NamespaceString ns() const override {
+ return NamespaceString(request().getDbName(), "");
+ }
+ };
+
+ typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
+ return TC::AllowedOnSecondary::kOptIn;
}
+};
-} cmdRolesInfo;
+CmdUMCInfo<UsersInfoCommand, UsersInfoReply> cmdUsersInfo;
+CmdUMCInfo<RolesInfoCommand, RolesInfoReply> cmdRolesInfo;
class CmdInvalidateUserCache : public BasicCommand {
public: