summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/deleted_recreated_user.js74
-rw-r--r--src/mongo/bson/bsonelement.h10
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp1
-rw-r--r--src/mongo/db/auth/authorization_manager.h8
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.cpp19
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.h3
-rw-r--r--src/mongo/db/auth/authorization_session_impl.cpp2
-rw-r--r--src/mongo/db/auth/user.h13
-rw-r--r--src/mongo/db/auth/user_document_parser.cpp28
-rw-r--r--src/mongo/db/auth/user_document_parser.h1
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp2
-rw-r--r--src/mongo/embedded/embedded_auth_manager.cpp6
12 files changed, 160 insertions, 7 deletions
diff --git a/jstests/auth/deleted_recreated_user.js b/jstests/auth/deleted_recreated_user.js
new file mode 100644
index 00000000000..87517f48297
--- /dev/null
+++ b/jstests/auth/deleted_recreated_user.js
@@ -0,0 +1,74 @@
+// Test that sessions can not be resumed by deleted and recreated user.
+
+(function() {
+ 'use strict';
+
+ const kInvalidationIntervalSecs = 5;
+
+ function runTest(s0, s1) {
+ assert(s0);
+ assert(s1);
+ const admin = s0.getDB('admin');
+
+ function checkIdType(username) {
+ const user = admin.system.users.find({user: username, db: 'admin'}).toArray()[0];
+ const id = user._id;
+ const userId = user.userId;
+ assert.eq(typeof(id), 'string');
+ assert.eq(id, 'admin.' + username);
+ assert.eq(typeof(userId), 'object');
+ assert.eq(tojson(userId).substring(0, 5), 'UUID(');
+ }
+
+ admin.createUser({user: 'admin', pwd: 'pass', roles: jsTest.adminUserRoles});
+ assert(admin.auth('admin', 'pass'));
+ checkIdType('admin');
+
+ admin.createUser({user: 'user', pwd: 'pass', roles: jsTest.basicUserRoles});
+ checkIdType('user');
+ admin.logout();
+
+ // Connect as basic user and create a session.
+ assert(admin.auth('user', 'pass'));
+ assert.writeOK(admin.mycoll.insert({_id: "foo", data: "bar"}));
+
+ // Perform administrative commands via separate shell.
+ function evalCmd(cmd) {
+ const uri = 'mongodb://admin:pass@localhost:' + s1.port + '/admin';
+ const result = runMongoProgram('./mongo', uri, '--eval', cmd);
+ assert.eq(result, 0, "Command failed");
+ }
+ evalCmd('db.dropUser("user"); ');
+ evalCmd('db.createUser({user: "user", pwd: "secret", roles: ["root"]});');
+
+ if (s0 !== s1) {
+ // Wait for twice the invalidation interval when sharding.
+ sleep(2 * kInvalidationIntervalSecs * 1000);
+ }
+
+ // This should fail due to invalid user session.
+ const thrown =
+ assert.throws(() => admin.mycoll.find({}).toArray(), [], "Able to find after recreate");
+ assert.eq(thrown.code, ErrorCodes.Unauthorized, "Threw something other than unauthorized");
+ }
+
+ const mongod = MongoRunner.runMongod({auth: ''});
+ runTest(mongod, mongod);
+ MongoRunner.stopMongod(mongod);
+
+ // TODO: Remove 'shardAsReplicaSet: false' when SERVER-32672 is fixed.
+ const st = new ShardingTest({
+ shards: 1,
+ mongos: 2,
+ config: 1,
+ other: {
+ keyFile: 'jstests/libs/key1',
+ shardAsReplicaSet: false,
+ mongosOptions: {
+ setParameter: 'userCacheInvalidationIntervalSecs=' + kInvalidationIntervalSecs,
+ },
+ },
+ });
+ runTest(st.s0, st.s1);
+ st.stop();
+})();
diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h
index e9e19fd0584..a42f993c4a5 100644
--- a/src/mongo/bson/bsonelement.h
+++ b/src/mongo/bson/bsonelement.h
@@ -614,11 +614,16 @@ public:
return Timestamp();
}
+ bool isBinData(BinDataType bdt) const {
+ return (type() == BinData) && (binDataType() == bdt);
+ }
+
const std::array<unsigned char, 16> uuid() const {
int len = 0;
const char* data = nullptr;
- if (type() == BinData && binDataType() == BinDataType::newUUID)
+ if (isBinData(BinDataType::newUUID)) {
data = binData(len);
+ }
uassert(ErrorCodes::InvalidUUID,
"uuid must be a 16-byte binary field with UUID (4) subtype",
len == 16);
@@ -630,8 +635,9 @@ public:
const std::array<unsigned char, 16> md5() const {
int len = 0;
const char* data = nullptr;
- if (type() == BinData && binDataType() == BinDataType::MD5Type)
+ if (isBinData(BinDataType::MD5Type)) {
data = binData(len);
+ }
uassert(40437, "md5 must be a 16-byte binary field with MD5 (5) subtype", len == 16);
std::array<unsigned char, 16> result;
memcpy(&result, data, len);
diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp
index 6bbb5f486dc..9d9575c1a08 100644
--- a/src/mongo/db/auth/authorization_manager.cpp
+++ b/src/mongo/db/auth/authorization_manager.cpp
@@ -68,6 +68,7 @@ mongo::AuthInfo mongo::internalSecurity;
namespace mongo {
+constexpr StringData AuthorizationManager::USERID_FIELD_NAME;
constexpr StringData AuthorizationManager::USER_NAME_FIELD_NAME;
constexpr StringData AuthorizationManager::USER_DB_FIELD_NAME;
constexpr StringData AuthorizationManager::ROLE_NAME_FIELD_NAME;
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h
index f9cd1248d4b..1ec9f421a4c 100644
--- a/src/mongo/db/auth/authorization_manager.h
+++ b/src/mongo/db/auth/authorization_manager.h
@@ -99,6 +99,7 @@ public:
static MONGO_DECLARE_SHIM(()->std::unique_ptr<AuthorizationManager>) create;
+ static constexpr StringData USERID_FIELD_NAME = "userId"_sd;
static constexpr StringData USER_NAME_FIELD_NAME = "user"_sd;
static constexpr StringData USER_DB_FIELD_NAME = "db"_sd;
static constexpr StringData ROLE_NAME_FIELD_NAME = "role"_sd;
@@ -267,6 +268,13 @@ public:
const UserName& userName) = 0;
/**
+ * Validate the ID associated with a known user while refreshing session cache.
+ */
+ virtual StatusWith<UserHandle> acquireUserForSessionRefresh(OperationContext* opCtx,
+ const UserName& userName,
+ const User::UserId& uid) = 0;
+
+ /**
* Marks the given user as invalid and removes it from the user cache.
*/
virtual void invalidateUserByName(OperationContext* opCtx, const UserName& user) = 0;
diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp
index 93ce163d6c6..5230d3b03b4 100644
--- a/src/mongo/db/auth/authorization_manager_impl.cpp
+++ b/src/mongo/db/auth/authorization_manager_impl.cpp
@@ -456,6 +456,8 @@ Status AuthorizationManagerImpl::_initializeUserFromPrivilegeDocument(User* user
<< "\"");
}
+ user->setID(parser.extractUserIDFromUserDocument(privDoc));
+
Status status = parser.initializeUserCredentialsFromUserDocument(user, privDoc);
if (!status.isOK()) {
return status;
@@ -576,6 +578,23 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o
return user;
}
+StatusWith<UserHandle> AuthorizationManagerImpl::acquireUserForSessionRefresh(
+ OperationContext* opCtx, const UserName& userName, const User::UserId& uid) {
+ auto swUserHandle = acquireUser(opCtx, userName);
+ if (!swUserHandle.isOK()) {
+ return swUserHandle.getStatus();
+ }
+
+ auto ret = std::move(swUserHandle.getValue());
+ if (uid != ret->getID()) {
+ return {ErrorCodes::UserNotFound,
+ str::stream() << "User id from privilege document '" << userName.toString()
+ << "' does not match user id in session."};
+ }
+
+ return ret;
+}
+
StatusWith<UserHandle> AuthorizationManagerImpl::_acquireUserSlowPath(CacheGuard& guard,
OperationContext* opCtx,
const UserName& userName) {
diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h
index 80f052338ff..66f088b69b6 100644
--- a/src/mongo/db/auth/authorization_manager_impl.h
+++ b/src/mongo/db/auth/authorization_manager_impl.h
@@ -118,6 +118,9 @@ public:
std::vector<BSONObj>* result) override;
StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override;
+ StatusWith<UserHandle> acquireUserForSessionRefresh(OperationContext* opCtx,
+ const UserName& userName,
+ const User::UserId& uid) override;
void invalidateUserByName(OperationContext* opCtx, const UserName& user) override;
diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp
index 4bd6f2dd62c..65e7558a7f5 100644
--- a/src/mongo/db/auth/authorization_session_impl.cpp
+++ b/src/mongo/db/auth/authorization_session_impl.cpp
@@ -729,7 +729,7 @@ void AuthorizationSessionImpl::_refreshUserInfoAsNeeded(OperationContext* opCtx)
UserName name = user->getName();
UserHandle updatedUser;
- auto swUser = authMan.acquireUser(opCtx, name);
+ auto swUser = authMan.acquireUserForSessionRefresh(opCtx, name, user->getID());
auto& status = swUser.getStatus();
switch (status.code()) {
case ErrorCodes::OK: {
diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h
index 715b849fe9b..5b3edfb767f 100644
--- a/src/mongo/db/auth/user.h
+++ b/src/mongo/db/auth/user.h
@@ -104,6 +104,15 @@ public:
explicit User(const UserName& name);
+ using UserId = std::vector<std::uint8_t>;
+ const UserId& getID() const {
+ return _id;
+ }
+
+ void setID(UserId id) {
+ _id = std::move(id);
+ }
+
/**
* Returns the user name for this user.
*/
@@ -230,6 +239,10 @@ protected:
void _invalidate();
private:
+ // Unique ID (often UUID) for this user.
+ // May be empty for legacy users.
+ UserId _id;
+
UserName _name;
// Digest of the full username
diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp
index 0cbed973483..552898b8552 100644
--- a/src/mongo/db/auth/user_document_parser.cpp
+++ b/src/mongo/db/auth/user_document_parser.cpp
@@ -127,10 +127,18 @@ Status _checkV2RolesArray(const BSONElement& rolesElement) {
}
Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const {
- BSONElement userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME];
- BSONElement userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME];
- BSONElement credentialsElement = doc[CREDENTIALS_FIELD_NAME];
- BSONElement rolesElement = doc[ROLES_FIELD_NAME];
+ auto userIdElement = doc[AuthorizationManager::USERID_FIELD_NAME];
+ auto userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME];
+ auto userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME];
+ auto credentialsElement = doc[CREDENTIALS_FIELD_NAME];
+ auto rolesElement = doc[ROLES_FIELD_NAME];
+
+ // Validate the "userId" element.
+ if (!userIdElement.eoo()) {
+ if (!userIdElement.isBinData(BinDataType::newUUID)) {
+ return _badValue("User document needs 'userId' field to be a UUID");
+ }
+ }
// Validate the "user" element.
if (userElement.type() != String)
@@ -213,6 +221,18 @@ Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const {
return Status::OK();
}
+User::UserId V2UserDocumentParser::extractUserIDFromUserDocument(const BSONObj& doc) const {
+ auto userId = doc[AuthorizationManager::USERID_FIELD_NAME];
+ if (userId.isBinData(BinDataType::newUUID)) {
+ auto id = userId.uuid();
+ User::UserId ret;
+ std::copy(id.begin(), id.end(), std::back_inserter(ret));
+ return ret;
+ }
+
+ return User::UserId();
+}
+
std::string V2UserDocumentParser::extractUserNameFromUserDocument(const BSONObj& doc) const {
return doc[AuthorizationManager::USER_NAME_FIELD_NAME].str();
}
diff --git a/src/mongo/db/auth/user_document_parser.h b/src/mongo/db/auth/user_document_parser.h
index a1349104224..7493ce7d324 100644
--- a/src/mongo/db/auth/user_document_parser.h
+++ b/src/mongo/db/auth/user_document_parser.h
@@ -54,6 +54,7 @@ public:
static Status parseRoleVector(const BSONArray& rolesArray, std::vector<RoleName>* result);
std::string extractUserNameFromUserDocument(const BSONObj& doc) const;
+ User::UserId extractUserIDFromUserDocument(const BSONObj& doc) const;
Status initializeUserCredentialsFromUserDocument(User* user, const BSONObj& privDoc) const;
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index 3c83c58fbf0..3520097a5c3 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -78,6 +78,7 @@
#include "mongo/util/password_digest.h"
#include "mongo/util/sequence_util.h"
#include "mongo/util/time_support.h"
+#include "mongo/util/uuid.h"
namespace mongo {
@@ -828,6 +829,7 @@ public:
BSONObjBuilder userObjBuilder;
userObjBuilder.append(
"_id", str::stream() << args.userName.getDB() << "." << args.userName.getUser());
+ UUID::gen().appendToBuilder(&userObjBuilder, AuthorizationManager::USERID_FIELD_NAME);
userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser());
userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB());
diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp
index 50841d42762..dc3a3ffd1eb 100644
--- a/src/mongo/embedded/embedded_auth_manager.cpp
+++ b/src/mongo/embedded/embedded_auth_manager.cpp
@@ -102,6 +102,12 @@ public:
UASSERT_NOT_IMPLEMENTED;
}
+ StatusWith<UserHandle> acquireUserForSessionRefresh(OperationContext*,
+ const UserName&,
+ const User::UserId&) override {
+ UASSERT_NOT_IMPLEMENTED;
+ }
+
void invalidateUserByName(OperationContext*, const UserName& user) override {
UASSERT_NOT_IMPLEMENTED;
}