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