1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl
32 
33 #include "mongo/platform/basic.h"
34 
35 #include "mongo/db/commands/user_management_commands.h"
36 
37 #include <string>
38 #include <vector>
39 
40 #include "mongo/base/status.h"
41 #include "mongo/bson/mutable/algorithm.h"
42 #include "mongo/bson/mutable/document.h"
43 #include "mongo/bson/mutable/element.h"
44 #include "mongo/bson/util/bson_extract.h"
45 #include "mongo/client/dbclientinterface.h"
46 #include "mongo/config.h"
47 #include "mongo/crypto/mechanism_scram.h"
48 #include "mongo/db/audit.h"
49 #include "mongo/db/auth/action_set.h"
50 #include "mongo/db/auth/action_type.h"
51 #include "mongo/db/auth/address_restriction.h"
52 #include "mongo/db/auth/authorization_manager.h"
53 #include "mongo/db/auth/authorization_manager_global.h"
54 #include "mongo/db/auth/authorization_session.h"
55 #include "mongo/db/auth/privilege.h"
56 #include "mongo/db/auth/privilege_parser.h"
57 #include "mongo/db/auth/resource_pattern.h"
58 #include "mongo/db/auth/sasl_options.h"
59 #include "mongo/db/auth/user.h"
60 #include "mongo/db/auth/user_document_parser.h"
61 #include "mongo/db/auth/user_management_commands_parser.h"
62 #include "mongo/db/client.h"
63 #include "mongo/db/commands.h"
64 #include "mongo/db/dbdirectclient.h"
65 #include "mongo/db/jsobj.h"
66 #include "mongo/db/operation_context.h"
67 #include "mongo/db/ops/write_ops.h"
68 #include "mongo/db/service_context.h"
69 #include "mongo/platform/unordered_set.h"
70 #include "mongo/rpc/get_status_from_command_result.h"
71 #include "mongo/s/write_ops/batched_command_response.h"
72 #include "mongo/stdx/functional.h"
73 #include "mongo/stdx/mutex.h"
74 #include "mongo/util/log.h"
75 #include "mongo/util/mongoutils/str.h"
76 #include "mongo/util/net/ssl_manager.h"
77 #include "mongo/util/sequence_util.h"
78 #include "mongo/util/time_support.h"
79 #include "mongo/util/uuid.h"
80 
81 namespace mongo {
82 
83 using std::endl;
84 using std::string;
85 using std::stringstream;
86 using std::vector;
87 
88 namespace {
89 
90 // Used to obtain mutex that guards modifications to persistent authorization data
91 const auto getAuthzDataMutex = ServiceContext::declareDecoration<stdx::mutex>();
92 
roleSetToBSONArray(const unordered_set<RoleName> & roles)93 BSONArray roleSetToBSONArray(const unordered_set<RoleName>& roles) {
94     BSONArrayBuilder rolesArrayBuilder;
95     for (unordered_set<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) {
96         const RoleName& role = *it;
97         rolesArrayBuilder.append(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
98                                       << role.getRole()
99                                       << AuthorizationManager::ROLE_DB_FIELD_NAME
100                                       << role.getDB()));
101     }
102     return rolesArrayBuilder.arr();
103 }
104 
rolesVectorToBSONArray(const std::vector<RoleName> & roles)105 BSONArray rolesVectorToBSONArray(const std::vector<RoleName>& roles) {
106     BSONArrayBuilder rolesArrayBuilder;
107     for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) {
108         const RoleName& role = *it;
109         rolesArrayBuilder.append(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
110                                       << role.getRole()
111                                       << AuthorizationManager::ROLE_DB_FIELD_NAME
112                                       << role.getDB()));
113     }
114     return rolesArrayBuilder.arr();
115 }
116 
privilegeVectorToBSONArray(const PrivilegeVector & privileges,BSONArray * result)117 Status privilegeVectorToBSONArray(const PrivilegeVector& privileges, BSONArray* result) {
118     BSONArrayBuilder arrBuilder;
119     for (PrivilegeVector::const_iterator it = privileges.begin(); it != privileges.end(); ++it) {
120         const Privilege& privilege = *it;
121 
122         ParsedPrivilege parsedPrivilege;
123         std::string errmsg;
124         if (!ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)) {
125             return Status(ErrorCodes::FailedToParse, errmsg);
126         }
127         if (!parsedPrivilege.isValid(&errmsg)) {
128             return Status(ErrorCodes::FailedToParse, errmsg);
129         }
130         arrBuilder.append(parsedPrivilege.toBSON());
131     }
132     *result = arrBuilder.arr();
133     return Status::OK();
134 }
135 
136 /**
137  * Used to get all current roles of the user identified by 'userName'.
138  */
getCurrentUserRoles(OperationContext * opCtx,AuthorizationManager * authzManager,const UserName & userName,unordered_set<RoleName> * roles)139 Status getCurrentUserRoles(OperationContext* opCtx,
140                            AuthorizationManager* authzManager,
141                            const UserName& userName,
142                            unordered_set<RoleName>* roles) {
143     User* user;
144     authzManager->invalidateUserByName(userName);  // Need to make sure cache entry is up to date
145     Status status = authzManager->acquireUser(opCtx, userName, &user);
146     if (!status.isOK()) {
147         return status;
148     }
149     RoleNameIterator rolesIt = user->getRoles();
150     while (rolesIt.more()) {
151         roles->insert(rolesIt.next());
152     }
153     authzManager->releaseUser(user);
154     return Status::OK();
155 }
156 
157 /**
158  * Checks that every role in "rolesToAdd" exists, that adding each of those roles to "role"
159  * will not result in a cycle to the role graph, and that every role being added comes from the
160  * same database as the role it is being added to (or that the role being added to is from the
161  * "admin" database.
162  */
checkOkayToGrantRolesToRole(OperationContext * opCtx,const RoleName & role,const std::vector<RoleName> rolesToAdd,AuthorizationManager * authzManager)163 Status checkOkayToGrantRolesToRole(OperationContext* opCtx,
164                                    const RoleName& role,
165                                    const std::vector<RoleName> rolesToAdd,
166                                    AuthorizationManager* authzManager) {
167     for (std::vector<RoleName>::const_iterator it = rolesToAdd.begin(); it != rolesToAdd.end();
168          ++it) {
169         const RoleName& roleToAdd = *it;
170         if (roleToAdd == role) {
171             return Status(ErrorCodes::InvalidRoleModification,
172                           mongoutils::str::stream() << "Cannot grant role " << role.getFullName()
173                                                     << " to itself.");
174         }
175 
176         if (role.getDB() != "admin" && roleToAdd.getDB() != role.getDB()) {
177             return Status(
178                 ErrorCodes::InvalidRoleModification,
179                 str::stream() << "Roles on the \'" << role.getDB()
180                               << "\' database cannot be granted roles from other databases");
181         }
182 
183         BSONObj roleToAddDoc;
184         Status status = authzManager->getRoleDescription(opCtx, roleToAdd, &roleToAddDoc);
185         if (status == ErrorCodes::RoleNotFound) {
186             return Status(ErrorCodes::RoleNotFound,
187                           "Cannot grant nonexistent role " + roleToAdd.toString());
188         }
189         if (!status.isOK()) {
190             return status;
191         }
192         std::vector<RoleName> indirectRoles;
193         status = auth::parseRoleNamesFromBSONArray(
194             BSONArray(roleToAddDoc["inheritedRoles"].Obj()), role.getDB(), &indirectRoles);
195         if (!status.isOK()) {
196             return status;
197         }
198 
199         if (sequenceContains(indirectRoles, role)) {
200             return Status(
201                 ErrorCodes::InvalidRoleModification,
202                 mongoutils::str::stream() << "Granting " << roleToAdd.getFullName() << " to "
203                                           << role.getFullName()
204                                           << " would introduce a cycle in the role graph.");
205         }
206     }
207     return Status::OK();
208 }
209 
210 /**
211  * Checks that every privilege being granted targets just the database the role is from, or that
212  * the role is from the "admin" db.
213  */
checkOkayToGrantPrivilegesToRole(const RoleName & role,const PrivilegeVector & privileges)214 Status checkOkayToGrantPrivilegesToRole(const RoleName& role, const PrivilegeVector& privileges) {
215     if (role.getDB() == "admin") {
216         return Status::OK();
217     }
218 
219     for (PrivilegeVector::const_iterator it = privileges.begin(); it != privileges.end(); ++it) {
220         const ResourcePattern& resource = (*it).getResourcePattern();
221         if ((resource.isDatabasePattern() || resource.isExactNamespacePattern()) &&
222             (resource.databaseToMatch() == role.getDB())) {
223             continue;
224         }
225 
226         return Status(ErrorCodes::InvalidRoleModification,
227                       str::stream() << "Roles on the \'" << role.getDB()
228                                     << "\' database cannot be granted privileges that target other "
229                                        "databases or the cluster");
230     }
231 
232     return Status::OK();
233 }
234 
appendBSONObjToBSONArrayBuilder(BSONArrayBuilder * array,const BSONObj & obj)235 void appendBSONObjToBSONArrayBuilder(BSONArrayBuilder* array, const BSONObj& obj) {
236     array->append(obj);
237 }
238 
239 /**
240  * Finds all documents matching "query" in "collectionName".  For each document returned,
241  * calls the function resultProcessor on it.
242  * Should only be called on collections with authorization documents in them
243  * (ie admin.system.users and admin.system.roles).
244  */
queryAuthzDocument(OperationContext * opCtx,const NamespaceString & collectionName,const BSONObj & query,const BSONObj & projection,const stdx::function<void (const BSONObj &)> & resultProcessor)245 Status queryAuthzDocument(OperationContext* opCtx,
246                           const NamespaceString& collectionName,
247                           const BSONObj& query,
248                           const BSONObj& projection,
249                           const stdx::function<void(const BSONObj&)>& resultProcessor) {
250     try {
251         DBDirectClient client(opCtx);
252         client.query(resultProcessor, collectionName.ns(), query, &projection);
253         return Status::OK();
254     } catch (const DBException& e) {
255         return e.toStatus();
256     }
257 }
258 
259 /**
260  * Inserts "document" into "collectionName".
261  * If there is a duplicate key error, returns a Status with code DuplicateKey.
262  *
263  * Should only be called on collections with authorization documents in them
264  * (ie admin.system.users and admin.system.roles).
265  */
insertAuthzDocument(OperationContext * opCtx,const NamespaceString & collectionName,const BSONObj & document)266 Status insertAuthzDocument(OperationContext* opCtx,
267                            const NamespaceString& collectionName,
268                            const BSONObj& document) {
269     try {
270         DBDirectClient client(opCtx);
271 
272         BSONObj res;
273         client.runCommand(collectionName.db().toString(),
274                           [&] {
275                               write_ops::Insert insertOp(collectionName);
276                               insertOp.setDocuments({document});
277                               return insertOp.toBSON({});
278                           }(),
279                           res);
280 
281         BatchedCommandResponse response;
282         std::string errmsg;
283         if (!response.parseBSON(res, &errmsg)) {
284             return Status(ErrorCodes::FailedToParse, errmsg);
285         }
286         return response.toStatus();
287     } catch (const DBException& e) {
288         return e.toStatus();
289     }
290 }
291 
292 /**
293  * Updates documents matching "query" according to "updatePattern" in "collectionName".
294  *
295  * Should only be called on collections with authorization documents in them
296  * (ie admin.system.users and admin.system.roles).
297  */
updateAuthzDocuments(OperationContext * opCtx,const NamespaceString & collectionName,const BSONObj & query,const BSONObj & updatePattern,bool upsert,bool multi,long long * nMatched)298 Status updateAuthzDocuments(OperationContext* opCtx,
299                             const NamespaceString& collectionName,
300                             const BSONObj& query,
301                             const BSONObj& updatePattern,
302                             bool upsert,
303                             bool multi,
304                             long long* nMatched) {
305     try {
306         DBDirectClient client(opCtx);
307 
308         BSONObj res;
309         client.runCommand(collectionName.db().toString(),
310                           [&] {
311                               write_ops::Update updateOp(collectionName);
312                               updateOp.setUpdates({[&] {
313                                   write_ops::UpdateOpEntry entry;
314                                   entry.setQ(query);
315                                   entry.setU(updatePattern);
316                                   entry.setMulti(multi);
317                                   entry.setUpsert(upsert);
318                                   return entry;
319                               }()});
320                               return updateOp.toBSON({});
321                           }(),
322                           res);
323 
324         BatchedCommandResponse response;
325         std::string errmsg;
326         if (!response.parseBSON(res, &errmsg)) {
327             return Status(ErrorCodes::FailedToParse, errmsg);
328         }
329         if (response.getOk()) {
330             *nMatched = response.getN();
331         }
332         return response.toStatus();
333     } catch (const DBException& e) {
334         return e.toStatus();
335     }
336 }
337 
338 /**
339  * Update one document matching "query" according to "updatePattern" in "collectionName".
340  *
341  * If "upsert" is true and no document matches "query", inserts one using "query" as a
342  * template.
343  * If "upsert" is false and no document matches "query", return a Status with the code
344  * NoMatchingDocument.  The Status message in that case is not very descriptive and should
345  * not be displayed to the end user.
346  *
347  * Should only be called on collections with authorization documents in them
348  * (ie admin.system.users and admin.system.roles).
349  */
updateOneAuthzDocument(OperationContext * opCtx,const NamespaceString & collectionName,const BSONObj & query,const BSONObj & updatePattern,bool upsert)350 Status updateOneAuthzDocument(OperationContext* opCtx,
351                               const NamespaceString& collectionName,
352                               const BSONObj& query,
353                               const BSONObj& updatePattern,
354                               bool upsert) {
355     long long nMatched;
356     Status status =
357         updateAuthzDocuments(opCtx, collectionName, query, updatePattern, upsert, false, &nMatched);
358     if (!status.isOK()) {
359         return status;
360     }
361     dassert(nMatched == 1 || nMatched == 0);
362     if (nMatched == 0) {
363         return Status(ErrorCodes::NoMatchingDocument, "No document found");
364     }
365     return Status::OK();
366 }
367 
368 /**
369  * Removes all documents matching "query" from "collectionName".
370  *
371  * Should only be called on collections with authorization documents in them
372  * (ie admin.system.users and admin.system.roles).
373  */
removeAuthzDocuments(OperationContext * opCtx,const NamespaceString & collectionName,const BSONObj & query,long long * numRemoved)374 Status removeAuthzDocuments(OperationContext* opCtx,
375                             const NamespaceString& collectionName,
376                             const BSONObj& query,
377                             long long* numRemoved) {
378     try {
379         DBDirectClient client(opCtx);
380 
381         BSONObj res;
382         client.runCommand(collectionName.db().toString(),
383                           [&] {
384                               write_ops::Delete deleteOp(collectionName);
385                               deleteOp.setDeletes({[&] {
386                                   write_ops::DeleteOpEntry entry;
387                                   entry.setQ(query);
388                                   entry.setMulti(true);
389                                   return entry;
390                               }()});
391                               return deleteOp.toBSON({});
392                           }(),
393                           res);
394 
395         BatchedCommandResponse response;
396         std::string errmsg;
397         if (!response.parseBSON(res, &errmsg)) {
398             return Status(ErrorCodes::FailedToParse, errmsg);
399         }
400         if (response.getOk()) {
401             *numRemoved = response.getN();
402         }
403         return response.toStatus();
404     } catch (const DBException& e) {
405         return e.toStatus();
406     }
407 }
408 
409 /**
410  * Creates the given role object in the given database.
411  */
insertRoleDocument(OperationContext * opCtx,const BSONObj & roleObj)412 Status insertRoleDocument(OperationContext* opCtx, const BSONObj& roleObj) {
413     Status status =
414         insertAuthzDocument(opCtx, AuthorizationManager::rolesCollectionNamespace, roleObj);
415     if (status.isOK()) {
416         return status;
417     }
418     if (status.code() == ErrorCodes::DuplicateKey) {
419         std::string name = roleObj[AuthorizationManager::ROLE_NAME_FIELD_NAME].String();
420         std::string source = roleObj[AuthorizationManager::ROLE_DB_FIELD_NAME].String();
421         return Status(ErrorCodes::DuplicateKey,
422                       str::stream() << "Role \"" << name << "@" << source << "\" already exists");
423     }
424     if (status.code() == ErrorCodes::UnknownError) {
425         return Status(ErrorCodes::RoleModificationFailed, status.reason());
426     }
427     return status;
428 }
429 
430 /**
431  * Updates the given role object with the given update modifier.
432  */
updateRoleDocument(OperationContext * opCtx,const RoleName & role,const BSONObj & updateObj)433 Status updateRoleDocument(OperationContext* opCtx, const RoleName& role, const BSONObj& updateObj) {
434     Status status = updateOneAuthzDocument(opCtx,
435                                            AuthorizationManager::rolesCollectionNamespace,
436                                            BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
437                                                 << role.getRole()
438                                                 << AuthorizationManager::ROLE_DB_FIELD_NAME
439                                                 << role.getDB()),
440                                            updateObj,
441                                            false);
442     if (status.isOK()) {
443         return status;
444     }
445     if (status.code() == ErrorCodes::NoMatchingDocument) {
446         return Status(ErrorCodes::RoleNotFound,
447                       str::stream() << "Role " << role.getFullName() << " not found");
448     }
449     if (status.code() == ErrorCodes::UnknownError) {
450         return Status(ErrorCodes::RoleModificationFailed, status.reason());
451     }
452     return status;
453 }
454 
455 /**
456  * Removes roles matching the given query.
457  * Writes into *numRemoved the number of role documents that were modified.
458  */
removeRoleDocuments(OperationContext * opCtx,const BSONObj & query,long long * numRemoved)459 Status removeRoleDocuments(OperationContext* opCtx, const BSONObj& query, long long* numRemoved) {
460     Status status = removeAuthzDocuments(
461         opCtx, AuthorizationManager::rolesCollectionNamespace, query, numRemoved);
462     if (status.code() == ErrorCodes::UnknownError) {
463         return Status(ErrorCodes::RoleModificationFailed, status.reason());
464     }
465     return status;
466 }
467 
468 /**
469  * Creates the given user object in the given database.
470  */
insertPrivilegeDocument(OperationContext * opCtx,const BSONObj & userObj)471 Status insertPrivilegeDocument(OperationContext* opCtx, const BSONObj& userObj) {
472     Status status =
473         insertAuthzDocument(opCtx, AuthorizationManager::usersCollectionNamespace, userObj);
474     if (status.isOK()) {
475         return status;
476     }
477     if (status.code() == ErrorCodes::DuplicateKey) {
478         std::string name = userObj[AuthorizationManager::USER_NAME_FIELD_NAME].String();
479         std::string source = userObj[AuthorizationManager::USER_DB_FIELD_NAME].String();
480         return Status(ErrorCodes::DuplicateKey,
481                       str::stream() << "User \"" << name << "@" << source << "\" already exists");
482     }
483     if (status.code() == ErrorCodes::UnknownError) {
484         return Status(ErrorCodes::UserModificationFailed, status.reason());
485     }
486     return status;
487 }
488 
489 /**
490  * Updates the given user object with the given update modifier.
491  */
updatePrivilegeDocument(OperationContext * opCtx,const UserName & user,const BSONObj & updateObj)492 Status updatePrivilegeDocument(OperationContext* opCtx,
493                                const UserName& user,
494                                const BSONObj& updateObj) {
495     Status status = updateOneAuthzDocument(opCtx,
496                                            AuthorizationManager::usersCollectionNamespace,
497                                            BSON(AuthorizationManager::USER_NAME_FIELD_NAME
498                                                 << user.getUser()
499                                                 << AuthorizationManager::USER_DB_FIELD_NAME
500                                                 << user.getDB()),
501                                            updateObj,
502                                            false);
503     if (status.isOK()) {
504         return status;
505     }
506     if (status.code() == ErrorCodes::NoMatchingDocument) {
507         return Status(ErrorCodes::UserNotFound,
508                       str::stream() << "User " << user.getFullName() << " not found");
509     }
510     if (status.code() == ErrorCodes::UnknownError) {
511         return Status(ErrorCodes::UserModificationFailed, status.reason());
512     }
513     return status;
514 }
515 
516 /**
517  * Removes users for the given database matching the given query.
518  * Writes into *numRemoved the number of user documents that were modified.
519  */
removePrivilegeDocuments(OperationContext * opCtx,const BSONObj & query,long long * numRemoved)520 Status removePrivilegeDocuments(OperationContext* opCtx,
521                                 const BSONObj& query,
522                                 long long* numRemoved) {
523     Status status = removeAuthzDocuments(
524         opCtx, AuthorizationManager::usersCollectionNamespace, query, numRemoved);
525     if (status.code() == ErrorCodes::UnknownError) {
526         return Status(ErrorCodes::UserModificationFailed, status.reason());
527     }
528     return status;
529 }
530 
531 /**
532  * Updates the auth schema version document to reflect the current state of the system.
533  * 'foundSchemaVersion' is the authSchemaVersion to update with.
534  */
writeAuthSchemaVersionIfNeeded(OperationContext * opCtx,AuthorizationManager * authzManager,int foundSchemaVersion)535 Status writeAuthSchemaVersionIfNeeded(OperationContext* opCtx,
536                                       AuthorizationManager* authzManager,
537                                       int foundSchemaVersion) {
538     Status status = updateOneAuthzDocument(
539         opCtx,
540         AuthorizationManager::versionCollectionNamespace,
541         AuthorizationManager::versionDocumentQuery,
542         BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName << foundSchemaVersion)),
543         true);  // upsert
544 
545     if (status == ErrorCodes::NoMatchingDocument) {  // SERVER-11492
546         status = Status::OK();
547     }
548 
549     return status;
550 }
551 
552 /**
553  * Returns Status::OK() if the current Auth schema version is at least the auth schema version
554  * for the MongoDB 2.6 and 3.0 MongoDB-CR/SCRAM mixed auth mode.
555  * Returns an error otherwise.
556  */
requireAuthSchemaVersion26Final(OperationContext * opCtx,AuthorizationManager * authzManager)557 Status requireAuthSchemaVersion26Final(OperationContext* opCtx,
558                                        AuthorizationManager* authzManager) {
559     int foundSchemaVersion;
560     Status status = authzManager->getAuthorizationVersion(opCtx, &foundSchemaVersion);
561     if (!status.isOK()) {
562         return status;
563     }
564 
565     if (foundSchemaVersion < AuthorizationManager::schemaVersion26Final) {
566         return Status(ErrorCodes::AuthSchemaIncompatible,
567                       str::stream()
568                           << "User and role management commands require auth data to have "
569                           << "at least schema version "
570                           << AuthorizationManager::schemaVersion26Final
571                           << " but found "
572                           << foundSchemaVersion);
573     }
574     return writeAuthSchemaVersionIfNeeded(opCtx, authzManager, foundSchemaVersion);
575 }
576 
577 /**
578  * Returns Status::OK() if the current Auth schema version is at least the auth schema version
579  * for MongoDB 2.6 during the upgrade process.
580  * Returns an error otherwise.
581  */
requireAuthSchemaVersion26UpgradeOrFinal(OperationContext * opCtx,AuthorizationManager * authzManager)582 Status requireAuthSchemaVersion26UpgradeOrFinal(OperationContext* opCtx,
583                                                 AuthorizationManager* authzManager) {
584     int foundSchemaVersion;
585     Status status = authzManager->getAuthorizationVersion(opCtx, &foundSchemaVersion);
586     if (!status.isOK()) {
587         return status;
588     }
589 
590     if (foundSchemaVersion < AuthorizationManager::schemaVersion26Upgrade) {
591         return Status(ErrorCodes::AuthSchemaIncompatible,
592                       str::stream() << "The usersInfo and rolesInfo commands require auth data to "
593                                     << "have at least schema version "
594                                     << AuthorizationManager::schemaVersion26Upgrade
595                                     << " but found "
596                                     << foundSchemaVersion);
597     }
598     return Status::OK();
599 }
600 
601 }  // namespace
602 
603 
604 class CmdCreateUser : public BasicCommand {
605 public:
CmdCreateUser()606     CmdCreateUser() : BasicCommand("createUser") {}
607 
slaveOk() const608     virtual bool slaveOk() const {
609         return false;
610     }
611 
supportsWriteConcern(const BSONObj & cmd) const612     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
613         return true;
614     }
615 
help(stringstream & ss) const616     virtual void help(stringstream& ss) const {
617         ss << "Adds a user to the system" << endl;
618     }
619 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)620     virtual Status checkAuthForCommand(Client* client,
621                                        const std::string& dbname,
622                                        const BSONObj& cmdObj) {
623         return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj);
624     }
625 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)626     bool run(OperationContext* opCtx,
627              const string& dbname,
628              const BSONObj& cmdObj,
629              BSONObjBuilder& result) {
630         auth::CreateOrUpdateUserArgs args;
631         Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "createUser", dbname, &args);
632         if (!status.isOK()) {
633             return appendCommandStatus(result, status);
634         }
635 
636         if (args.userName.getDB() == "local") {
637             return appendCommandStatus(
638                 result, Status(ErrorCodes::BadValue, "Cannot create users in the local database"));
639         }
640 
641         if (!args.hasHashedPassword && args.userName.getDB() != "$external") {
642             return appendCommandStatus(
643                 result,
644                 Status(ErrorCodes::BadValue,
645                        "Must provide a 'pwd' field for all user documents, except those"
646                        " with '$external' as the user's source db"));
647         }
648 
649         if ((args.hasHashedPassword) && args.userName.getDB() == "$external") {
650             return appendCommandStatus(
651                 result,
652                 Status(ErrorCodes::BadValue,
653                        "Cannot set the password for users defined on the '$external' "
654                        "database"));
655         }
656 
657         if (!args.hasRoles) {
658             return appendCommandStatus(
659                 result,
660                 Status(ErrorCodes::BadValue, "\"createUser\" command requires a \"roles\" array"));
661         }
662 
663 #ifdef MONGO_CONFIG_SSL
664         if (args.userName.getDB() == "$external" && getSSLManager() &&
665             getSSLManager()->getSSLConfiguration().isClusterMember(args.userName.getUser())) {
666             return appendCommandStatus(result,
667                                        Status(ErrorCodes::BadValue,
668                                               "Cannot create an x.509 user with a subjectname "
669                                               "that would be recognized as an internal "
670                                               "cluster member."));
671         }
672 #endif
673 
674         BSONObjBuilder userObjBuilder;
675         userObjBuilder.append(
676             "_id", str::stream() << args.userName.getDB() << "." << args.userName.getUser());
677         UUID::gen().appendToBuilder(&userObjBuilder, AuthorizationManager::USERID_FIELD_NAME);
678         userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser());
679         userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB());
680 
681         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
682         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
683         int authzVersion;
684         status = authzManager->getAuthorizationVersion(opCtx, &authzVersion);
685         if (!status.isOK()) {
686             return appendCommandStatus(result, status);
687         }
688 
689         BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials"));
690         if (!args.hasHashedPassword) {
691             // Must be an external user
692             credentialsBuilder.append("external", true);
693         } else {
694             // Add SCRAM credentials for appropriate authSchemaVersions.
695             if (authzVersion > AuthorizationManager::schemaVersion26Final) {
696                 BSONObj scramCred = scram::generateCredentials(
697                     args.hashedPassword, saslGlobalParams.scramIterationCount.load());
698                 credentialsBuilder.append("SCRAM-SHA-1", scramCred);
699             } else {  // Otherwise default to MONGODB-CR.
700                 credentialsBuilder.append("MONGODB-CR", args.hashedPassword);
701             }
702         }
703         credentialsBuilder.done();
704 
705         if (args.authenticationRestrictions && !args.authenticationRestrictions->isEmpty()) {
706             credentialsBuilder.append("authenticationRestrictions",
707                                       *args.authenticationRestrictions);
708         }
709 
710         if (args.hasCustomData) {
711             userObjBuilder.append("customData", args.customData);
712         }
713 
714         userObjBuilder.append("roles", rolesVectorToBSONArray(args.roles));
715 
716         BSONObj userObj = userObjBuilder.obj();
717         V2UserDocumentParser parser;
718         status = parser.checkValidUserDocument(userObj);
719         if (!status.isOK()) {
720             return appendCommandStatus(result, status);
721         }
722 
723         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
724 
725         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
726         if (!status.isOK()) {
727             return appendCommandStatus(result, status);
728         }
729 
730         // Role existence has to be checked after acquiring the update lock
731         for (size_t i = 0; i < args.roles.size(); ++i) {
732             BSONObj ignored;
733             status = authzManager->getRoleDescription(opCtx, args.roles[i], &ignored);
734             if (!status.isOK()) {
735                 return appendCommandStatus(result, status);
736             }
737         }
738 
739         audit::logCreateUser(Client::getCurrent(),
740                              args.userName,
741                              args.hasHashedPassword,
742                              args.hasCustomData ? &args.customData : NULL,
743                              args.roles,
744                              args.authenticationRestrictions);
745         status = insertPrivilegeDocument(opCtx, userObj);
746         return appendCommandStatus(result, status);
747     }
748 
redactForLogging(mutablebson::Document * cmdObj)749     virtual void redactForLogging(mutablebson::Document* cmdObj) {
750         auth::redactPasswordData(cmdObj->root());
751     }
752 
753 } cmdCreateUser;
754 
755 class CmdUpdateUser : public BasicCommand {
756 public:
CmdUpdateUser()757     CmdUpdateUser() : BasicCommand("updateUser") {}
758 
slaveOk() const759     virtual bool slaveOk() const {
760         return false;
761     }
762 
supportsWriteConcern(const BSONObj & cmd) const763     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
764         return true;
765     }
766 
help(stringstream & ss) const767     virtual void help(stringstream& ss) const {
768         ss << "Used to update a user, for example to change its password" << endl;
769     }
770 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)771     virtual Status checkAuthForCommand(Client* client,
772                                        const std::string& dbname,
773                                        const BSONObj& cmdObj) {
774         return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj);
775     }
776 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)777     bool run(OperationContext* opCtx,
778              const string& dbname,
779              const BSONObj& cmdObj,
780              BSONObjBuilder& result) {
781         auth::CreateOrUpdateUserArgs args;
782         Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "updateUser", dbname, &args);
783         if (!status.isOK()) {
784             return appendCommandStatus(result, status);
785         }
786 
787         if (!args.hasHashedPassword && !args.hasCustomData && !args.hasRoles &&
788             !args.authenticationRestrictions) {
789             return appendCommandStatus(
790                 result,
791                 Status(ErrorCodes::BadValue,
792                        "Must specify at least one field to update in updateUser"));
793         }
794 
795         if (args.hasHashedPassword && args.userName.getDB() == "$external") {
796             return appendCommandStatus(
797                 result,
798                 Status(ErrorCodes::BadValue,
799                        "Cannot set the password for users defined on the '$external' "
800                        "database"));
801         }
802 
803         BSONObjBuilder updateSetBuilder;
804         BSONObjBuilder updateUnsetBuilder;
805         if (args.hasHashedPassword) {
806             BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials"));
807 
808             AuthorizationManager* authzManager = getGlobalAuthorizationManager();
809             int authzVersion;
810             Status status = authzManager->getAuthorizationVersion(opCtx, &authzVersion);
811             if (!status.isOK()) {
812                 return appendCommandStatus(result, status);
813             }
814 
815             // Add SCRAM credentials for appropriate authSchemaVersions
816             if (authzVersion > AuthorizationManager::schemaVersion26Final) {
817                 BSONObj scramCred = scram::generateCredentials(
818                     args.hashedPassword, saslGlobalParams.scramIterationCount.load());
819                 credentialsBuilder.append("SCRAM-SHA-1", scramCred);
820             } else {  // Otherwise default to MONGODB-CR
821                 credentialsBuilder.append("MONGODB-CR", args.hashedPassword);
822             }
823             credentialsBuilder.done();
824         }
825 
826         if (args.hasCustomData) {
827             updateSetBuilder.append("customData", args.customData);
828         }
829 
830         if (args.authenticationRestrictions) {
831             if (args.authenticationRestrictions->isEmpty()) {
832                 updateUnsetBuilder.append("authenticationRestrictions", "");
833             } else {
834                 auto swParsedRestrictions =
835                     parseAuthenticationRestriction(*args.authenticationRestrictions);
836                 if (!swParsedRestrictions.isOK()) {
837                     return appendCommandStatus(result, swParsedRestrictions.getStatus());
838                 }
839 
840                 updateSetBuilder.append("authenticationRestrictions",
841                                         *args.authenticationRestrictions);
842             }
843         }
844 
845         if (args.hasRoles) {
846             updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles));
847         }
848 
849         BSONObj updateSet = updateSetBuilder.done();
850         BSONObj updateUnset = updateUnsetBuilder.done();
851         BSONObjBuilder updateDocumentBuilder;
852         if (!updateSet.isEmpty()) {
853             updateDocumentBuilder << "$set" << updateSet;
854         }
855         if (!updateUnset.isEmpty()) {
856             updateDocumentBuilder << "$unset" << updateUnset;
857         }
858 
859         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
860         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
861 
862         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
863         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
864         if (!status.isOK()) {
865             return appendCommandStatus(result, status);
866         }
867 
868         // Role existence has to be checked after acquiring the update lock
869         if (args.hasRoles) {
870             for (size_t i = 0; i < args.roles.size(); ++i) {
871                 BSONObj ignored;
872                 status = authzManager->getRoleDescription(opCtx, args.roles[i], &ignored);
873                 if (!status.isOK()) {
874                     return appendCommandStatus(result, status);
875                 }
876             }
877         }
878 
879         audit::logUpdateUser(Client::getCurrent(),
880                              args.userName,
881                              args.hasHashedPassword,
882                              args.hasCustomData ? &args.customData : NULL,
883                              args.hasRoles ? &args.roles : NULL,
884                              args.authenticationRestrictions);
885 
886         status = updatePrivilegeDocument(opCtx, args.userName, updateDocumentBuilder.done());
887         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
888         authzManager->invalidateUserByName(args.userName);
889         return appendCommandStatus(result, status);
890     }
891 
redactForLogging(mutablebson::Document * cmdObj)892     virtual void redactForLogging(mutablebson::Document* cmdObj) {
893         auth::redactPasswordData(cmdObj->root());
894     }
895 
896 } cmdUpdateUser;
897 
898 class CmdDropUser : public BasicCommand {
899 public:
CmdDropUser()900     CmdDropUser() : BasicCommand("dropUser") {}
901 
slaveOk() const902     virtual bool slaveOk() const {
903         return false;
904     }
905 
supportsWriteConcern(const BSONObj & cmd) const906     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
907         return true;
908     }
909 
help(stringstream & ss) const910     virtual void help(stringstream& ss) const {
911         ss << "Drops a single user." << endl;
912     }
913 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)914     virtual Status checkAuthForCommand(Client* client,
915                                        const std::string& dbname,
916                                        const BSONObj& cmdObj) {
917         return auth::checkAuthForDropUserCommand(client, dbname, cmdObj);
918     }
919 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)920     bool run(OperationContext* opCtx,
921              const string& dbname,
922              const BSONObj& cmdObj,
923              BSONObjBuilder& result) {
924         UserName userName;
925         Status status = auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName);
926         if (!status.isOK()) {
927             return appendCommandStatus(result, status);
928         }
929 
930         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
931         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
932         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
933         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
934         if (!status.isOK()) {
935             return appendCommandStatus(result, status);
936         }
937 
938         audit::logDropUser(Client::getCurrent(), userName);
939 
940         long long nMatched;
941         status = removePrivilegeDocuments(opCtx,
942                                           BSON(AuthorizationManager::USER_NAME_FIELD_NAME
943                                                << userName.getUser()
944                                                << AuthorizationManager::USER_DB_FIELD_NAME
945                                                << userName.getDB()),
946                                           &nMatched);
947         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
948         authzManager->invalidateUserByName(userName);
949         if (!status.isOK()) {
950             return appendCommandStatus(result, status);
951         }
952 
953         if (nMatched == 0) {
954             return appendCommandStatus(
955                 result,
956                 Status(ErrorCodes::UserNotFound,
957                        str::stream() << "User '" << userName.getFullName() << "' not found"));
958         }
959 
960         return true;
961     }
962 
963 } cmdDropUser;
964 
965 class CmdDropAllUsersFromDatabase : public BasicCommand {
966 public:
CmdDropAllUsersFromDatabase()967     CmdDropAllUsersFromDatabase() : BasicCommand("dropAllUsersFromDatabase") {}
968 
slaveOk() const969     virtual bool slaveOk() const {
970         return false;
971     }
972 
supportsWriteConcern(const BSONObj & cmd) const973     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
974         return true;
975     }
976 
help(stringstream & ss) const977     virtual void help(stringstream& ss) const {
978         ss << "Drops all users for a single database." << endl;
979     }
980 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)981     virtual Status checkAuthForCommand(Client* client,
982                                        const std::string& dbname,
983                                        const BSONObj& cmdObj) {
984         return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname);
985     }
986 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)987     bool run(OperationContext* opCtx,
988              const string& dbname,
989              const BSONObj& cmdObj,
990              BSONObjBuilder& result) {
991         Status status = auth::parseAndValidateDropAllUsersFromDatabaseCommand(cmdObj, dbname);
992         if (!status.isOK()) {
993             return appendCommandStatus(result, status);
994         }
995         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
996         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
997 
998         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
999         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1000         if (!status.isOK()) {
1001             return appendCommandStatus(result, status);
1002         }
1003 
1004         audit::logDropAllUsersFromDatabase(Client::getCurrent(), dbname);
1005 
1006         long long numRemoved;
1007         status = removePrivilegeDocuments(
1008             opCtx, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname), &numRemoved);
1009         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1010         authzManager->invalidateUsersFromDB(dbname);
1011         if (!status.isOK()) {
1012             return appendCommandStatus(result, status);
1013         }
1014 
1015         result.append("n", numRemoved);
1016         return true;
1017     }
1018 
1019 } cmdDropAllUsersFromDatabase;
1020 
1021 class CmdGrantRolesToUser : public BasicCommand {
1022 public:
CmdGrantRolesToUser()1023     CmdGrantRolesToUser() : BasicCommand("grantRolesToUser") {}
1024 
slaveOk() const1025     virtual bool slaveOk() const {
1026         return false;
1027     }
1028 
supportsWriteConcern(const BSONObj & cmd) const1029     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1030         return true;
1031     }
1032 
help(stringstream & ss) const1033     virtual void help(stringstream& ss) const {
1034         ss << "Grants roles to a user." << endl;
1035     }
1036 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1037     virtual Status checkAuthForCommand(Client* client,
1038                                        const std::string& dbname,
1039                                        const BSONObj& cmdObj) {
1040         return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj);
1041     }
1042 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1043     bool run(OperationContext* opCtx,
1044              const string& dbname,
1045              const BSONObj& cmdObj,
1046              BSONObjBuilder& result) {
1047         std::string userNameString;
1048         std::vector<RoleName> roles;
1049         Status status = auth::parseRolePossessionManipulationCommands(
1050             cmdObj, "grantRolesToUser", dbname, &userNameString, &roles);
1051         if (!status.isOK()) {
1052             return appendCommandStatus(result, status);
1053         }
1054 
1055         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1056         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1057 
1058         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1059         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1060         if (!status.isOK()) {
1061             return appendCommandStatus(result, status);
1062         }
1063 
1064         UserName userName(userNameString, dbname);
1065         unordered_set<RoleName> userRoles;
1066         status = getCurrentUserRoles(opCtx, authzManager, userName, &userRoles);
1067         if (!status.isOK()) {
1068             return appendCommandStatus(result, status);
1069         }
1070 
1071         for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) {
1072             RoleName& roleName = *it;
1073             BSONObj roleDoc;
1074             status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc);
1075             if (!status.isOK()) {
1076                 return appendCommandStatus(result, status);
1077             }
1078 
1079             userRoles.insert(roleName);
1080         }
1081 
1082         audit::logGrantRolesToUser(Client::getCurrent(), userName, roles);
1083         BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles);
1084         status = updatePrivilegeDocument(
1085             opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray)));
1086         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1087         authzManager->invalidateUserByName(userName);
1088         return appendCommandStatus(result, status);
1089     }
1090 
1091 } cmdGrantRolesToUser;
1092 
1093 class CmdRevokeRolesFromUser : public BasicCommand {
1094 public:
CmdRevokeRolesFromUser()1095     CmdRevokeRolesFromUser() : BasicCommand("revokeRolesFromUser") {}
1096 
slaveOk() const1097     virtual bool slaveOk() const {
1098         return false;
1099     }
1100 
supportsWriteConcern(const BSONObj & cmd) const1101     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1102         return true;
1103     }
1104 
help(stringstream & ss) const1105     virtual void help(stringstream& ss) const {
1106         ss << "Revokes roles from a user." << endl;
1107     }
1108 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1109     virtual Status checkAuthForCommand(Client* client,
1110                                        const std::string& dbname,
1111                                        const BSONObj& cmdObj) {
1112         return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj);
1113     }
1114 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1115     bool run(OperationContext* opCtx,
1116              const string& dbname,
1117              const BSONObj& cmdObj,
1118              BSONObjBuilder& result) {
1119         std::string userNameString;
1120         std::vector<RoleName> roles;
1121         Status status = auth::parseRolePossessionManipulationCommands(
1122             cmdObj, "revokeRolesFromUser", dbname, &userNameString, &roles);
1123         if (!status.isOK()) {
1124             return appendCommandStatus(result, status);
1125         }
1126 
1127         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1128         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1129 
1130         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1131         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1132         if (!status.isOK()) {
1133             return appendCommandStatus(result, status);
1134         }
1135 
1136         UserName userName(userNameString, dbname);
1137         unordered_set<RoleName> userRoles;
1138         status = getCurrentUserRoles(opCtx, authzManager, userName, &userRoles);
1139         if (!status.isOK()) {
1140             return appendCommandStatus(result, status);
1141         }
1142 
1143         for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) {
1144             RoleName& roleName = *it;
1145             BSONObj roleDoc;
1146             status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc);
1147             if (!status.isOK()) {
1148                 return appendCommandStatus(result, status);
1149             }
1150 
1151             userRoles.erase(roleName);
1152         }
1153 
1154         audit::logRevokeRolesFromUser(Client::getCurrent(), userName, roles);
1155         BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles);
1156         status = updatePrivilegeDocument(
1157             opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray)));
1158         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1159         authzManager->invalidateUserByName(userName);
1160         return appendCommandStatus(result, status);
1161     }
1162 
1163 } cmdRevokeRolesFromUser;
1164 
1165 class CmdUsersInfo : public BasicCommand {
1166 public:
slaveOk() const1167     virtual bool slaveOk() const {
1168         return false;
1169     }
1170 
slaveOverrideOk() const1171     virtual bool slaveOverrideOk() const {
1172         return true;
1173     }
1174 
supportsWriteConcern(const BSONObj & cmd) const1175     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1176         return false;
1177     }
1178 
CmdUsersInfo()1179     CmdUsersInfo() : BasicCommand("usersInfo") {}
1180 
help(stringstream & ss) const1181     virtual void help(stringstream& ss) const {
1182         ss << "Returns information about users." << endl;
1183     }
1184 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1185     virtual Status checkAuthForCommand(Client* client,
1186                                        const std::string& dbname,
1187                                        const BSONObj& cmdObj) {
1188         return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj);
1189     }
1190 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1191     bool run(OperationContext* opCtx,
1192              const string& dbname,
1193              const BSONObj& cmdObj,
1194              BSONObjBuilder& result) {
1195         auth::UsersInfoArgs args;
1196         Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args);
1197         if (!status.isOK()) {
1198             return appendCommandStatus(result, status);
1199         }
1200 
1201         status = requireAuthSchemaVersion26UpgradeOrFinal(opCtx, getGlobalAuthorizationManager());
1202         if (!status.isOK()) {
1203             return appendCommandStatus(result, status);
1204         }
1205 
1206         if (args.allForDB &&
1207             (args.showPrivileges ||
1208              args.authenticationRestrictionsFormat == AuthenticationRestrictionsFormat::kShow)) {
1209             return appendCommandStatus(
1210                 result,
1211                 Status(ErrorCodes::IllegalOperation,
1212                        "Can only get privilege or restriction details on exact-match usersInfo "
1213                        "queries."));
1214         }
1215 
1216         BSONArrayBuilder usersArrayBuilder;
1217         if (args.showPrivileges ||
1218             args.authenticationRestrictionsFormat == AuthenticationRestrictionsFormat::kShow) {
1219             // If you want privileges or restrictions you need to call getUserDescription on each
1220             // user.
1221             for (size_t i = 0; i < args.userNames.size(); ++i) {
1222                 BSONObj userDetails;
1223                 status = getGlobalAuthorizationManager()->getUserDescription(
1224                     opCtx, args.userNames[i], &userDetails);
1225                 if (status.code() == ErrorCodes::UserNotFound) {
1226                     continue;
1227                 }
1228                 if (!status.isOK()) {
1229                     return appendCommandStatus(result, status);
1230                 }
1231 
1232                 // getUserDescription always includes credentials and restrictions, which may need
1233                 // to be stripped out
1234                 BSONObjBuilder strippedUser(usersArrayBuilder.subobjStart());
1235                 for (const BSONElement& e : userDetails) {
1236                     if (!args.showCredentials && e.fieldNameStringData() == "credentials") {
1237                         continue;
1238                     }
1239 
1240                     if (e.fieldNameStringData() == "authenticationRestrictions" &&
1241                         args.authenticationRestrictionsFormat ==
1242                             AuthenticationRestrictionsFormat::kOmit) {
1243                         continue;
1244                     }
1245 
1246                     strippedUser.append(e);
1247                 }
1248                 strippedUser.doneFast();
1249             }
1250         } else {
1251             // If you don't need privileges, or authenticationRestrictions, you can just do a
1252             // regular query on system.users
1253             BSONObjBuilder queryBuilder;
1254             if (args.allForDB) {
1255                 queryBuilder.append("query",
1256                                     BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname));
1257             } else {
1258                 BSONArrayBuilder usersMatchArray;
1259                 for (size_t i = 0; i < args.userNames.size(); ++i) {
1260                     usersMatchArray.append(BSON(AuthorizationManager::USER_NAME_FIELD_NAME
1261                                                 << args.userNames[i].getUser()
1262                                                 << AuthorizationManager::USER_DB_FIELD_NAME
1263                                                 << args.userNames[i].getDB()));
1264                 }
1265                 queryBuilder.append("query", BSON("$or" << usersMatchArray.arr()));
1266             }
1267             // Order results by user field then db field, matching how UserNames are ordered
1268             queryBuilder.append("orderby", BSON("user" << 1 << "db" << 1));
1269 
1270             BSONObjBuilder projection;
1271             projection.append("authenticationRestrictions", 0);
1272             if (!args.showCredentials) {
1273                 projection.append("credentials", 0);
1274             }
1275             const stdx::function<void(const BSONObj&)> function = stdx::bind(
1276                 appendBSONObjToBSONArrayBuilder, &usersArrayBuilder, stdx::placeholders::_1);
1277 
1278             Status status = queryAuthzDocument(opCtx,
1279                                                AuthorizationManager::usersCollectionNamespace,
1280                                                queryBuilder.done(),
1281                                                projection.done(),
1282                                                function);
1283             if (!status.isOK()) {
1284                 return appendCommandStatus(result, status);
1285             }
1286         }
1287         result.append("users", usersArrayBuilder.arr());
1288         return true;
1289     }
1290 
1291 } cmdUsersInfo;
1292 
1293 class CmdCreateRole : public BasicCommand {
1294 public:
CmdCreateRole()1295     CmdCreateRole() : BasicCommand("createRole") {}
1296 
slaveOk() const1297     virtual bool slaveOk() const {
1298         return false;
1299     }
1300 
supportsWriteConcern(const BSONObj & cmd) const1301     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1302         return true;
1303     }
1304 
help(stringstream & ss) const1305     virtual void help(stringstream& ss) const {
1306         ss << "Adds a role to the system" << endl;
1307     }
1308 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1309     virtual Status checkAuthForCommand(Client* client,
1310                                        const std::string& dbname,
1311                                        const BSONObj& cmdObj) {
1312         return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj);
1313     }
1314 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1315     bool run(OperationContext* opCtx,
1316              const string& dbname,
1317              const BSONObj& cmdObj,
1318              BSONObjBuilder& result) {
1319         auth::CreateOrUpdateRoleArgs args;
1320         Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "createRole", dbname, &args);
1321         if (!status.isOK()) {
1322             return appendCommandStatus(result, status);
1323         }
1324 
1325         if (args.roleName.getRole().empty()) {
1326             return appendCommandStatus(result,
1327                                        Status(ErrorCodes::BadValue, "Role name must be non-empty"));
1328         }
1329 
1330         if (args.roleName.getDB() == "local") {
1331             return appendCommandStatus(
1332                 result, Status(ErrorCodes::BadValue, "Cannot create roles in the local database"));
1333         }
1334 
1335         if (args.roleName.getDB() == "$external") {
1336             return appendCommandStatus(
1337                 result,
1338                 Status(ErrorCodes::BadValue, "Cannot create roles in the $external database"));
1339         }
1340 
1341         if (RoleGraph::isBuiltinRole(args.roleName)) {
1342             return appendCommandStatus(
1343                 result,
1344                 Status(ErrorCodes::BadValue,
1345                        "Cannot create roles with the same name as a built-in role"));
1346         }
1347 
1348         if (!args.hasRoles) {
1349             return appendCommandStatus(
1350                 result,
1351                 Status(ErrorCodes::BadValue, "\"createRole\" command requires a \"roles\" array"));
1352         }
1353 
1354         if (!args.hasPrivileges) {
1355             return appendCommandStatus(
1356                 result,
1357                 Status(ErrorCodes::BadValue,
1358                        "\"createRole\" command requires a \"privileges\" array"));
1359         }
1360 
1361         BSONObjBuilder roleObjBuilder;
1362 
1363         roleObjBuilder.append(
1364             "_id", str::stream() << args.roleName.getDB() << "." << args.roleName.getRole());
1365         roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, args.roleName.getRole());
1366         roleObjBuilder.append(AuthorizationManager::ROLE_DB_FIELD_NAME, args.roleName.getDB());
1367 
1368         BSONArray privileges;
1369         status = privilegeVectorToBSONArray(args.privileges, &privileges);
1370         if (!status.isOK()) {
1371             return appendCommandStatus(result, status);
1372         }
1373         roleObjBuilder.append("privileges", privileges);
1374 
1375         roleObjBuilder.append("roles", rolesVectorToBSONArray(args.roles));
1376 
1377         if (args.authenticationRestrictions && !args.authenticationRestrictions->isEmpty()) {
1378             roleObjBuilder.append("authenticationRestrictions",
1379                                   args.authenticationRestrictions.get());
1380         }
1381 
1382         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1383         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1384 
1385         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1386         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1387         if (!status.isOK()) {
1388             return appendCommandStatus(result, status);
1389         }
1390 
1391         // Role existence has to be checked after acquiring the update lock
1392         status = checkOkayToGrantRolesToRole(opCtx, args.roleName, args.roles, authzManager);
1393         if (!status.isOK()) {
1394             return appendCommandStatus(result, status);
1395         }
1396 
1397         status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges);
1398         if (!status.isOK()) {
1399             return appendCommandStatus(result, status);
1400         }
1401 
1402         audit::logCreateRole(Client::getCurrent(),
1403                              args.roleName,
1404                              args.roles,
1405                              args.privileges,
1406                              args.authenticationRestrictions);
1407 
1408         status = insertRoleDocument(opCtx, roleObjBuilder.done());
1409         return appendCommandStatus(result, status);
1410     }
1411 
1412 } cmdCreateRole;
1413 
1414 class CmdUpdateRole : public BasicCommand {
1415 public:
CmdUpdateRole()1416     CmdUpdateRole() : BasicCommand("updateRole") {}
1417 
slaveOk() const1418     virtual bool slaveOk() const {
1419         return false;
1420     }
1421 
supportsWriteConcern(const BSONObj & cmd) const1422     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1423         return true;
1424     }
1425 
help(stringstream & ss) const1426     virtual void help(stringstream& ss) const {
1427         ss << "Used to update a role" << endl;
1428     }
1429 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1430     virtual Status checkAuthForCommand(Client* client,
1431                                        const std::string& dbname,
1432                                        const BSONObj& cmdObj) {
1433         return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj);
1434     }
1435 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1436     bool run(OperationContext* opCtx,
1437              const string& dbname,
1438              const BSONObj& cmdObj,
1439              BSONObjBuilder& result) {
1440         auth::CreateOrUpdateRoleArgs args;
1441         Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "updateRole", dbname, &args);
1442         if (!status.isOK()) {
1443             return appendCommandStatus(result, status);
1444         }
1445 
1446         if (!args.hasPrivileges && !args.hasRoles && !args.authenticationRestrictions) {
1447             return appendCommandStatus(
1448                 result,
1449                 Status(ErrorCodes::BadValue,
1450                        "Must specify at least one field to update in updateRole"));
1451         }
1452 
1453         BSONObjBuilder updateSetBuilder;
1454         BSONObjBuilder updateUnsetBuilder;
1455 
1456         if (args.hasPrivileges) {
1457             BSONArray privileges;
1458             status = privilegeVectorToBSONArray(args.privileges, &privileges);
1459             if (!status.isOK()) {
1460                 return appendCommandStatus(result, status);
1461             }
1462             updateSetBuilder.append("privileges", privileges);
1463         }
1464 
1465         if (args.hasRoles) {
1466             updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles));
1467         }
1468 
1469         if (args.authenticationRestrictions) {
1470             if (args.authenticationRestrictions->isEmpty()) {
1471                 updateUnsetBuilder.append("authenticationRestrictions", "");
1472             } else {
1473                 updateSetBuilder.append("authenticationRestrictions",
1474                                         args.authenticationRestrictions.get());
1475             }
1476         }
1477 
1478         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1479         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1480 
1481         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1482         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1483         if (!status.isOK()) {
1484             return appendCommandStatus(result, status);
1485         }
1486 
1487         // Role existence has to be checked after acquiring the update lock
1488         BSONObj ignored;
1489         status = authzManager->getRoleDescription(opCtx, args.roleName, &ignored);
1490         if (!status.isOK()) {
1491             return appendCommandStatus(result, status);
1492         }
1493 
1494         if (args.hasRoles) {
1495             status = checkOkayToGrantRolesToRole(opCtx, args.roleName, args.roles, authzManager);
1496             if (!status.isOK()) {
1497                 return appendCommandStatus(result, status);
1498             }
1499         }
1500 
1501         if (args.hasPrivileges) {
1502             status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges);
1503             if (!status.isOK()) {
1504                 return appendCommandStatus(result, status);
1505             }
1506         }
1507 
1508         audit::logUpdateRole(Client::getCurrent(),
1509                              args.roleName,
1510                              args.hasRoles ? &args.roles : nullptr,
1511                              args.hasPrivileges ? &args.privileges : nullptr,
1512                              args.authenticationRestrictions);
1513 
1514         const auto updateSet = updateSetBuilder.obj();
1515         const auto updateUnset = updateUnsetBuilder.obj();
1516         BSONObjBuilder updateDocumentBuilder;
1517         if (!updateSet.isEmpty()) {
1518             updateDocumentBuilder.append("$set", updateSet);
1519         }
1520         if (!updateUnset.isEmpty()) {
1521             updateDocumentBuilder.append("$unset", updateUnset);
1522         }
1523 
1524         status = updateRoleDocument(opCtx, args.roleName, updateDocumentBuilder.obj());
1525         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1526         authzManager->invalidateUserCache();
1527         return appendCommandStatus(result, status);
1528     }
1529 } cmdUpdateRole;
1530 
1531 class CmdGrantPrivilegesToRole : public BasicCommand {
1532 public:
CmdGrantPrivilegesToRole()1533     CmdGrantPrivilegesToRole() : BasicCommand("grantPrivilegesToRole") {}
1534 
slaveOk() const1535     virtual bool slaveOk() const {
1536         return false;
1537     }
1538 
supportsWriteConcern(const BSONObj & cmd) const1539     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1540         return true;
1541     }
1542 
help(stringstream & ss) const1543     virtual void help(stringstream& ss) const {
1544         ss << "Grants privileges to a role" << endl;
1545     }
1546 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1547     virtual Status checkAuthForCommand(Client* client,
1548                                        const std::string& dbname,
1549                                        const BSONObj& cmdObj) {
1550         return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj);
1551     }
1552 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1553     bool run(OperationContext* opCtx,
1554              const string& dbname,
1555              const BSONObj& cmdObj,
1556              BSONObjBuilder& result) {
1557 
1558         RoleName roleName;
1559         PrivilegeVector privilegesToAdd;
1560         Status status = auth::parseAndValidateRolePrivilegeManipulationCommands(
1561             cmdObj, "grantPrivilegesToRole", dbname, &roleName, &privilegesToAdd);
1562         if (!status.isOK()) {
1563             return appendCommandStatus(result, status);
1564         }
1565 
1566         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1567         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1568 
1569         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1570         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1571         if (!status.isOK()) {
1572             return appendCommandStatus(result, status);
1573         }
1574 
1575         if (RoleGraph::isBuiltinRole(roleName)) {
1576             return appendCommandStatus(
1577                 result,
1578                 Status(ErrorCodes::InvalidRoleModification,
1579                        str::stream() << roleName.getFullName()
1580                                      << " is a built-in role and cannot be modified."));
1581         }
1582 
1583         status = checkOkayToGrantPrivilegesToRole(roleName, privilegesToAdd);
1584         if (!status.isOK()) {
1585             return appendCommandStatus(result, status);
1586         }
1587 
1588         BSONObj roleDoc;
1589         status = authzManager->getRoleDescription(opCtx,
1590                                                   roleName,
1591                                                   PrivilegeFormat::kShowSeparate,
1592                                                   AuthenticationRestrictionsFormat::kOmit,
1593                                                   &roleDoc);
1594         if (!status.isOK()) {
1595             return appendCommandStatus(result, status);
1596         }
1597 
1598         PrivilegeVector privileges;
1599         status = auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()),
1600                                                       &privileges);
1601 
1602         if (!status.isOK()) {
1603             return appendCommandStatus(result, status);
1604         }
1605 
1606         for (PrivilegeVector::iterator it = privilegesToAdd.begin(); it != privilegesToAdd.end();
1607              ++it) {
1608             Privilege::addPrivilegeToPrivilegeVector(&privileges, *it);
1609         }
1610 
1611         // Build up update modifier object to $set privileges.
1612         mutablebson::Document updateObj;
1613         mutablebson::Element setElement = updateObj.makeElementObject("$set");
1614         status = updateObj.root().pushBack(setElement);
1615         if (!status.isOK()) {
1616             return appendCommandStatus(result, status);
1617         }
1618         mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges");
1619         status = setElement.pushBack(privilegesElement);
1620         if (!status.isOK()) {
1621             return appendCommandStatus(result, status);
1622         }
1623         status = authzManager->getBSONForPrivileges(privileges, privilegesElement);
1624         if (!status.isOK()) {
1625             return appendCommandStatus(result, status);
1626         }
1627 
1628         BSONObjBuilder updateBSONBuilder;
1629         updateObj.writeTo(&updateBSONBuilder);
1630 
1631         audit::logGrantPrivilegesToRole(Client::getCurrent(), roleName, privilegesToAdd);
1632 
1633         status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done());
1634         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1635         authzManager->invalidateUserCache();
1636         return appendCommandStatus(result, status);
1637     }
1638 
1639 } cmdGrantPrivilegesToRole;
1640 
1641 class CmdRevokePrivilegesFromRole : public BasicCommand {
1642 public:
CmdRevokePrivilegesFromRole()1643     CmdRevokePrivilegesFromRole() : BasicCommand("revokePrivilegesFromRole") {}
1644 
slaveOk() const1645     virtual bool slaveOk() const {
1646         return false;
1647     }
1648 
supportsWriteConcern(const BSONObj & cmd) const1649     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1650         return true;
1651     }
1652 
help(stringstream & ss) const1653     virtual void help(stringstream& ss) const {
1654         ss << "Revokes privileges from a role" << endl;
1655     }
1656 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1657     virtual Status checkAuthForCommand(Client* client,
1658                                        const std::string& dbname,
1659                                        const BSONObj& cmdObj) {
1660         return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj);
1661     }
1662 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1663     bool run(OperationContext* opCtx,
1664              const string& dbname,
1665              const BSONObj& cmdObj,
1666              BSONObjBuilder& result) {
1667         RoleName roleName;
1668         PrivilegeVector privilegesToRemove;
1669         Status status = auth::parseAndValidateRolePrivilegeManipulationCommands(
1670             cmdObj, "revokePrivilegesFromRole", dbname, &roleName, &privilegesToRemove);
1671         if (!status.isOK()) {
1672             return appendCommandStatus(result, status);
1673         }
1674 
1675         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1676         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1677 
1678         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1679         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1680         if (!status.isOK()) {
1681             return appendCommandStatus(result, status);
1682         }
1683 
1684         if (RoleGraph::isBuiltinRole(roleName)) {
1685             return appendCommandStatus(
1686                 result,
1687                 Status(ErrorCodes::InvalidRoleModification,
1688                        str::stream() << roleName.getFullName()
1689                                      << " is a built-in role and cannot be modified."));
1690         }
1691 
1692         BSONObj roleDoc;
1693         status = authzManager->getRoleDescription(opCtx,
1694                                                   roleName,
1695                                                   PrivilegeFormat::kShowSeparate,
1696                                                   AuthenticationRestrictionsFormat::kOmit,
1697                                                   &roleDoc);
1698         if (!status.isOK()) {
1699             return appendCommandStatus(result, status);
1700         }
1701 
1702         PrivilegeVector privileges;
1703         status = auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()),
1704                                                       &privileges);
1705         if (!status.isOK()) {
1706             return appendCommandStatus(result, status);
1707         }
1708 
1709         for (PrivilegeVector::iterator itToRm = privilegesToRemove.begin();
1710              itToRm != privilegesToRemove.end();
1711              ++itToRm) {
1712             for (PrivilegeVector::iterator curIt = privileges.begin(); curIt != privileges.end();
1713                  ++curIt) {
1714                 if (curIt->getResourcePattern() == itToRm->getResourcePattern()) {
1715                     curIt->removeActions(itToRm->getActions());
1716                     if (curIt->getActions().empty()) {
1717                         privileges.erase(curIt);
1718                     }
1719                     break;
1720                 }
1721             }
1722         }
1723 
1724         // Build up update modifier object to $set privileges.
1725         mutablebson::Document updateObj;
1726         mutablebson::Element setElement = updateObj.makeElementObject("$set");
1727         status = updateObj.root().pushBack(setElement);
1728         if (!status.isOK()) {
1729             return appendCommandStatus(result, status);
1730         }
1731         mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges");
1732         status = setElement.pushBack(privilegesElement);
1733         if (!status.isOK()) {
1734             return appendCommandStatus(result, status);
1735         }
1736         status = authzManager->getBSONForPrivileges(privileges, privilegesElement);
1737         if (!status.isOK()) {
1738             return appendCommandStatus(result, status);
1739         }
1740 
1741         audit::logRevokePrivilegesFromRole(Client::getCurrent(), roleName, privilegesToRemove);
1742 
1743         BSONObjBuilder updateBSONBuilder;
1744         updateObj.writeTo(&updateBSONBuilder);
1745         status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done());
1746         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1747         authzManager->invalidateUserCache();
1748         return appendCommandStatus(result, status);
1749     }
1750 
1751 } cmdRevokePrivilegesFromRole;
1752 
1753 class CmdGrantRolesToRole : public BasicCommand {
1754 public:
CmdGrantRolesToRole()1755     CmdGrantRolesToRole() : BasicCommand("grantRolesToRole") {}
1756 
slaveOk() const1757     virtual bool slaveOk() const {
1758         return false;
1759     }
1760 
supportsWriteConcern(const BSONObj & cmd) const1761     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1762         return true;
1763     }
1764 
help(stringstream & ss) const1765     virtual void help(stringstream& ss) const {
1766         ss << "Grants roles to another role." << endl;
1767     }
1768 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1769     virtual Status checkAuthForCommand(Client* client,
1770                                        const std::string& dbname,
1771                                        const BSONObj& cmdObj) {
1772         return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj);
1773     }
1774 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1775     bool run(OperationContext* opCtx,
1776              const string& dbname,
1777              const BSONObj& cmdObj,
1778              BSONObjBuilder& result) {
1779         std::string roleNameString;
1780         std::vector<RoleName> rolesToAdd;
1781         Status status = auth::parseRolePossessionManipulationCommands(
1782             cmdObj, "grantRolesToRole", dbname, &roleNameString, &rolesToAdd);
1783         if (!status.isOK()) {
1784             return appendCommandStatus(result, status);
1785         }
1786 
1787         RoleName roleName(roleNameString, dbname);
1788         if (RoleGraph::isBuiltinRole(roleName)) {
1789             return appendCommandStatus(
1790                 result,
1791                 Status(ErrorCodes::InvalidRoleModification,
1792                        str::stream() << roleName.getFullName()
1793                                      << " is a built-in role and cannot be modified."));
1794         }
1795 
1796         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1797         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1798 
1799         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1800         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1801         if (!status.isOK()) {
1802             return appendCommandStatus(result, status);
1803         }
1804 
1805         // Role existence has to be checked after acquiring the update lock
1806         BSONObj roleDoc;
1807         status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc);
1808         if (!status.isOK()) {
1809             return appendCommandStatus(result, status);
1810         }
1811 
1812         // Check for cycles
1813         status = checkOkayToGrantRolesToRole(opCtx, roleName, rolesToAdd, authzManager);
1814         if (!status.isOK()) {
1815             return appendCommandStatus(result, status);
1816         }
1817 
1818         // Add new roles to existing roles
1819         std::vector<RoleName> directRoles;
1820         status = auth::parseRoleNamesFromBSONArray(
1821             BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &directRoles);
1822         if (!status.isOK()) {
1823             return appendCommandStatus(result, status);
1824         }
1825         for (vector<RoleName>::iterator it = rolesToAdd.begin(); it != rolesToAdd.end(); ++it) {
1826             const RoleName& roleToAdd = *it;
1827             if (!sequenceContains(directRoles, roleToAdd))  // Don't double-add role
1828                 directRoles.push_back(*it);
1829         }
1830 
1831         audit::logGrantRolesToRole(Client::getCurrent(), roleName, rolesToAdd);
1832 
1833         status = updateRoleDocument(
1834             opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles))));
1835         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1836         authzManager->invalidateUserCache();
1837         return appendCommandStatus(result, status);
1838     }
1839 
1840 } cmdGrantRolesToRole;
1841 
1842 class CmdRevokeRolesFromRole : public BasicCommand {
1843 public:
CmdRevokeRolesFromRole()1844     CmdRevokeRolesFromRole() : BasicCommand("revokeRolesFromRole") {}
1845 
slaveOk() const1846     virtual bool slaveOk() const {
1847         return false;
1848     }
1849 
supportsWriteConcern(const BSONObj & cmd) const1850     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1851         return true;
1852     }
1853 
help(stringstream & ss) const1854     virtual void help(stringstream& ss) const {
1855         ss << "Revokes roles from another role." << endl;
1856     }
1857 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1858     virtual Status checkAuthForCommand(Client* client,
1859                                        const std::string& dbname,
1860                                        const BSONObj& cmdObj) {
1861         return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj);
1862     }
1863 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1864     bool run(OperationContext* opCtx,
1865              const string& dbname,
1866              const BSONObj& cmdObj,
1867              BSONObjBuilder& result) {
1868         std::string roleNameString;
1869         std::vector<RoleName> rolesToRemove;
1870         Status status = auth::parseRolePossessionManipulationCommands(
1871             cmdObj, "revokeRolesFromRole", dbname, &roleNameString, &rolesToRemove);
1872         if (!status.isOK()) {
1873             return appendCommandStatus(result, status);
1874         }
1875 
1876         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1877         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1878 
1879         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1880         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1881         if (!status.isOK()) {
1882             return appendCommandStatus(result, status);
1883         }
1884 
1885         RoleName roleName(roleNameString, dbname);
1886         if (RoleGraph::isBuiltinRole(roleName)) {
1887             return appendCommandStatus(
1888                 result,
1889                 Status(ErrorCodes::InvalidRoleModification,
1890                        str::stream() << roleName.getFullName()
1891                                      << " is a built-in role and cannot be modified."));
1892         }
1893 
1894         BSONObj roleDoc;
1895         status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc);
1896         if (!status.isOK()) {
1897             return appendCommandStatus(result, status);
1898         }
1899 
1900         std::vector<RoleName> roles;
1901         status = auth::parseRoleNamesFromBSONArray(
1902             BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles);
1903         if (!status.isOK()) {
1904             return appendCommandStatus(result, status);
1905         }
1906 
1907         for (vector<RoleName>::const_iterator it = rolesToRemove.begin(); it != rolesToRemove.end();
1908              ++it) {
1909             vector<RoleName>::iterator itToRm = std::find(roles.begin(), roles.end(), *it);
1910             if (itToRm != roles.end()) {
1911                 roles.erase(itToRm);
1912             }
1913         }
1914 
1915         audit::logRevokeRolesFromRole(Client::getCurrent(), roleName, rolesToRemove);
1916 
1917         status = updateRoleDocument(
1918             opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles))));
1919         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
1920         authzManager->invalidateUserCache();
1921         return appendCommandStatus(result, status);
1922     }
1923 
1924 } cmdRevokeRolesFromRole;
1925 
1926 class CmdDropRole : public BasicCommand {
1927 public:
CmdDropRole()1928     CmdDropRole() : BasicCommand("dropRole") {}
1929 
slaveOk() const1930     virtual bool slaveOk() const {
1931         return false;
1932     }
1933 
supportsWriteConcern(const BSONObj & cmd) const1934     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
1935         return true;
1936     }
1937 
help(stringstream & ss) const1938     virtual void help(stringstream& ss) const {
1939         ss << "Drops a single role.  Before deleting the role completely it must remove it "
1940               "from any users or roles that reference it.  If any errors occur in the middle "
1941               "of that process it's possible to be left in a state where the role has been "
1942               "removed from some user/roles but otherwise still exists."
1943            << endl;
1944     }
1945 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)1946     virtual Status checkAuthForCommand(Client* client,
1947                                        const std::string& dbname,
1948                                        const BSONObj& cmdObj) {
1949         return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj);
1950     }
1951 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)1952     bool run(OperationContext* opCtx,
1953              const string& dbname,
1954              const BSONObj& cmdObj,
1955              BSONObjBuilder& result) {
1956         RoleName roleName;
1957         Status status = auth::parseDropRoleCommand(cmdObj, dbname, &roleName);
1958         if (!status.isOK()) {
1959             return appendCommandStatus(result, status);
1960         }
1961 
1962         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
1963         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
1964 
1965         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
1966         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
1967         if (!status.isOK()) {
1968             return appendCommandStatus(result, status);
1969         }
1970 
1971         if (RoleGraph::isBuiltinRole(roleName)) {
1972             return appendCommandStatus(
1973                 result,
1974                 Status(ErrorCodes::InvalidRoleModification,
1975                        str::stream() << roleName.getFullName()
1976                                      << " is a built-in role and cannot be modified."));
1977         }
1978 
1979         BSONObj roleDoc;
1980         status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc);
1981         if (!status.isOK()) {
1982             return appendCommandStatus(result, status);
1983         }
1984 
1985         // Remove this role from all users
1986         long long nMatched;
1987         status = updateAuthzDocuments(
1988             opCtx,
1989             AuthorizationManager::usersCollectionNamespace,
1990             BSON("roles" << BSON("$elemMatch" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
1991                                                       << roleName.getRole()
1992                                                       << AuthorizationManager::ROLE_DB_FIELD_NAME
1993                                                       << roleName.getDB()))),
1994             BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
1995                                                  << roleName.getRole()
1996                                                  << AuthorizationManager::ROLE_DB_FIELD_NAME
1997                                                  << roleName.getDB()))),
1998             false,
1999             true,
2000             &nMatched);
2001         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
2002         authzManager->invalidateUserCache();
2003         if (!status.isOK()) {
2004             ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError
2005                 ? ErrorCodes::UserModificationFailed
2006                 : status.code();
2007             return appendCommandStatus(result,
2008                                        Status(code,
2009                                               str::stream() << "Failed to remove role "
2010                                                             << roleName.getFullName()
2011                                                             << " from all users: "
2012                                                             << status.reason()));
2013         }
2014 
2015         // Remove this role from all other roles
2016         status = updateAuthzDocuments(
2017             opCtx,
2018             AuthorizationManager::rolesCollectionNamespace,
2019             BSON("roles" << BSON("$elemMatch" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
2020                                                       << roleName.getRole()
2021                                                       << AuthorizationManager::ROLE_DB_FIELD_NAME
2022                                                       << roleName.getDB()))),
2023             BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
2024                                                  << roleName.getRole()
2025                                                  << AuthorizationManager::ROLE_DB_FIELD_NAME
2026                                                  << roleName.getDB()))),
2027             false,
2028             true,
2029             &nMatched);
2030         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
2031         authzManager->invalidateUserCache();
2032         if (!status.isOK()) {
2033             ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError
2034                 ? ErrorCodes::RoleModificationFailed
2035                 : status.code();
2036             return appendCommandStatus(
2037                 result,
2038                 Status(code,
2039                        str::stream() << "Removed role " << roleName.getFullName()
2040                                      << " from all users but failed to remove from all roles: "
2041                                      << status.reason()));
2042         }
2043 
2044         audit::logDropRole(Client::getCurrent(), roleName);
2045         // Finally, remove the actual role document
2046         status = removeRoleDocuments(opCtx,
2047                                      BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
2048                                           << roleName.getRole()
2049                                           << AuthorizationManager::ROLE_DB_FIELD_NAME
2050                                           << roleName.getDB()),
2051                                      &nMatched);
2052         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
2053         authzManager->invalidateUserCache();
2054         if (!status.isOK()) {
2055             return appendCommandStatus(
2056                 result,
2057                 Status(status.code(),
2058                        str::stream() << "Removed role " << roleName.getFullName()
2059                                      << " from all users and roles but failed to actually delete"
2060                                         " the role itself: "
2061                                      << status.reason()));
2062         }
2063 
2064         dassert(nMatched == 0 || nMatched == 1);
2065         if (nMatched == 0) {
2066             return appendCommandStatus(
2067                 result,
2068                 Status(ErrorCodes::RoleNotFound,
2069                        str::stream() << "Role '" << roleName.getFullName() << "' not found"));
2070         }
2071 
2072         return true;
2073     }
2074 
2075 } cmdDropRole;
2076 
2077 class CmdDropAllRolesFromDatabase : public BasicCommand {
2078 public:
CmdDropAllRolesFromDatabase()2079     CmdDropAllRolesFromDatabase() : BasicCommand("dropAllRolesFromDatabase") {}
2080 
slaveOk() const2081     virtual bool slaveOk() const {
2082         return false;
2083     }
2084 
supportsWriteConcern(const BSONObj & cmd) const2085     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
2086         return true;
2087     }
2088 
help(stringstream & ss) const2089     virtual void help(stringstream& ss) const {
2090         ss << "Drops all roles from the given database.  Before deleting the roles completely "
2091               "it must remove them from any users or other roles that reference them.  If any "
2092               "errors occur in the middle of that process it's possible to be left in a state "
2093               "where the roles have been removed from some user/roles but otherwise still "
2094               "exist."
2095            << endl;
2096     }
2097 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)2098     virtual Status checkAuthForCommand(Client* client,
2099                                        const std::string& dbname,
2100                                        const BSONObj& cmdObj) {
2101         return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname);
2102     }
2103 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)2104     bool run(OperationContext* opCtx,
2105              const string& dbname,
2106              const BSONObj& cmdObj,
2107              BSONObjBuilder& result) {
2108         Status status = auth::parseDropAllRolesFromDatabaseCommand(cmdObj, dbname);
2109         if (!status.isOK()) {
2110             return appendCommandStatus(result, status);
2111         }
2112 
2113         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
2114         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
2115 
2116         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
2117         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
2118         if (!status.isOK()) {
2119             return appendCommandStatus(result, status);
2120         }
2121 
2122         // Remove these roles from all users
2123         long long nMatched;
2124         status = updateAuthzDocuments(
2125             opCtx,
2126             AuthorizationManager::usersCollectionNamespace,
2127             BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname)),
2128             BSON("$pull" << BSON("roles"
2129                                  << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))),
2130             false,
2131             true,
2132             &nMatched);
2133         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
2134         authzManager->invalidateUserCache();
2135         if (!status.isOK()) {
2136             ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError
2137                 ? ErrorCodes::UserModificationFailed
2138                 : status.code();
2139             return appendCommandStatus(result,
2140                                        Status(code,
2141                                               str::stream() << "Failed to remove roles from \""
2142                                                             << dbname
2143                                                             << "\" db from all users: "
2144                                                             << status.reason()));
2145         }
2146 
2147         // Remove these roles from all other roles
2148         std::string sourceFieldName = str::stream() << "roles."
2149                                                     << AuthorizationManager::ROLE_DB_FIELD_NAME;
2150         status = updateAuthzDocuments(
2151             opCtx,
2152             AuthorizationManager::rolesCollectionNamespace,
2153             BSON(sourceFieldName << dbname),
2154             BSON("$pull" << BSON("roles"
2155                                  << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))),
2156             false,
2157             true,
2158             &nMatched);
2159         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
2160         authzManager->invalidateUserCache();
2161         if (!status.isOK()) {
2162             ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError
2163                 ? ErrorCodes::RoleModificationFailed
2164                 : status.code();
2165             return appendCommandStatus(result,
2166                                        Status(code,
2167                                               str::stream() << "Failed to remove roles from \""
2168                                                             << dbname
2169                                                             << "\" db from all roles: "
2170                                                             << status.reason()));
2171         }
2172 
2173         audit::logDropAllRolesFromDatabase(Client::getCurrent(), dbname);
2174         // Finally, remove the actual role documents
2175         status = removeRoleDocuments(
2176             opCtx, BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname), &nMatched);
2177         // Must invalidate even on bad status - what if the write succeeded but the GLE failed?
2178         authzManager->invalidateUserCache();
2179         if (!status.isOK()) {
2180             return appendCommandStatus(
2181                 result,
2182                 Status(status.code(),
2183                        str::stream() << "Removed roles from \"" << dbname
2184                                      << "\" db "
2185                                         " from all users and roles but failed to actually delete"
2186                                         " those roles themselves: "
2187                                      << status.reason()));
2188         }
2189 
2190         result.append("n", nMatched);
2191 
2192         return true;
2193     }
2194 
2195 } cmdDropAllRolesFromDatabase;
2196 
2197 /**
2198  * Provides information about one or more roles, the indirect roles they are members of, and
2199  * optionally the privileges they provide.
2200  *
2201  * This command accepts the following arguments:
2202  * rolesInfo:
2203  *   (String) Returns information about a single role on the current database.
2204  *   {role: (String), db: (String)} Returns information about a specified role, on a specific db
2205  *   (BooleanTrue) Returns information about all roles in this database
2206  *   [ //Zero or more of
2207  *     {role: (String), db: (String) ] Returns information about all specified roles
2208  *
2209  * showBuiltinRoles:
2210  *   (Boolean) If true, and rolesInfo == (BooleanTrue), include built-in roles from the database
2211  *
2212  * showPrivileges:
2213  *   (BooleanFalse) Do not show information about privileges
2214  *   (BooleanTrue) Attach all privileges inherited from roles to role descriptions
2215  *   "asUserFragment" Render results as a partial user document as-if a user existed which possessed
2216  *                    these roles. This format may change over time with changes to the auth
2217  *                    schema.
2218  */
2219 
2220 class CmdRolesInfo : public BasicCommand {
2221 public:
slaveOk() const2222     virtual bool slaveOk() const {
2223         return false;
2224     }
2225 
slaveOverrideOk() const2226     virtual bool slaveOverrideOk() const {
2227         return true;
2228     }
2229 
supportsWriteConcern(const BSONObj & cmd) const2230     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
2231         return false;
2232     }
2233 
CmdRolesInfo()2234     CmdRolesInfo() : BasicCommand("rolesInfo") {}
2235 
help(stringstream & ss) const2236     virtual void help(stringstream& ss) const {
2237         ss << "Returns information about roles." << endl;
2238     }
2239 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)2240     virtual Status checkAuthForCommand(Client* client,
2241                                        const std::string& dbname,
2242                                        const BSONObj& cmdObj) {
2243         return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj);
2244     }
2245 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)2246     bool run(OperationContext* opCtx,
2247              const string& dbname,
2248              const BSONObj& cmdObj,
2249              BSONObjBuilder& result) {
2250         auth::RolesInfoArgs args;
2251         Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args);
2252         if (!status.isOK()) {
2253             return appendCommandStatus(result, status);
2254         }
2255 
2256         status = requireAuthSchemaVersion26UpgradeOrFinal(opCtx, getGlobalAuthorizationManager());
2257         if (!status.isOK()) {
2258             return appendCommandStatus(result, status);
2259         }
2260 
2261         if (args.allForDB) {
2262             std::vector<BSONObj> rolesDocs;
2263             status = getGlobalAuthorizationManager()->getRoleDescriptionsForDB(
2264                 opCtx,
2265                 dbname,
2266                 args.privilegeFormat,
2267                 args.authenticationRestrictionsFormat,
2268                 args.showBuiltinRoles,
2269                 &rolesDocs);
2270             if (!status.isOK()) {
2271                 return appendCommandStatus(result, status);
2272             }
2273 
2274             if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) {
2275                 return appendCommandStatus(
2276                     result,
2277                     Status(ErrorCodes::IllegalOperation,
2278                            "Cannot get user fragment for all roles in a database"));
2279             }
2280             BSONArrayBuilder rolesArrayBuilder;
2281             for (size_t i = 0; i < rolesDocs.size(); ++i) {
2282                 rolesArrayBuilder.append(rolesDocs[i]);
2283             }
2284             result.append("roles", rolesArrayBuilder.arr());
2285         } else {
2286             BSONObj roleDetails;
2287             status = getGlobalAuthorizationManager()->getRolesDescription(
2288                 opCtx,
2289                 args.roleNames,
2290                 args.privilegeFormat,
2291                 args.authenticationRestrictionsFormat,
2292                 &roleDetails);
2293             if (!status.isOK()) {
2294                 return appendCommandStatus(result, status);
2295             }
2296 
2297             if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) {
2298                 result.append("userFragment", roleDetails);
2299             } else {
2300                 result.append("roles", BSONArray(roleDetails));
2301             }
2302         }
2303 
2304         return true;
2305     }
2306 
2307 } cmdRolesInfo;
2308 
2309 class CmdInvalidateUserCache : public BasicCommand {
2310 public:
slaveOk() const2311     virtual bool slaveOk() const {
2312         return true;
2313     }
2314 
adminOnly() const2315     virtual bool adminOnly() const {
2316         return true;
2317     }
2318 
supportsWriteConcern(const BSONObj & cmd) const2319     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
2320         return false;
2321     }
2322 
CmdInvalidateUserCache()2323     CmdInvalidateUserCache() : BasicCommand("invalidateUserCache") {}
2324 
help(stringstream & ss) const2325     virtual void help(stringstream& ss) const {
2326         ss << "Invalidates the in-memory cache of user information" << endl;
2327     }
2328 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)2329     virtual Status checkAuthForCommand(Client* client,
2330                                        const std::string& dbname,
2331                                        const BSONObj& cmdObj) {
2332         return auth::checkAuthForInvalidateUserCacheCommand(client);
2333     }
2334 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)2335     bool run(OperationContext* opCtx,
2336              const string& dbname,
2337              const BSONObj& cmdObj,
2338              BSONObjBuilder& result) {
2339         AuthorizationManager* authzManager = getGlobalAuthorizationManager();
2340         authzManager->invalidateUserCache();
2341         return true;
2342     }
2343 
2344 } cmdInvalidateUserCache;
2345 
2346 class CmdGetCacheGeneration : public BasicCommand {
2347 public:
slaveOk() const2348     virtual bool slaveOk() const {
2349         return true;
2350     }
2351 
adminOnly() const2352     virtual bool adminOnly() const {
2353         return true;
2354     }
2355 
supportsWriteConcern(const BSONObj & cmd) const2356     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
2357         return false;
2358     }
2359 
CmdGetCacheGeneration()2360     CmdGetCacheGeneration() : BasicCommand("_getUserCacheGeneration") {}
2361 
help(stringstream & ss) const2362     virtual void help(stringstream& ss) const {
2363         ss << "internal" << endl;
2364     }
2365 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)2366     virtual Status checkAuthForCommand(Client* client,
2367                                        const std::string& dbname,
2368                                        const BSONObj& cmdObj) {
2369         return auth::checkAuthForGetUserCacheGenerationCommand(client);
2370     }
2371 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)2372     bool run(OperationContext* opCtx,
2373              const string& dbname,
2374              const BSONObj& cmdObj,
2375              BSONObjBuilder& result) {
2376         AuthorizationManager* authzManager = getGlobalAuthorizationManager();
2377         result.append("cacheGeneration", authzManager->getCacheGeneration());
2378         return true;
2379     }
2380 
2381 } CmdGetCacheGeneration;
2382 
2383 /**
2384  * This command is used only by mongorestore to handle restoring users/roles.  We do this so
2385  * that mongorestore doesn't do direct inserts into the admin.system.users and
2386  * admin.system.roles, which would bypass the authzUpdateLock and allow multiple concurrent
2387  * modifications to users/roles.  What mongorestore now does instead is it inserts all user/role
2388  * definitions it wants to restore into temporary collections, then this command moves those
2389  * user/role definitions into their proper place in admin.system.users and admin.system.roles.
2390  * It either adds the users/roles to the existing ones or replaces the existing ones, depending
2391  * on whether the "drop" argument is true or false.
2392  */
2393 class CmdMergeAuthzCollections : public BasicCommand {
2394 public:
CmdMergeAuthzCollections()2395     CmdMergeAuthzCollections() : BasicCommand("_mergeAuthzCollections") {}
2396 
slaveOk() const2397     virtual bool slaveOk() const {
2398         return false;
2399     }
2400 
supportsWriteConcern(const BSONObj & cmd) const2401     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
2402         return true;
2403     }
2404 
adminOnly() const2405     virtual bool adminOnly() const {
2406         return true;
2407     }
2408 
help(stringstream & ss) const2409     virtual void help(stringstream& ss) const {
2410         ss << "Internal command used by mongorestore for updating user/role data" << endl;
2411     }
2412 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)2413     virtual Status checkAuthForCommand(Client* client,
2414                                        const std::string& dbname,
2415                                        const BSONObj& cmdObj) {
2416         return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj);
2417     }
2418 
extractUserNameFromBSON(const BSONObj & userObj)2419     static UserName extractUserNameFromBSON(const BSONObj& userObj) {
2420         std::string name;
2421         std::string db;
2422         Status status =
2423             bsonExtractStringField(userObj, AuthorizationManager::USER_NAME_FIELD_NAME, &name);
2424         uassertStatusOK(status);
2425         status = bsonExtractStringField(userObj, AuthorizationManager::USER_DB_FIELD_NAME, &db);
2426         uassertStatusOK(status);
2427         return UserName(name, db);
2428     }
2429 
2430     /**
2431      * Extracts the UserName from the user document and adds it to set of existing users.
2432      * This function is written so it can used with stdx::bind over the result set of a query
2433      * on admin.system.users to add the user names of all existing users to the "usersToDrop"
2434      * set used in the command body.
2435      */
extractAndInsertUserName(unordered_set<UserName> * existingUsers,const BSONObj & userObj)2436     static void extractAndInsertUserName(unordered_set<UserName>* existingUsers,
2437                                          const BSONObj& userObj) {
2438         UserName userName = extractUserNameFromBSON(userObj);
2439         existingUsers->insert(userName);
2440     }
2441 
extractRoleNameFromBSON(const BSONObj & roleObj)2442     static RoleName extractRoleNameFromBSON(const BSONObj& roleObj) {
2443         std::string name;
2444         std::string db;
2445         Status status =
2446             bsonExtractStringField(roleObj, AuthorizationManager::ROLE_NAME_FIELD_NAME, &name);
2447         uassertStatusOK(status);
2448         status = bsonExtractStringField(roleObj, AuthorizationManager::ROLE_DB_FIELD_NAME, &db);
2449         uassertStatusOK(status);
2450         return RoleName(name, db);
2451     }
2452 
2453     /**
2454      * Extracts the RoleName from the role document and adds it to set of existing roles.
2455      * This function is written so it can used with stdx::bind over the result set of a query
2456      * on admin.system.roles to add the role names of all existing roles to the "rolesToDrop"
2457      * set used in the command body.
2458      */
extractAndInsertRoleName(unordered_set<RoleName> * existingRoles,const BSONObj & roleObj)2459     static void extractAndInsertRoleName(unordered_set<RoleName>* existingRoles,
2460                                          const BSONObj& roleObj) {
2461         RoleName roleName = extractRoleNameFromBSON(roleObj);
2462         existingRoles->insert(roleName);
2463     }
2464 
2465     /**
2466      * Audits the fact that we are creating or updating the user described by userObj.
2467      */
auditCreateOrUpdateUser(const BSONObj & userObj,bool create)2468     static void auditCreateOrUpdateUser(const BSONObj& userObj, bool create) {
2469         UserName userName = extractUserNameFromBSON(userObj);
2470         std::vector<RoleName> roles;
2471         uassertStatusOK(auth::parseRoleNamesFromBSONArray(
2472             BSONArray(userObj["roles"].Obj()), userName.getDB(), &roles));
2473         BSONObj customData;
2474         if (userObj.hasField("customData")) {
2475             customData = userObj["customData"].Obj();
2476         }
2477 
2478         boost::optional<BSONArray> authenticationRestrictions;
2479         if (userObj.hasField("authenticationRestrictions")) {
2480             auto r = getRawAuthenticationRestrictions(
2481                 BSONArray(userObj["authenticationRestrictions"].Obj()));
2482             uassertStatusOK(r);
2483             authenticationRestrictions = r.getValue();
2484         }
2485 
2486         if (create) {
2487             audit::logCreateUser(Client::getCurrent(),
2488                                  userName,
2489                                  userObj["credentials"].Obj().hasField("MONGODB-CR"),
2490                                  userObj.hasField("customData") ? &customData : NULL,
2491                                  roles,
2492                                  authenticationRestrictions);
2493         } else {
2494             audit::logUpdateUser(Client::getCurrent(),
2495                                  userName,
2496                                  userObj["credentials"].Obj().hasField("MONGODB-CR"),
2497                                  userObj.hasField("customData") ? &customData : NULL,
2498                                  &roles,
2499                                  authenticationRestrictions);
2500         }
2501     }
2502 
2503     /**
2504      * Audits the fact that we are creating or updating the role described by roleObj.
2505      */
auditCreateOrUpdateRole(const BSONObj & roleObj,bool create)2506     static void auditCreateOrUpdateRole(const BSONObj& roleObj, bool create) {
2507         RoleName roleName = extractRoleNameFromBSON(roleObj);
2508         std::vector<RoleName> roles;
2509         std::vector<Privilege> privileges;
2510         uassertStatusOK(auth::parseRoleNamesFromBSONArray(
2511             BSONArray(roleObj["roles"].Obj()), roleName.getDB(), &roles));
2512         uassertStatusOK(auth::parseAndValidatePrivilegeArray(BSONArray(roleObj["privileges"].Obj()),
2513                                                              &privileges));
2514 
2515         boost::optional<BSONArray> authenticationRestrictions;
2516         if (roleObj.hasField("authenticationRestrictions")) {
2517             auto r = getRawAuthenticationRestrictions(
2518                 BSONArray(roleObj["authenticationRestrictions"].Obj()));
2519             uassertStatusOK(r);
2520             authenticationRestrictions = r.getValue();
2521         }
2522 
2523         if (create) {
2524             audit::logCreateRole(
2525                 Client::getCurrent(), roleName, roles, privileges, authenticationRestrictions);
2526         } else {
2527             audit::logUpdateRole(
2528                 Client::getCurrent(), roleName, &roles, &privileges, authenticationRestrictions);
2529         }
2530     }
2531 
2532     /**
2533      * Designed to be used with stdx::bind to be called on every user object in the result
2534      * set of a query over the tempUsersCollection provided to the command.  For each user
2535      * in the temp collection that is defined on the given db, adds that user to the actual
2536      * admin.system.users collection.
2537      * Also removes any users it encounters from the usersToDrop set.
2538      */
addUser(OperationContext * opCtx,AuthorizationManager * authzManager,StringData db,bool update,unordered_set<UserName> * usersToDrop,const BSONObj & userObj)2539     static void addUser(OperationContext* opCtx,
2540                         AuthorizationManager* authzManager,
2541                         StringData db,
2542                         bool update,
2543                         unordered_set<UserName>* usersToDrop,
2544                         const BSONObj& userObj) {
2545         UserName userName = extractUserNameFromBSON(userObj);
2546         if (!db.empty() && userName.getDB() != db) {
2547             return;
2548         }
2549 
2550         if (update && usersToDrop->count(userName)) {
2551             auditCreateOrUpdateUser(userObj, false);
2552             Status status = updatePrivilegeDocument(opCtx, userName, userObj);
2553             if (!status.isOK()) {
2554                 // Match the behavior of mongorestore to continue on failure
2555                 warning() << "Could not update user " << userName
2556                           << " in _mergeAuthzCollections command: " << redact(status);
2557             }
2558         } else {
2559             auditCreateOrUpdateUser(userObj, true);
2560             Status status = insertPrivilegeDocument(opCtx, userObj);
2561             if (!status.isOK()) {
2562                 // Match the behavior of mongorestore to continue on failure
2563                 warning() << "Could not insert user " << userName
2564                           << " in _mergeAuthzCollections command: " << redact(status);
2565             }
2566         }
2567         usersToDrop->erase(userName);
2568     }
2569 
2570     /**
2571      * Designed to be used with stdx::bind to be called on every role object in the result
2572      * set of a query over the tempRolesCollection provided to the command.  For each role
2573      * in the temp collection that is defined on the given db, adds that role to the actual
2574      * admin.system.roles collection.
2575      * Also removes any roles it encounters from the rolesToDrop set.
2576      */
addRole(OperationContext * opCtx,AuthorizationManager * authzManager,StringData db,bool update,unordered_set<RoleName> * rolesToDrop,const BSONObj roleObj)2577     static void addRole(OperationContext* opCtx,
2578                         AuthorizationManager* authzManager,
2579                         StringData db,
2580                         bool update,
2581                         unordered_set<RoleName>* rolesToDrop,
2582                         const BSONObj roleObj) {
2583         RoleName roleName = extractRoleNameFromBSON(roleObj);
2584         if (!db.empty() && roleName.getDB() != db) {
2585             return;
2586         }
2587 
2588         if (update && rolesToDrop->count(roleName)) {
2589             auditCreateOrUpdateRole(roleObj, false);
2590             Status status = updateRoleDocument(opCtx, roleName, roleObj);
2591             if (!status.isOK()) {
2592                 // Match the behavior of mongorestore to continue on failure
2593                 warning() << "Could not update role " << roleName
2594                           << " in _mergeAuthzCollections command: " << redact(status);
2595             }
2596         } else {
2597             auditCreateOrUpdateRole(roleObj, true);
2598             Status status = insertRoleDocument(opCtx, roleObj);
2599             if (!status.isOK()) {
2600                 // Match the behavior of mongorestore to continue on failure
2601                 warning() << "Could not insert role " << roleName
2602                           << " in _mergeAuthzCollections command: " << redact(status);
2603             }
2604         }
2605         rolesToDrop->erase(roleName);
2606     }
2607 
2608     /**
2609      * Moves all user objects from usersCollName into admin.system.users.  If drop is true,
2610      * removes any users that were in admin.system.users but not in usersCollName.
2611      */
processUsers(OperationContext * opCtx,AuthorizationManager * authzManager,StringData usersCollName,StringData db,bool drop)2612     Status processUsers(OperationContext* opCtx,
2613                         AuthorizationManager* authzManager,
2614                         StringData usersCollName,
2615                         StringData db,
2616                         bool drop) {
2617         // When the "drop" argument has been provided, we use this set to store the users
2618         // that are currently in the system, and remove from it as we encounter
2619         // same-named users in the collection we are restoring from.  Once we've fully
2620         // moved over the temp users collection into its final location, we drop
2621         // any users that previously existed there but weren't in the temp collection.
2622         // This is so that we can completely replace the system.users
2623         // collection with the users from the temp collection, without removing all
2624         // users at the beginning and thus potentially locking ourselves out by having
2625         // no users in the whole system for a time.
2626         unordered_set<UserName> usersToDrop;
2627 
2628         if (drop) {
2629             // Create map of the users currently in the DB
2630             BSONObj query =
2631                 db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db);
2632             BSONObj fields = BSON(AuthorizationManager::USER_NAME_FIELD_NAME
2633                                   << 1
2634                                   << AuthorizationManager::USER_DB_FIELD_NAME
2635                                   << 1);
2636 
2637             Status status =
2638                 queryAuthzDocument(opCtx,
2639                                    AuthorizationManager::usersCollectionNamespace,
2640                                    query,
2641                                    fields,
2642                                    stdx::bind(&CmdMergeAuthzCollections::extractAndInsertUserName,
2643                                               &usersToDrop,
2644                                               stdx::placeholders::_1));
2645             if (!status.isOK()) {
2646                 return status;
2647             }
2648         }
2649 
2650         Status status = queryAuthzDocument(
2651             opCtx,
2652             NamespaceString(usersCollName),
2653             db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db),
2654             BSONObj(),
2655             stdx::bind(&CmdMergeAuthzCollections::addUser,
2656                        opCtx,
2657                        authzManager,
2658                        db,
2659                        drop,
2660                        &usersToDrop,
2661                        stdx::placeholders::_1));
2662         if (!status.isOK()) {
2663             return status;
2664         }
2665 
2666         if (drop) {
2667             long long numRemoved;
2668             for (unordered_set<UserName>::iterator it = usersToDrop.begin();
2669                  it != usersToDrop.end();
2670                  ++it) {
2671                 const UserName& userName = *it;
2672                 audit::logDropUser(Client::getCurrent(), userName);
2673                 status = removePrivilegeDocuments(opCtx,
2674                                                   BSON(AuthorizationManager::USER_NAME_FIELD_NAME
2675                                                        << userName.getUser().toString()
2676                                                        << AuthorizationManager::USER_DB_FIELD_NAME
2677                                                        << userName.getDB().toString()),
2678                                                   &numRemoved);
2679                 if (!status.isOK()) {
2680                     return status;
2681                 }
2682                 dassert(numRemoved == 1);
2683             }
2684         }
2685 
2686         return Status::OK();
2687     }
2688 
2689     /**
2690      * Moves all user objects from usersCollName into admin.system.users.  If drop is true,
2691      * removes any users that were in admin.system.users but not in usersCollName.
2692      */
processRoles(OperationContext * opCtx,AuthorizationManager * authzManager,StringData rolesCollName,StringData db,bool drop)2693     Status processRoles(OperationContext* opCtx,
2694                         AuthorizationManager* authzManager,
2695                         StringData rolesCollName,
2696                         StringData db,
2697                         bool drop) {
2698         // When the "drop" argument has been provided, we use this set to store the roles
2699         // that are currently in the system, and remove from it as we encounter
2700         // same-named roles in the collection we are restoring from.  Once we've fully
2701         // moved over the temp roles collection into its final location, we drop
2702         // any roles that previously existed there but weren't in the temp collection.
2703         // This is so that we can completely replace the system.roles
2704         // collection with the roles from the temp collection, without removing all
2705         // roles at the beginning and thus potentially locking ourselves out.
2706         unordered_set<RoleName> rolesToDrop;
2707 
2708         if (drop) {
2709             // Create map of the roles currently in the DB
2710             BSONObj query =
2711                 db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db);
2712             BSONObj fields = BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
2713                                   << 1
2714                                   << AuthorizationManager::ROLE_DB_FIELD_NAME
2715                                   << 1);
2716 
2717             Status status =
2718                 queryAuthzDocument(opCtx,
2719                                    AuthorizationManager::rolesCollectionNamespace,
2720                                    query,
2721                                    fields,
2722                                    stdx::bind(&CmdMergeAuthzCollections::extractAndInsertRoleName,
2723                                               &rolesToDrop,
2724                                               stdx::placeholders::_1));
2725             if (!status.isOK()) {
2726                 return status;
2727             }
2728         }
2729 
2730         Status status = queryAuthzDocument(
2731             opCtx,
2732             NamespaceString(rolesCollName),
2733             db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db),
2734             BSONObj(),
2735             stdx::bind(&CmdMergeAuthzCollections::addRole,
2736                        opCtx,
2737                        authzManager,
2738                        db,
2739                        drop,
2740                        &rolesToDrop,
2741                        stdx::placeholders::_1));
2742         if (!status.isOK()) {
2743             return status;
2744         }
2745 
2746         if (drop) {
2747             long long numRemoved;
2748             for (unordered_set<RoleName>::iterator it = rolesToDrop.begin();
2749                  it != rolesToDrop.end();
2750                  ++it) {
2751                 const RoleName& roleName = *it;
2752                 audit::logDropRole(Client::getCurrent(), roleName);
2753                 status = removeRoleDocuments(opCtx,
2754                                              BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
2755                                                   << roleName.getRole().toString()
2756                                                   << AuthorizationManager::ROLE_DB_FIELD_NAME
2757                                                   << roleName.getDB().toString()),
2758                                              &numRemoved);
2759                 if (!status.isOK()) {
2760                     return status;
2761                 }
2762                 dassert(numRemoved == 1);
2763             }
2764         }
2765 
2766         return Status::OK();
2767     }
2768 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)2769     bool run(OperationContext* opCtx,
2770              const string& dbname,
2771              const BSONObj& cmdObj,
2772              BSONObjBuilder& result) {
2773         auth::MergeAuthzCollectionsArgs args;
2774         Status status = auth::parseMergeAuthzCollectionsCommand(cmdObj, &args);
2775         if (!status.isOK()) {
2776             return appendCommandStatus(result, status);
2777         }
2778 
2779         if (args.usersCollName.empty() && args.rolesCollName.empty()) {
2780             return appendCommandStatus(
2781                 result,
2782                 Status(ErrorCodes::BadValue,
2783                        "Must provide at least one of \"tempUsersCollection\" and "
2784                        "\"tempRolescollection\""));
2785         }
2786 
2787         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
2788         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
2789 
2790         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
2791         status = requireAuthSchemaVersion26Final(opCtx, authzManager);
2792         if (!status.isOK()) {
2793             return appendCommandStatus(result, status);
2794         }
2795 
2796         if (!args.usersCollName.empty()) {
2797             Status status =
2798                 processUsers(opCtx, authzManager, args.usersCollName, args.db, args.drop);
2799             if (!status.isOK()) {
2800                 return appendCommandStatus(result, status);
2801             }
2802         }
2803 
2804         if (!args.rolesCollName.empty()) {
2805             Status status =
2806                 processRoles(opCtx, authzManager, args.rolesCollName, args.db, args.drop);
2807             if (!status.isOK()) {
2808                 return appendCommandStatus(result, status);
2809             }
2810         }
2811 
2812         return true;
2813     }
2814 
2815 } cmdMergeAuthzCollections;
2816 
2817 /**
2818  * Logs that the auth schema upgrade failed because of "status" and returns "status".
2819  */
logUpgradeFailed(const Status & status)2820 Status logUpgradeFailed(const Status& status) {
2821     log() << "Auth schema upgrade failed with " << redact(status);
2822     return status;
2823 }
2824 
2825 /**
2826  * Updates a single user document from MONGODB-CR to SCRAM credentials.
2827  *
2828  * Throws a DBException on errors.
2829  */
updateUserCredentials(OperationContext * opCtx,const StringData & sourceDB,const BSONObj & userDoc)2830 void updateUserCredentials(OperationContext* opCtx,
2831                            const StringData& sourceDB,
2832                            const BSONObj& userDoc) {
2833     // Skip users in $external, SERVER-18475
2834     if (userDoc["db"].String() == "$external") {
2835         return;
2836     }
2837 
2838     BSONElement credentialsElement = userDoc["credentials"];
2839     uassert(18806,
2840             mongoutils::str::stream()
2841                 << "While preparing to upgrade user doc from "
2842                    "2.6/3.0 user data schema to the 3.0+ SCRAM only schema, found a user doc "
2843                    "with missing or incorrectly formatted credentials: "
2844                 << userDoc.toString(),
2845             credentialsElement.type() == Object);
2846 
2847     BSONObj credentialsObj = credentialsElement.Obj();
2848     BSONElement mongoCRElement = credentialsObj["MONGODB-CR"];
2849     BSONElement scramElement = credentialsObj["SCRAM-SHA-1"];
2850 
2851     // Ignore any user documents that already have SCRAM credentials. This should only
2852     // occur if a previous authSchemaUpgrade was interrupted halfway.
2853     if (!scramElement.eoo()) {
2854         return;
2855     }
2856 
2857     uassert(18744,
2858             mongoutils::str::stream()
2859                 << "While preparing to upgrade user doc from "
2860                    "2.6/3.0 user data schema to the 3.0+ SCRAM only schema, found a user doc "
2861                    "missing MONGODB-CR credentials :"
2862                 << userDoc.toString(),
2863             !mongoCRElement.eoo());
2864 
2865     std::string hashedPassword = mongoCRElement.String();
2866 
2867     BSONObj query = BSON("_id" << userDoc["_id"].String());
2868     BSONObjBuilder updateBuilder;
2869     {
2870         BSONObjBuilder toSetBuilder(updateBuilder.subobjStart("$set"));
2871         toSetBuilder << "credentials"
2872                      << BSON("SCRAM-SHA-1" << scram::generateCredentials(
2873                                  hashedPassword, saslGlobalParams.scramIterationCount.load()));
2874     }
2875 
2876     uassertStatusOK(updateOneAuthzDocument(
2877         opCtx, NamespaceString("admin", "system.users"), query, updateBuilder.obj(), true));
2878 }
2879 
2880 /** Loop through all the user documents in the admin.system.users collection.
2881  *  For each user document:
2882  *   1. Compute SCRAM credentials based on the MONGODB-CR hash
2883  *   2. Remove the MONGODB-CR hash
2884  *   3. Add SCRAM credentials to the user document credentials section
2885  */
updateCredentials(OperationContext * opCtx)2886 Status updateCredentials(OperationContext* opCtx) {
2887     // Loop through and update the user documents in admin.system.users.
2888     Status status = queryAuthzDocument(
2889         opCtx,
2890         NamespaceString("admin", "system.users"),
2891         BSONObj(),
2892         BSONObj(),
2893         stdx::bind(updateUserCredentials, opCtx, "admin", stdx::placeholders::_1));
2894     if (!status.isOK())
2895         return logUpgradeFailed(status);
2896 
2897     // Update the schema version document.
2898     status =
2899         updateOneAuthzDocument(opCtx,
2900                                AuthorizationManager::versionCollectionNamespace,
2901                                AuthorizationManager::versionDocumentQuery,
2902                                BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName
2903                                                    << AuthorizationManager::schemaVersion28SCRAM)),
2904                                true);
2905     if (!status.isOK())
2906         return logUpgradeFailed(status);
2907 
2908     return Status::OK();
2909 }
2910 
2911 /**
2912  * Performs one step in the process of upgrading the stored authorization data to the
2913  * newest schema.
2914  *
2915  * On success, returns Status::OK(), and *isDone will indicate whether there are more
2916  * steps to perform.
2917  *
2918  * If the authorization data is already fully upgraded, returns Status::OK and sets *isDone
2919  * to true, so this is safe to call on a fully upgraded system.
2920  *
2921  * On failure, returns a status other than Status::OK().  In this case, is is typically safe
2922  * to try again.
2923  */
upgradeAuthSchemaStep(OperationContext * opCtx,AuthorizationManager * authzManager,bool * isDone)2924 Status upgradeAuthSchemaStep(OperationContext* opCtx,
2925                              AuthorizationManager* authzManager,
2926                              bool* isDone) {
2927     int authzVersion;
2928     Status status = authzManager->getAuthorizationVersion(opCtx, &authzVersion);
2929     if (!status.isOK()) {
2930         return status;
2931     }
2932 
2933     switch (authzVersion) {
2934         case AuthorizationManager::schemaVersion26Final:
2935         case AuthorizationManager::schemaVersion28SCRAM: {
2936             Status status = updateCredentials(opCtx);
2937             if (status.isOK())
2938                 *isDone = true;
2939             return status;
2940         }
2941         default:
2942             return Status(ErrorCodes::AuthSchemaIncompatible,
2943                           mongoutils::str::stream()
2944                               << "Do not know how to upgrade auth schema from version "
2945                               << authzVersion);
2946     }
2947 }
2948 
2949 /**
2950  * Performs up to maxSteps steps in the process of upgrading the stored authorization data
2951  * to the newest schema.  Behaves as if by repeatedly calling upgradeSchemaStep up to
2952  * maxSteps times until either it completes the upgrade or returns a non-OK status.
2953  *
2954  * Invalidates the user cache before the first step and after each attempted step.
2955  *
2956  * Returns Status::OK() to indicate that the upgrade process has completed successfully.
2957  * Returns ErrorCodes::OperationIncomplete to indicate that progress was made, but that more
2958  * steps must be taken to complete the process.  Other returns indicate a failure to make
2959  * progress performing the upgrade, and the specific code and message in the returned status
2960  * may provide additional information.
2961  */
upgradeAuthSchema(OperationContext * opCtx,AuthorizationManager * authzManager,int maxSteps)2962 Status upgradeAuthSchema(OperationContext* opCtx,
2963                          AuthorizationManager* authzManager,
2964                          int maxSteps) {
2965     if (maxSteps < 1) {
2966         return Status(ErrorCodes::BadValue,
2967                       "Minimum value for maxSteps parameter to upgradeAuthSchema is 1");
2968     }
2969     authzManager->invalidateUserCache();
2970     for (int i = 0; i < maxSteps; ++i) {
2971         bool isDone;
2972         Status status = upgradeAuthSchemaStep(opCtx, authzManager, &isDone);
2973         authzManager->invalidateUserCache();
2974         if (!status.isOK() || isDone) {
2975             return status;
2976         }
2977     }
2978     return Status(ErrorCodes::OperationIncomplete,
2979                   mongoutils::str::stream() << "Auth schema upgrade incomplete after " << maxSteps
2980                                             << " successful steps.");
2981 }
2982 
2983 class CmdAuthSchemaUpgrade : public BasicCommand {
2984 public:
CmdAuthSchemaUpgrade()2985     CmdAuthSchemaUpgrade() : BasicCommand("authSchemaUpgrade") {}
2986 
slaveOk() const2987     virtual bool slaveOk() const {
2988         return false;
2989     }
2990 
adminOnly() const2991     virtual bool adminOnly() const {
2992         return true;
2993     }
2994 
supportsWriteConcern(const BSONObj & cmd) const2995     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
2996         return true;
2997     }
2998 
help(stringstream & ss) const2999     virtual void help(stringstream& ss) const {
3000         ss << "Upgrades the auth data storage schema";
3001     }
3002 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)3003     virtual Status checkAuthForCommand(Client* client,
3004                                        const std::string& dbname,
3005                                        const BSONObj& cmdObj) {
3006         return auth::checkAuthForAuthSchemaUpgradeCommand(client);
3007     }
3008 
run(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,BSONObjBuilder & result)3009     virtual bool run(OperationContext* opCtx,
3010                      const string& dbname,
3011                      const BSONObj& cmdObj,
3012                      BSONObjBuilder& result) {
3013         auth::AuthSchemaUpgradeArgs parsedArgs;
3014         Status status = auth::parseAuthSchemaUpgradeCommand(cmdObj, dbname, &parsedArgs);
3015         if (!status.isOK()) {
3016             return appendCommandStatus(result, status);
3017         }
3018 
3019         ServiceContext* serviceContext = opCtx->getClient()->getServiceContext();
3020         AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
3021 
3022         stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
3023 
3024         status = upgradeAuthSchema(opCtx, authzManager, parsedArgs.maxSteps);
3025         if (status.isOK())
3026             result.append("done", true);
3027         return appendCommandStatus(result, status);
3028     }
3029 
3030 } cmdAuthSchemaUpgrade;
3031 }  // namespace mongo
3032