diff options
author | Spencer T Brody <spencer@10gen.com> | 2013-06-11 15:17:05 -0400 |
---|---|---|
committer | Spencer T Brody <spencer@10gen.com> | 2013-06-14 16:08:33 -0400 |
commit | 85a5413d651d00849d63b96386584e36d5d845e3 (patch) | |
tree | 3b322fb6f2491bd38d735bca01ad471ce27a6883 | |
parent | fcc103ba403fd35d96675f217c0e56d73eb14469 (diff) | |
download | mongo-85a5413d651d00849d63b96386584e36d5d845e3.tar.gz |
SERVER-6246 Add updateUser command and shell helper
-rw-r--r-- | src/mongo/client/dbclientinterface.h | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 3 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_d.cpp | 30 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_d.h | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_mock.h | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.h | 4 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 100 | ||||
-rw-r--r-- | src/mongo/shell/db.js | 20 | ||||
-rw-r--r-- | src/mongo/shell/dbshell.cpp | 5 |
12 files changed, 199 insertions, 10 deletions
diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h index 04c44cdb0fc..0ece5b3a2a1 100644 --- a/src/mongo/client/dbclientinterface.h +++ b/src/mongo/client/dbclientinterface.h @@ -658,7 +658,7 @@ namespace mongo { */ virtual unsigned long long count(const string &ns, const BSONObj& query = BSONObj(), int options=0, int limit=0, int skip=0 ); - string createPasswordDigest( const string &username , const string &clearTextPassword ); + static string createPasswordDigest(const string &username, const string &clearTextPassword); /** returns true in isMaster parm if this db is the current master of a replica pair. diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index 593c5e72b19..85e64c68a8d 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -287,6 +287,11 @@ namespace { return _externalState->insertPrivilegeDocument(dbname, userObj); } + Status AuthorizationManager::updatePrivilegeDocument(const UserName& user, + const BSONObj& updateObj) const { + return _externalState->updatePrivilegeDocument(user, updateObj); + } + ActionSet AuthorizationManager::getAllUserActions() const { ActionSet allActions; allActions.addAllActionsFromSet(readRoleActions); diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 4b9c906ff89..730f72f6a11 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -95,6 +95,9 @@ namespace mongo { // Creates the given user object in the given database. Status insertPrivilegeDocument(const std::string& dbname, const BSONObj& userObj) const; + // Updates the given user object with the given update modifier. + Status updatePrivilegeDocument(const UserName& user, const BSONObj& updateObj) const; + // Checks to see if "doc" is a valid privilege document, assuming it is stored in the // "system.users" collection of database "dbname". // diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index d5c8d92aa20..a9af078e675 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -53,6 +53,10 @@ namespace mongo { virtual Status insertPrivilegeDocument(const std::string& dbname, const BSONObj& userObj) const = 0; + // Updates the given user object with the given update modifier. + virtual Status updatePrivilegeDocument(const UserName& user, + const BSONObj& updateObj) const = 0; + protected: AuthzManagerExternalState(); // This class should never be instantiated directly. diff --git a/src/mongo/db/auth/authz_manager_external_state_d.cpp b/src/mongo/db/auth/authz_manager_external_state_d.cpp index e3ac620cb0b..5147fe826a5 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_d.cpp @@ -17,6 +17,7 @@ #include "mongo/db/auth/authz_manager_external_state_d.h" #include "mongo/base/status.h" +#include "mongo/db/auth/user_name.h" #include "mongo/db/client.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/instance.h" @@ -52,6 +53,35 @@ namespace mongo { return Status(ErrorCodes::UserModificationFailed, errstr); } + Status AuthzManagerExternalStateMongod::updatePrivilegeDocument( + const UserName& user, const BSONObj& updateObj) const { + string userNS = mongoutils::str::stream() << user.getDB() << ".system.users"; + Client::GodScope gs; + Client::WriteContext ctx(userNS); + + DBDirectClient client; + client.update(userNS, + QUERY("user" << user.getUser() << "userSource" << BSONNULL), + updateObj); + + // 30 second timeout for w:majority + BSONObj res = client.getLastErrorDetailed(false, false, -1, 30*1000); + string err = client.getLastErrorString(res); + if (!err.empty()) { + return Status(ErrorCodes::UserModificationFailed, err); + } + + int numUpdated = res["n"].numberInt(); + dassert(numUpdated <= 1 && numUpdated >= 0); + if (numUpdated == 0) { + return Status(ErrorCodes::UserNotFound, + mongoutils::str::stream() << "User " << user.getFullName() << + " not found"); + } + + return Status::OK(); + } + bool AuthzManagerExternalStateMongod::_findUser(const string& usersNamespace, const BSONObj& query, BSONObj* result) const { diff --git a/src/mongo/db/auth/authz_manager_external_state_d.h b/src/mongo/db/auth/authz_manager_external_state_d.h index 15982c27305..2158b642648 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.h +++ b/src/mongo/db/auth/authz_manager_external_state_d.h @@ -21,6 +21,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" #include "mongo/db/auth/authz_manager_external_state.h" +#include "mongo/db/auth/user_name.h" namespace mongo { @@ -37,6 +38,9 @@ namespace mongo { virtual Status insertPrivilegeDocument(const std::string& dbname, const BSONObj& userObj) const; + virtual Status updatePrivilegeDocument(const UserName& user, + const BSONObj& updateObj) const; + protected: virtual bool _findUser(const string& usersNamespace, const BSONObj& query, diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.h b/src/mongo/db/auth/authz_manager_external_state_mock.h index 4b40a57de01..3a2eadd5456 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.h +++ b/src/mongo/db/auth/authz_manager_external_state_mock.h @@ -40,6 +40,10 @@ namespace mongo { return Status::OK(); } + virtual Status updatePrivilegeDocument(const UserName& user, + const BSONObj& updateObj) const { + return Status::OK(); + } virtual bool _findUser(const std::string& usersNamespace, const BSONObj& query, 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 05cb28be292..15bfd9ec7c4 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -19,6 +19,7 @@ #include <string> #include "mongo/client/dbclientinterface.h" +#include "mongo/db/auth/user_name.h" #include "mongo/db/jsobj.h" #include "mongo/s/grid.h" #include "mongo/util/assert_util.h" @@ -74,4 +75,31 @@ namespace mongo { return Status(ErrorCodes::UserModificationFailed, errstr); } + Status AuthzManagerExternalStateMongos::updatePrivilegeDocument( + const UserName& user, const BSONObj& updateObj) const { + string userNS = mongoutils::str::stream() << user.getDB() << ".system.users"; + scoped_ptr<ScopedDbConnection> conn(getConnectionForUsersCollection(userNS)); + + conn->get()->update(userNS, + QUERY("user" << user.getUser() << "userSource" << BSONNULL), + updateObj); + + // 30 second timeout for w:majority + BSONObj res = conn->get()->getLastErrorDetailed(false, false, -1, 30*1000); + string err = conn->get()->getLastErrorString(res); + if (!err.empty()) { + return Status(ErrorCodes::UserModificationFailed, err); + } + + int numUpdated = res["n"].numberInt(); + dassert(numUpdated <= 1 && numUpdated >= 0); + if (numUpdated == 0) { + return Status(ErrorCodes::UserNotFound, + mongoutils::str::stream() << "User " << user.getFullName() << + " not found"); + } + + return Status::OK(); + } + } // namespace mongo 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 cf57205033c..2ff3d7fa4fb 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.h +++ b/src/mongo/db/auth/authz_manager_external_state_s.h @@ -21,6 +21,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" #include "mongo/db/auth/authz_manager_external_state.h" +#include "mongo/db/auth/user_name.h" namespace mongo { @@ -37,6 +38,9 @@ namespace mongo { virtual Status insertPrivilegeDocument(const std::string& dbname, const BSONObj& userObj) const; + virtual Status updatePrivilegeDocument(const UserName& user, + const BSONObj& updateObj) const; + protected: virtual bool _findUser(const string& usersNamespace, const BSONObj& query, diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 6221ffc2a08..acf482e29b6 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -19,6 +19,7 @@ #include "mongo/base/status.h" #include "mongo/bson/util/bson_extract.h" +#include "mongo/client/dbclientinterface.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" @@ -165,7 +166,7 @@ namespace mongo { } if (cmdObj.hasField("extraData")) { - userObjBuilder.append("extraData", extraData); + userObjBuilder.append(extraData); } if (cmdObj.hasField("roles")) { @@ -182,4 +183,101 @@ namespace mongo { return true; } } cmdCreateUser; + + class CmdUpdateUser : public Command { + public: + + virtual bool logTheOp() { + return false; + } + + virtual bool slaveOk() const { + return false; + } + + virtual LockType locktype() const { + return NONE; + } + + CmdUpdateUser() : Command("updateUser") {} + + virtual void help(stringstream& ss) const { + ss << "Used to update a user, for example to change its password" << endl; + } + + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // TODO: update this with the new rules around user creation in 2.6. + ActionSet actions; + actions.addAction(ActionType::userAdmin); + out->push_back(Privilege(dbname, actions)); + } + + bool run(const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + std::string userName; + std::string clearTextPassword; + std::string password; + BSONElement extraData; + + if (!cmdObj.hasField("pwd") && !cmdObj.hasField("extraData")) { + errmsg = "updateUser: must specify at least one of 'pwd' and 'extraData'"; + return false; + } + + Status status = bsonExtractStringField(cmdObj, "user", &userName); + if (!status.isOK()) { + addStatus(Status(ErrorCodes::UserModificationFailed, + "\"user\" string not specified"), + result); + return false; + } + + + status = bsonExtractStringFieldWithDefault(cmdObj, "pwd", "", &clearTextPassword); + if (!status.isOK()) { + addStatus(Status(ErrorCodes::UserModificationFailed, + "invalid \"pwd\" string"), + result); + return false; + } + password = DBClientWithCommands::createPasswordDigest(userName, clearTextPassword); + + if (cmdObj.hasField("extraData")) { + status = bsonExtractField(cmdObj, "extraData", &extraData); + if (!status.isOK()) { + addStatus(Status(ErrorCodes::UserModificationFailed, + "invalid \"extraData\" string"), + result); + return false; + } + } + + // TODO: This update will have to change once we're using the new v2 user + // storage format. + BSONObjBuilder setBuilder; + if (cmdObj.hasField("pwd")) { + setBuilder.append("pwd", password); + } + if (cmdObj.hasField("extraData")) { + setBuilder.append(extraData); + } + BSONObj updateObj = BSON("$set" << setBuilder.obj()); + + status = getGlobalAuthorizationManager()->updatePrivilegeDocument( + UserName(userName, dbname), updateObj); + + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + return true; + } + } cmdUpdateUser; } diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index a41172ecf1e..06c3a45971a 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -178,14 +178,20 @@ DB.prototype.addUser = function() { } } -DB.prototype.changeUserPassword = function(username, password) { - var hashedPassword = _hashPassword(username, password); - db.system.users.update({user : username, userSource : null}, {$set : {pwd : hashedPassword}}); - var err = db.getLastError(); - if (err) { - throw "Changing password failed: " + err; +DB.prototype.updateUser = function(updateObject) { + var cmdObj = {updateUser:1}; + cmdObj = Object.extend(cmdObj, updateObject); + var res = this.runCommand(cmdObj); + if (res.ok) { + return; } -} + throw Error("Updating user failed: " + res.errmsg); +}; + +DB.prototype.changeUserPassword = function(username, password) { + var updateObject = { user: username, pwd: password}; + this.updateUser(updateObject); +}; DB.prototype.logout = function(){ return this.getMongo().logout(this.getName()); diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp index cfaeef04433..7fb986b8928 100644 --- a/src/mongo/shell/dbshell.cpp +++ b/src/mongo/shell/dbshell.cpp @@ -129,7 +129,10 @@ void shellHistoryAdd( const char * line ) { // We don't want any .auth() or .addUser() commands added, but we want to // be able to add things like `.author`, so be smart about how this is // detected by using regular expresions. - static pcrecpp::RE hiddenCommands("\\.(auth|addUser)\\s*\\("); + // TODO(spencer): If the createUser or updateUser commands are run directly via runCommand they + // are not filtered from the history. + static pcrecpp::RE hiddenCommands( + "\\.(auth|addUser|createUser|updateUser|changeUserPassword)\\s*\\("); if (!hiddenCommands.PartialMatch(line)) { linenoiseHistoryAdd( line ); |