diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2020-10-26 17:26:31 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-11-09 18:16:52 +0000 |
commit | 5e0d73d0d8e559e34203740af93b6ca03d573ea5 (patch) | |
tree | 3ddc4e4ddde8af5fb575ffe819d0bb4b3acee271 /src/mongo | |
parent | fa826f6a5b77eb059fe03d411276c3ee7eb303d5 (diff) | |
download | mongo-5e0d73d0d8e559e34203740af93b6ca03d573ea5.tar.gz |
SERVER-51864 IDLify usersInfo and rolesInfo commands
Diffstat (limited to 'src/mongo')
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: |