From 49f03e2be48dac56c27b220b11cd70676df50f99 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Wed, 26 Nov 2014 23:17:04 +0000 Subject: [PATCH 1/3] Teach dev/run to clear nodes DB This commit makes dev/run replace the nodes DB with an empty DB before adding the nodes doc. This changes the previous behaviour which was to just try adding the docs and ignore conflicts. The change makes it possible for a developer to run `dev/run -n 1` (to spawn a single node cluster) having previously run `dev/run` (which spawns a three node cluster). Without replacing the nodes DB the `dev/run -n 1` single-node cluster would still have two other nodes in the nodes DB and therefore not work correctly. Note that we explicitly delete each node doc rather than deleting the whole database because the cluster membership layer gets upset when the nodes database itself is deleted/recreated. --- dev/run | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dev/run b/dev/run index ada5492f4ed..bce55f164f2 100755 --- a/dev/run +++ b/dev/run @@ -17,6 +17,7 @@ import contextlib import functools import glob import inspect +import json import optparse import os import re @@ -230,6 +231,7 @@ def hashify(pwd, salt=COMMON_SALT, iterations=10, keylen=20): def startup(ctx): atexit.register(kill_processes, ctx) boot_nodes(ctx) + reset_nodes_db(ctx, "127.0.0.1") join_nodes(ctx, "127.0.0.1", 15986) @@ -295,6 +297,27 @@ def boot_node(ctx, node): return sp.Popen(cmd, stdin=sp.PIPE, stdout=log, stderr=sp.STDOUT, env=env) +@log('Reset nodes DB') +def reset_nodes_db(ctx, host): + _, port = get_ports(1) + conn = httpclient.HTTPConnection(host, port) + conn.request('GET', '/nodes/_all_docs') + resp = conn.getresponse() + if not resp.status == 200: + print('Could not read nodes DB documents', resp.reason) + exit(1) + nodes = json.loads(resp.read())['rows'] + for node in nodes: + node_id = node['id'] + rev = node['value']['rev'] + conn = httpclient.HTTPConnection(host, port) + conn.request('DELETE', '/nodes/{0}?rev={1}'.format(node_id, rev)) + resp = conn.getresponse() + if not resp.status == 200: + print('Failed to reset nodes DB', resp.reason) + exit(1) + + @log('Join nodes into cluster') def join_nodes(ctx, host, port): for node in ctx['nodes']: From 23d79b3f606245ab657900fe7b1bc6ef77ace16e Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Thu, 27 Nov 2014 15:13:44 +0000 Subject: [PATCH 2/3] Test _users security on the cluster and admin port This commit modifies the _users DB security test so that it tests the authentication DB against the clustered interface in addition to the admin interface. Previously this test was only being run against the admin port. COUCHDB-2452 --- share/www/script/test/users_db_security.js | 687 +++++++++++---------- 1 file changed, 369 insertions(+), 318 deletions(-) diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js index f2ca8bc7ea4..ae139508f48 100644 --- a/share/www/script/test/users_db_security.js +++ b/share/www/script/test/users_db_security.js @@ -9,100 +9,111 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. - couchTests.users_db_security = function(debug) { - var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); - if (debug) debugger; - - function wait(ms) { - var t0 = new Date(), t1; - do { - CouchDB.request("GET", "/"); - t1 = new Date(); - } while ((t1 - t0) <= ms); - } + function run_test_against_url(url) { + CouchDB.urlPrefix = url; + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + if (debug) debugger; + + function wait(ms) { + var t0 = new Date(), t1; + do { + CouchDB.request("GET", "/"); + t1 = new Date(); + } while ((t1 - t0) <= ms); + } - var loginUser = function(username) { - var pws = { - jan: "apple", - jchris: "mp3", - jchris1: "couch", - fdmanana: "foobar", - benoitc: "test" + var loginUser = function(username) { + var pws = { + jan: "apple", + jchris: "mp3", + jchris1: "couch", + fdmanana: "foobar", + benoitc: "test" + }; + var username1 = username.replace(/[0-9]$/, ""); + var password = pws[username]; + T(CouchDB.login(username1, pws[username]).ok); }; - var username1 = username.replace(/[0-9]$/, ""); - var password = pws[username]; - T(CouchDB.login(username1, pws[username]).ok); - }; - - var open_as = function(db, docId, username) { - loginUser(username); - try { - return db.open(docId, {"anti-cache": Math.round(Math.random() * 100000)}); - } finally { - CouchDB.logout(); - } - }; - - var view_as = function(db, viewname, username) { - loginUser(username); - try { - return db.view(viewname); - } finally { - CouchDB.logout(); - } - }; - - var save_as = function(db, doc, username) - { - loginUser(username); - try { - return db.save(doc); - } catch (ex) { - return ex; - } finally { - CouchDB.logout(); - } - }; - - var changes_as = function(db, username) - { - loginUser(username); - try { - return db.changes(); - } catch(ex) { - return ex; - } finally { - CouchDB.logout(); - } - }; - - var testFun = function() - { - - // _users db - // a doc with a field 'password' should be hashed to 'derived_key' - // with salt and salt stored in 'salt', 'password' is set to null. - // Exising 'derived_key' and 'salt' fields are overwritten with new values - // when a non-null 'password' field exists. - // anonymous should be able to create a user document - var userDoc = { - _id: "org.couchdb.user:jchris", - type: "user", - name: "jchris", - password: "mp3", - roles: [] + + var open_as = function(db, docId, username) { + loginUser(username); + try { + return db.open(docId, {"anti-cache": Math.round(Math.random() * 100000)}); + } finally { + CouchDB.logout(); + } + }; + + var view_as = function(db, viewname, username) { + loginUser(username); + try { + return db.view(viewname); + } finally { + CouchDB.logout(); + } }; - // jan's gonna be admin as he's the first user - TEquals(true, usersDb.save(userDoc).ok, "should save document"); - userDoc = usersDb.open("org.couchdb.user:jchris"); - TEquals(undefined, userDoc.password, "password field should be null 1"); - TEquals(40, userDoc.derived_key.length, "derived_key should exist"); - TEquals(32, userDoc.salt.length, "salt should exist"); + var save_as = function(db, doc, username) + { + loginUser(username); + try { + return db.save(doc); + } catch (ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; + + var changes_as = function(db, username) + { + loginUser(username); + try { + return db.changes(); + } catch(ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; - // create server admin - run_on_modified_server([ + var testFun = function() + { + + // _users db + // a doc with a field 'password' should be hashed to 'derived_key' + // with salt and salt stored in 'salt', 'password' is set to null. + // Exising 'derived_key' and 'salt' fields are overwritten with new values + // when a non-null 'password' field exists. + // anonymous should be able to create a user document + var userDoc = { + _id: "org.couchdb.user:jchris", + type: "user", + name: "jchris", + password: "mp3", + roles: [] + }; + + CouchDB.urlPrefix = url; + // The users DB may or may not exist at this point depending on whether + // the test is being run against the cluster or admin port so use allDocs + // to check + try { + usersDb.allDocs(); + } catch(e) { + usersDb.createDb(); + } + // jan's gonna be admin as he's the first user + TEquals(true, usersDb.save(userDoc).ok, "should save document"); + userDoc = usersDb.open("org.couchdb.user:jchris"); + TEquals(undefined, userDoc.password, "password field should be null 1"); + TEquals(40, userDoc.derived_key.length, "derived_key should exist"); + TEquals(32, userDoc.salt.length, "salt should exist"); + + CouchDB.urlPrefix = ''; + // create server admin + run_on_modified_server([ { section: "couch_httpd_auth", key: "iterations", @@ -115,205 +126,208 @@ couchTests.users_db_security = function(debug) { } ], function() { - // anonymous should not be able to read an existing user's user document - var res = usersDb.open("org.couchdb.user:jchris"); - TEquals(null, res, "anonymous user doc read should be not found"); - - // anonymous should not be able to read /_users/_changes - try { - var ch = usersDb.changes(); - T(false, "anonymous can read _changes"); - } catch(e) { - TEquals("unauthorized", e.error, "anoymous can't read _changes"); - } - - // user should be able to read their own document - var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris"); - TEquals("org.couchdb.user:jchris", jchrisDoc._id); - - // user should not be able to read /_users/_changes - var changes = changes_as(usersDb, "jchris"); - TEquals("unauthorized", changes.error, "user can't read _changes"); - - // new 'password' fields should trigger new hashing routine - jchrisDoc.password = "couch"; - - TEquals(true, save_as(usersDb, jchrisDoc, "jchris").ok); - wait(100); - var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris1"); - - TEquals(undefined, jchrisDoc.password, "password field should be null 2"); - TEquals(40, jchrisDoc.derived_key.length, "derived_key should exist"); - TEquals(32, jchrisDoc.salt.length, "salt should exist"); - - TEquals(true, userDoc.salt != jchrisDoc.salt, "should have new salt"); - TEquals(true, userDoc.derived_key != jchrisDoc.derived_key, - "should have new derived_key"); - - // SHA-1 password hashes are upgraded to PBKDF2 on successful - // authentication - var rnewsonDoc = { - _id: "org.couchdb.user:rnewson", - type: "user", - name: "rnewson", - // password: "plaintext_password", - password_sha: "e29dc3aeed5abf43185c33e479f8998558c59474", - salt: "24f1e0a87c2e374212bda1073107e8ae", - roles: [] - }; + CouchDB.urlPrefix = url; + // anonymous should not be able to read an existing user's user document + var res = usersDb.open("org.couchdb.user:jchris"); + TEquals(null, res, "anonymous user doc read should be not found"); + + // anonymous should not be able to read /_users/_changes + try { + var ch = usersDb.changes(); + T(false, "anonymous can read _changes"); + } catch(e) { + TEquals("unauthorized", e.error, "anoymous can't read _changes"); + } - var password_sha = rnewsonDoc.password_sha, + // user should be able to read their own document + var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris"); + TEquals("org.couchdb.user:jchris", jchrisDoc._id); + + // user should not be able to read /_users/_changes + var changes = changes_as(usersDb, "jchris"); + TEquals("unauthorized", changes.error, "user can't read _changes"); + + // new 'password' fields should trigger new hashing routine + jchrisDoc.password = "couch"; + + TEquals(true, save_as(usersDb, jchrisDoc, "jchris").ok); + wait(5000); // chttpd_auth_cache may not have started listening for changes + var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris1"); + + TEquals(undefined, jchrisDoc.password, "password field should be null 2"); + TEquals(40, jchrisDoc.derived_key.length, "derived_key should exist"); + TEquals(32, jchrisDoc.salt.length, "salt should exist"); + + TEquals(true, userDoc.salt != jchrisDoc.salt, "should have new salt"); + TEquals(true, userDoc.derived_key != jchrisDoc.derived_key, + "should have new derived_key"); + + // SHA-1 password hashes are upgraded to PBKDF2 on successful + // authentication + var rnewsonDoc = { + _id: "org.couchdb.user:rnewson", + type: "user", + name: "rnewson", + // password: "plaintext_password", + password_sha: "e29dc3aeed5abf43185c33e479f8998558c59474", + salt: "24f1e0a87c2e374212bda1073107e8ae", + roles: [] + }; + + var password_sha = rnewsonDoc.password_sha, salt = rnewsonDoc.salt, derived_key, iterations; - usersDb.save(rnewsonDoc); - rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan"); - T(!rnewsonDoc.password_scheme); - T(!rnewsonDoc.derived_key); - T(!rnewsonDoc.iterations); - - // check that we don't upgrade when the password is wrong - TEquals("unauthorized", CouchDB.login("rnewson", "wrong_password").error); - rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan"); - TEquals(salt, rnewsonDoc.salt); - TEquals(password_sha, rnewsonDoc.password_sha); - T(!rnewsonDoc.password_scheme); - T(!rnewsonDoc.derived_key); - T(!rnewsonDoc.iterations); - - TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok); - rnewsonDoc = usersDb.open(rnewsonDoc._id); - TEquals("pbkdf2", rnewsonDoc.password_scheme); - T(rnewsonDoc.salt != salt); - T(!rnewsonDoc.password_sha); - T(rnewsonDoc.derived_key); - T(rnewsonDoc.iterations); - - salt = rnewsonDoc.salt, - derived_key = rnewsonDoc.derived_key, - iterations = rnewsonDoc.iterations; - - // check that authentication is still working - // and everything is staying the same now - CouchDB.logout(); - TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok); - rnewsonDoc = usersDb.open(rnewsonDoc._id); - TEquals("pbkdf2", rnewsonDoc.password_scheme); - TEquals(salt, rnewsonDoc.salt); - T(!rnewsonDoc.password_sha); - TEquals(derived_key, rnewsonDoc.derived_key); - TEquals(iterations, rnewsonDoc.iterations); - - CouchDB.logout(); - - // user should not be able to read another user's user document - var fdmananaDoc = { - _id: "org.couchdb.user:fdmanana", - type: "user", - name: "fdmanana", - password: "foobar", - roles: [] - }; - - usersDb.save(fdmananaDoc); - - var fdmananaDocAsReadByjchris = - open_as(usersDb, "org.couchdb.user:fdmanana", "jchris1"); - TEquals(null, fdmananaDocAsReadByjchris, - "should not_found opening another user's user doc"); + usersDb.save(rnewsonDoc); + rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan"); + T(!rnewsonDoc.password_scheme); + T(!rnewsonDoc.derived_key); + T(!rnewsonDoc.iterations); + + // check that we don't upgrade when the password is wrong + TEquals("unauthorized", CouchDB.login("rnewson", "wrong_password").error); + rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan"); + TEquals(salt, rnewsonDoc.salt); + TEquals(password_sha, rnewsonDoc.password_sha); + T(!rnewsonDoc.password_scheme); + T(!rnewsonDoc.derived_key); + T(!rnewsonDoc.iterations); + + TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok); + rnewsonDoc = usersDb.open(rnewsonDoc._id); + TEquals("pbkdf2", rnewsonDoc.password_scheme); + T(rnewsonDoc.salt != salt); + T(!rnewsonDoc.password_sha); + T(rnewsonDoc.derived_key); + T(rnewsonDoc.iterations); + salt = rnewsonDoc.salt, + derived_key = rnewsonDoc.derived_key, + iterations = rnewsonDoc.iterations; + + // check that authentication is still working + // and everything is staying the same now + CouchDB.logout(); + wait(5000); // Increased because change seems to take a while to propagate to auth cache + TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok); + rnewsonDoc = usersDb.open(rnewsonDoc._id); + TEquals("pbkdf2", rnewsonDoc.password_scheme); + TEquals(salt, rnewsonDoc.salt); + T(!rnewsonDoc.password_sha); + TEquals(derived_key, rnewsonDoc.derived_key); + TEquals(iterations, rnewsonDoc.iterations); + + CouchDB.logout(); + + // user should not be able to read another user's user document + var fdmananaDoc = { + _id: "org.couchdb.user:fdmanana", + type: "user", + name: "fdmanana", + password: "foobar", + roles: [] + }; + + usersDb.save(fdmananaDoc); + + var fdmananaDocAsReadByjchris = + open_as(usersDb, "org.couchdb.user:fdmanana", "jchris1"); + TEquals(null, fdmananaDocAsReadByjchris, + "should not_found opening another user's user doc"); + + + // save a db admin + var benoitcDoc = { + _id: "org.couchdb.user:benoitc", + type: "user", + name: "benoitc", + password: "test", + roles: ["user_admin"] + }; + save_as(usersDb, benoitcDoc, "jan"); + + TEquals(true, CouchDB.login("jan", "apple").ok); + T(usersDb.setSecObj({ + "admins" : { + roles : [], + names : ["benoitc"] + } + }).ok); + CouchDB.logout(); + + // user should not be able to read from any view + var ddoc = { + _id: "_design/user_db_auth", + views: { + test: { + map: "function(doc) { emit(doc._id, null); }" + } + } + }; - // save a db admin - var benoitcDoc = { - _id: "org.couchdb.user:benoitc", - type: "user", - name: "benoitc", - password: "test", - roles: ["user_admin"] - }; - save_as(usersDb, benoitcDoc, "jan"); + save_as(usersDb, ddoc, "jan"); - TEquals(true, CouchDB.login("jan", "apple").ok); - T(usersDb.setSecObj({ - "admins" : { - roles : [], - names : ["benoitc"] + try { + usersDb.view("user_db_auth/test"); + T(false, "user had access to view in admin db"); + } catch(e) { + TEquals("forbidden", e.error, + "non-admins should not be able to read a view"); } - }).ok); - CouchDB.logout(); - - // user should not be able to read from any view - var ddoc = { - _id: "_design/user_db_auth", - views: { - test: { - map: "function(doc) { emit(doc._id, null); }" - } - } - }; - save_as(usersDb, ddoc, "jan"); + // admin should be able to read from any view + var result = view_as(usersDb, "user_db_auth/test", "jan"); + TEquals(4, result.total_rows, "should allow access and list four users to admin"); - try { - usersDb.view("user_db_auth/test"); - T(false, "user had access to view in admin db"); - } catch(e) { - TEquals("forbidden", e.error, - "non-admins should not be able to read a view"); - } + // db admin should be able to read from any view + var result = view_as(usersDb, "user_db_auth/test", "benoitc"); + TEquals(4, result.total_rows, "should allow access and list four users to db admin"); - // admin should be able to read from any view - var result = view_as(usersDb, "user_db_auth/test", "jan"); - TEquals(4, result.total_rows, "should allow access and list four users to admin"); - // db admin should be able to read from any view - var result = view_as(usersDb, "user_db_auth/test", "benoitc"); - TEquals(4, result.total_rows, "should allow access and list four users to db admin"); + // non-admins can't read design docs + try { + open_as(usersDb, "_design/user_db_auth", "jchris1"); + T(false, "non-admin read design doc, should not happen"); + } catch(e) { + TEquals("forbidden", e.error, "non-admins can't read design docs"); + } + // admin should be able to read and edit any user doc + fdmananaDoc.password = "mobile"; - // non-admins can't read design docs - try { - open_as(usersDb, "_design/user_db_auth", "jchris1"); - T(false, "non-admin read design doc, should not happen"); - } catch(e) { - TEquals("forbidden", e.error, "non-admins can't read design docs"); - } + var result = save_as(usersDb, fdmananaDoc, "jan"); + TEquals(true, result.ok, "admin should be able to update any user doc"); - // admin should be able to read and edit any user doc - fdmananaDoc.password = "mobile"; - var result = save_as(usersDb, fdmananaDoc, "jan"); - TEquals(true, result.ok, "admin should be able to update any user doc"); - - // admin should be able to read and edit any user doc - fdmananaDoc.password = "mobile1"; - var result = save_as(usersDb, fdmananaDoc, "benoitc"); - TEquals(true, result.ok, "db admin by role should be able to update any user doc"); - - TEquals(true, CouchDB.login("jan", "apple").ok); - T(usersDb.setSecObj({ - "admins" : { - roles : ["user_admin"], - names : [] - } - }).ok); - CouchDB.logout(); + // admin should be able to read and edit any user doc + fdmananaDoc.password = "mobile1"; + var result = save_as(usersDb, fdmananaDoc, "benoitc"); + TEquals(true, result.ok, "db admin by role should be able to update any user doc"); - // db admin should be able to read and edit any user doc - fdmananaDoc.password = "mobile2"; - var result = save_as(usersDb, fdmananaDoc, "benoitc"); - TEquals(true, result.ok, "db admin should be able to update any user doc"); + TEquals(true, CouchDB.login("jan", "apple").ok); + T(usersDb.setSecObj({ + "admins" : { + roles : ["user_admin"], + names : [] + } + }).ok); + CouchDB.logout(); - // ensure creation of old-style docs still works - var robertDoc = CouchDB.prepareUserDoc({ name: "robert" }, "anchovy"); - var result = usersDb.save(robertDoc); - TEquals(true, result.ok, "old-style user docs should still be accepted"); + // db admin should be able to read and edit any user doc + fdmananaDoc.password = "mobile2"; + var result = save_as(usersDb, fdmananaDoc, "benoitc"); + TEquals(true, result.ok, "db admin should be able to update any user doc"); - // log in one last time so run_on_modified_server can clean up the admin account - TEquals(true, CouchDB.login("jan", "apple").ok); - }); + // ensure creation of old-style docs still works + var robertDoc = CouchDB.prepareUserDoc({ name: "robert" }, "anchovy"); + var result = usersDb.save(robertDoc); + TEquals(true, result.ok, "old-style user docs should still be accepted"); - run_on_modified_server([ + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + + run_on_modified_server([ { section: "couch_httpd_auth", key: "iterations", @@ -329,6 +343,21 @@ couchTests.users_db_security = function(debug) { key: "users_db_public", value: "true" }, + { + section: "chttpd_auth", + key: "iterations", + value: "1" + }, + { + section: "chttpd_auth", + key: "public_fields", + value: "name,type" + }, + { + section: "chttpd_auth", + key: "users_db_public", + value: "true" + }, { section: "admins", key: "jan", @@ -363,61 +392,83 @@ couchTests.users_db_security = function(debug) { } })); } - // log in one last time so run_on_modified_server can clean up the admin account - TEquals(true, CouchDB.login("jan", "apple").ok); - }); - - run_on_modified_server([ - { - section: "couch_httpd_auth", - key: "iterations", - value: "1" - }, - { - section: "couch_httpd_auth", - key: "public_fields", - value: "name" - }, - { - section: "couch_httpd_auth", - key: "users_db_public", - value: "false" - }, - { - section: "admins", - key: "jan", - value: "apple" - } - ], function() { - TEquals(true, CouchDB.login("jchris", "couch").ok); + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); + }); - try { - var all = usersDb.allDocs({ include_docs: true }); - T(false); // should never hit - } catch(e) { - TEquals("forbidden", e.error, "should throw"); - } + run_on_modified_server([ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "couch_httpd_auth", + key: "public_fields", + value: "name" + }, + { + section: "couch_httpd_auth", + key: "users_db_public", + value: "false" + }, + { + section: "chttpd_auth", + key: "iterations", + value: "1" + }, + { + section: "chttpd_auth", + key: "public_fields", + value: "name" + }, + { + section: "chttpd_auth", + key: "users_db_public", + value: "false" + }, + { + section: "admins", + key: "jan", + value: "apple" + } + ], function() { + TEquals(true, CouchDB.login("jchris", "couch").ok); - // COUCHDB-1888 make sure admins always get all fields - TEquals(true, CouchDB.login("jan", "apple").ok); - var all_admin = usersDb.allDocs({ include_docs: "true" }); - TEquals("user", all_admin.rows[2].doc.type, - "should return type"); + try { + var all = usersDb.allDocs({ include_docs: true }); + T(false); // should never hit + } catch(e) { + TEquals("forbidden", e.error, "should throw"); + } + // COUCHDB-1888 make sure admins always get all fields + TEquals(true, CouchDB.login("jan", "apple").ok); + var all_admin = usersDb.allDocs({ include_docs: "true" }); + TEquals("user", all_admin.rows[2].doc.type, + "should return type"); - // log in one last time so run_on_modified_server can clean up the admin account - TEquals(true, CouchDB.login("jan", "apple").ok); - }); - }; - usersDb.deleteDb(); - run_on_modified_server( - [{section: "couch_httpd_auth", - key: "iterations", value: "1"}, - {section: "couch_httpd_auth", - key: "authentication_db", value: usersDb.name}], - testFun - ); - usersDb.deleteDb(); // cleanup + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + }; + + usersDb.deleteDb(); + run_on_modified_server( + [{section: "couch_httpd_auth", + key: "iterations", value: "1"}, + {section: "couch_httpd_auth", + key: "authentication_db", value: usersDb.name}, + {section: "chttpd_auth", + key: "authentication_db", value: usersDb.name}], + testFun + ); + usersDb.deleteDb(); // cleanup + } + // Run the test suite against the admin port + run_test_against_url("http://127.0.0.1:15986"); + // Run the test suite against the cluster port + run_test_against_url("http://127.0.0.1:15984"); }; From ed4a4a9e2bfbc906bc2339007fe7ef13137eba88 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Mon, 1 Dec 2014 13:22:46 +0000 Subject: [PATCH 3/3] Make users_db_security.js use N=1 clusters only The users_db_security.js test will not work against a multi-node cluster because it relies on config settings being made by the test code. Because there is no generic way of discovering the locations of the other nodes on a dev cluster (they may be on unexpected ports for one reason or another) it is only possible to guarantee those settings are made on a single node. This commit therefore forces the users_db_security.js test to run against a single node cluster by: - setting the cluster variables to N=Q=R=W=1 - excluding the test in the Makefile and running it explicitly with `dev/run -n 1` - teaching run_on_modified_server to correctly preserve the old config settings when nested run_on_modified_server calls are made COUCHDB-2452 --- Makefile | 1 + share/www/script/couch_test_runner.js | 8 ++++ share/www/script/test/users_db_security.js | 43 ++++++++++++++++++---- test/javascript/run | 7 +++- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 986d2dc9521..7f57830d302 100644 --- a/Makefile +++ b/Makefile @@ -69,3 +69,4 @@ eunit: compile javascript: compile @dev/run -q test/javascript/run + @dev/run -n 1 -q test/javascript/run share/www/script/test/users_db_security.js diff --git a/share/www/script/couch_test_runner.js b/share/www/script/couch_test_runner.js index efc4dc24272..0617efd184e 100644 --- a/share/www/script/couch_test_runner.js +++ b/share/www/script/couch_test_runner.js @@ -363,6 +363,14 @@ function makeDocs(start, end, templateDoc) { } function run_on_modified_server(settings, fun) { + // Clone settings so we don't overwrite oldValue when making nested run_on_modified_server calls + var settings = settings.map(function(s) { + return { + section: s.section, + key: s.key, + value: s.value + }; + }); try { // set the settings for(var i=0; i < settings.length; i++) { diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js index ae139508f48..3eb446cf33f 100644 --- a/share/www/script/test/users_db_security.js +++ b/share/www/script/test/users_db_security.js @@ -10,7 +10,34 @@ // License for the specific language governing permissions and limitations under // the License. couchTests.users_db_security = function(debug) { + var clusterVars = [{ + section: "cluster", + key: "q", + value: "1" + }, + { + section: "cluster", + key: "n", + value: "1" + }, + { + section: "cluster", + key: "w", + value: "1" + }, + { + section: "cluster", + key: "r", + value: "1" + } + ]; function run_test_against_url(url) { + // Ensure users DB is created with desired Q/N + run_on_modified_server(clusterVars, function() { + CouchDB.urlPrefix = url; + new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + }); + CouchDB.urlPrefix = url; var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); if (debug) debugger; @@ -113,7 +140,7 @@ couchTests.users_db_security = function(debug) { CouchDB.urlPrefix = ''; // create server admin - run_on_modified_server([ + run_on_modified_server(clusterVars.concat([ { section: "couch_httpd_auth", key: "iterations", @@ -124,7 +151,7 @@ couchTests.users_db_security = function(debug) { key: "jan", value: "apple" } - ], function() { + ]), function() { CouchDB.urlPrefix = url; // anonymous should not be able to read an existing user's user document @@ -327,7 +354,7 @@ couchTests.users_db_security = function(debug) { TEquals(true, CouchDB.login("jan", "apple").ok); }); - run_on_modified_server([ + run_on_modified_server(clusterVars.concat([ { section: "couch_httpd_auth", key: "iterations", @@ -363,7 +390,7 @@ couchTests.users_db_security = function(debug) { key: "jan", value: "apple" } - ], function() { + ]), function() { var res = usersDb.open("org.couchdb.user:jchris"); TEquals("jchris", res.name); TEquals("user", res.type); @@ -396,7 +423,7 @@ couchTests.users_db_security = function(debug) { TEquals(true, CouchDB.login("jan", "apple").ok); }); - run_on_modified_server([ + run_on_modified_server(clusterVars.concat([ { section: "couch_httpd_auth", key: "iterations", @@ -432,7 +459,7 @@ couchTests.users_db_security = function(debug) { key: "jan", value: "apple" } - ], function() { + ]), function() { TEquals(true, CouchDB.login("jchris", "couch").ok); try { @@ -455,13 +482,13 @@ couchTests.users_db_security = function(debug) { }; usersDb.deleteDb(); - run_on_modified_server( + run_on_modified_server(clusterVars.concat( [{section: "couch_httpd_auth", key: "iterations", value: "1"}, {section: "couch_httpd_auth", key: "authentication_db", value: usersDb.name}, {section: "chttpd_auth", - key: "authentication_db", value: usersDb.name}], + key: "authentication_db", value: usersDb.name}]), testFun ); usersDb.deleteDb(); // cleanup diff --git a/test/javascript/run b/test/javascript/run index ab145b1082b..4b272ba2513 100755 --- a/test/javascript/run +++ b/test/javascript/run @@ -37,6 +37,10 @@ SCRIPTS = """ test/javascript/test_setup.js """.split() +EXCLUDE = """ + share/www/script/test/users_db_security.js +""".split() + RUNNER = "test/javascript/cli_runner.js" @@ -109,7 +113,8 @@ def main(): args = ["share/www/script/test"] for name in args: if os.path.isdir(name): - tests.extend(glob.glob(os.path.join(name, "*.js"))) + tests_in_dir = glob.glob(os.path.join(name, "*.js")) + tests.extend([t for t in tests_in_dir if not t in EXCLUDE]) elif os.path.isfile(name): tests.append(name) else: