You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2012/01/03 20:33:21 UTC

[4/11] git commit: JavaScript tests for System Database Security

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/554959ca
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/554959ca
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/554959ca

Branch: refs/heads/1.2.x
Commit: 554959cad656b8a909f3e499565b2a3dc926f2b0
Parents: 1386bfe
Author: Jan Lehnardt <ja...@apache.org>
Authored: Thu Dec 22 15:52:56 2011 +0100
Committer: Jan Lehnardt <ja...@apache.org>
Committed: Tue Jan 3 19:23:59 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      |  187 ++++++++-
 7 files changed, 647 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/554959ca/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/554959ca/share/www/script/couch.js
----------------------------------------------------------------------
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index 75cae8e..2ee5cbf 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/554959ca/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/554959ca/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/554959ca/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 180d42e..59459d5 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({
@@ -182,21 +210,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);
@@ -206,11 +229,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);
@@ -240,16 +263,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/554959ca/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/554959ca/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
index 8ea93db..811ea7f 100644
--- a/share/www/script/test/users_db_security.js
+++ b/share/www/script/test/users_db_security.js
@@ -14,6 +14,7 @@ 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",
@@ -61,8 +62,53 @@ couchTests.users_db_security = function(debug) {
   var testFun = function()
   {
     usersDb.deleteDb();
-    usersDb.createDb();
 
+	if (debug) debugger;
+
+    var loginUser = function(username) {
+    var pws = {
+      jan: "apple",
+      jchris: "mp3",
+      jchris1: "couch",
+      fdmanana: "foobar"
+    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()
+  {
     // _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.
@@ -80,6 +126,7 @@ couchTests.users_db_security = function(debug) {
     // 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");
@@ -140,12 +187,11 @@ couchTests.users_db_security = function(debug) {
         type: "user",
         name: "benoitc",
         password: "test",
-        roles: []
+        roles: ["user_admin"]
       };
-      usersDb.save(benoitcDoc);
+      save_as(usersDb, benoitcDoc, "jan");
 
       TEquals(true, CouchDB.login("jan", "apple").ok);
-
       T(usersDb.setSecObj({
         "admins" : {
           roles : [],
@@ -198,6 +244,132 @@ couchTests.users_db_security = function(debug) {
       // db 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);
+    });
+
+    userDoc = usersDb.open("org.couchdb.user:jchris");
+    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");
+
+      // 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");
+
+      // 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 should be able to update any user doc");
 
       // ensure creation of old-style docs still works
@@ -205,12 +377,17 @@ couchTests.users_db_security = function(debug) {
       var result = usersDb.save(robertDoc);
       TEquals(true, result.ok, "old-style user docs should still be accepted");
 
+	  // ensure creation of old-style docs still works
+      var robertDoc = CouchDB.prepareUserDoc({ name: "robert" }, "anchovy");
+      var result = userDb.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();
+  usersDb.deleteDb(); 
   run_on_modified_server(
     [{section: "couch_httpd_auth",
       key: "authentication_db", value: usersDb.name}],