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/db/auth/authz_manager_external_state_local.h"
34 
35 #include "mongo/base/status.h"
36 #include "mongo/bson/mutable/algorithm.h"
37 #include "mongo/bson/mutable/document.h"
38 #include "mongo/bson/mutable/element.h"
39 #include "mongo/bson/util/bson_extract.h"
40 #include "mongo/db/auth/authorization_manager.h"
41 #include "mongo/db/auth/privilege_parser.h"
42 #include "mongo/db/auth/user_document_parser.h"
43 #include "mongo/db/operation_context.h"
44 #include "mongo/db/server_options.h"
45 #include "mongo/util/log.h"
46 #include "mongo/util/mongoutils/str.h"
47 
48 namespace mongo {
49 
50 using std::vector;
51 
initialize(OperationContext * opCtx)52 Status AuthzManagerExternalStateLocal::initialize(OperationContext* opCtx) {
53     Status status = _initializeRoleGraph(opCtx);
54     if (!status.isOK()) {
55         if (status == ErrorCodes::GraphContainsCycle) {
56             error() << "Cycle detected in admin.system.roles; role inheritance disabled. "
57                        "Remove the listed cycle and any others to re-enable role inheritance. "
58                     << redact(status);
59         } else {
60             error() << "Could not generate role graph from admin.system.roles; "
61                        "only system roles available: "
62                     << redact(status);
63         }
64     }
65 
66     return Status::OK();
67 }
68 
getStoredAuthorizationVersion(OperationContext * opCtx,int * outVersion)69 Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(OperationContext* opCtx,
70                                                                      int* outVersion) {
71     BSONObj versionDoc;
72     Status status = findOne(opCtx,
73                             AuthorizationManager::versionCollectionNamespace,
74                             AuthorizationManager::versionDocumentQuery,
75                             &versionDoc);
76     if (status.isOK()) {
77         BSONElement versionElement = versionDoc[AuthorizationManager::schemaVersionFieldName];
78         if (versionElement.isNumber()) {
79             *outVersion = versionElement.numberInt();
80             return Status::OK();
81         } else if (versionElement.eoo()) {
82             return Status(ErrorCodes::NoSuchKey,
83                           mongoutils::str::stream() << "No "
84                                                     << AuthorizationManager::schemaVersionFieldName
85                                                     << " field in version document.");
86         } else {
87             return Status(ErrorCodes::TypeMismatch,
88                           mongoutils::str::stream()
89                               << "Could not determine schema version of authorization data.  "
90                                  "Bad (non-numeric) type "
91                               << typeName(versionElement.type())
92                               << " ("
93                               << versionElement.type()
94                               << ") for "
95                               << AuthorizationManager::schemaVersionFieldName
96                               << " field in version document");
97         }
98     } else if (status == ErrorCodes::NoMatchingDocument) {
99         *outVersion = AuthorizationManager::schemaVersion28SCRAM;
100         return Status::OK();
101     } else {
102         return status;
103     }
104 }
105 
106 namespace {
addRoleNameToObjectElement(mutablebson::Element object,const RoleName & role)107 void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) {
108     fassert(17153, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, role.getRole()));
109     fassert(17154, object.appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, role.getDB()));
110 }
111 
addRoleNameObjectsToArrayElement(mutablebson::Element array,RoleNameIterator roles)112 void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) {
113     for (; roles.more(); roles.next()) {
114         mutablebson::Element roleElement = array.getDocument().makeElementObject("");
115         addRoleNameToObjectElement(roleElement, roles.get());
116         fassert(17155, array.pushBack(roleElement));
117     }
118 }
119 
addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement,mutablebson::Element warningsElement,const PrivilegeVector & privileges)120 void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement,
121                                                  mutablebson::Element warningsElement,
122                                                  const PrivilegeVector& privileges) {
123     std::string errmsg;
124     for (size_t i = 0; i < privileges.size(); ++i) {
125         ParsedPrivilege pp;
126         if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) {
127             fassert(17156, privilegesElement.appendObject("", pp.toBSON()));
128         } else {
129             fassert(17157,
130                     warningsElement.appendString(
131                         "",
132                         std::string(mongoutils::str::stream()
133                                     << "Skipped privileges on resource "
134                                     << privileges[i].getResourcePattern().toString()
135                                     << ". Reason: "
136                                     << errmsg)));
137         }
138     }
139 }
140 
addAuthenticationRestrictionObjectsToArrayElement(mutablebson::Element restrictionsElement,const std::vector<SharedRestrictionDocument> & restrictions)141 void addAuthenticationRestrictionObjectsToArrayElement(
142     mutablebson::Element restrictionsElement,
143     const std::vector<SharedRestrictionDocument>& restrictions) {
144     for (const auto& r : restrictions) {
145         fassert(40560, restrictionsElement.appendArray("", r->toBSON()));
146     }
147 }
148 }  // namespace
149 
hasAnyPrivilegeDocuments(OperationContext * opCtx)150 bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext* opCtx) {
151     BSONObj userBSONObj;
152     Status statusFindUsers =
153         findOne(opCtx, AuthorizationManager::usersCollectionNamespace, BSONObj(), &userBSONObj);
154 
155     // If we were unable to complete the query,
156     // it's best to assume that there _are_ privilege documents.
157     if (statusFindUsers != ErrorCodes::NoMatchingDocument) {
158         return true;
159     }
160     Status statusFindRoles =
161         findOne(opCtx, AuthorizationManager::rolesCollectionNamespace, BSONObj(), &userBSONObj);
162     return statusFindRoles != ErrorCodes::NoMatchingDocument;
163 }
164 
getUserDescription(OperationContext * opCtx,const UserName & userName,BSONObj * result)165 Status AuthzManagerExternalStateLocal::getUserDescription(OperationContext* opCtx,
166                                                           const UserName& userName,
167                                                           BSONObj* result) {
168     Status status = Status::OK();
169 
170     if (!shouldUseRolesFromConnection(opCtx, userName)) {
171         status = _getUserDocument(opCtx, userName, result);
172         if (!status.isOK())
173             return status;
174     } else {
175         // We are able to artifically construct the external user from the request
176         BSONArrayBuilder userRoles;
177         auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
178         for (const RoleName& role : sslPeerInfo.roles) {
179             userRoles << BSON("role" << role.getRole() << "db" << role.getDB());
180         }
181         *result = BSON("_id" << userName.getUser() << "user" << userName.getUser() << "db"
182                              << userName.getDB()
183                              << "credentials"
184                              << BSON("external" << true)
185                              << "roles"
186                              << userRoles.arr());
187     }
188 
189     BSONElement directRolesElement;
190     status = bsonExtractTypedField(*result, "roles", Array, &directRolesElement);
191     if (!status.isOK())
192         return status;
193     std::vector<RoleName> directRoles;
194     status =
195         V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), &directRoles);
196     if (!status.isOK())
197         return status;
198 
199     mutablebson::Document resultDoc(*result, mutablebson::Document::kInPlaceDisabled);
200     resolveUserRoles(&resultDoc, directRoles);
201     *result = resultDoc.getObject();
202 
203     return Status::OK();
204 }
205 
resolveUserRoles(mutablebson::Document * userDoc,const std::vector<RoleName> & directRoles)206 void AuthzManagerExternalStateLocal::resolveUserRoles(mutablebson::Document* userDoc,
207                                                       const std::vector<RoleName>& directRoles) {
208     unordered_set<RoleName> indirectRoles;
209     PrivilegeVector allPrivileges;
210     std::vector<SharedRestrictionDocument> allAuthenticationRestrictions;
211     bool isRoleGraphConsistent = false;
212 
213     {
214         stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex);
215         isRoleGraphConsistent = _roleGraphState == roleGraphStateConsistent;
216         for (const auto& role : directRoles) {
217             indirectRoles.insert(role);
218             if (isRoleGraphConsistent) {
219                 for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates(role);
220                      subordinates.more();
221                      subordinates.next()) {
222                     indirectRoles.insert(subordinates.get());
223                 }
224             }
225 
226             const auto& currentPrivileges = isRoleGraphConsistent
227                 ? _roleGraph.getAllPrivileges(role)
228                 : _roleGraph.getDirectPrivileges(role);
229             for (const auto& priv : currentPrivileges) {
230                 Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, priv);
231             }
232 
233             if (isRoleGraphConsistent) {
234                 const auto& currentAuthenticationRestrictions =
235                     _roleGraph.getAllAuthenticationRestrictions(role);
236                 allAuthenticationRestrictions.insert(allAuthenticationRestrictions.end(),
237                                                      currentAuthenticationRestrictions.begin(),
238                                                      currentAuthenticationRestrictions.end());
239             } else {
240                 const auto& dar = _roleGraph.getDirectAuthenticationRestrictions(role);
241                 if (dar.get()) {
242                     allAuthenticationRestrictions.push_back(dar);
243                 }
244             }
245         }
246     }
247 
248     auto warningsElement = userDoc->makeElementArray("warnings");
249 
250     auto inheritedRolesElement = userDoc->makeElementArray("inheritedRoles");
251     fassert(17159, userDoc->root().pushBack(inheritedRolesElement));
252     addRoleNameObjectsToArrayElement(inheritedRolesElement,
253                                      makeRoleNameIteratorForContainer(indirectRoles));
254 
255     auto privilegesElement = userDoc->makeElementArray("inheritedPrivileges");
256     fassert(17158, userDoc->root().pushBack(privilegesElement));
257     addPrivilegeObjectsOrWarningsToArrayElement(privilegesElement, warningsElement, allPrivileges);
258 
259     auto inheritedAuthenticationRestrictionsElement =
260         userDoc->makeElementArray("inheritedAuthenticationRestrictions");
261     fassert(40558, userDoc->root().pushBack(inheritedAuthenticationRestrictionsElement));
262     addAuthenticationRestrictionObjectsToArrayElement(inheritedAuthenticationRestrictionsElement,
263                                                       allAuthenticationRestrictions);
264 
265     if (!mutablebson::findFirstChildNamed(userDoc->root(), "authenticationRestrictions").ok()) {
266         auto authenticationRestrictionsElement =
267             userDoc->makeElementArray("authenticationRestrictions");
268         fassert(40572, userDoc->root().pushBack(authenticationRestrictionsElement));
269     }
270 
271     if (!isRoleGraphConsistent) {
272         fassert(17160,
273                 warningsElement.appendString(
274                     "", "Role graph inconsistent, only direct privileges available."));
275     }
276 
277     if (warningsElement.hasChildren()) {
278         fassert(17161, userDoc->root().pushBack(warningsElement));
279     }
280 }
281 
_getUserDocument(OperationContext * opCtx,const UserName & userName,BSONObj * userDoc)282 Status AuthzManagerExternalStateLocal::_getUserDocument(OperationContext* opCtx,
283                                                         const UserName& userName,
284                                                         BSONObj* userDoc) {
285     Status status = findOne(opCtx,
286                             AuthorizationManager::usersCollectionNamespace,
287                             BSON(AuthorizationManager::USER_NAME_FIELD_NAME
288                                  << userName.getUser()
289                                  << AuthorizationManager::USER_DB_FIELD_NAME
290                                  << userName.getDB()),
291                             userDoc);
292 
293     if (status == ErrorCodes::NoMatchingDocument) {
294         status =
295             Status(ErrorCodes::UserNotFound,
296                    mongoutils::str::stream() << "Could not find user " << userName.getFullName());
297     }
298     return status;
299 }
300 
getRoleDescription(OperationContext * opCtx,const RoleName & roleName,PrivilegeFormat showPrivileges,AuthenticationRestrictionsFormat showRestrictions,BSONObj * result)301 Status AuthzManagerExternalStateLocal::getRoleDescription(
302     OperationContext* opCtx,
303     const RoleName& roleName,
304     PrivilegeFormat showPrivileges,
305     AuthenticationRestrictionsFormat showRestrictions,
306     BSONObj* result) {
307     if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) {
308         mutablebson::Document resultDoc;
309         mutablebson::Element rolesElement = resultDoc.makeElementArray("roles");
310         fassert(40273, resultDoc.root().pushBack(rolesElement));
311         addRoleNameObjectsToArrayElement(
312             rolesElement, makeRoleNameIteratorForContainer(std::vector<RoleName>{roleName}));
313         resolveUserRoles(&resultDoc, {roleName});
314         *result = resultDoc.getObject();
315         return Status::OK();
316     }
317     stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex);
318     return _getRoleDescription_inlock(roleName, showPrivileges, showRestrictions, result);
319 }
320 
getRolesDescription(OperationContext * opCtx,const std::vector<RoleName> & roles,PrivilegeFormat showPrivileges,AuthenticationRestrictionsFormat showRestrictions,BSONObj * result)321 Status AuthzManagerExternalStateLocal::getRolesDescription(
322     OperationContext* opCtx,
323     const std::vector<RoleName>& roles,
324     PrivilegeFormat showPrivileges,
325     AuthenticationRestrictionsFormat showRestrictions,
326     BSONObj* result) {
327     if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) {
328         mutablebson::Document resultDoc;
329         mutablebson::Element rolesElement = resultDoc.makeElementArray("roles");
330         fassert(40274, resultDoc.root().pushBack(rolesElement));
331         addRoleNameObjectsToArrayElement(rolesElement, makeRoleNameIteratorForContainer(roles));
332         resolveUserRoles(&resultDoc, roles);
333         *result = resultDoc.getObject();
334         return Status::OK();
335     }
336 
337     stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex);
338     BSONArrayBuilder resultBuilder;
339     for (const RoleName& role : roles) {
340         BSONObj roleDoc;
341         Status status =
342             _getRoleDescription_inlock(role, showPrivileges, showRestrictions, &roleDoc);
343         if (!status.isOK()) {
344             if (status.code() == ErrorCodes::RoleNotFound) {
345                 continue;
346             }
347             return status;
348         }
349         resultBuilder << roleDoc;
350     }
351     *result = resultBuilder.arr();
352     return Status::OK();
353 }
354 
_getRoleDescription_inlock(const RoleName & roleName,PrivilegeFormat showPrivileges,AuthenticationRestrictionsFormat showRestrictions,BSONObj * result)355 Status AuthzManagerExternalStateLocal::_getRoleDescription_inlock(
356     const RoleName& roleName,
357     PrivilegeFormat showPrivileges,
358     AuthenticationRestrictionsFormat showRestrictions,
359     BSONObj* result) {
360     if (!_roleGraph.roleExists(roleName))
361         return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString());
362 
363     mutablebson::Document resultDoc;
364     fassert(17162,
365             resultDoc.root().appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME,
366                                           roleName.getRole()));
367     fassert(
368         17163,
369         resultDoc.root().appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB()));
370     fassert(17267, resultDoc.root().appendBool("isBuiltin", _roleGraph.isBuiltinRole(roleName)));
371 
372     auto warningsElement = resultDoc.makeElementArray("warnings");
373 
374     auto rolesElement = resultDoc.makeElementArray("roles");
375     fassert(17164, resultDoc.root().pushBack(rolesElement));
376     addRoleNameObjectsToArrayElement(rolesElement, _roleGraph.getDirectSubordinates(roleName));
377 
378     auto inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles");
379     fassert(17165, resultDoc.root().pushBack(inheritedRolesElement));
380 
381     auto privilegesElement = resultDoc.makeElementArray("privileges");
382     if (showPrivileges == PrivilegeFormat::kShowSeparate) {
383         fassert(17166, resultDoc.root().pushBack(privilegesElement));
384     }
385 
386     if (showRestrictions == AuthenticationRestrictionsFormat::kShow) {
387         auto authenticationRestrictionsElement =
388             resultDoc.makeElementArray("authenticationRestrictions");
389         fassert(40559, resultDoc.root().pushBack(authenticationRestrictionsElement));
390 
391         const auto& restrictions = _roleGraph.getDirectAuthenticationRestrictions(roleName);
392         if (restrictions.get()) {
393             fassert(40561,
394                     authenticationRestrictionsElement.appendArray("", restrictions->toBSON()));
395         }
396     }
397 
398     if (_roleGraphState == roleGraphStateConsistent) {
399         addRoleNameObjectsToArrayElement(inheritedRolesElement,
400                                          _roleGraph.getIndirectSubordinates(roleName));
401 
402         if (showPrivileges == PrivilegeFormat::kShowSeparate) {
403             auto inheritedPrivilegesElement = resultDoc.makeElementArray("inheritedPrivileges");
404             addPrivilegeObjectsOrWarningsToArrayElement(
405                 privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName));
406 
407             addPrivilegeObjectsOrWarningsToArrayElement(
408                 inheritedPrivilegesElement, warningsElement, _roleGraph.getAllPrivileges(roleName));
409 
410             fassert(17323, resultDoc.root().pushBack(inheritedPrivilegesElement));
411         }
412 
413         if (showRestrictions == AuthenticationRestrictionsFormat::kShow) {
414             auto inheritedAuthenticationRestrictionsElement =
415                 resultDoc.makeElementArray("inheritedAuthenticationRestrictions");
416             fassert(40563, resultDoc.root().pushBack(inheritedAuthenticationRestrictionsElement));
417 
418             for (const auto& restrictions : _roleGraph.getAllAuthenticationRestrictions(roleName)) {
419                 fassert(40562,
420                         inheritedAuthenticationRestrictionsElement.appendArray(
421                             "", restrictions->toBSON()));
422             }
423         }
424     } else if (showPrivileges == PrivilegeFormat::kShowSeparate) {
425         addPrivilegeObjectsOrWarningsToArrayElement(
426             privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName));
427         fassert(40557,
428                 warningsElement.appendString("",
429                                              "Role graph state inconsistent; only direct "
430                                              "privileges and restrictions available."));
431     }
432 
433     if (warningsElement.hasChildren()) {
434         fassert(17167, resultDoc.root().pushBack(warningsElement));
435     }
436     *result = resultDoc.getObject();
437     return Status::OK();
438 }
439 
getRoleDescriptionsForDB(OperationContext * opCtx,const std::string & dbname,PrivilegeFormat showPrivileges,AuthenticationRestrictionsFormat showRestrictions,bool showBuiltinRoles,vector<BSONObj> * result)440 Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(
441     OperationContext* opCtx,
442     const std::string& dbname,
443     PrivilegeFormat showPrivileges,
444     AuthenticationRestrictionsFormat showRestrictions,
445     bool showBuiltinRoles,
446     vector<BSONObj>* result) {
447     if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) {
448         return Status(ErrorCodes::IllegalOperation,
449                       "Cannot get user fragment for all roles in a database");
450     }
451 
452     stdx::lock_guard<stdx::mutex> lk(_roleGraphMutex);
453     for (RoleNameIterator it = _roleGraph.getRolesForDatabase(dbname); it.more(); it.next()) {
454         if (!showBuiltinRoles && _roleGraph.isBuiltinRole(it.get())) {
455             continue;
456         }
457         BSONObj roleDoc;
458         Status status =
459             _getRoleDescription_inlock(it.get(), showPrivileges, showRestrictions, &roleDoc);
460         if (!status.isOK()) {
461             return status;
462         }
463         result->push_back(roleDoc);
464     }
465     return Status::OK();
466 }
467 
468 namespace {
469 
470 /**
471  * Adds the role described in "doc" to "roleGraph".  If the role cannot be added, due to
472  * some error in "doc", logs a warning.
473  */
addRoleFromDocumentOrWarn(RoleGraph * roleGraph,const BSONObj & doc)474 void addRoleFromDocumentOrWarn(RoleGraph* roleGraph, const BSONObj& doc) {
475     Status status = roleGraph->addRoleFromDocument(doc);
476     if (!status.isOK()) {
477         warning() << "Skipping invalid admin.system.roles document while calculating privileges"
478                      " for user-defined roles:  "
479                   << redact(status) << "; document " << redact(doc);
480     }
481 }
482 
483 
484 }  // namespace
485 
_initializeRoleGraph(OperationContext * opCtx)486 Status AuthzManagerExternalStateLocal::_initializeRoleGraph(OperationContext* opCtx) {
487     stdx::lock_guard<stdx::mutex> lkInitialzeRoleGraph(_roleGraphMutex);
488 
489     _roleGraphState = roleGraphStateInitial;
490     _roleGraph = RoleGraph();
491 
492     RoleGraph newRoleGraph;
493     Status status =
494         query(opCtx,
495               AuthorizationManager::rolesCollectionNamespace,
496               BSONObj(),
497               BSONObj(),
498               stdx::bind(addRoleFromDocumentOrWarn, &newRoleGraph, stdx::placeholders::_1));
499     if (!status.isOK())
500         return status;
501 
502     status = newRoleGraph.recomputePrivilegeData();
503 
504     RoleGraphState newState;
505     if (status == ErrorCodes::GraphContainsCycle) {
506         error() << "Inconsistent role graph during authorization manager initialization.  Only "
507                    "direct privileges available. "
508                 << redact(status);
509         newState = roleGraphStateHasCycle;
510         status = Status::OK();
511     } else if (status.isOK()) {
512         newState = roleGraphStateConsistent;
513     } else {
514         newState = roleGraphStateInitial;
515     }
516 
517     if (status.isOK()) {
518         _roleGraph = std::move(newRoleGraph);
519         _roleGraphState = std::move(newState);
520     }
521     return status;
522 }
523 
524 class AuthzManagerExternalStateLocal::AuthzManagerLogOpHandler : public RecoveryUnit::Change {
525 public:
526     // None of the parameters below (except opCtx and externalState) need to live longer than the
527     // instantiations of this class
AuthzManagerLogOpHandler(OperationContext * opCtx,AuthzManagerExternalStateLocal * externalState,const char * op,const NamespaceString & nss,const BSONObj & o,const BSONObj * o2)528     AuthzManagerLogOpHandler(OperationContext* opCtx,
529                              AuthzManagerExternalStateLocal* externalState,
530                              const char* op,
531                              const NamespaceString& nss,
532                              const BSONObj& o,
533                              const BSONObj* o2)
534         : _opCtx(opCtx),
535           _externalState(externalState),
536           _op(op),
537           _nss(nss),
538           _o(o.getOwned()),
539 
540           _isO2Set(o2 ? true : false),
541           _o2(_isO2Set ? o2->getOwned() : BSONObj()) {}
542 
commit()543     virtual void commit() {
544         stdx::lock_guard<stdx::mutex> lk(_externalState->_roleGraphMutex);
545         Status status = _externalState->_roleGraph.handleLogOp(
546             _opCtx, _op.c_str(), _nss, _o, _isO2Set ? &_o2 : NULL);
547 
548         if (status == ErrorCodes::OplogOperationUnsupported) {
549             _externalState->_roleGraph = RoleGraph();
550             _externalState->_roleGraphState = _externalState->roleGraphStateInitial;
551             BSONObjBuilder oplogEntryBuilder;
552             oplogEntryBuilder << "op" << _op << "ns" << _nss.ns() << "o" << _o;
553             if (_isO2Set)
554                 oplogEntryBuilder << "o2" << _o2;
555             error() << "Unsupported modification to roles collection in oplog; "
556                        "restart this process to reenable user-defined roles; "
557                     << redact(status) << "; Oplog entry: " << redact(oplogEntryBuilder.done());
558         } else if (!status.isOK()) {
559             warning() << "Skipping bad update to roles collection in oplog. " << redact(status)
560                       << " Oplog entry: " << redact(_op);
561         }
562         status = _externalState->_roleGraph.recomputePrivilegeData();
563         if (status == ErrorCodes::GraphContainsCycle) {
564             _externalState->_roleGraphState = _externalState->roleGraphStateHasCycle;
565             error() << "Inconsistent role graph during authorization manager initialization.  "
566                        "Only direct privileges available. "
567                     << redact(status) << " after applying oplog entry " << redact(_op);
568         } else {
569             fassert(17183, status);
570             _externalState->_roleGraphState = _externalState->roleGraphStateConsistent;
571         }
572     }
573 
rollback()574     virtual void rollback() {}
575 
576 private:
577     OperationContext* _opCtx;
578     AuthzManagerExternalStateLocal* _externalState;
579     const std::string _op;
580     const NamespaceString _nss;
581     const BSONObj _o;
582 
583     const bool _isO2Set;
584     const BSONObj _o2;
585 };
586 
logOp(OperationContext * opCtx,const char * op,const NamespaceString & nss,const BSONObj & o,const BSONObj * o2)587 void AuthzManagerExternalStateLocal::logOp(OperationContext* opCtx,
588                                            const char* op,
589                                            const NamespaceString& nss,
590                                            const BSONObj& o,
591                                            const BSONObj* o2) {
592     if (nss == AuthorizationManager::rolesCollectionNamespace ||
593         nss == AuthorizationManager::adminCommandNamespace) {
594         opCtx->recoveryUnit()->registerChange(
595             new AuthzManagerLogOpHandler(opCtx, this, op, nss, o, o2));
596     }
597 }
598 
599 }  // namespace mongo
600