From dcce5e795e5728214d6973a2e4f7168d05b22ecf Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 26 Jan 2010 17:04:09 -0800 Subject: [PATCH] SERVER-258 add readOnly auth mode --- db/dbcommands.cpp | 8 ++++++- db/instance.cpp | 2 +- db/instance.h | 2 +- db/query.cpp | 2 +- db/repl.cpp | 2 +- db/security.h | 15 +++++++++---- db/security_commands.cpp | 7 +++++- jstests/auth/auth1.js | 38 +++++++++++++++++++++++++++++++++ jstests/auth1.js | 2 -- mongo.xcodeproj/project.pbxproj | 12 +++++++++-- shell/db.js | 4 +++- 11 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 jstests/auth/auth1.js diff --git a/db/dbcommands.cpp b/db/dbcommands.cpp index b4cd1cd16a5..61f925a82cc 100644 --- a/db/dbcommands.cpp +++ b/db/dbcommands.cpp @@ -1431,7 +1431,13 @@ namespace mongo { if ( c ){ string errmsg; AuthenticationInfo *ai = currentClient.get()->ai; - uassert( 10045 , "unauthorized", ai->isAuthorized(cc().database()->name.c_str()) || !c->requiresAuth()); + if ( c->requiresAuth() ) { + if ( c->readOnly() ) { + uassert( 12593 , "readOnly unauthorized", ai->isReadOnlyAuthorized(cc().database()->name.c_str())); + } else { + uassert( 10045 , "unauthorized", ai->isAuthorized(cc().database()->name.c_str())); + } + } bool admin = c->adminOnly(); diff --git a/db/instance.cpp b/db/instance.cpp index a5c1eebd4af..1011771d433 100644 --- a/db/instance.cpp +++ b/db/instance.cpp @@ -562,7 +562,7 @@ namespace mongo { QueryResult* msgdata; try { AuthenticationInfo *ai = currentClient.get()->ai; - uassert( 10057 , "unauthorized", ai->isAuthorized(cc().database()->name.c_str())); + uassert( 10057 , "unauthorized", ai->isReadOnlyAuthorized(cc().database()->name.c_str())); msgdata = getMore(ns, ntoreturn, cursorid, curop); } catch ( AssertionException& e ) { diff --git a/db/instance.h b/db/instance.h index b2b2c94ef2e..ce99f53f9db 100644 --- a/db/instance.h +++ b/db/instance.h @@ -125,7 +125,7 @@ namespace mongo { return say( toSend ); } class AlwaysAuthorized : public AuthenticationInfo { - virtual bool isAuthorized( const char *dbname ) { + virtual bool _isAuthorized( const char *dbname, int level ) { return true; } }; diff --git a/db/query.cpp b/db/query.cpp index 053c207adfa..d7085083514 100644 --- a/db/query.cpp +++ b/db/query.cpp @@ -722,7 +722,7 @@ namespace mongo { /* regular query */ AuthenticationInfo *ai = currentClient.get()->ai; - uassert( 10106 , "unauthorized", ai->isAuthorized(c.database()->name.c_str())); + uassert( 10106 , "unauthorized", ai->isReadOnlyAuthorized(c.database()->name.c_str())); /* we allow queries to SimpleSlave's -- but not to the slave (nonmaster) member of a replica pair so that queries to a pair are realtime consistent as much as possible. use setSlaveOk() to diff --git a/db/repl.cpp b/db/repl.cpp index d310446fb3a..584d4709fbc 100644 --- a/db/repl.cpp +++ b/db/repl.cpp @@ -267,7 +267,7 @@ namespace mongo { one is not authenticated for admin db to be safe. */ AuthenticationInfo *ai = currentClient.get()->ai; - bool authed = ai->isAuthorized("admin"); + bool authed = ai->isReadOnlyAuthorized("admin"); if ( replAllDead ) { result.append("ismaster", 0.0); diff --git a/db/security.h b/db/security.h index f61d5e16323..693187978eb 100644 --- a/db/security.h +++ b/db/security.h @@ -53,11 +53,18 @@ namespace mongo { assertInWriteLock(); m[dbname].level = 2; } - virtual bool isAuthorized(const char *dbname) { - if( m[dbname].level == 2 ) return true; + void authorizeReadOnly(const char *dbname) { + assertInWriteLock(); + m[dbname].level = 1; + } + bool isAuthorized(const char *dbname) { return _isAuthorized( dbname, 2 ); } + bool isReadOnlyAuthorized(const char *dbname) { return _isAuthorized( dbname, 1 ); } + protected: + virtual bool _isAuthorized(const char *dbname, int level) { + if( m[dbname].level >= level ) return true; if( noauth ) return true; - if( m["admin"].level == 2 ) return true; - if( m["local"].level == 2 ) return true; + if( m["admin"].level >= level ) return true; + if( m["local"].level >= level ) return true; if( isLocalHost ) { readlock l(""); Client::Context c("admin.system.users"); diff --git a/db/security_commands.cpp b/db/security_commands.cpp index 9d63744045b..ab11adf3bf4 100644 --- a/db/security_commands.cpp +++ b/db/security_commands.cpp @@ -152,7 +152,12 @@ namespace mongo { } AuthenticationInfo *ai = currentClient.get()->ai; - ai->authorize(cc().database()->name.c_str()); + + if ( userObj[ "readOnly" ].isBoolean() && userObj[ "readOnly" ].boolean() ) { + ai->authorizeReadOnly( cc().database()->name.c_str() ); + } else { + ai->authorize( cc().database()->name.c_str() ); + } return true; } } cmdAuthenticate; diff --git a/jstests/auth/auth1.js b/jstests/auth/auth1.js new file mode 100644 index 00000000000..1bb8f8a70ba --- /dev/null +++ b/jstests/auth/auth1.js @@ -0,0 +1,38 @@ +// test read/write permissions + +port = allocatePorts( 1 )[ 0 ]; +baseName = "jstests_auth_auth1"; + +m = startMongod( "--auth", "--port", port, "--dbpath", "/data/db/" + baseName, "--nohttpinterface", "--bind_ip", "127.0.0.1" ); +db = m.getDB( "test" ); + +t = db[ baseName ]; +t.drop(); + +users = db.getCollection( "system.users" ); +users.remove( {} ); + +db.addUser( "eliot" , "eliot" ); +db.addUser( "guest" , "guest", true ); +db.getSisterDB( "admin" ).addUser( "super", "super" ); + +assert.throws( function() { t.findOne() }, [], "read without login" ); + +assert( db.auth( "eliot" , "eliot" ) , "auth failed" ); + +for( i = 0; i < 1000; ++i ) { + t.save( {i:i} ); +} +assert.eq( 1000, t.count() ); +assert.eq( 1000, t.find().toArray().length ); + +assert( db.auth( "guest", "guest" ), "auth failed 2" ); + +assert.eq( 1000, t.count() ); +assert.eq( 1000, t.find().toArray().length ); // make sure we have a getMore in play +assert.commandWorked( db.runCommand( {ismaster:1} ) ); + +assert( !db.getLastError() ); +t.save( {} ); // fail +assert( db.getLastError() ); +assert.eq( 1000, t.count() ); \ No newline at end of file diff --git a/jstests/auth1.js b/jstests/auth1.js index f6890cce8fa..ce0159b17ed 100644 --- a/jstests/auth1.js +++ b/jstests/auth1.js @@ -1,5 +1,3 @@ - - users = db.getCollection( "system.users" ); users.remove( {} ); diff --git a/mongo.xcodeproj/project.pbxproj b/mongo.xcodeproj/project.pbxproj index 6751fb2f574..4f9c69e03e1 100644 --- a/mongo.xcodeproj/project.pbxproj +++ b/mongo.xcodeproj/project.pbxproj @@ -399,7 +399,6 @@ 934DD88D0EFAD23B00459CC1 /* unittest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unittest.h; sourceTree = ""; }; 934DD88E0EFAD23B00459CC1 /* util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = util.cpp; sourceTree = ""; }; 935C941B1106709800439EB1 /* preallocate.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = preallocate.js; sourceTree = ""; }; - 935C9632110794F500439EB1 /* capped6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = capped6.js; sourceTree = ""; }; 936B89590F4C899400934AF2 /* file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file.h; sourceTree = ""; }; 936B895A0F4C899400934AF2 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md5.c; sourceTree = ""; }; 936B895B0F4C899400934AF2 /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = ""; }; @@ -424,6 +423,7 @@ 938E5EB3110E1ED700A8760A /* repair.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repair.js; sourceTree = ""; }; 938E60AB110F721900A8760A /* perdbpath.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = perdbpath.js; sourceTree = ""; }; 938E60AC110F734800A8760A /* directoryperdb.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = directoryperdb.js; sourceTree = ""; }; + 938E639B110FC66900A8760A /* auth1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = auth1.js; sourceTree = ""; }; 93A13A210F4620A500AF1B0D /* commands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = commands.cpp; sourceTree = ""; }; 93A13A230F4620A500AF1B0D /* config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = config.cpp; sourceTree = ""; }; 93A13A240F4620A500AF1B0D /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; @@ -732,7 +732,7 @@ 934BEB9A10DFFA9600178102 /* jstests */ = { isa = PBXGroup; children = ( - 935C9632110794F500439EB1 /* capped6.js */, + 938E639A110FC66900A8760A /* auth */, 93BCE41810F3AF1B00FA139B /* capped2.js */, 93BCE1D310F26CDA00FA139B /* fsync2.js */, 93BCE15610F25DFE00FA139B /* arrayfind1.js */, @@ -1112,6 +1112,14 @@ path = util; sourceTree = ""; }; + 938E639A110FC66900A8760A /* auth */ = { + isa = PBXGroup; + children = ( + 938E639B110FC66900A8760A /* auth1.js */, + ); + path = auth; + sourceTree = ""; + }; 93A13A200F4620A500AF1B0D /* s */ = { isa = PBXGroup; children = ( diff --git a/shell/db.js b/shell/db.js index ab79e22a1bd..3e00c32c4bc 100644 --- a/shell/db.js +++ b/shell/db.js @@ -48,10 +48,12 @@ DB.prototype._adminCommand = function( obj ){ return this.getSisterDB( "admin" ).runCommand( obj ); } -DB.prototype.addUser = function( username , pass ){ +DB.prototype.addUser = function( username , pass, readOnly ){ + readOnly = readOnly || false; var c = this.getCollection( "system.users" ); var u = c.findOne( { user : username } ) || { user : username }; + u.readOnly = readOnly; u.pwd = hex_md5( username + ":mongo:" + pass ); print( tojson( u ) );