diff options
Diffstat (limited to 'src')
20 files changed, 353 insertions, 149 deletions
diff --git a/src/mongo/client/internal_auth.cpp b/src/mongo/client/internal_auth.cpp index 1effff0a08a..59b8d6da5dc 100644 --- a/src/mongo/client/internal_auth.cpp +++ b/src/mongo/client/internal_auth.cpp @@ -104,8 +104,8 @@ BSONObj getInternalAuthParams(size_t idx, StringData mechanism) { auto password = internalAuthKeys.at(idx); if (mechanism == kMechanismScramSha1) { - password = mongo::createPasswordDigest( - internalSecurity.user->getName().getUser().toString(), password); + password = + mongo::createPasswordDigest(internalSecurity.user->getName().getUser(), password); } return BSON(saslCommandMechanismFieldName @@ -131,8 +131,11 @@ std::string getInternalAuthDB() { return getBSONString(internalAuthParams, saslCommandUserDBFieldName); } - auto isu = internalSecurity.user; - return isu ? isu->getName().getDB().toString() : "admin"; + if (auto isu = internalSecurity.user) { + return isu->getName().getDB(); + } + + return "admin"; } } // namespace auth diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index f83622f3075..c99651c652e 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -521,6 +521,7 @@ env.CppUnitTest( source=[ 'action_set_test.cpp', 'address_restriction_test.cpp', + 'auth_identifier_test.cpp', 'authorization_contract_test.cpp', 'auth_op_observer_test.cpp', 'authorization_manager_test.cpp', diff --git a/src/mongo/db/auth/auth_identifier_test.cpp b/src/mongo/db/auth/auth_identifier_test.cpp new file mode 100644 index 00000000000..0598c568b21 --- /dev/null +++ b/src/mongo/db/auth/auth_identifier_test.cpp @@ -0,0 +1,168 @@ +/** + * Copyright (C) 2021-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. + */ + +/** + * Unit tests of the UserName and RoleName types. + */ + +#include "mongo/platform/basic.h" + +#include <string> + +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/auth/role_name.h" +#include "mongo/db/auth/user_name.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +template <typename T> +struct Traits {}; + +template <> +struct Traits<UserName> { + static constexpr auto kFieldName = "user"_sd; + static const std::string& getName(const UserName& obj) { + return obj.getUser(); + } +}; + +template <> +struct Traits<RoleName> { + static constexpr auto kFieldName = "role"_sd; + static const std::string& getName(const RoleName& obj) { + return obj.getRole(); + } +}; + +template <typename Stream, typename T> +std::string stream(const T& obj) { + Stream sb; + sb << obj; + return sb.str(); +} + +template <typename T, typename Name, typename Db> +void checkValueAssertions(const T& obj, Name name, Db db) { + const bool expectEmpty = StringData(name).empty() && StringData(db).empty(); + ASSERT_EQ(obj.empty(), expectEmpty); + + ASSERT_EQ(obj.getDB(), db); + ASSERT_EQ(Traits<T>::getName(obj), name); + + std::string expectDisplay, expectUnique; + if (!expectEmpty) { + expectDisplay = str::stream() << name << '@' << db; + expectUnique = str::stream() << db << '.' << name; + } + ASSERT_EQ(obj.getDisplayName(), expectDisplay); + ASSERT_EQ(stream<StringBuilder>(obj), expectDisplay); + ASSERT_EQ(stream<std::ostringstream>(obj), expectDisplay); + ASSERT_EQ(obj.getUnambiguousName(), expectUnique); +} + +template <typename T> +void doConstructorTest() { + checkValueAssertions(T(), "", ""); + + checkValueAssertions(T("", ""), "", ""); + checkValueAssertions(T(std::string(), std::string()), "", ""); + checkValueAssertions(T(StringData(), StringData()), "", ""); + checkValueAssertions(T(std::string(), StringData()), "", ""); + + checkValueAssertions(T("name1", "db1"), "name1", "db1"); + checkValueAssertions(T("name1", ""), "name1", ""); + checkValueAssertions(T("", "db1"), "", "db1"); +} + +TEST(AuthName, ConstructorTest) { + doConstructorTest<UserName>(); + doConstructorTest<RoleName>(); +} + +template <typename T, typename Name, typename Db> +void doBSONParseTest(Name name, Db db) { + auto obj = BSON(Traits<T>::kFieldName << name << "db" << db); + checkValueAssertions(T::parseFromBSON(BSON("" << obj).firstElement()), name, db); + + // RoleName doesn't support parseFromBSONObj() + if constexpr (std::is_same_v<T, UserName>) { + checkValueAssertions(T::parseFromBSONObj(obj), name, db); + } +} + +template <typename T, typename Name, typename Db> +void doBSONParseFailure(Name name, Db db) { + auto obj = BSON(Traits<T>::kFieldName << name << "db" << db); + ASSERT_THROWS(T::parseFromBSON(BSON("" << obj).firstElement()), AssertionException); + + // RoleName doesn't support parseFromBSONObj() + if constexpr (std::is_same_v<T, UserName>) { + ASSERT_THROWS(T::parseFromBSONObj(obj), AssertionException); + } +} + +template <typename T> +void doBSONParseTests() { + doBSONParseTest<T>("", ""); + doBSONParseTest<T>("name", ""); + doBSONParseTest<T>("", "db"); + doBSONParseTest<T>("name", "db"); + + doBSONParseFailure<T>(123, "db"); + doBSONParseFailure<T>("name", 123); + doBSONParseFailure<T>(OID(), "db"); +} + +TEST(AuthName, BSONParseTests) { + doBSONParseTests<UserName>(); + doBSONParseTests<RoleName>(); +} + +template <typename T> +void doStringParseTests() { + checkValueAssertions(uassertStatusOK(T::parse("db.name")), "name", "db"); + checkValueAssertions(uassertStatusOK(T::parse("db.")), "", "db"); + checkValueAssertions(uassertStatusOK(T::parse(".name")), "name", ""); + checkValueAssertions(uassertStatusOK(T::parse("db.name.str")), "name.str", "db"); + checkValueAssertions(uassertStatusOK(T::parse(".")), "", ""); + + ASSERT_NOT_OK(T::parse("")); +} + +TEST(AuthName, StringParseTests) { + doStringParseTests<UserName>(); + // RoleName doesn't support parse(StringData) +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/auth/authentication_session.cpp b/src/mongo/db/auth/authentication_session.cpp index 6de2fe5d5ea..30d6f74fc31 100644 --- a/src/mongo/db/auth/authentication_session.cpp +++ b/src/mongo/db/auth/authentication_session.cpp @@ -41,7 +41,7 @@ namespace { constexpr auto kDiagnosticLogLevel = 3; Status crossVerifyUserNames(const UserName& oldUser, const UserName& newUser) noexcept { - if (oldUser.getFullName().empty()) { + if (oldUser.empty()) { return Status::OK(); } @@ -53,7 +53,7 @@ Status crossVerifyUserNames(const UserName& oldUser, const UserName& newUser) no return {ErrorCodes::ProtocolError, str::stream() << "Attempt to switch database target during SASL authentication from " - << oldUser.toString() << " to " << newUser.toString()}; + << oldUser << " to " << newUser}; } } @@ -65,7 +65,7 @@ Status crossVerifyUserNames(const UserName& oldUser, const UserName& newUser) no if (oldUser.getUser() != newUser.getUser()) { return {ErrorCodes::ProtocolError, str::stream() << "Attempt to switch user during SASL authentication from " - << oldUser.toString() << " to " << newUser.toString()}; + << oldUser << " to " << newUser}; } return Status::OK(); diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index 5ddc068c1d2..3a24e2d8165 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -532,7 +532,7 @@ StatusWith<UserHandle> AuthorizationManagerImpl::reacquireUser(OperationContext* auto ret = std::move(swUserHandle.getValue()); if (user->getID() != ret->getID()) { return {ErrorCodes::UserNotFound, - str::stream() << "User id from privilege document '" << userName.toString() + str::stream() << "User id from privilege document '" << userName << "' does not match user id in session."}; } @@ -629,7 +629,7 @@ void AuthorizationManagerImpl::_pinnedUsersThreadRoutine() noexcept try { if (status != ErrorCodes::UserNotFound) { LOGV2_WARNING(20239, "Unable to fetch pinned user", - "user"_attr = userName.toString(), + "user"_attr = userName, "error"_attr = status); } else { LOGV2_DEBUG(20233, 2, "Pinned user not found", "user"_attr = userName); diff --git a/src/mongo/db/auth/authz_manager_external_state.cpp b/src/mongo/db/auth/authz_manager_external_state.cpp index 5d2eb103f95..1e979d19b2f 100644 --- a/src/mongo/db/auth/authz_manager_external_state.cpp +++ b/src/mongo/db/auth/authz_manager_external_state.cpp @@ -57,7 +57,7 @@ Status AuthzManagerExternalState::makeRoleNotFoundStatus( sb << 's'; } for (const auto& unknownRole : unknownRoles) { - sb << delim << ' ' << unknownRole.toString(); + sb << delim << ' ' << unknownRole; delim = ','; } return {ErrorCodes::RoleNotFound, sb.str()}; 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 f5067d6aa33..3b71f8dce4d 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -441,7 +441,7 @@ StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles( if (elem.type() != Array) { return {ErrorCodes::BadValue, str::stream() - << "Invalid 'roles' field in role document '" << role.getFullName() + << "Invalid 'roles' field in role document '" << role << "', expected an array but found " << typeName(elem.type())}; } for (const auto& subroleElem : elem.Obj()) { @@ -461,8 +461,8 @@ StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles( if (processPrivs && (elem = roleDoc["privileges"])) { if (elem.type() != Array) { return {ErrorCodes::UnsupportedFormat, - str::stream() << "Invalid 'privileges' field in role document '" - << role.getFullName() << "'"}; + str::stream() + << "Invalid 'privileges' field in role document '" << role << "'"}; } for (const auto& privElem : elem.Obj()) { auto priv = Privilege::fromBSON(privElem); @@ -475,7 +475,7 @@ StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles( return {ErrorCodes::UnsupportedFormat, str::stream() << "Invalid 'authenticationRestrictions' field in role document '" - << role.getFullName() << "'"}; + << role << "'"}; } inheritedRestrictions.push_back( uassertStatusOK(parseAuthenticationRestriction(BSONArray(elem.Obj())))); @@ -589,8 +589,7 @@ Status AuthzManagerExternalStateLocal::getRolesDescription( result->push_back(roleBuilder.obj()); } catch (const AssertionException& ex) { return {ex.code(), - str::stream() << "Failed fetching role '" << role.getFullName() - << "': " << ex.reason()}; + str::stream() << "Failed fetching role '" << role << "': " << ex.reason()}; } } 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 b3f0483ecc1..1b2c0b7331c 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -150,7 +150,7 @@ Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* opC std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); if (foundUsers.size() == 0) { return Status(ErrorCodes::UserNotFound, - "User \"" + userName.toString() + "\" not found"); + str::stream() << "User \"" << userName << "\" not found"); } if (foundUsers.size() > 1) { diff --git a/src/mongo/db/auth/role_name.cpp b/src/mongo/db/auth/role_name.cpp index d8cf76f48d5..126e0b665b2 100644 --- a/src/mongo/db/auth/role_name.cpp +++ b/src/mongo/db/auth/role_name.cpp @@ -39,21 +39,6 @@ namespace mongo { -RoleName::RoleName(StringData role, StringData dbname) { - _fullName.resize(role.size() + dbname.size() + 1); - std::string::iterator iter = - std::copy(role.rawData(), role.rawData() + role.size(), _fullName.begin()); - *iter = '@'; - ++iter; - iter = std::copy(dbname.rawData(), dbname.rawData() + dbname.size(), iter); - dassert(iter == _fullName.end()); - _splitPoint = role.size(); -} - -std::ostream& operator<<(std::ostream& os, const RoleName& name) { - return os << name.getFullName(); -} - RoleName RoleName::parseFromBSON(const BSONElement& elem) { auto obj = elem.embeddedObjectUserCheck(); std::array<BSONElement, 2> fields; diff --git a/src/mongo/db/auth/role_name.h b/src/mongo/db/auth/role_name.h index 81055b5ac20..9a0d5978541 100644 --- a/src/mongo/db/auth/role_name.h +++ b/src/mongo/db/auth/role_name.h @@ -49,8 +49,22 @@ namespace mongo { */ class RoleName { public: - RoleName() : _splitPoint(0) {} - RoleName(StringData role, StringData dbname); + RoleName() = default; + + template <typename Role, typename DB> + RoleName(Role role, DB db) { + if constexpr (std::is_same_v<Role, std::string>) { + _role = std::move(role); + } else { + _role = StringData(role).toString(); + } + + if constexpr (std::is_same_v<DB, std::string>) { + _db = std::move(db); + } else { + _db = StringData(db).toString(); + } + } // Added for IDL support static RoleName parseFromBSON(const BSONElement& elem); @@ -62,19 +76,19 @@ public: /** * Gets the name of the role excluding the "@dbname" component. */ - StringData getRole() const { - return StringData(_fullName).substr(0, _splitPoint); + const std::string& getRole() const { + return _role; } /** * Gets the database name part of a role name. */ - StringData getDB() const { - return StringData(_fullName).substr(_splitPoint + 1); + const std::string& getDB() const { + return _db; } bool empty() const { - return _fullName.empty(); + return _role.empty() && _db.empty(); } /** @@ -82,33 +96,41 @@ public: * * Allowed for keys in non-persistent data structures, such as std::map. */ - const std::string& getFullName() const { - return _fullName; + std::string getDisplayName() const { + if (empty()) { + return ""; + } + return str::stream() << _role << '@' << _db; } /** - * Stringifies the object, for logging/debugging. + * Gets the full unambiguous unique name of a user as a string, formatted as "db.role" */ - const std::string& toString() const { - return getFullName(); + std::string getUnambiguousName() const { + if (empty()) { + return ""; + } + return str::stream() << _db << '.' << _role; } template <typename H> friend H AbslHashValue(H h, const RoleName& rname) { - return H::combine(std::move(h), rname.getFullName()); + auto state = H::combine(std::move(h), rname._db); + state = H::combine(std::move(state), '.'); + return H::combine(std::move(state), rname._role); } private: - std::string _fullName; // The full name, stored as a string. "role@db". - size_t _splitPoint; // The index of the "@" separating the role and db name parts. + std::string _role; + std::string _db; }; static inline bool operator==(const RoleName& lhs, const RoleName& rhs) { - return lhs.getFullName() == rhs.getFullName(); + return (lhs.getRole() == rhs.getRole()) && (lhs.getDB() == rhs.getDB()); } static inline bool operator!=(const RoleName& lhs, const RoleName& rhs) { - return lhs.getFullName() != rhs.getFullName(); + return (lhs.getRole() != rhs.getRole()) || (lhs.getDB() != rhs.getDB()); } static inline bool operator<(const RoleName& lhs, const RoleName& rhs) { @@ -118,8 +140,19 @@ static inline bool operator<(const RoleName& lhs, const RoleName& rhs) { return lhs.getDB() < rhs.getDB(); } -std::ostream& operator<<(std::ostream& os, const RoleName& name); +static inline std::ostream& operator<<(std::ostream& os, const RoleName& role) { + if (!role.empty()) { + os << role.getRole() << '@' << role.getDB(); + } + return os; +} +static inline StringBuilder& operator<<(StringBuilder& os, const RoleName& role) { + if (!role.empty()) { + os << role.getRole() << '@' << role.getDB(); + } + return os; +} /** * Iterator over an unspecified container of RoleName objects. diff --git a/src/mongo/db/auth/sasl_mechanism_registry.h b/src/mongo/db/auth/sasl_mechanism_registry.h index cceff146750..cc6a233c11b 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.h +++ b/src/mongo/db/auth/sasl_mechanism_registry.h @@ -166,7 +166,7 @@ public: * authentication mechanisms */ bool isClusterMember() const { - return _principalName == internalSecurity.user->getName().getUser().toString() && + return _principalName == internalSecurity.user->getName().getUser() && getAuthenticationDatabase() == internalSecurity.user->getName().getDB(); }; diff --git a/src/mongo/db/auth/security_key.cpp b/src/mongo/db/auth/security_key.cpp index c09075d4f31..f69dfeba5e1 100644 --- a/src/mongo/db/auth/security_key.cpp +++ b/src/mongo/db/auth/security_key.cpp @@ -83,8 +83,8 @@ public: "error"_attr = swSaslPassword.getStatus()); return boost::none; } - const auto passwordDigest = mongo::createPasswordDigest( - internalSecurity.user->getName().getUser().toString(), password); + const auto passwordDigest = + mongo::createPasswordDigest(internalSecurity.user->getName().getUser(), password); User::CredentialData credentials; if (!_copyCredentials( diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index 6ed98aae6f3..ff967a9f8a0 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -47,7 +47,7 @@ namespace mongo { namespace { SHA256Block computeDigest(const UserName& name) { - const auto& fn = name.getFullName(); + auto fn = name.getDisplayName(); return SHA256Block::computeHash({ConstDataRange(fn.c_str(), fn.size())}); }; diff --git a/src/mongo/db/auth/user_name.cpp b/src/mongo/db/auth/user_name.cpp index 65aa84cba1e..2a95f374167 100644 --- a/src/mongo/db/auth/user_name.cpp +++ b/src/mongo/db/auth/user_name.cpp @@ -38,17 +38,6 @@ namespace mongo { -UserName::UserName(StringData user, StringData dbname) { - _fullName.resize(user.size() + dbname.size() + 1); - std::string::iterator iter = - std::copy(user.rawData(), user.rawData() + user.size(), _fullName.begin()); - *iter = '@'; - ++iter; - iter = std::copy(dbname.rawData(), dbname.rawData() + dbname.size(), iter); - dassert(iter == _fullName.end()); - _splitPoint = user.size(); -} - /** * Don't change the logic of this function as it will break stable API version 1. */ @@ -82,18 +71,15 @@ UserName UserName::parseFromVariant(const stdx::variant<std::string, BSONObj>& h */ UserName UserName::parseFromBSONObj(const BSONObj& obj) { std::bitset<2> usedFields; - const auto kUserNameFieldName = AuthorizationManager::USER_NAME_FIELD_NAME; - const auto kUserDbFieldName = AuthorizationManager::USER_DB_FIELD_NAME; - const size_t kUserNameFieldBit = 0; - const size_t kUserDbFieldBit = 1; + constexpr auto kUserNameFieldName = AuthorizationManager::USER_NAME_FIELD_NAME; + constexpr auto kUserDbFieldName = AuthorizationManager::USER_DB_FIELD_NAME; + constexpr size_t kUserNameFieldBit = 0; + constexpr size_t kUserDbFieldBit = 1; StringData userName, userDb; for (const auto& element : obj) { const auto fieldName = element.fieldNameStringData(); - uassert(ErrorCodes::BadValue, - str::stream() << "username contains an unknown field named: '" << fieldName, - fieldName == kUserNameFieldName || fieldName == kUserDbFieldName); uassert(ErrorCodes::BadValue, str::stream() << "username must contain a string field named: " << fieldName, @@ -114,18 +100,19 @@ UserName UserName::parseFromBSONObj(const BSONObj& obj) { usedFields.set(kUserDbFieldBit); userDb = element.valueStringData(); + } else { + uasserted(ErrorCodes::BadValue, + str::stream() << "username contains an unknown field named: '" << fieldName); } } - if (!usedFields[kUserNameFieldBit]) { - uasserted(ErrorCodes::BadValue, - str::stream() << "username must contain a field named: " << kUserNameFieldName); - } + uassert(ErrorCodes::BadValue, + str::stream() << "username must contain a field named: " << kUserNameFieldName, + usedFields[kUserNameFieldBit]); - if (!usedFields[kUserDbFieldBit]) { - uasserted(ErrorCodes::BadValue, - str::stream() << "username must contain a field named: " << kUserDbFieldName); - } + uassert(ErrorCodes::BadValue, + str::stream() << "username must contain a field named: " << kUserDbFieldName, + usedFields[kUserDbFieldBit]); return UserName(userName, userDb); } @@ -168,8 +155,4 @@ BSONObj UserName::toBSON() const { return ret.obj(); } -std::ostream& operator<<(std::ostream& os, const UserName& name) { - return os << name.getFullName(); -} - } // namespace mongo diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h index 4654ce4dabb..681d4a9f736 100644 --- a/src/mongo/db/auth/user_name.h +++ b/src/mongo/db/auth/user_name.h @@ -50,8 +50,22 @@ namespace mongo { */ class UserName { public: - UserName() : _splitPoint(0) {} - UserName(StringData user, StringData dbname); + UserName() = default; + + template <typename User, typename DB> + UserName(User user, DB db) { + if constexpr (std::is_same_v<User, std::string>) { + _user = std::move(user); + } else { + _user = StringData(user).toString(); + } + + if constexpr (std::is_same_v<DB, std::string>) { + _db = std::move(db); + } else { + _db = StringData(db).toString(); + } + } /** * Parses a string of the form "db.username" into a UserName object. @@ -83,67 +97,91 @@ public: /** * Gets the user part of a UserName. */ - StringData getUser() const { - return StringData(_fullName.data(), _splitPoint); + const std::string& getUser() const { + return _user; } /** * Gets the database name part of a UserName. */ - StringData getDB() const { - if (_fullName.empty()) { - return _fullName; - } - - const auto offset = _splitPoint + 1; - invariant(offset <= _fullName.size()); - return StringData(_fullName.data() + offset, _fullName.size() - offset); + const std::string& getDB() const { + return _db; } /** * Gets the full unique name of a user as a string, formatted as "user@db". */ - const std::string& getFullName() const { - return _fullName; + std::string getDisplayName() const { + if (empty()) { + return ""; + } + return str::stream() << _user << "@" << _db; + } + + /** + * Predict name length without actually forming string. + */ + std::size_t getDisplayNameLength() const { + if (empty()) { + return 0; + } + return _db.size() + 1 + _user.size(); } /** * Gets the full unambiguous unique name of a user as a string, formatted as "db.user" */ std::string getUnambiguousName() const { - return str::stream() << getDB() << "." << getUser(); + if (empty()) { + return ""; + } + return str::stream() << _db << "." << _user; } /** - * Stringifies the object, for logging/debugging. + * True if the username and dbname have not been set. */ - std::string toString() const { - return getFullName(); + bool empty() const { + return _db.empty() && _user.empty(); } bool operator==(const UserName& rhs) const { - return _splitPoint == rhs._splitPoint && getFullName() == rhs.getFullName(); + return (_user == rhs._user) && (_db == rhs._db); } bool operator!=(const UserName& rhs) const { - return _splitPoint != rhs._splitPoint || getFullName() != rhs.getFullName(); + return !(*this == rhs); } bool operator<(const UserName& rhs) const { - return getUser() < rhs.getUser() || (getUser() == rhs.getUser() && getDB() < rhs.getDB()); + return (_user < rhs._user) || ((_user == rhs._user) && (_db < rhs._db)); } template <typename H> friend H AbslHashValue(H h, const UserName& userName) { - return H::combine(std::move(h), userName.getFullName()); + auto state = H::combine(std::move(h), userName._db); + state = H::combine(std::move(state), '.'); + return H::combine(std::move(state), userName._user); } private: - std::string _fullName; // The full name, stored as a string. "user@db". - size_t _splitPoint; // The index of the "@" separating the user and db name parts. + std::string _user; + std::string _db; }; -std::ostream& operator<<(std::ostream& os, const UserName& name); +static inline std::ostream& operator<<(std::ostream& os, const UserName& user) { + if (!user.empty()) { + os << user.getUser() << '@' << user.getDB(); + } + return os; +} + +static inline StringBuilder& operator<<(StringBuilder& os, const UserName& user) { + if (!user.empty()) { + os << user.getUser() << '@' << user.getDB(); + } + return os; +} /** * Iterator over an unspecified container of UserName objects. diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index e5a43d3a7bc..28c1590e8d5 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -148,7 +148,7 @@ Status checkOkayToGrantRolesToRole(OperationContext* opCtx, for (const auto& roleToAdd : rolesToAdd) { if (roleToAdd == role) { return {ErrorCodes::InvalidRoleModification, - str::stream() << "Cannot grant role " << role.getFullName() << " to itself."}; + str::stream() << "Cannot grant role " << role << " to itself."}; } if (role.getDB() != "admin" && roleToAdd.getDB() != role.getDB()) { @@ -161,21 +161,20 @@ Status checkOkayToGrantRolesToRole(OperationContext* opCtx, auto status = authzManager->rolesExist(opCtx, rolesToAdd); if (!status.isOK()) { return {status.code(), - str::stream() << "Cannot grant roles to '" << role.toString() - << "': " << status.reason()}; + str::stream() << "Cannot grant roles to '" << role << "': " << status.reason()}; } auto swData = authzManager->resolveRoles( opCtx, rolesToAdd, AuthorizationManager::ResolveRoleOption::kRoles); if (!swData.isOK()) { return {swData.getStatus().code(), - str::stream() << "Cannot grant roles to '" << role.toString() + str::stream() << "Cannot grant roles to '" << role << "': " << swData.getStatus().reason()}; } if (sequenceContains(swData.getValue().roles.get(), role)) { return {ErrorCodes::InvalidRoleModification, - str::stream() << "Granting roles to " << role.getFullName() + str::stream() << "Granting roles to " << role << " would introduce a cycle in the role graph"}; } @@ -414,8 +413,7 @@ Status updateRoleDocument(OperationContext* opCtx, const RoleName& role, const B return status; } if (status.code() == ErrorCodes::NoMatchingDocument) { - return Status(ErrorCodes::RoleNotFound, - str::stream() << "Role " << role.getFullName() << " not found"); + return Status(ErrorCodes::RoleNotFound, str::stream() << "Role " << role << " not found"); } if (status.code() == ErrorCodes::UnknownError) { return Status(ErrorCodes::RoleModificationFailed, status.reason()); @@ -476,8 +474,7 @@ Status updatePrivilegeDocument(OperationContext* opCtx, return {ErrorCodes::UserModificationFailed, status.reason()}; } if (status.code() == ErrorCodes::NoMatchingDocument) { - return {ErrorCodes::UserNotFound, - str::stream() << "User " << user.getFullName() << " not found"}; + return {ErrorCodes::UserNotFound, str::stream() << "User " << user << " not found"}; } return status; } @@ -1228,7 +1225,7 @@ void CmdUMCTyped<DropUserCommand>::Invocation::typedRun(OperationContext* opCtx) uassertStatusOK(status); uassert(ErrorCodes::UserNotFound, - str::stream() << "User '" << userName.getFullName() << "' not found", + str::stream() << "User '" << userName << "' not found", numMatched > 0); } @@ -1598,7 +1595,7 @@ void CmdUMCTyped<GrantPrivilegesToRoleCommand>::Invocation::typedRun(OperationCo !cmd.getPrivileges().empty()); uassert(ErrorCodes::BadValue, - str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + str::stream() << roleName << " is a built-in role and cannot be modified", !auth::isBuiltinRole(roleName)); auto* client = opCtx->getClient(); @@ -1647,7 +1644,7 @@ void CmdUMCTyped<RevokePrivilegesFromRoleCommand>::Invocation::typedRun(Operatio !cmd.getPrivileges().empty()); uassert(ErrorCodes::BadValue, - str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + str::stream() << roleName << " is a built-in role and cannot be modified", !auth::isBuiltinRole(roleName)); auto* client = opCtx->getClient(); @@ -1701,7 +1698,7 @@ void CmdUMCTyped<GrantRolesToRoleCommand>::Invocation::typedRun(OperationContext !cmd.getRoles().empty()); uassert(ErrorCodes::BadValue, - str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + str::stream() << roleName << " is a built-in role and cannot be modified", !auth::isBuiltinRole(roleName)); auto rolesToAdd = auth::resolveRoleNames(cmd.getRoles(), dbname); @@ -1741,7 +1738,7 @@ void CmdUMCTyped<RevokeRolesFromRoleCommand>::Invocation::typedRun(OperationCont !cmd.getRoles().empty()); uassert(ErrorCodes::BadValue, - str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + str::stream() << roleName << " is a built-in role and cannot be modified", !auth::isBuiltinRole(roleName)); auto rolesToRemove = auth::resolveRoleNames(cmd.getRoles(), dbname); @@ -1842,7 +1839,7 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx) RoleName roleName(cmd.getCommandParameter(), dbname); uassert(ErrorCodes::BadValue, - str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + str::stream() << roleName << " is a built-in role and cannot be modified", !auth::isBuiltinRole(roleName)); auto* client = opCtx->getClient(); @@ -1868,8 +1865,8 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx) BSON("$pull" << BSON("roles" << roleName.toBSON()))); if (!swCount.isOK()) { return useDefaultCode(swCount.getStatus(), ErrorCodes::UserModificationFailed) - .withContext(str::stream() << "Failed to remove role " << roleName.getFullName() - << " from all users"); + .withContext(str::stream() + << "Failed to remove role " << roleName << " from all users"); } // Remove this role from all other roles @@ -1878,15 +1875,15 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx) BSON("$pull" << BSON("roles" << roleName.toBSON()))); if (!swCount.isOK()) { return useDefaultCode(swCount.getStatus(), ErrorCodes::RoleModificationFailed) - .withContext(str::stream() << "Failed to remove role " << roleName.getFullName() - << " from all users"); + .withContext(str::stream() + << "Failed to remove role " << roleName << " from all users"); } // Finally, remove the actual role document swCount = txn.remove(AuthorizationManager::rolesCollectionNamespace, roleName.toBSON()); if (!swCount.isOK()) { - return swCount.getStatus().withContext(str::stream() << "Failed to remove role " - << roleName.getFullName()); + return swCount.getStatus().withContext(str::stream() + << "Failed to remove role " << roleName); } return Status::OK(); diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp index 0bc6f32cf49..7e2a861bfb1 100644 --- a/src/mongo/db/commands/user_management_commands_common.cpp +++ b/src/mongo/db/commands/user_management_commands_common.cpp @@ -90,8 +90,7 @@ Status checkAuthorizedToGrantRoles(AuthorizationSession* authzSession, if (!authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forDatabaseName(roles[i].getDB()), ActionType::grantRole)) { return Status(ErrorCodes::Unauthorized, - str::stream() - << "Not authorized to grant role: " << roles[i].getFullName()); + str::stream() << "Not authorized to grant role: " << roles[i]); } } @@ -116,8 +115,7 @@ Status checkAuthorizedToRevokeRoles(AuthorizationSession* authzSession, if (!authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forDatabaseName(roles[i].getDB()), ActionType::revokeRole)) { return Status(ErrorCodes::Unauthorized, - str::stream() - << "Not authorized to revoke role: " << roles[i].getFullName()); + str::stream() << "Not authorized to revoke role: " << roles[i]); } } return Status::OK(); @@ -201,16 +199,15 @@ void checkAuthForTypedCommand(Client* client, const UpdateUserCommand& request) auto* as = AuthorizationSession::get(client); UserName userName(request.getCommandParameter(), dbname); - uassert( - ErrorCodes::Unauthorized, - str::stream() << "Not authorized to change password of user: " << userName.getFullName(), - (request.getPwd() == boost::none) || isAuthorizedToChangeOwnPasswordAsUser(as, userName) || - as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), - ActionType::changePassword)); + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to change password of user: " << userName, + (request.getPwd() == boost::none) || + isAuthorizedToChangeOwnPasswordAsUser(as, userName) || + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::changePassword)); uassert(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to change customData of user: " - << userName.getFullName(), + str::stream() << "Not authorized to change customData of user: " << userName, (request.getCustomData() == boost::none) || isAuthorizedToChangeOwnCustomDataAsUser(as, userName) || as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 64de6da5a53..3043e65a06f 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -1113,7 +1113,7 @@ void OpDebug::appendUserInfo(const CurOp& c, } allUsers.doneFast(); - builder.append("user", bestUser.getUser().empty() ? "" : bestUser.getFullName()); + builder.append("user", bestUser.getUser().empty() ? "" : bestUser.getDisplayName()); } std::function<BSONObj(ProfileFilter::Args)> OpDebug::appendStaged(StringSet requestedFields, diff --git a/src/mongo/db/logical_session_id_helpers.cpp b/src/mongo/db/logical_session_id_helpers.cpp index 46f15cb01b6..525ed561db2 100644 --- a/src/mongo/db/logical_session_id_helpers.cpp +++ b/src/mongo/db/logical_session_id_helpers.cpp @@ -58,7 +58,7 @@ SHA256Block getLogicalSessionUserDigestForLoggedInUser(const OperationContext* o uassert(ErrorCodes::BadValue, "Username too long to use with logical sessions", - user->getName().getFullName().length() < kMaximumUserNameLengthForLogicalSessions); + user->getName().getDisplayNameLength() < kMaximumUserNameLengthForLogicalSessions); return user->getDigest(); } else { @@ -71,7 +71,7 @@ SHA256Block getLogicalSessionUserDigestFor(StringData user, StringData db) { return kNoAuthDigest; } const UserName un(user, db); - const auto& fn = un.getFullName(); + auto fn = un.getDisplayName(); return SHA256Block::computeHash({ConstDataRange(fn.c_str(), fn.size())}); } @@ -153,7 +153,7 @@ LogicalSessionRecord makeLogicalSessionRecord(OperationContext* opCtx, Date_t la invariant(user); id.setUid(user->getDigest()); - lsr.setUser(StringData(user->getName().toString())); + lsr.setUser(StringData(user->getName().getDisplayName())); } else { id.setUid(kNoAuthDigest); } @@ -191,7 +191,7 @@ LogicalSessionRecord makeLogicalSessionRecord(OperationContext* opCtx, invariant(user); if (user->getDigest() == lsid.getUid()) { - lsr.setUser(StringData(user->getName().toString())); + lsr.setUser(StringData(user->getName().getDisplayName())); } } diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index 7612f08a205..e175d584e59 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -3088,7 +3088,7 @@ Status _validatePeerRoles(const stdx::unordered_set<RoleName>& embeddedRoles, SS auto allowedRoles = it->second; // See TLSCATrustsSetParameter::set() for a description of tlsCATrusts format. - if (allowedRoles.count(RoleName("", ""))) { + if (allowedRoles.count(RoleName())) { // CA is authorized for all role assignments. return Status::OK(); } @@ -3099,7 +3099,7 @@ Status _validatePeerRoles(const stdx::unordered_set<RoleName>& embeddedRoles, SS !allowedRoles.count(RoleName("", role.getDB()))) { return {ErrorCodes::BadValue, str::stream() << "CA: " << sha256.toHexString() - << " is not authorized to grant role " << role.toString() + << " is not authorized to grant role " << role << " due to tlsCATrusts parameter"}; } } |