/** * Copyright (C) 2012 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl #include "mongo/platform/basic.h" #include "mongo/db/auth/authorization_session.h" #include #include #include "mongo/base/status.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/restriction_environment.h" #include "mongo/db/auth/security_key.h" #include "mongo/db/auth/user_management_commands_parser.h" #include "mongo/db/bson/dotted_path_support.h" #include "mongo/db/catalog/document_validation.h" #include "mongo/db/client.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/pipeline/aggregation_request.h" #include "mongo/db/pipeline/lite_parsed_pipeline.h" #include "mongo/util/assert_util.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" namespace mongo { namespace dps = ::mongo::dotted_path_support; using std::vector; namespace { const std::string ADMIN_DBNAME = "admin"; // Checks if this connection has the privileges necessary to create or modify the view 'viewNs' // to be a view on 'viewOnNs' with pipeline 'viewPipeline'. Call this function after verifying // that the user has the 'createCollection' or 'collMod' action, respectively. Status checkAuthForCreateOrModifyView(AuthorizationSession* authzSession, const NamespaceString& viewNs, const NamespaceString& viewOnNs, const BSONArray& viewPipeline, bool isMongos) { // It's safe to allow a user to create or modify a view if they can't read it anyway. if (!authzSession->isAuthorizedForActionsOnNamespace(viewNs, ActionType::find)) { return Status::OK(); } // This check performs some validation but it is not exhaustive and may allow for an invalid // pipeline specification. In this case the authorization check will succeed but the pipeline // will fail to parse later in Command::run(). return authzSession->checkAuthForAggregate( viewOnNs, BSON("aggregate" << viewOnNs.coll() << "pipeline" << viewPipeline << "cursor" << BSONObj()), isMongos); } /** Deleter for User*. * Will release a User* back to its owning AuthorizationManager on destruction. * If a borrowing UserSet and the iterator it uses to store the User* is provided, this * deleter will release the User* from the set if the iterator still points to the deleting User*. */ class UserReleaser { public: explicit UserReleaser(AuthorizationManager* owner) : _owner(owner), _borrower(nullptr) {} UserReleaser(AuthorizationManager* owner, UserSet* borrower, UserSet::iterator borrowerIt) : _owner(owner), _borrower(borrower), _it(borrowerIt) {} void operator()(User* user) { // Remove the user from the borrower if it hasn't already been swapped out. if (_borrower && *_it == user) { fassert(40546, _borrower->removeAt(_it) == user); } _owner->releaseUser(user); } protected: AuthorizationManager* _owner; UserSet* _borrower; UserSet::iterator _it; }; /** Holder for User*s. If this Holder falls out of scope while holding a User*, it will release * the User* from its AuthorizationManager, and extract it from a UserSet if the set still contains * it. Use this object to guard User*s which will need to be destroyed in the event of an exception. */ using UserHolder = std::unique_ptr; } // namespace AuthorizationSession::AuthorizationSession(std::unique_ptr externalState) : _externalState(std::move(externalState)), _impersonationFlag(false) {} AuthorizationSession::~AuthorizationSession() { for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); ++it) { getAuthorizationManager().releaseUser(*it); } } AuthorizationManager& AuthorizationSession::getAuthorizationManager() { return _externalState->getAuthorizationManager(); } void AuthorizationSession::startRequest(OperationContext* opCtx) { _externalState->startRequest(opCtx); _refreshUserInfoAsNeeded(opCtx); } Status AuthorizationSession::addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) { User* user; AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); Status status = authzManager->acquireUserForInitialAuth(opCtx, userName, &user); if (!status.isOK()) { return status; } UserHolder userHolder(user, UserReleaser(authzManager)); const auto& restrictionSet = userHolder->getRestrictions(); if (opCtx->getClient() == nullptr) { return Status(ErrorCodes::AuthenticationFailed, "Unable to evaluate restrictions, OperationContext has no Client"); } Status restrictionStatus = restrictionSet.validate(RestrictionEnvironment::get(*opCtx->getClient())); if (!restrictionStatus.isOK()) { log() << "Failed to acquire user '" << userName << "' because of unmet authentication restrictions: " << restrictionStatus.reason(); return AuthorizationManager::authenticationFailedStatus; } // Calling add() on the UserSet may return a user that was replaced because it was from the // same database. userHolder.reset(_authenticatedUsers.add(userHolder.release())); // If there are any users and roles in the impersonation data, clear it out. clearImpersonatedUserData(); _buildAuthenticatedRolesVector(); return Status::OK(); } User* AuthorizationSession::lookupUser(const UserName& name) { return _authenticatedUsers.lookup(name); } User* AuthorizationSession::getSingleUser() { UserName userName; auto userNameItr = getAuthenticatedUserNames(); if (userNameItr.more()) { userName = userNameItr.next(); if (userNameItr.more()) { uasserted(ErrorCodes::Unauthorized, "there are no users authenticated"); } } else { uasserted(ErrorCodes::Unauthorized, "too many users are authenticated"); } return lookupUser(userName); } void AuthorizationSession::logoutDatabase(const std::string& dbname) { User* removedUser = _authenticatedUsers.removeByDBName(dbname); if (removedUser) { getAuthorizationManager().releaseUser(removedUser); } clearImpersonatedUserData(); _buildAuthenticatedRolesVector(); } UserNameIterator AuthorizationSession::getAuthenticatedUserNames() { return _authenticatedUsers.getNames(); } RoleNameIterator AuthorizationSession::getAuthenticatedRoleNames() { return makeRoleNameIterator(_authenticatedRoleNames.begin(), _authenticatedRoleNames.end()); } std::string AuthorizationSession::getAuthenticatedUserNamesToken() { std::string ret; for (UserNameIterator nameIter = getAuthenticatedUserNames(); nameIter.more(); nameIter.next()) { ret += '\0'; // Using a NUL byte which isn't valid in usernames to separate them. ret += nameIter->getFullName(); } return ret; } void AuthorizationSession::grantInternalAuthorization() { _authenticatedUsers.add(internalSecurity.user); _buildAuthenticatedRolesVector(); } PrivilegeVector AuthorizationSession::getDefaultPrivileges() { PrivilegeVector defaultPrivileges; // If localhost exception is active (and no users exist), // return a vector of the minimum privileges required to bootstrap // a system and add the first user. if (_externalState->shouldAllowLocalhost()) { ResourcePattern adminDBResource = ResourcePattern::forDatabaseName(ADMIN_DBNAME); ActionSet setupAdminUserActionSet; setupAdminUserActionSet.addAction(ActionType::createUser); setupAdminUserActionSet.addAction(ActionType::grantRole); Privilege setupAdminUserPrivilege = Privilege(adminDBResource, setupAdminUserActionSet); ResourcePattern externalDBResource = ResourcePattern::forDatabaseName("$external"); Privilege setupExternalUserPrivilege = Privilege(externalDBResource, ActionType::createUser); ActionSet setupServerConfigActionSet; // If this server is an arbiter, add specific privileges meant to circumvent // the behavior of an arbiter in an authenticated replset. See SERVER-5479. if (_externalState->serverIsArbiter()) { setupServerConfigActionSet.addAction(ActionType::getCmdLineOpts); setupServerConfigActionSet.addAction(ActionType::getParameter); setupServerConfigActionSet.addAction(ActionType::serverStatus); setupServerConfigActionSet.addAction(ActionType::shutdown); } setupServerConfigActionSet.addAction(ActionType::addShard); setupServerConfigActionSet.addAction(ActionType::replSetConfigure); setupServerConfigActionSet.addAction(ActionType::replSetGetStatus); Privilege setupServerConfigPrivilege = Privilege(ResourcePattern::forClusterResource(), setupServerConfigActionSet); Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupAdminUserPrivilege); Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupExternalUserPrivilege); Privilege::addPrivilegeToPrivilegeVector(&defaultPrivileges, setupServerConfigPrivilege); return defaultPrivileges; } return defaultPrivileges; } Status AuthorizationSession::checkAuthForAggregate(const NamespaceString& nss, const BSONObj& cmdObj, bool isMongos) { if (!nss.isValid()) { return Status(ErrorCodes::InvalidNamespace, mongoutils::str::stream() << "Invalid input namespace, " << nss.ns()); } // If this connection does not need to be authenticated (for instance, if auth is disabled), // return Status::OK() immediately. if (_externalState->shouldIgnoreAuthChecks()) { return Status::OK(); } // We require at least one authenticated user when running aggregate with auth enabled. if (!getAuthenticatedUserNames().more()) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } auto statusWithAggRequest = AggregationRequest::parseFromBSON(nss, cmdObj); if (!statusWithAggRequest.isOK()) { return statusWithAggRequest.getStatus(); } AggregationRequest aggRequest = std::move(statusWithAggRequest.getValue()); const auto& pipeline = aggRequest.getPipeline(); // If the aggregation pipeline is empty, confirm the user is authorized for find on 'nss'. if (pipeline.empty()) { if (!isAuthorizedForPrivilege( Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find))) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } return Status::OK(); } // Confirm the user is authorized for the pipeline's initial document source. We confirm a user // is authorized incrementally rather than once for the entire pipeline. This will prevent a // malicious user, who doesn't have access to the initial document source, from consuming the // resources needed to parse a potentially large pipeline. auto liteParsedFirstDocumentSource = LiteParsedDocumentSource::parse(aggRequest, pipeline[0]); if (!liteParsedFirstDocumentSource->isInitialSource() && !isAuthorizedForPrivilege( Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find))) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } // We have done the work to lite parse the first stage. Given that, we check required privileges // for it using 'liteParsedFirstDocumentSource' regardless of whether is an initial source or // not. if (!isAuthorizedForPrivileges(liteParsedFirstDocumentSource->requiredPrivileges(isMongos))) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } // Confirm privileges for the remainder of the pipepline. Start with the second stage as we have // already authorized the first. auto pipelineIter = pipeline.begin() + 1; for (; pipelineIter != pipeline.end(); ++pipelineIter) { auto liteParsedDocSource = LiteParsedDocumentSource::parse(aggRequest, *pipelineIter); if (!isAuthorizedForPrivileges(liteParsedDocSource->requiredPrivileges(isMongos))) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } } return Status::OK(); } Status AuthorizationSession::checkAuthForFind(const NamespaceString& ns, bool hasTerm) { if (MONGO_unlikely(ns.isCommand())) { return Status(ErrorCodes::InternalError, str::stream() << "Checking query auth on command namespace " << ns.ns()); } if (!isAuthorizedForActionsOnNamespace(ns, ActionType::find)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for query on " << ns.ns()); } // Only internal clients (such as other nodes in a replica set) are allowed to use // the 'term' field in a find operation. Use of this field could trigger changes // in the receiving server's replication state and should be protected. if (hasTerm && !isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::internal)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for query with term on " << ns.ns()); } return Status::OK(); } Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, long long cursorID, bool hasTerm) { // "ns" can be in one of several formats: "listCollections" format, "listIndexes" format, // "collectionless aggregation" format, and normal format. if (ns.isListCollectionsCursorNS()) { // "ns" is of the form ".$cmd.listCollections". Check if we can perform the // listCollections action on the database resource for "". if (!isAuthorizedToListCollections(ns.db())) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for listCollections getMore on " << ns.ns()); } } else if (ns.isListIndexesCursorNS()) { // "ns" is of the form ".$cmd.listIndexes.". Check if we can perform the // listIndexes action on the "." namespace. NamespaceString targetNS = ns.getTargetNSForListIndexes(); if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::listIndexes)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for listIndexes getMore on " << ns.ns()); } } else if (ns.isCollectionlessAggregateNS()) { // "ns" is of the form ".$cmd.aggregate". We don't have access to the parameters of the // original command at this point, but since users can only getMore their own cursors we // verify that a user either is authenticated or does not need to be. if (!_externalState->shouldIgnoreAuthChecks() && !getAuthenticatedUserNames().more()) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for collectionless aggregate getMore on " << ns.db()); } } else { // "ns" is a regular namespace string. Check if we can perform the find action on it. if (!isAuthorizedForActionsOnNamespace(ns, ActionType::find)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for getMore on " << ns.ns()); } } // Only internal clients (such as other nodes in a replica set) are allowed to use // the 'term' field in a getMore operation. Use of this field could trigger changes // in the receiving server's replication state and should be protected. if (hasTerm && !isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::internal)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for getMore with term on " << ns.ns()); } return Status::OK(); } Status AuthorizationSession::checkAuthForInsert(OperationContext* opCtx, const NamespaceString& ns, const BSONObj& document) { if (ns.coll() == "system.indexes"_sd) { BSONElement nsElement = document["ns"]; if (nsElement.type() != String) { return Status(nsElement.type() == BSONType::EOO ? ErrorCodes::NoSuchKey : ErrorCodes::TypeMismatch, "Cannot authorize inserting into " "system.indexes documents without a string-typed \"ns\" field."); } NamespaceString indexNS(nsElement.valueStringData()); if (!isAuthorizedForActionsOnNamespace(indexNS, ActionType::createIndex)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to create index on " << indexNS.ns()); } } else { ActionSet required{ActionType::insert}; if (documentValidationDisabled(opCtx)) { required.addAction(ActionType::bypassDocumentValidation); } if (!isAuthorizedForActionsOnNamespace(ns, required)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for insert on " << ns.ns()); } } return Status::OK(); } Status AuthorizationSession::checkAuthForUpdate(OperationContext* opCtx, const NamespaceString& ns, const BSONObj& query, const BSONObj& update, bool upsert) { ActionSet required{ActionType::update}; StringData operationType = "update"_sd; if (upsert) { required.addAction(ActionType::insert); operationType = "upsert"_sd; } if (documentValidationDisabled(opCtx)) { required.addAction(ActionType::bypassDocumentValidation); } if (!isAuthorizedForActionsOnNamespace(ns, required)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for " << operationType << " on " << ns.ns()); } return Status::OK(); } Status AuthorizationSession::checkAuthForDelete(OperationContext* opCtx, const NamespaceString& ns, const BSONObj& query) { if (!isAuthorizedForActionsOnNamespace(ns, ActionType::remove)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to remove from " << ns.ns()); } return Status::OK(); } Status AuthorizationSession::checkAuthForKillCursors(const NamespaceString& ns, long long cursorID) { // See implementation comments in checkAuthForGetMore(). This method looks very similar. // SERVER-20364 Check for find or killCursor privileges until we have a way of associating // a cursor with an owner. if (ns.isListCollectionsCursorNS()) { if (!(isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()), ActionType::killCursors) || isAuthorizedToListCollections(ns.db()))) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to kill listCollections cursor on " << ns.ns()); } } else if (ns.isListIndexesCursorNS()) { NamespaceString targetNS = ns.getTargetNSForListIndexes(); if (!(isAuthorizedForActionsOnNamespace(targetNS, ActionType::killCursors) || isAuthorizedForActionsOnNamespace(targetNS, ActionType::listIndexes))) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to kill listIndexes cursor on " << ns.ns()); } } else { if (!(isAuthorizedForActionsOnNamespace(ns, ActionType::killCursors) || isAuthorizedForActionsOnNamespace(ns, ActionType::find))) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to kill cursor on " << ns.ns()); } } return Status::OK(); } Status AuthorizationSession::checkAuthForCreate(const NamespaceString& ns, const BSONObj& cmdObj, bool isMongos) { if (cmdObj["capped"].trueValue() && !isAuthorizedForActionsOnNamespace(ns, ActionType::convertToCapped)) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } const bool hasCreateCollectionAction = isAuthorizedForActionsOnNamespace(ns, ActionType::createCollection); // If attempting to create a view, check for additional required privileges. if (cmdObj["viewOn"]) { // You need the createCollection action on this namespace; the insert action is not // sufficient. if (!hasCreateCollectionAction) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } // Parse the viewOn namespace and the pipeline. If no pipeline was specified, use the empty // pipeline. NamespaceString viewOnNs(ns.db(), cmdObj["viewOn"].checkAndGetStringData()); auto pipeline = cmdObj.hasField("pipeline") ? BSONArray(cmdObj["pipeline"].Obj()) : BSONArray(); return checkAuthForCreateOrModifyView(this, ns, viewOnNs, pipeline, isMongos); } // To create a regular collection, ActionType::createCollection or ActionType::insert are // both acceptable. if (hasCreateCollectionAction || isAuthorizedForActionsOnNamespace(ns, ActionType::insert)) { return Status::OK(); } return Status(ErrorCodes::Unauthorized, "unauthorized"); } Status AuthorizationSession::checkAuthForCollMod(const NamespaceString& ns, const BSONObj& cmdObj, bool isMongos) { if (!isAuthorizedForActionsOnNamespace(ns, ActionType::collMod)) { return Status(ErrorCodes::Unauthorized, "unauthorized"); } // Check for additional required privileges if attempting to modify a view. When auth is // enabled, users must specify both "viewOn" and "pipeline" together. This prevents a user from // exposing more information in the original underlying namespace by only changing "pipeline", // or looking up more information via the original pipeline by only changing "viewOn". const bool hasViewOn = cmdObj.hasField("viewOn"); const bool hasPipeline = cmdObj.hasField("pipeline"); if (hasViewOn != hasPipeline) { return Status( ErrorCodes::InvalidOptions, "Must specify both 'viewOn' and 'pipeline' when modifying a view and auth is enabled"); } if (hasViewOn) { NamespaceString viewOnNs(ns.db(), cmdObj["viewOn"].checkAndGetStringData()); auto viewPipeline = BSONArray(cmdObj["pipeline"].Obj()); return checkAuthForCreateOrModifyView(this, ns, viewOnNs, viewPipeline, isMongos); } return Status::OK(); } Status AuthorizationSession::checkAuthorizedToGrantPrivilege(const Privilege& privilege) { const ResourcePattern& resource = privilege.getResourcePattern(); if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { if (!isAuthorizedForActionsOnResource( ResourcePattern::forDatabaseName(resource.databaseToMatch()), ActionType::grantRole)) { return Status(ErrorCodes::Unauthorized, str::stream() << "Not authorized to grant privileges on the " << resource.databaseToMatch() << "database"); } } else if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName("admin"), ActionType::grantRole)) { return Status(ErrorCodes::Unauthorized, "To grant privileges affecting multiple databases or the cluster," " must be authorized to grant roles from the admin database"); } return Status::OK(); } Status AuthorizationSession::checkAuthorizedToRevokePrivilege(const Privilege& privilege) { const ResourcePattern& resource = privilege.getResourcePattern(); if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { if (!isAuthorizedForActionsOnResource( ResourcePattern::forDatabaseName(resource.databaseToMatch()), ActionType::revokeRole)) { return Status(ErrorCodes::Unauthorized, str::stream() << "Not authorized to revoke privileges on the " << resource.databaseToMatch() << "database"); } } else if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName("admin"), ActionType::revokeRole)) { return Status(ErrorCodes::Unauthorized, "To revoke privileges affecting multiple databases or the cluster," " must be authorized to revoke roles from the admin database"); } return Status::OK(); } bool AuthorizationSession::isAuthorizedToCreateRole( const struct auth::CreateOrUpdateRoleArgs& args) { // A user is allowed to create a role under either of two conditions. // The user may create a role if the authorization system says they are allowed to. if (isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(args.roleName.getDB()), ActionType::createRole)) { return true; } // The user may create a role if the localhost exception is enabled, and they already own the // role. This implies they have obtained the role through an external authorization mechanism. if (_externalState->shouldAllowLocalhost()) { for (const User* const user : _authenticatedUsers) { if (user->hasRole(args.roleName)) { return true; } } log() << "Not authorized to create the first role in the system '" << args.roleName << "' using the localhost exception. The user needs to acquire the role through " "external authentication first."; } return false; } bool AuthorizationSession::isAuthorizedToGrantRole(const RoleName& role) { return isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(role.getDB()), ActionType::grantRole); } bool AuthorizationSession::isAuthorizedToRevokeRole(const RoleName& role) { return isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(role.getDB()), ActionType::revokeRole); } bool AuthorizationSession::isAuthorizedForPrivilege(const Privilege& privilege) { if (_externalState->shouldIgnoreAuthChecks()) return true; return _isAuthorizedForPrivilege(privilege); } bool AuthorizationSession::isAuthorizedForPrivileges(const vector& privileges) { if (_externalState->shouldIgnoreAuthChecks()) return true; for (size_t i = 0; i < privileges.size(); ++i) { if (!_isAuthorizedForPrivilege(privileges[i])) return false; } return true; } bool AuthorizationSession::isAuthorizedForActionsOnResource(const ResourcePattern& resource, ActionType action) { return isAuthorizedForPrivilege(Privilege(resource, action)); } bool AuthorizationSession::isAuthorizedForActionsOnResource(const ResourcePattern& resource, const ActionSet& actions) { return isAuthorizedForPrivilege(Privilege(resource, actions)); } bool AuthorizationSession::isAuthorizedForActionsOnNamespace(const NamespaceString& ns, ActionType action) { return isAuthorizedForPrivilege(Privilege(ResourcePattern::forExactNamespace(ns), action)); } bool AuthorizationSession::isAuthorizedForActionsOnNamespace(const NamespaceString& ns, const ActionSet& actions) { return isAuthorizedForPrivilege(Privilege(ResourcePattern::forExactNamespace(ns), actions)); } static const int resourceSearchListCapacity = 5; /** * Builds from "target" an exhaustive list of all ResourcePatterns that match "target". * * Stores the resulting list into resourceSearchList, and returns the length. * * The seach lists are as follows, depending on the type of "target": * * target is ResourcePattern::forAnyResource(): * searchList = { ResourcePattern::forAnyResource(), ResourcePattern::forAnyResource() } * target is the ResourcePattern::forClusterResource(): * searchList = { ResourcePattern::forAnyResource(), ResourcePattern::forClusterResource() } * target is a database, db: * searchList = { ResourcePattern::forAnyResource(), * ResourcePattern::forAnyNormalResource(), * db } * target is a non-system collection, db.coll: * searchList = { ResourcePattern::forAnyResource(), * ResourcePattern::forAnyNormalResource(), * db, * coll, * db.coll } * target is a system collection, db.system.coll: * searchList = { ResourcePattern::forAnyResource(), * system.coll, * db.system.coll } */ static int buildResourceSearchList(const ResourcePattern& target, ResourcePattern resourceSearchList[resourceSearchListCapacity]) { int size = 0; resourceSearchList[size++] = ResourcePattern::forAnyResource(); if (target.isExactNamespacePattern()) { if (!target.ns().isSystem()) { // Some databases should not be matchable with ResourcePattern::forAnyNormalResource. // 'local' and 'config' are used to store special system collections, which user level // administrators should not be able to manipulate. if (target.ns().db() != "local" && target.ns().db() != "config") { resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); } resourceSearchList[size++] = ResourcePattern::forDatabaseName(target.ns().db()); } resourceSearchList[size++] = ResourcePattern::forCollectionName(target.ns().coll()); } else if (target.isDatabasePattern()) { resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); } resourceSearchList[size++] = target; dassert(size <= resourceSearchListCapacity); return size; } bool AuthorizationSession::isAuthorizedToChangeAsUser(const UserName& userName, ActionType actionType) { User* user = lookupUser(userName); if (!user) { return false; } ResourcePattern resourceSearchList[resourceSearchListCapacity]; const int resourceSearchListLength = buildResourceSearchList( ResourcePattern::forDatabaseName(userName.getDB()), resourceSearchList); ActionSet actions; for (int i = 0; i < resourceSearchListLength; ++i) { actions.addAllActionsFromSet(user->getActionsForResource(resourceSearchList[i])); } return actions.contains(actionType); } bool AuthorizationSession::isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName) { return AuthorizationSession::isAuthorizedToChangeAsUser(userName, ActionType::changeOwnPassword); } bool AuthorizationSession::isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName) { return AuthorizationSession::isAuthorizedToChangeAsUser(userName, ActionType::changeOwnCustomData); } bool AuthorizationSession::isAuthorizedToListCollections(StringData dbname) { // Check for the listCollections ActionType on the database or find on system.namespaces for // pre 3.0 systems. return AuthorizationSession::isAuthorizedForActionsOnResource( ResourcePattern::forDatabaseName(dbname), ActionType::listCollections) || AuthorizationSession::isAuthorizedForActionsOnResource( ResourcePattern::forExactNamespace(NamespaceString(dbname, "system.namespaces")), ActionType::find); } bool AuthorizationSession::isAuthenticatedAsUserWithRole(const RoleName& roleName) { for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); ++it) { if ((*it)->hasRole(roleName)) { return true; } } return false; } void AuthorizationSession::_refreshUserInfoAsNeeded(OperationContext* opCtx) { AuthorizationManager& authMan = getAuthorizationManager(); UserSet::iterator it = _authenticatedUsers.begin(); while (it != _authenticatedUsers.end()) { User* user = *it; if (!user->isValid()) { // Make a good faith effort to acquire an up-to-date user object, since the one // we've cached is marked "out-of-date." UserName name = user->getName(); User* updatedUser; Status status = authMan.acquireUserToRefreshSessionCache(opCtx, name, user->getID(), &updatedUser); switch (status.code()) { case ErrorCodes::OK: { // Verify the updated user object's authentication restrictions. UserHolder userHolder(user, UserReleaser(&authMan, &_authenticatedUsers, it)); UserHolder updatedUserHolder(updatedUser, UserReleaser(&authMan)); try { const auto& restrictionSet = updatedUserHolder->getRestrictions(); // Owned by updatedUser invariant(opCtx->getClient()); Status restrictionStatus = restrictionSet.validate( RestrictionEnvironment::get(*opCtx->getClient())); if (!restrictionStatus.isOK()) { log() << "Removed user " << name << " with unmet authentication restrictions from session cache of" << " user information. Restriction failed because: " << restrictionStatus.reason(); // If we remove from the UserSet, we cannot increment the iterator. continue; } } catch (...) { log() << "Evaluating authentication restrictions for " << name << " resulted in an unknown exception. Removing user from the" << " session cache."; continue; } // Success! Replace the old User object with the updated one. fassert(17067, _authenticatedUsers.replaceAt(it, updatedUserHolder.release()) == userHolder.get()); LOG(1) << "Updated session cache of user information for " << name; break; } case ErrorCodes::UserNotFound: { // User does not exist anymore; remove it from _authenticatedUsers. fassert(17068, _authenticatedUsers.removeAt(it) == user); authMan.releaseUser(user); log() << "Removed deleted user " << name << " from session cache of user information."; continue; // No need to advance "it" in this case. } case ErrorCodes::UnsupportedFormat: { // An auth subsystem has explicitly indicated a failure. fassert(40555, _authenticatedUsers.removeAt(it) == user); authMan.releaseUser(user); log() << "Removed user " << name << " from session cache of user information because of refresh failure:" << " '" << status << "'."; continue; // No need to advance "it" in this case. } default: // Unrecognized error; assume that it's transient, and continue working with the // out-of-date privilege data. warning() << "Could not fetch updated user privilege information for " << name << "; continuing to use old information. Reason is " << redact(status); break; } } ++it; } _buildAuthenticatedRolesVector(); } void AuthorizationSession::_buildAuthenticatedRolesVector() { _authenticatedRoleNames.clear(); for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); ++it) { RoleNameIterator roles = (*it)->getIndirectRoles(); while (roles.more()) { RoleName roleName = roles.next(); _authenticatedRoleNames.push_back(RoleName(roleName.getRole(), roleName.getDB())); } } } bool AuthorizationSession::_isAuthorizedForPrivilege(const Privilege& privilege) { const ResourcePattern& target(privilege.getResourcePattern()); ResourcePattern resourceSearchList[resourceSearchListCapacity]; const int resourceSearchListLength = buildResourceSearchList(target, resourceSearchList); ActionSet unmetRequirements = privilege.getActions(); PrivilegeVector defaultPrivileges = getDefaultPrivileges(); for (PrivilegeVector::iterator it = defaultPrivileges.begin(); it != defaultPrivileges.end(); ++it) { for (int i = 0; i < resourceSearchListLength; ++i) { if (!(it->getResourcePattern() == resourceSearchList[i])) continue; ActionSet userActions = it->getActions(); unmetRequirements.removeAllActionsFromSet(userActions); if (unmetRequirements.empty()) return true; } } for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); ++it) { User* user = *it; for (int i = 0; i < resourceSearchListLength; ++i) { ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); unmetRequirements.removeAllActionsFromSet(userActions); if (unmetRequirements.empty()) return true; } } return false; } void AuthorizationSession::setImpersonatedUserData(std::vector usernames, std::vector roles) { _impersonatedUserNames = usernames; _impersonatedRoleNames = roles; _impersonationFlag = true; } bool AuthorizationSession::isCoauthorizedWithClient(Client* opClient) { auto getUserNames = [](AuthorizationSession* authSession) { if (authSession->isImpersonating()) { return authSession->getImpersonatedUserNames(); } else { return authSession->getAuthenticatedUserNames(); } }; UserNameIterator it = getUserNames(this); while (it.more()) { UserNameIterator opIt = getUserNames(AuthorizationSession::get(opClient)); while (opIt.more()) { if (it.get() == opIt.get()) { return true; } opIt.next(); } it.next(); } return false; } bool AuthorizationSession::isCoauthorizedWith(UserNameIterator userNameIter) { if (!getAuthorizationManager().isAuthEnabled()) { return true; } if (!userNameIter.more() && !getAuthenticatedUserNames().more()) { return true; } for (; userNameIter.more(); userNameIter.next()) { for (UserNameIterator thisUserNameIter = getAuthenticatedUserNames(); thisUserNameIter.more(); thisUserNameIter.next()) { if (*userNameIter == *thisUserNameIter) { return true; } } } return false; } UserNameIterator AuthorizationSession::getImpersonatedUserNames() { return makeUserNameIterator(_impersonatedUserNames.begin(), _impersonatedUserNames.end()); } RoleNameIterator AuthorizationSession::getImpersonatedRoleNames() { return makeRoleNameIterator(_impersonatedRoleNames.begin(), _impersonatedRoleNames.end()); } // Clear the vectors of impersonated usernames and roles. void AuthorizationSession::clearImpersonatedUserData() { _impersonatedUserNames.clear(); _impersonatedRoleNames.clear(); _impersonationFlag = false; } bool AuthorizationSession::isImpersonating() const { return _impersonationFlag; } } // namespace mongo