/**
* 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/authz_manager_external_state_s.h"
#include
#include
#include
#include
#include "mongo/client/connpool.h"
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/auth/authz_session_external_state_s.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/jsobj.h"
#include "mongo/s/catalog/catalog_cache.h"
#include "mongo/s/catalog/catalog_manager.h"
#include "mongo/s/config.h"
#include "mongo/s/grid.h"
#include "mongo/s/write_ops/batched_command_response.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
using boost::scoped_ptr;
using boost::shared_ptr;
using std::endl;
using std::vector;
namespace {
ScopedDbConnection* getConnectionForAuthzCollection(const NamespaceString& nss) {
// Note: The connection mechanism here is *not* ideal, and should not be used elsewhere.
// If the primary for the collection moves, this approach may throw rather than handle
// version exceptions.
auto config = uassertStatusOK(grid.catalogCache()->getDatabase(nss.db().toString()));
Shard s = config->getShard(nss.ns());
return new ScopedDbConnection(s.getConnString(), 30.0);
}
Status getRemoteStoredAuthorizationVersion(DBClientBase* conn, int* outVersion) {
try {
BSONObj cmdResult;
conn->runCommand(
"admin",
BSON("getParameter" << 1 << authSchemaVersionServerParameter << 1),
cmdResult);
if (!cmdResult["ok"].trueValue()) {
std::string errmsg = cmdResult["errmsg"].str();
if (errmsg == "no option found to get" ||
StringData(errmsg).startsWith("no such cmd")) {
*outVersion = 1;
return Status::OK();
}
int code = cmdResult["code"].numberInt();
if (code == 0) {
code = ErrorCodes::UnknownError;
}
return Status(ErrorCodes::Error(code), errmsg);
}
BSONElement versionElement = cmdResult[authSchemaVersionServerParameter];
if (versionElement.eoo())
return Status(ErrorCodes::UnknownError, "getParameter misbehaved.");
*outVersion = versionElement.numberInt();
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
} // namespace
AuthzManagerExternalStateMongos::AuthzManagerExternalStateMongos() {}
AuthzManagerExternalStateMongos::~AuthzManagerExternalStateMongos() {}
Status AuthzManagerExternalStateMongos::initialize(OperationContext* txn) {
return Status::OK();
}
std::unique_ptr
AuthzManagerExternalStateMongos::makeAuthzSessionExternalState(
AuthorizationManager* authzManager) {
return stdx::make_unique(authzManager);
}
Status AuthzManagerExternalStateMongos::getStoredAuthorizationVersion(
OperationContext* txn, int* outVersion) {
try {
scoped_ptr conn(getConnectionForAuthzCollection(
AuthorizationManager::usersCollectionNamespace));
Status status = getRemoteStoredAuthorizationVersion(conn->get(), outVersion);
conn->done();
return status;
}
catch (const DBException& ex) {
return ex.toStatus();
}
}
Status AuthzManagerExternalStateMongos::getUserDescription(
OperationContext* txn, const UserName& userName, BSONObj* result) {
try {
scoped_ptr conn(getConnectionForAuthzCollection(
AuthorizationManager::usersCollectionNamespace));
BSONObj cmdResult;
conn->get()->runCommand(
"admin",
BSON("usersInfo" <<
BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME <<
userName.getUser() <<
AuthorizationManager::USER_DB_FIELD_NAME <<
userName.getDB())) <<
"showPrivileges" << true <<
"showCredentials" << true),
cmdResult);
if (!cmdResult["ok"].trueValue()) {
int code = cmdResult["code"].numberInt();
if (code == 0) code = ErrorCodes::UnknownError;
return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str());
}
std::vector foundUsers = cmdResult["users"].Array();
if (foundUsers.size() == 0) {
return Status(ErrorCodes::UserNotFound,
"User \"" + userName.toString() + "\" not found");
}
if (foundUsers.size() > 1) {
return Status(ErrorCodes::UserDataInconsistent,
mongoutils::str::stream() << "Found multiple users on the \"" <<
userName.getDB() << "\" database with name \"" <<
userName.getUser() << "\"");
}
*result = foundUsers[0].Obj().getOwned();
conn->done();
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status AuthzManagerExternalStateMongos::getRoleDescription(const RoleName& roleName,
bool showPrivileges,
BSONObj* result) {
try {
scoped_ptr conn(getConnectionForAuthzCollection(
AuthorizationManager::rolesCollectionNamespace));
BSONObj cmdResult;
conn->get()->runCommand(
"admin",
BSON("rolesInfo" <<
BSON_ARRAY(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME <<
roleName.getRole() <<
AuthorizationManager::ROLE_DB_FIELD_NAME <<
roleName.getDB())) <<
"showPrivileges" << showPrivileges),
cmdResult);
if (!cmdResult["ok"].trueValue()) {
int code = cmdResult["code"].numberInt();
if (code == 0) code = ErrorCodes::UnknownError;
return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str());
}
std::vector foundRoles = cmdResult["roles"].Array();
if (foundRoles.size() == 0) {
return Status(ErrorCodes::RoleNotFound,
"Role \"" + roleName.toString() + "\" not found");
}
if (foundRoles.size() > 1) {
return Status(ErrorCodes::RoleDataInconsistent,
mongoutils::str::stream() << "Found multiple roles on the \"" <<
roleName.getDB() << "\" database with name \"" <<
roleName.getRole() << "\"");
}
*result = foundRoles[0].Obj().getOwned();
conn->done();
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB(const std::string dbname,
bool showPrivileges,
bool showBuiltinRoles,
vector* result) {
try {
scoped_ptr conn(getConnectionForAuthzCollection(
AuthorizationManager::rolesCollectionNamespace));
BSONObj cmdResult;
conn->get()->runCommand(
dbname,
BSON("rolesInfo" << 1 <<
"showPrivileges" << showPrivileges <<
"showBuiltinRoles" << showBuiltinRoles),
cmdResult);
if (!cmdResult["ok"].trueValue()) {
int code = cmdResult["code"].numberInt();
if (code == 0) code = ErrorCodes::UnknownError;
return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str());
}
for (BSONObjIterator it(cmdResult["roles"].Obj()); it.more(); it.next()) {
result->push_back((*it).Obj().getOwned());
}
conn->done();
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status AuthzManagerExternalStateMongos::findOne(
OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& queryDoc,
BSONObj* result) {
try {
scoped_ptr conn(getConnectionForAuthzCollection(collectionName));
Query query(queryDoc);
query.readPref(ReadPreference_PrimaryPreferred, BSONArray());
*result = conn->get()->findOne(collectionName, query).getOwned();
conn->done();
if (result->isEmpty()) {
return Status(ErrorCodes::NoMatchingDocument, mongoutils::str::stream() <<
"No document in " << collectionName.ns() << " matches " << queryDoc);
}
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status AuthzManagerExternalStateMongos::query(
OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& queryDoc,
const BSONObj& projection,
const stdx::function& resultProcessor) {
try {
scoped_ptr conn(getConnectionForAuthzCollection(collectionName));
Query query(queryDoc);
query.readPref(ReadPreference_PrimaryPreferred, BSONArray());
conn->get()->query(resultProcessor, collectionName.ns(), query, &projection);
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status AuthzManagerExternalStateMongos::insert(
OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& document,
const BSONObj& writeConcern) {
return grid.catalogManager()->insert(collectionName, document, NULL);
}
Status AuthzManagerExternalStateMongos::update(OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& updatePattern,
bool upsert,
bool multi,
const BSONObj& writeConcern,
int* nMatched) {
BatchedCommandResponse response;
Status res = grid.catalogManager()->update(collectionName,
query,
updatePattern,
upsert,
multi,
&response);
if (res.isOK()) {
*nMatched = response.getN();
}
return res;
}
Status AuthzManagerExternalStateMongos::remove(
OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& writeConcern,
int* numRemoved) {
BatchedCommandResponse response;
Status res = grid.catalogManager()->remove(collectionName, query, 0, &response);
if (res.isOK()) {
*numRemoved = response.getN();
}
return res;
}
bool AuthzManagerExternalStateMongos::tryAcquireAuthzUpdateLock(StringData why) {
boost::lock_guard lkLocal(_distLockGuard);
if (_authzDataUpdateLock.get()) {
return false;
}
auto timeout = stdx::chrono::milliseconds(_authzUpdateLockAcquisitionTimeoutMillis);
auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
"authorizationData", why, timeout);
if (!scopedDistLock.isOK()) {
warning() << "Error while attempting to acquire distributed lock for "
<< "user modification: " << scopedDistLock.getStatus().toString();
return false;
}
_authzDataUpdateLock = stdx::make_unique(
std::move(scopedDistLock.getValue()));
return true;
}
void AuthzManagerExternalStateMongos::releaseAuthzUpdateLock() {
boost::lock_guard lkLocal(_distLockGuard);
_authzDataUpdateLock.reset();
}
} // namespace mongo