SERVER-6246 SERVER-9515 Update usersInfo and rolesInfo commands to new API

This commit is contained in:
Spencer T Brody
2013-10-06 14:49:49 -04:00
parent 4de73d9215
commit ee7ea7ea7f
14 changed files with 275 additions and 141 deletions

View File

@@ -186,7 +186,7 @@ var testOps = function(db, allowedActions) {
}, db);
checkErr(allowedActions.hasOwnProperty('user_r'), function() {
var result = db.runCommand({usersInfo: /.*/});
var result = db.runCommand({usersInfo: 1});
if (!result.ok) {
throw new Error(tojson(result));
}

View File

@@ -225,8 +225,9 @@ namespace mongo {
Status AuthorizationManager::queryAuthzDocument(
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor) {
return _externalState->query(collectionName, query, resultProcessor);
return _externalState->query(collectionName, query, projection, resultProcessor);
}
Status AuthorizationManager::updateAuthzDocuments(const NamespaceString& collectionName,

View File

@@ -217,6 +217,7 @@ namespace mongo {
*/
Status queryAuthzDocument(const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor);
// Checks to see if "doc" is a valid privilege document, assuming it is stored in the

View File

@@ -156,6 +156,7 @@ namespace mongo {
*/
virtual Status query(const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor) = 0;
/**

View File

@@ -237,11 +237,12 @@ namespace {
Status AuthzManagerExternalStateMongod::query(
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor) {
try {
DBDirectClient client;
Client::GodScope gs;
client.query(resultProcessor, collectionName.ns(), query);
client.query(resultProcessor, collectionName.ns(), query, &projection);
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
@@ -452,6 +453,7 @@ namespace {
Status status = query(
AuthorizationManager::rolesCollectionNamespace,
BSONObj(),
BSONObj(),
boost::bind(addRoleFromDocumentOrWarn, &newRoleGraph, _1));
if (!status.isOK())
return status;

View File

@@ -65,6 +65,7 @@ namespace mongo {
BSONObj* result);
virtual Status query(const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor);
virtual Status insert(const NamespaceString& collectionName,
const BSONObj& document,

View File

@@ -213,6 +213,7 @@ namespace {
Status AuthzManagerExternalStateMock::query(
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj&,
const boost::function<void(const BSONObj&)>& resultProcessor) {
std::vector<BSONObjCollection::iterator> iterVector;
Status status = _queryVector(collectionName, query, &iterVector);

View File

@@ -87,6 +87,7 @@ namespace mongo {
virtual Status query(const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection, // Currently unused in mock
const boost::function<void(const BSONObj&)>& resultProcessor);
// This implementation does not understand uniqueness constraints.

View File

@@ -79,8 +79,14 @@ namespace {
AuthorizationManager::usersCollectionNamespace));
BSONObj cmdResult;
conn->get()->runCommand(
userName.getDB().toString(), // TODO: Change usersInfo so this command can always go to "admin".
BSON("usersInfo" << userName.getUser() << "details" << true),
"admin",
BSON("usersInfo" <<
BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME <<
userName.getUser() <<
AuthorizationManager::USER_SOURCE_FIELD_NAME <<
userName.getDB())) <<
"showPrivileges" << true <<
"showCredentials" << true),
cmdResult);
if (!cmdResult["ok"].trueValue()) {
int code = cmdResult["code"].numberInt();
@@ -102,8 +108,12 @@ namespace {
AuthorizationManager::rolesCollectionNamespace));
BSONObj cmdResult;
conn->get()->runCommand(
roleName.getDB().toString(), // TODO: Change rolesInfo so this command can always go to "admin".
BSON("rolesInfo" << roleName.getRole()),
"admin",
BSON("rolesInfo" <<
BSON_ARRAY(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME <<
roleName.getRole() <<
AuthorizationManager::ROLE_SOURCE_FIELD_NAME <<
roleName.getDB()))),
cmdResult);
if (!cmdResult["ok"].trueValue()) {
int code = cmdResult["code"].numberInt();
@@ -138,10 +148,11 @@ namespace {
Status AuthzManagerExternalStateMongos::query(
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor) {
try {
scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection(collectionName));
conn->get()->query(resultProcessor, collectionName.ns(), query);
conn->get()->query(resultProcessor, collectionName.ns(), query, &projection);
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();

View File

@@ -66,6 +66,7 @@ namespace mongo {
BSONObj* result);
virtual Status query(const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const boost::function<void(const BSONObj&)>& resultProcessor);
virtual Status insert(const NamespaceString& collectionName,
const BSONObj& document,

View File

@@ -81,44 +81,82 @@ namespace auth {
return Status::OK();
}
Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray,
const StringData& dbname,
const StringData& rolesFieldName,
std::vector<RoleName>* parsedRoleNames) {
for (BSONObjIterator it(rolesArray); it.more(); it.next()) {
BSONElement element = *it;
if (element.type() == String) {
parsedRoleNames->push_back(RoleName(element.String(), dbname));
}
else if (element.type() == Object) {
BSONObj roleObj = element.Obj();
// Extracts a UserName or RoleName object from a BSONElement.
template <typename Name>
Status _parseNameFromBSONElement(const BSONElement& element,
const StringData& dbname,
const StringData& nameFieldName,
const StringData& sourceFieldName,
Name* parsedName) {
if (element.type() == String) {
*parsedName = Name(element.String(), dbname);
}
else if (element.type() == Object) {
BSONObj obj = element.Obj();
std::string roleNameString;
std::string roleSource;
Status status = bsonExtractStringField(roleObj,
AuthorizationManager::ROLE_NAME_FIELD_NAME,
&roleNameString);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringField(roleObj,
AuthorizationManager::ROLE_SOURCE_FIELD_NAME,
&roleSource);
if (!status.isOK()) {
return status;
}
std::string name;
std::string source;
Status status = bsonExtractStringField(obj, nameFieldName, &name);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringField(obj, sourceFieldName, &source);
if (!status.isOK()) {
return status;
}
parsedRoleNames->push_back(RoleName(roleNameString, roleSource));
}
else {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "Values in \"" << rolesFieldName <<
"\" array must be sub-documents or strings");
}
*parsedName = Name(name, source);
}
else {
return Status(ErrorCodes::BadValue,
"User and role names must be either strings or objects");
}
return Status::OK();
}
// Extracts UserName or RoleName objects from a BSONArray of role/user names.
template <typename Name>
Status _parseNamesFromBSONArray(const BSONArray& array,
const StringData& dbname,
const StringData& nameFieldName,
const StringData& sourceFieldName,
std::vector<Name>* parsedNames) {
for (BSONObjIterator it(array); it.more(); it.next()) {
BSONElement element = *it;
Name name;
Status status = _parseNameFromBSONElement(element,
dbname,
nameFieldName,
sourceFieldName,
&name);
if (!status.isOK()) {
return status;
}
parsedNames->push_back(name);
}
return Status::OK();
}
Status _parseUserNamesFromBSONArray(const BSONArray& usersArray,
const StringData& dbname,
std::vector<UserName>* parsedUserNames) {
return _parseNamesFromBSONArray(usersArray,
dbname,
AuthorizationManager::USER_NAME_FIELD_NAME,
AuthorizationManager::USER_SOURCE_FIELD_NAME,
parsedUserNames);
}
Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray,
const StringData& dbname,
std::vector<RoleName>* parsedRoleNames) {
return _parseNamesFromBSONArray(rolesArray,
dbname,
AuthorizationManager::ROLE_NAME_FIELD_NAME,
AuthorizationManager::ROLE_SOURCE_FIELD_NAME,
parsedRoleNames);
}
Status _extractRoleDataFromBSONArray(const BSONElement& rolesElement,
const std::string& dbname,
std::vector<User::RoleData> *parsedRoleData) {
@@ -208,7 +246,6 @@ namespace auth {
status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()),
dbname,
rolesFieldName,
parsedRoleNames);
if (!status.isOK()) {
return status;
@@ -342,44 +379,89 @@ namespace auth {
return Status::OK();
}
Status parseAndValidateInfoCommands(const BSONObj& cmdObj,
const StringData& cmdName,
const std::string& dbname,
bool* parsedAnyDB,
BSONElement* parsedNameFilter) {
Status parseUsersInfoCommand(const BSONObj& cmdObj,
const StringData& dbname,
UsersInfoArgs* parsedArgs) {
unordered_set<std::string> validFieldNames;
validFieldNames.insert(cmdName.toString());
validFieldNames.insert("anyDB");
validFieldNames.insert("writeConcern");
validFieldNames.insert("details");
validFieldNames.insert("usersInfo");
validFieldNames.insert("showPrivileges");
validFieldNames.insert("showCredentials");
Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames);
Status status = _checkNoExtraFields(cmdObj, "usersInfo", validFieldNames);
if (!status.isOK()) {
return status;
}
if (cmdObj[cmdName].type() != String && cmdObj[cmdName].type() != RegEx) {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "Argument to \"" << cmdName <<
"\"command must be either a string or a regex");
}
*parsedNameFilter = cmdObj[cmdName];
bool anyDB = false;
if (cmdObj.hasField("anyDB")) {
if (dbname == "admin") {
Status status = bsonExtractBooleanField(cmdObj, "anyDB", &anyDB);
if (!status.isOK()) {
return status;
}
} else {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "\"anyDB\" argument to \"" << cmdName <<
"\"command is only valid when run on the \"admin\" database");
if (cmdObj["usersInfo"].numberInt() == 1) {
parsedArgs->allForDB = true;
} else if (cmdObj["usersInfo"].type() == Array) {
status = _parseUserNamesFromBSONArray(BSONArray(cmdObj["usersInfo"].Obj()),
dbname,
&parsedArgs->userNames);
if (!status.isOK()) {
return status;
}
} else {
UserName name;
status = _parseNameFromBSONElement(cmdObj["usersInfo"],
dbname,
AuthorizationManager::USER_NAME_FIELD_NAME,
AuthorizationManager::USER_SOURCE_FIELD_NAME,
&name);
if (!status.isOK()) {
return status;
}
parsedArgs->userNames.push_back(name);
}
status = bsonExtractBooleanFieldWithDefault(cmdObj,
"showPrivileges",
false,
&parsedArgs->showPrivileges);
if (!status.isOK()) {
return status;
}
status = bsonExtractBooleanFieldWithDefault(cmdObj,
"showCredentials",
false,
&parsedArgs->showCredentials);
if (!status.isOK()) {
return status;
}
return Status::OK();
}
Status parseRolesInfoCommand(const BSONObj& cmdObj,
const StringData& dbname,
std::vector<RoleName>* parsedRoleNames) {
unordered_set<std::string> validFieldNames;
validFieldNames.insert("rolesInfo");
Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames);
if (!status.isOK()) {
return status;
}
if (cmdObj["rolesInfo"].type() == Array) {
status = parseRoleNamesFromBSONArray(BSONArray(cmdObj["rolesInfo"].Obj()),
dbname,
parsedRoleNames);
if (!status.isOK()) {
return status;
}
} else {
RoleName name;
status = _parseNameFromBSONElement(cmdObj["rolesInfo"],
dbname,
AuthorizationManager::ROLE_NAME_FIELD_NAME,
AuthorizationManager::ROLE_SOURCE_FIELD_NAME,
&name);
if (!status.isOK()) {
return status;
}
parsedRoleNames->push_back(name);
}
*parsedAnyDB = anyDB;
return Status::OK();
}
@@ -467,7 +549,6 @@ namespace auth {
}
status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()),
dbname,
"roles",
&parsedArgs->roles);
if (!status.isOK()) {
return status;

View File

@@ -99,17 +99,29 @@ namespace auth {
const std::string& dbname,
BSONObj* parsedWriteConcern);
struct UsersInfoArgs {
std::vector<UserName> userNames;
bool allForDB;
bool showPrivileges;
bool showCredentials;
UsersInfoArgs() : allForDB(false), showPrivileges(false), showCredentials(false) {}
};
/**
* Takes a command object describing an invocation of the "usersInfo" or "rolesInfo" commands
* (which command it is is specified in the "cmdName" argument) and parses out a BSONElement
* with the user/role name filter to be applied, as well as the anyDB boolean.
* Also validates the input and returns a non-ok Status if there is anything wrong.
* Takes a command object describing an invocation of the "usersInfo" command and parses out
* all the arguments into the "parsedArgs" output param.
*/
Status parseAndValidateInfoCommands(const BSONObj& cmdObj,
const StringData& cmdName,
const std::string& dbname,
bool* parsedAnyDb,
BSONElement* parsedNameFilter);
Status parseUsersInfoCommand(const BSONObj& cmdObj,
const StringData& dbname,
UsersInfoArgs* parsedArgs);
/**
* Takes a command object describing an invocation of the "rolesInfo" command and parses out
* the role names requested into the "parsedRoleNames" output param.
*/
Status parseRolesInfoCommand(const BSONObj& cmdObj,
const StringData& dbname,
std::vector<RoleName>* parsedRoleNames);
struct CreateOrUpdateRoleArgs {
RoleName roleName;
@@ -173,7 +185,6 @@ namespace auth {
*/
Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray,
const StringData& dbname,
const StringData& rolesFieldName,
std::vector<RoleName>* parsedRoleNames);
} // namespace auth

View File

@@ -980,56 +980,79 @@ namespace mongo {
BSONObjBuilder& result,
bool fromRepl) {
bool anyDB = false;
BSONElement usersFilter;
Status status = auth::parseAndValidateInfoCommands(cmdObj,
"usersInfo",
dbname,
&anyDB,
&usersFilter);
auth::UsersInfoArgs args;
Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
bool wantsDetails = cmdObj["details"].trueValue();
if (wantsDetails) {
if (anyDB || usersFilter.type() != String) {
addStatus(Status(ErrorCodes::IllegalOperation,
"Cannot only get privilege details on exact-match usersInfo "
"queries."),
result);
return false;
}
BSONObj userDetails;
status = getGlobalAuthorizationManager()->getUserDescription(
UserName(usersFilter.str(), dbname), &userDetails);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
result.append("users", BSON_ARRAY(userDetails));
return true;
}
BSONObjBuilder queryBuilder;
queryBuilder.appendAs(usersFilter, AuthorizationManager::USER_NAME_FIELD_NAME);
if (!anyDB) {
queryBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, dbname);
if (args.allForDB && args.showPrivileges) {
addStatus(Status(ErrorCodes::IllegalOperation,
"Cannot only get privilege details on exact-match usersInfo "
"queries."),
result);
return false;
}
BSONArrayBuilder usersArrayBuilder;
BSONArrayBuilder& (BSONArrayBuilder::* appendBSONObj) (const BSONObj&) =
&BSONArrayBuilder::append<BSONObj>;
const boost::function<void(const BSONObj&)> function =
boost::bind(appendBSONObj, &usersArrayBuilder, _1);
AuthorizationManager* authzManager = getGlobalAuthorizationManager();
authzManager->queryAuthzDocument(NamespaceString("admin.system.users"),
queryBuilder.done(),
function);
if (args.showPrivileges) {
// If you want privileges you need to call getUserDescription on each user.
for (size_t i = 0; i < args.userNames.size(); ++i) {
BSONObj userDetails;
status = getGlobalAuthorizationManager()->getUserDescription(
args.userNames[i], &userDetails);
if (status.code() == ErrorCodes::UserNotFound) {
continue;
}
if (!status.isOK()) {
addStatus(status, result);
return false;
}
if (!args.showCredentials) {
// getUserDescription always includes credentials, need to strip it out
BSONObjBuilder userWithoutCredentials(usersArrayBuilder.subobjStart());
for (BSONObjIterator it(userDetails); it.more(); ) {
BSONElement e = it.next();
if (e.fieldNameStringData() != "credentials")
userWithoutCredentials.append(e);
}
userWithoutCredentials.doneFast();
} else {
usersArrayBuilder.append(userDetails);
}
}
} else {
// If you don't need privileges, you can just do a regular query on system.users
BSONObjBuilder queryBuilder;
if (args.allForDB) {
queryBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, dbname);
} else {
BSONArrayBuilder usersMatchArray;
for (size_t i = 0; i < args.userNames.size(); ++i) {
usersMatchArray.append(BSON(AuthorizationManager::USER_NAME_FIELD_NAME <<
args.userNames[i].getUser() <<
AuthorizationManager::USER_SOURCE_FIELD_NAME <<
args.userNames[i].getDB()));
}
queryBuilder.append("$or", usersMatchArray.arr());
}
AuthorizationManager* authzManager = getGlobalAuthorizationManager();
BSONObjBuilder projection;
if (!args.showCredentials) {
projection.append("credentials", 0);
}
BSONArrayBuilder& (BSONArrayBuilder::* appendBSONObj) (const BSONObj&) =
&BSONArrayBuilder::append<BSONObj>;
const boost::function<void(const BSONObj&)> function =
boost::bind(appendBSONObj, &usersArrayBuilder, _1);
authzManager->queryAuthzDocument(AuthorizationManager::usersCollectionNamespace,
queryBuilder.done(),
projection.done(),
function);
}
result.append("users", usersArrayBuilder.arr());
return true;
}
@@ -1577,7 +1600,6 @@ namespace mongo {
std::vector<RoleName> roles;
status = auth::parseRoleNamesFromBSONArray(BSONArray(roleDoc["roles"].Obj()),
roleName.getDB(),
"roles",
&roles);
for (vector<RoleName>::iterator it = rolesToAdd.begin(); it != rolesToAdd.end(); ++it) {
@@ -1600,7 +1622,6 @@ namespace mongo {
status = auth::parseRoleNamesFromBSONArray(
BSONArray(roleDoc["indirectRoles"].Obj()),
roleName.getDB(),
"indirectRoles",
&indirectSubordinatesOfToAdd);
if (!status.isOK()) {
addStatus(status, result);
@@ -1712,7 +1733,6 @@ namespace mongo {
std::vector<RoleName> roles;
status = auth::parseRoleNamesFromBSONArray(BSONArray(roleDoc["roles"].Obj()),
roleName.getDB(),
"roles",
&roles);
if (!status.isOK()) {
addStatus(status, result);
@@ -1940,26 +1960,28 @@ namespace mongo {
BSONObjBuilder& result,
bool fromRepl) {
bool anyDB = false;
BSONElement rolesFilter;
Status status = auth::parseAndValidateInfoCommands(cmdObj,
"rolesInfo",
dbname,
&anyDB,
&rolesFilter);
std::vector<RoleName> roleNames;
Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &roleNames);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
BSONObj roleObj;
status = getGlobalAuthorizationManager()->getRoleDescription(
RoleName(rolesFilter.str(), dbname), &roleObj);
if (!status.isOK()) {
addStatus(status, result);
return false;
BSONArrayBuilder rolesArrayBuilder;
for (size_t i = 0; i < roleNames.size(); ++i) {
BSONObj roleDetails;
status = getGlobalAuthorizationManager()->getRoleDescription(
roleNames[i], &roleDetails);
if (status.code() == ErrorCodes::RoleNotFound) {
continue;
}
if (!status.isOK()) {
addStatus(status, result);
return false;
}
rolesArrayBuilder.append(roleDetails);
}
result.append("roles", BSON_ARRAY(roleObj));
result.append("roles", rolesArrayBuilder.arr());
return true;
}

View File

@@ -1234,7 +1234,7 @@ DB.prototype.getUser = function(username) {
}
DB.prototype.getUsers = function() {
var res = this.runCommand({usersInfo: /.*/});
var res = this.runCommand({usersInfo: 1});
if (!res.ok) {
throw Error(res.errmsg);
}