Permalink
Browse files

SERVER-7767 Implement a validator for compatibility and extended priv…

…ilege documents.

This will be for use in validating inserts and updates to system.users collections.
  • Loading branch information...
1 parent ca6558f commit 6c71d937eaa1599d7a233d368d7a9d57c3cf7d4a @amschwerin amschwerin committed Dec 19, 2012
@@ -29,6 +29,7 @@
#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/privilege_set.h"
#include "mongo/db/client.h"
+#include "mongo/db/jsobj.h"
#include "mongo/db/namespacestring.h"
#include "mongo/db/security_common.h"
#include "mongo/util/mongoutils/str.h"
@@ -212,6 +213,119 @@ namespace {
return Status::OK();
}
+ static inline Status _badValue(const char* reason, int location) {
+ return Status(ErrorCodes::BadValue, reason, location);
+ }
+
+ static inline Status _badValue(const std::string& reason, int location) {
+ return Status(ErrorCodes::BadValue, reason, location);
+ }
+
+ static inline StringData makeStringDataFromBSONElement(const BSONElement& element) {
+ return StringData(element.valuestr(), element.valuestrsize() - 1);
+ }
+
+ static Status _checkRolesArray(const BSONElement& rolesElement) {
+ if (rolesElement.type() != Array) {
+ return _badValue("Role fields must be an array when present in system.users entries",
+ 0);
+ }
+ for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) {
+ BSONElement element = *iter;
+ if (element.type() != String || makeStringDataFromBSONElement(element).empty()) {
+ return _badValue("Roles must be non-empty strings.", 0);
+ }
+ }
+ return Status::OK();
+ }
+
+ Status AuthorizationManager::checkValidPrivilegeDocument(const StringData& dbname,
+ const BSONObj& doc) {
+ BSONElement userElement = doc[USERNAME_FIELD_NAME];
+ BSONElement userSourceElement = doc[USERSOURCE_FIELD_NAME];
+ BSONElement passwordElement = doc[PASSWORD_FIELD_NAME];
+ BSONElement rolesElement = doc[ROLES_FIELD_NAME];
+ BSONElement otherDBRolesElement = doc[OTHER_DB_ROLES_FIELD_NAME];
+ BSONElement readOnlyElement = doc[READONLY_FIELD_NAME];
+
+ // Validate the "user" element.
+ if (userElement.type() != String)
+ return _badValue("system.users entry needs 'user' field to be a string", 14051);
+ if (makeStringDataFromBSONElement(userElement).empty())
+ return _badValue("system.users entry needs 'user' field to be non-empty", 14053);
+
+ // Must set exactly one of "userSource" and "pwd" fields.
+ if (userSourceElement.eoo() == passwordElement.eoo()) {
+ return _badValue("system.users entry must have either a 'pwd' field or a 'userSource' "
+ "field, but not both", 0);
+ }
+
+ // Cannot have both "roles" and "readOnly" elements.
+ if (!rolesElement.eoo() && !readOnlyElement.eoo()) {
+ return _badValue("system.users entry must not have both 'roles' and 'readOnly' fields",
+ 0);
+ }
+
+ // Validate the "pwd" element, if present.
+ if (!passwordElement.eoo()) {
+ if (passwordElement.type() != String)
+ return _badValue("system.users entry needs 'pwd' field to be a string", 14052);
+ if (makeStringDataFromBSONElement(passwordElement).empty())
+ return _badValue("system.users entry needs 'pwd' field to be non-empty", 14054);
+ }
+
+ // Validate the "userSource" element, if present.
+ if (!userSourceElement.eoo()) {
+ if (userSourceElement.type() != String ||
+ makeStringDataFromBSONElement(userSourceElement).empty()) {
+
+ return _badValue("system.users entry needs 'userSource' field to be a non-empty "
+ "string, if present", 0);
+ }
+ if (userSourceElement.str() == dbname) {
+ return _badValue(mongoutils::str::stream() << "'" << dbname <<
+ "' is not a valid value for the userSource field in " <<
+ dbname << ".system.users entries",
+ 0);
+ }
+ if (rolesElement.eoo()) {
+ return _badValue("system.users entry needs 'roles' field if 'userSource' field "
+ "is present.", 0);
+ }
+ }
+
+ // Validate the "roles" element.
+ if (!rolesElement.eoo()) {
+ Status status = _checkRolesArray(rolesElement);
+ if (!status.isOK())
+ return status;
+ }
+
+ if (!otherDBRolesElement.eoo()) {
+ if (dbname != ADMIN_DBNAME) {
+ return _badValue("Only admin.system.users entries may contain 'otherDBRoles' "
+ "fields", 0);
+ }
+ if (rolesElement.eoo()) {
+ return _badValue("system.users entries with 'otherDBRoles' fields must contain "
+ "'roles' fields", 0);
+ }
+ if (otherDBRolesElement.type() != Object) {
+ return _badValue("'otherDBRoles' field must be an object when present in "
+ "system.users entries", 0);
+ }
+ for (BSONObjIterator iter(otherDBRolesElement.embeddedObject());
+ iter.more(); iter.next()) {
+
+ Status status = _checkRolesArray(*iter);
+ if (!status.isOK())
+ return status;
+ }
+ }
+
+ return Status::OK();
+ }
+
AuthorizationManager::AuthorizationManager(AuthExternalState* externalState) {
_externalState.reset(externalState);
}
@@ -59,6 +59,12 @@ namespace mongo {
static const std::string SERVER_RESOURCE_NAME;
static const std::string CLUSTER_RESOURCE_NAME;
+ // Checks to see if "doc" is a valid privilege document, assuming it is stored in the
+ // "system.users" collection of database "dbname".
+ //
+ // Returns Status::OK() if the document is good, or Status(ErrorCodes::BadValue), otherwise.
+ static Status checkValidPrivilegeDocument(const StringData& dbname, const BSONObj& doc);
+
// Takes ownership of the externalState.
explicit AuthorizationManager(AuthExternalState* externalState);
~AuthorizationManager();
@@ -371,5 +371,104 @@ namespace {
"anydb", principal, oldAndNewMixed, &result));
}
+ TEST(AuthorizationManagerTest, DocumentValidationCompatibility) {
+ Status (*check)(const StringData&, const BSONObj&) =
+ &AuthorizationManager::checkValidPrivilegeDocument;
+
+ // Good documents, with and without "readOnly" fields.
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a")));
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << 1)));
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << false)));
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << "yes")));
+
+ // Must have a "pwd" field.
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy")));
+
+ // "pwd" field must be a string.
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << 100)));
+
+ // "pwd" field string must not be empty.
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "")));
+
+ // Must have a "user" field.
+ ASSERT_NOT_OK(check("test", BSON("pwd" << "a")));
+
+ // "user" field must be a string.
+ ASSERT_NOT_OK(check("test", BSON("user" << 100 << "pwd" << "a")));
+
+ // "user" field string must not be empty.
+ ASSERT_NOT_OK(check("test", BSON("user" << "" << "pwd" << "a")));
+ }
+
+ TEST(AuthorizationManagerTest, DocumentValidationExtended) {
+ Status (*check)(const StringData&, const BSONObj&) =
+ &AuthorizationManager::checkValidPrivilegeDocument;
+
+ // Document describing new-style user on "test".
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSON_ARRAY("read"))));
+
+ // Document giving roles on "test" to a user from "test2".
+ ASSERT_OK(check("test", BSON("user" << "andy" << "userSource" << "test2" <<
+ "roles" << BSON_ARRAY("read"))));
+
+ // Cannot have "userSource" field value == dbname.
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "userSource" << "test" <<
+ "roles" << BSON_ARRAY("read"))));
+
+ // Cannot have both "userSource" and "pwd"
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "userSource" << "test2" <<
+ "pwd" << "a" << "roles" << BSON_ARRAY("read"))));
+
+ // Cannot have an otherDBRoles field except in the admin database.
+ ASSERT_NOT_OK(check("test",
+ BSON("user" << "andy" << "userSource" << "test2" <<
+ "roles" << BSON_ARRAY("read") <<
+ "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite")))));
+
+ ASSERT_OK(check("admin",
+ BSON("user" << "andy" << "userSource" << "test2" <<
+ "roles" << BSON_ARRAY("read") <<
+ "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite")))));
+
+ // Must have "roles" to have "otherDBRoles".
+ ASSERT_NOT_OK(check("admin",
+ BSON("user" << "andy" << "pwd" << "a" <<
+ "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite")))));
+
+ ASSERT_OK(check("admin",
+ BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSONArrayBuilder().arr() <<
+ "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite")))));
+
+ // "otherDBRoles" may be empty.
+ ASSERT_OK(check("admin",
+ BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSONArrayBuilder().arr() <<
+ "otherDBRoles" << BSONObjBuilder().obj())));
+
+ // Cannot omit "roles" if "userSource" is present.
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "userSource" << "test2")));
+
+ // Cannot have both "roles" and "readOnly".
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << 1 <<
+ "roles" << BSON_ARRAY("read"))));
+
+ // Roles must be strings, not empty.
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSON_ARRAY("read" << ""))));
+
+ ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSON_ARRAY(1 << "read"))));
+
+ // Multiple roles OK.
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSON_ARRAY("dbAdmin" << "read"))));
+
+ // Empty roles list OK.
+ ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" <<
+ "roles" << BSONArrayBuilder().arr())));
+ }
+
} // namespace
} // namespace mongo

0 comments on commit 6c71d93

Please sign in to comment.