Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B983792C2 for ; Tue, 3 Jan 2012 19:34:34 +0000 (UTC) Received: (qmail 41478 invoked by uid 500); 3 Jan 2012 19:34:34 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 41419 invoked by uid 500); 3 Jan 2012 19:34:34 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 41282 invoked by uid 99); 3 Jan 2012 19:34:34 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 03 Jan 2012 19:34:34 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.114] (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 03 Jan 2012 19:34:29 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id D8DEB315798; Tue, 3 Jan 2012 19:33:21 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jan@apache.org To: commits@couchdb.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [6/11] git commit: JavaScript tests for System Database Security Message-Id: <20120103193321.D8DEB315798@tyr.zones.apache.org> Date: Tue, 3 Jan 2012 19:33:21 +0000 (UTC) X-Virus-Checked: Checked by ClamAV on apache.org JavaScript tests for System Database Security Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/b184aa13 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/b184aa13 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/b184aa13 Branch: refs/heads/master Commit: b184aa13425283a72e69de6afa2a7d54791e4357 Parents: e5503ff Author: Jan Lehnardt Authored: Thu Dec 22 18:45:23 2011 +0100 Committer: Jan Lehnardt Committed: Tue Jan 3 19:21:24 2012 +0100 ---------------------------------------------------------------------- share/Makefile.am | 2 + share/www/script/couch.js | 12 +- share/www/script/couch_test_runner.js | 5 + share/www/script/couch_tests.js | 2 + share/www/script/test/cookie_auth.js | 82 +++-- share/www/script/test/replicator_db_security.js | 395 ++++++++++++++++++ share/www/script/test/users_db_security.js | 238 +++++++++++ 7 files changed, 703 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/Makefile.am ---------------------------------------------------------------------- diff --git a/share/Makefile.am b/share/Makefile.am index 6ea910e..2ef679a 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -174,6 +174,7 @@ nobase_dist_localdata_DATA = \ www/script/test/reduce_false_temp.js \ www/script/test/replication.js \ www/script/test/replicator_db.js \ + www/script/test/replicator_db_security.js \ www/script/test/rev_stemming.js \ www/script/test/rewrite.js \ www/script/test/security_validation.js \ @@ -181,6 +182,7 @@ nobase_dist_localdata_DATA = \ www/script/test/stats.js \ www/script/test/update_documents.js \ www/script/test/users_db.js \ + www/script/test/users_db_security.js \ www/script/test/utf8.js \ www/script/test/uuids.js \ www/script/test/view_collation.js \ http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/www/script/couch.js ---------------------------------------------------------------------- diff --git a/share/www/script/couch.js b/share/www/script/couch.js index a381d31..982f4d4 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -45,14 +45,14 @@ function CouchDB(name, httpHeaders) { }; // Save a document to the database - this.save = function(doc, options) { + this.save = function(doc, options, http_headers) { if (doc._id == undefined) { doc._id = CouchDB.newUuids(1)[0]; } - + http_headers = http_headers || {}; this.last_req = this.request("PUT", this.uri + encodeURIComponent(doc._id) + encodeOptions(options), - {body: JSON.stringify(doc)}); + {body: JSON.stringify(doc), headers: http_headers}); CouchDB.maybeThrowError(this.last_req); var result = JSON.parse(this.last_req.responseText); doc._rev = result.rev; @@ -60,9 +60,9 @@ function CouchDB(name, httpHeaders) { }; // Open a document from the database - this.open = function(docId, options) { + this.open = function(docId, url_params, http_headers) { this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) - + encodeOptions(options)); + + encodeOptions(url_params), {headers:http_headers}); if (this.last_req.status == 404) { return null; } @@ -218,7 +218,7 @@ function CouchDB(name, httpHeaders) { }; this.changes = function(options) { - this.last_req = this.request("GET", this.uri + "_changes" + this.last_req = this.request("GET", this.uri + "_changes" + encodeOptions(options)); CouchDB.maybeThrowError(this.last_req); return JSON.parse(this.last_req.responseText); http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/www/script/couch_test_runner.js ---------------------------------------------------------------------- diff --git a/share/www/script/couch_test_runner.js b/share/www/script/couch_test_runner.js index 61823c0..db0b8de 100644 --- a/share/www/script/couch_test_runner.js +++ b/share/www/script/couch_test_runner.js @@ -313,6 +313,11 @@ function T(arg1, arg2, testName) { } } +function TIsnull(actual, testName) { + T(actual === null, "expected 'null', got '" + + repr(actual) + "'", testName); +} + function TEquals(expected, actual, testName) { T(equals(expected, actual), "expected '" + repr(expected) + "', got '" + repr(actual) + "'", testName); http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/www/script/couch_tests.js ---------------------------------------------------------------------- diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index c890f68..a6d4b56 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -68,6 +68,7 @@ loadTest("reduce_false.js"); loadTest("reduce_false_temp.js"); loadTest("replication.js"); loadTest("replicator_db.js"); +loadTest("replicator_db_security.js"); loadTest("rev_stemming.js"); loadTest("rewrite.js"); loadTest("security_validation.js"); @@ -75,6 +76,7 @@ loadTest("show_documents.js"); loadTest("stats.js"); loadTest("update_documents.js"); loadTest("users_db.js"); +loadTest("users_db_security.js"); loadTest("utf8.js"); loadTest("uuids.js"); loadTest("view_collation.js"); http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/www/script/test/cookie_auth.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js index ffedba7..f252524 100644 --- a/share/www/script/test/cookie_auth.js +++ b/share/www/script/test/cookie_auth.js @@ -18,6 +18,41 @@ couchTests.cookie_auth = function(debug) { db.createDb(); if (debug) debugger; + var password = "3.141592653589"; + + var loginUser = function(username) { + var pws = { + jan: "apple", + "Jason Davies": password, + jchris: "funnybone" + }; + var username1 = username.replace(/[0-9]$/, ""); + var password = pws[username]; + //console.log("Logging in '" + username1 + "' with password '" + password + "'"); + 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 save_as = function(db, doc, username) + { + loginUser(username); + try { + return db.save(doc); + } catch (ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; + // Simple secret key generator function generateSecret(length) { var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -31,27 +66,20 @@ couchTests.cookie_auth = function(debug) { // this function will be called on the modified server var testFun = function () { try { - // try using an invalid cookie - var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); - usersDb.deleteDb(); - usersDb.createDb(); // test that the users db is born with the auth ddoc - var ddoc = usersDb.open("_design/_auth"); + var ddoc = open_as(usersDb, "_design/_auth", "jan"); T(ddoc.validate_doc_update); // TODO test that changing the config so an existing db becomes the users db installs the ddoc also - var password = "3.141592653589"; - // Create a user var jasonUserDoc = CouchDB.prepareUserDoc({ - name: "Jason Davies", - roles: ["dev"] + name: "Jason Davies" }, password); T(usersDb.save(jasonUserDoc).ok); - var checkDoc = usersDb.open(jasonUserDoc._id); + var checkDoc = open_as(usersDb, jasonUserDoc._id, "jan"); T(checkDoc.name == "Jason Davies"); var jchrisUserDoc = CouchDB.prepareUserDoc({ @@ -185,21 +213,16 @@ couchTests.cookie_auth = function(debug) { } T(CouchDB.logout().ok); - T(CouchDB.session().userCtx.roles[0] == "_admin"); jchrisUserDoc.foo = ["foo"]; - T(usersDb.save(jchrisUserDoc).ok); + T(save_as(usersDb, jchrisUserDoc, "jan")); // test that you can't save system (underscore) roles even if you are admin jchrisUserDoc.roles = ["_bar"]; - try { - usersDb.save(jchrisUserDoc); - T(false && "Can't add system roles to user's db. Should have thrown an error."); - } catch (e) { - T(e.error == "forbidden"); - T(usersDb.last_req.status == 403); - } + var res = save_as(usersDb, jchrisUserDoc, "jan"); + T(res.error == "forbidden"); + T(usersDb.last_req.status == 403); // make sure the foo role has been applied T(CouchDB.login("jchris@apache.org", "funnybone").ok); @@ -209,11 +232,11 @@ couchTests.cookie_auth = function(debug) { // now let's make jchris a server admin T(CouchDB.logout().ok); - T(CouchDB.session().userCtx.roles[0] == "_admin"); - T(CouchDB.session().userCtx.name == null); // set the -hashed- password so the salt matches // todo ask on the ML about this + + TEquals(true, CouchDB.login("jan", "apple").ok); run_on_modified_server([{section: "admins", key: "jchris@apache.org", value: "funnybone"}], function() { T(CouchDB.login("jchris@apache.org", "funnybone").ok); @@ -243,16 +266,21 @@ couchTests.cookie_auth = function(debug) { // Make sure we erase any auth cookies so we don't affect other tests T(CouchDB.logout().ok); } + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); }; + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + usersDb.deleteDb(); + usersDb.createDb(); + run_on_modified_server( - [{section: "httpd", - key: "authentication_handlers", - value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"}, - {section: "couch_httpd_auth", - key: "secret", value: generateSecret(64)}, + [ {section: "couch_httpd_auth", - key: "authentication_db", value: "test_suite_users"}], + key: "authentication_db", value: "test_suite_users"}, + {section: "admins", + key: "jan", value: "apple"} + ], testFun ); http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/www/script/test/replicator_db_security.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/replicator_db_security.js b/share/www/script/test/replicator_db_security.js new file mode 100644 index 0000000..67390cf --- /dev/null +++ b/share/www/script/test/replicator_db_security.js @@ -0,0 +1,395 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// 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.replicator_db_security = function(debug) { + var dbs = ["couch_test_rep_db", "couch_test_users_db", + "test_suite_db_a", "test_suite_db_b", "test_suite_db_c"] + .map(function(db_name) { + var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + return db; + }); + + var repDb = dbs[0]; + var usersDb = dbs[1]; + var dbA = dbs[2]; + var dbB = dbs[3]; + var dbC = dbs[4]; + + if (debug) debugger; + + var loginUser = function(username) { + var pws = { + jan: "apple", + jchris: "mp3", + fdmanana: "foobar", + benoitc: "test" + }; + T(CouchDB.login(username, pws[username]).ok); + }; + + var repChanges = function(username) { + var pws = { + jan: "apple", + jchris: "mp3", + fdmanana: "foobar", + benoitc: "test" + }; + T(CouchDB.login(username, pws[username]).ok); + var changes = CouchDB.request( + "GET", + "/" + repDb.name + "/_changes?include_docs=true" + + "&anti-cache=" + String(Math.round(Math.random() * 100000))); + return changes = JSON.parse(changes.responseText); + }; + + var save_as = function(db, doc, username) + { + loginUser(username); + try { + return db.save(doc); + } catch (ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; + + var open_as = function(db, docId, username) { + loginUser(username); + try { + return db.open(docId); + } finally { + CouchDB.logout(); + } + }; + + // from test replicator_db.js + function waitForDocPos(db, docId, pos) { + var doc, curPos, t0, t1, + maxWait = 3000; + + doc = db.open(docId); + curPos = Number(doc._rev.split("-", 1)); + t0 = t1 = new Date(); + + while ((curPos < pos) && ((t1 - t0) <= maxWait)) { + doc = db.open(docId); + curPos = Number(doc._rev.split("-", 1)); + t1 = new Date(); + } + + return doc; + } + + var testFun = function() + { + // _replicator db + // in admin party mode, anonymous should be able to create a replication + var repDoc = { + _id: "null-owner-rep", + source: dbA.name, + target: dbB.name + }; + var result = repDb.save(repDoc); + TEquals(true, result.ok, "should allow anonymous replication docs in admin party"); + // new docs should get an owner field enforced. In admin party mode owner is null + repDoc = repDb.open(repDoc._id); + TIsnull(repDoc.owner, "owner should be null in admin party"); + +// Uncomment when _users database security changes are implemented. +// +// var jchrisDoc = { +// _id: "org.couchdb.user:jchris", +// type: "user", +// name: "jchris", +// password: "mp3", +// roles: [] +// }; + var jchrisDoc = CouchDB.prepareUserDoc({ + name: "jchris", + roles: [] + }, "mp3"); + usersDb.save(jchrisDoc); // set up a non-admin user + +// Uncomment when _users database security changes are implemented. +// +// var jchrisDoc = { +// _id: "org.couchdb.user:fdmanana", +// type: "user", +// name: "fdmanana", +// password: "foobar", +// roles: [] +// }; + var fdmananaDoc = CouchDB.prepareUserDoc({ + name: "fdmanana", + roles: [] + }, "foobar"); + usersDb.save(fdmananaDoc); // set up a non-admin user + +// Uncomment when _users database security changes are implemented. +// +// var benoitcDoc = { +// _id: "org.couchdb.user:fdmanana", +// type: "user", +// name: "fdmanana", +// password: "foobar", +// roles: [] +// }; + var benoitcDoc = CouchDB.prepareUserDoc({ + name: "benoitc", + roles: [] + }, "test"); + usersDb.save(benoitcDoc); // set up a non-admin user + + T(repDb.setSecObj({ + "admins" : { + roles : [], + names : ["benoitc"] + } + }).ok); + + run_on_modified_server([ + { + section: "admins", + key: "jan", + value: "apple" + } + ], function() { + // replication docs from admin-party mode in non-admin party mode can not + // be edited by non-admins (non-server admins) + repDoc = repDb.open(repDoc._id); + repDoc.target = dbC.name; + var result = save_as(repDb, repDoc, "jchris"); + TEquals("forbidden", result.error, "should forbid editing null-owner docs"); + + // replication docs from admin-party mode in non-admin party mode can only + // be edited by admins (server admins) + repDoc = waitForDocPos(repDb, repDoc._id, 3); + repDoc.target = dbC.name; + var result = save_as(repDb, repDoc, "jan"); + repDoc = open_as(repDb, repDoc._id, "jchris"); + TEquals(true, result.ok, "should allow editing null-owner docs to admins"); + TEquals("jan", repDoc.owner, "owner should be the admin now"); + + // user can update their own replication docs (repDoc.owner) + var jchrisRepDoc = { + _id: "jchris-rep-doc", + source: dbC.name, + target: dbA.name, + user_ctx: { name: "jchris", roles: [] } + }; + + var result = save_as(repDb, jchrisRepDoc, "jchris"); + TEquals(true, result.ok, "should create rep doc"); + jchrisRepDoc = repDb.open(jchrisRepDoc._id); + TEquals("jchris", jchrisRepDoc.owner, "should assign correct owner"); + jchrisRepDoc = waitForDocPos(repDb, jchrisRepDoc._id, 3); + jchrisRepDoc = open_as(repDb, jchrisRepDoc._id, "jchris"); + jchrisRepDoc.target = dbB.name; + var result = save_as(repDb, jchrisRepDoc, "jchris"); + TEquals(true, result.ok, "should allow update of rep doc"); + + // user should not be able to read from any view + var ddoc = { + _id: "_design/reps", + views: { + test: { + map: "function(doc) {" + + "if (doc._replication_state) { " + + "emit(doc._id, doc._replication_state);" + + "}" + + "}" + } + } + }; + + save_as(repDb, ddoc, "jan"); + + try { + repDb.view("reps/test"); + T(false, "non-admin had view read access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to read a view"); + } + + // admin should be able to read from any view + TEquals(true, CouchDB.login("jan", "apple").ok); + var result = repDb.view("reps/test"); + CouchDB.logout(); + TEquals(2, result.total_rows, "should allow access and list two users"); + + // test _all_docs, only available for _admins + try { + repDb.allDocs({include_docs: true}); + T(false, "non-admin had _all_docs access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to access _all_docs"); + } + + TEquals(true, CouchDB.login("jan", "apple").ok); + try { + repDb.allDocs({include_docs: true}); + } catch (ex) { + T(false, "admin couldn't access _all_docs"); + } + CouchDB.logout(); + + try { + repDb.view("reps/test"); + T(false, "non-admin had view read access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to read a view"); + } + + // admin should be able to read from any view + TEquals(true, CouchDB.login("benoitc", "test").ok); + var result = repDb.view("reps/test"); + CouchDB.logout(); + TEquals(2, result.total_rows, "should allow access and list two users"); + + // test _all_docs, only available for _admins + try { + repDb.allDocs({include_docs: true}); + T(false, "non-admin had _all_docs access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to access _all_docs"); + } + + TEquals(true, CouchDB.login("benoitc", "test").ok); + try { + repDb.allDocs({include_docs: true}); + } catch (ex) { + T(false, "admin couldn't access _all_docs"); + } + CouchDB.logout(); + + // Verify that users can't access credentials in the "source" and + // "target" fields of replication documents owned by other users. + var fdmananaRepDoc = { + _id: "fdmanana-rep-doc", + source: "http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + target: dbA.name, + user_ctx: { name: "fdmanana", roles: [] } + }; + + var result = save_as(repDb, fdmananaRepDoc, "fdmanana"); + TEquals(true, result.ok, "should create rep doc"); + waitForDocPos(repDb, fdmananaRepDoc._id, 3); + fdmananaRepDoc = open_as(repDb, fdmananaRepDoc._id, "fdmanana"); + TEquals("fdmanana", fdmananaRepDoc.owner, "should assign correct owner"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + fdmananaRepDoc.source, "source field has credentials"); + + fdmananaRepDoc = open_as(repDb, fdmananaRepDoc._id, "jchris"); + TEquals("fdmanana", fdmananaRepDoc.owner, "should assign correct owner"); + TEquals("http://" + CouchDB.host + "/" + dbC.name, + fdmananaRepDoc.source, "source field doesn't contain credentials"); + + // _changes?include_docs=true, users shouldn't be able to see credentials + // in documents owned by other users. + var changes = repChanges("jchris"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://" + CouchDB.host + "/" + dbC.name, + doc.source, "source field doesn't contain credentials (doc from _changes)"); + CouchDB.logout(); + + // _changes?include_docs=true, user should be able to see credentials + // in documents they own. + var changes = repChanges("fdmanana"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + doc.source, "source field contains credentials (doc from _changes)"); + CouchDB.logout(); + + // _changes?include_docs=true, admins should be able to see credentials + // from all documents. + var changes = repChanges("jan"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + doc.source, "source field contains credentials (doc from _changes)"); + CouchDB.logout(); + + // _changes?include_docs=true, db admins should be able to see credentials + // from all documents. + var changes = repChanges("benoitc"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + doc.source, "source field contains credentials (doc from _changes)"); + CouchDB.logout(); + + var fdmananaRepDocOAuth = { + _id: "fdmanana-rep-doc-oauth", + source: dbC.name, + target: { + url: "http://" + CouchDB.host + "/" + dbA.name, + oauth: { + token: "abc", + token_secret: "foo", + consumer_key: "123", + consumer_secret: "321" + } + }, + user_ctx: { name: "fdmanana", roles: [] } + }; + + var result = save_as(repDb, fdmananaRepDocOAuth, "fdmanana"); + TEquals(true, result.ok, "should create rep doc"); + waitForDocPos(repDb, fdmananaRepDocOAuth._id, 3); + fdmananaRepDocOAuth = open_as(repDb, fdmananaRepDocOAuth._id, "fdmanana"); + TEquals("fdmanana", fdmananaRepDocOAuth.owner, "should assign correct owner"); + TEquals("object", typeof fdmananaRepDocOAuth.target.oauth, + "target field has oauth credentials"); + + fdmananaRepDocOAuth = open_as(repDb, fdmananaRepDocOAuth._id, "jchris"); + TEquals("fdmanana", fdmananaRepDocOAuth.owner, "should assign correct owner"); + TEquals("undefined", typeof fdmananaRepDocOAuth.target.oauth, + "target field doesn't have oauth credentials"); + + // ensure "old" replicator docs still work + // done in replicator_db.js? + + // Login as admin so run_on_modified_server can do its cleanup. + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + }; + + usersDb.deleteDb(); + repDb.deleteDb(); + + run_on_modified_server([ + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + }, + { + section: "replicator", + key: "db", + value: repDb.name + }], + testFun + ); + + // cleanup + usersDb.deleteDb(); + repDb.deleteDb(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/b184aa13/share/www/script/test/users_db_security.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js new file mode 100644 index 0000000..b3968b1 --- /dev/null +++ b/share/www/script/test/users_db_security.js @@ -0,0 +1,238 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// 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; + + + 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]; + //console.log("Logging in '" + username1 + "' with password '" + password + "'"); + 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 testFun = function() + { + usersDb.deleteDb(); + + // _users db + // a doc with a field 'password' should be hashed to 'password_sha' + // with salt and salt stored in 'salt', 'password' is set to null. + // Exising 'password_sha' 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: [] + }; + + // 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"); + console.log(userDoc); + TEquals(undefined, userDoc.password, "password field should be null 1"); + TEquals(40, userDoc.password_sha.length, "password_sha should exist"); + TEquals(32, userDoc.salt.length, "salt should exist"); + + // create server admin + run_on_modified_server([ + { + section: "admins", + key: "jan", + value: "apple" + } + ], 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"); + + // 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 bt able to update their own document + // new 'password' fields should trigger new hashing routine + jchrisDoc.password = "couch"; + + TEquals(true, save_as(usersDb, jchrisDoc, "jchris").ok); + var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris1"); + + TEquals(undefined, jchrisDoc.password, "password field should be null 2"); + TEquals(40, jchrisDoc.password_sha.length, "password_sha should exist"); + TEquals(32, jchrisDoc.salt.length, "salt should exist"); + + TEquals(true, userDoc.salt != jchrisDoc.salt, "should have new salt"); + TEquals(true, userDoc.password_sha != jchrisDoc.password_sha, + "should have new password_sha"); + + // 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 amin + 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_as(usersDb, ddoc, "jan"); + + 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"); + } + + // admin should be able to read from any view + var result = view_as(usersDb, "user_db_auth/test", "jan"); + TEquals(3, result.total_rows, "should allow access and list two users to admin"); + + // db admin should be able to read from any view + var result = view_as(usersDb, "user_db_auth/test", "benoitc"); + TEquals(3, result.total_rows, "should allow access and list two 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"); + } + + console.log(fdmananaDoc); + // 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"); + + console.log(fdmananaDoc); + // 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(); + + // 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"); + + // 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"); + + // 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: "authentication_db", value: usersDb.name}], + testFun + ); + usersDb.deleteDb(); // cleanup + +};