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 2016/04/23 12:29:10 UTC

couchdb commit: updated refs/heads/master to 17bbf26

Repository: couchdb
Updated Branches:
  refs/heads/master 82d2eb120 -> 17bbf2619


Bring security validation back


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/17bbf261
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/17bbf261
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/17bbf261

Branch: refs/heads/master
Commit: 17bbf26199aa9d62bee572d42007a1bf50bb9c10
Parents: 82d2eb1
Author: sebastianro <se...@apache.org>
Authored: Fri Apr 15 12:37:47 2016 +0200
Committer: Jan Lehnardt <ja...@apache.org>
Committed: Sat Apr 23 12:26:44 2016 +0200

----------------------------------------------------------------------
 test/javascript/tests/security_validation.js | 262 ++++++++++------------
 1 file changed, 123 insertions(+), 139 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/17bbf261/test/javascript/tests/security_validation.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/security_validation.js b/test/javascript/tests/security_validation.js
index 619ba34..5c50ff5 100644
--- a/test/javascript/tests/security_validation.js
+++ b/test/javascript/tests/security_validation.js
@@ -11,51 +11,44 @@
 // the License.
 
 couchTests.security_validation = function(debug) {
-  return console.log('TODO: config not available on cluster');
-
-  // This tests couchdb's security and validation features. This does
-  // not test authentication, except to use test authentication code made
-  // specifically for this testing. It is a WWW-Authenticate scheme named
-  // X-Couch-Test-Auth, and the user names and passwords are hard coded
-  // on the server-side.
-  //
-  // We could have used Basic authentication, however the XMLHttpRequest
-  // implementation for Firefox and Safari, and probably other browsers are
-  // broken (Firefox always prompts the user on 401 failures, Safari gives
-  // odd security errors when using different name/passwords, perhaps due
-  // to cross site scripting prevention). These problems essentially make Basic
-  // authentication testing in the browser impossible. But while hard to
-  // test automated in the browser, Basic auth may still useful for real
-  // world use where these bugs/behaviors don't matter.
-  //
-  // So for testing purposes we are using this custom X-Couch-Test-Auth.
-  // It's identical to Basic auth, except it doesn't even base64 encode
-  // the "username:password" string, it's sent completely plain text.
-  // Firefox and Safari both deal with this correctly (which is to say
-  // they correctly do nothing special).
 
   var db_name = get_random_db_name();
   var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
   db.createDb();
+  var authDb_name = get_random_db_name() + "_authdb";
+  var authDb = new CouchDB(authDb_name, {"X-Couch-Full-Commit":"false"});
+  authDb.createDb();
+  var adminDbA, adminDbB; // used later
   if (debug) debugger;
 
   run_on_modified_server(
     [{section: "httpd",
       key: "authentication_handlers",
-      value: "{couch_httpd_auth, special_test_authentication_handler}"},
-     {section:"httpd",
-      key: "WWW-Authenticate",
-      value:  "X-Couch-Test-Auth"}],
+      value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"},
+     {section: "couch_httpd_auth",
+      key: "authentication_db", value: authDb_name},
+     {section: "chttpd_auth",
+      key: "authentication_db", value: authDb_name}],
 
     function () {
+      // the special case handler does not exist (any longer) in clusters, so we have
+      // to replicate the behavior using a "normal" DB even though tests might no more
+      // run universally (why the "X-Couch-Test-Auth" header was introduced).
+      // btw: this needs to be INSIDE configured server to propagate correctly ;-)
+      // At least they'd run in the build, though
+      T(authDb.save(CouchDB.prepareUserDoc({name: "tom"}, "cat")).ok); // Basic dG9tOmNhdA==
+      T(authDb.save(CouchDB.prepareUserDoc({name: "jerry"}, "mouse")).ok); // Basic amVycnk6bW91c2U=
+      T(authDb.save(CouchDB.prepareUserDoc({name: "spike"}, "dog")).ok); // Basic c3Bpa2U6ZG9n
+      authDb.ensureFullCommit();
+
       // try saving document using the wrong credentials
       var wrongPasswordDb = new CouchDB(db_name + "",
-        {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"}
+        {"Authorization": "Basic c3Bpa2U6Y2F0"} // spike:cat - which is wrong
       );
 
       try {
         wrongPasswordDb.save({foo:1,author:"Damien Katz"});
-        T(false && "Can't get here. Should have thrown an error 1");
+        T(false, "Can't get here. Should have thrown an error 1");
       } catch (e) {
         T(e.error == "unauthorized");
         T(wrongPasswordDb.last_req.status == 401);
@@ -85,36 +78,41 @@ couchTests.security_validation = function(debug) {
           }
           if (oldDoc && oldDoc.author != userCtx.name) {
               throw {unauthorized:
-                  "You are not the author of this document. You jerk."};
+                  "You are '" + userCtx.name + "', not the author '" + oldDoc.author + "' of this document. You jerk."};
           }
         })
       }
 
       // Save a document normally
       var userDb = new CouchDB("" + db_name + "",
-        {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:pecan pie"}
+        {"Authorization": "Basic amVycnk6bW91c2U="}  // jerry
       );
+      // test session
+      TEquals("jerry", JSON.parse(userDb.request("GET", "/_session").responseText).userCtx.name);
 
-      T(userDb.save({_id:"testdoc", foo:1, author:"Damien Katz"}).ok);
+      T(userDb.save({_id:"testdoc", foo:1, author:"jerry"}).ok);
 
       // Attempt to save the design as a non-admin
       try {
         userDb.save(designDoc);
         T(false && "Can't get here. Should have thrown an error on design doc");
       } catch (e) {
-        T(e.error == "unauthorized");
-        T(userDb.last_req.status == 401);
+        // cluster changes from 401 unauthorized to 403 forbidden
+        TEquals("forbidden", e.error);
+        TEquals(403, userDb.last_req.status);
       }
 
       // set user as the admin
       T(db.setSecObj({
-        admins : {names : ["Damien Katz"]}
+        admins : {names : ["jerry"]}
       }).ok);
 
-      T(userDb.save(designDoc).ok);
+      // TODO: when _security is correctly honored (COUCHDB-2990), switch back
+      //T(userDb.save(designDoc).ok);
+      T(db.save(designDoc).ok);
 
       var user2Db = new CouchDB("" + db_name + "",
-        {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"}
+        {"Authorization": "Basic dG9tOmNhdA=="} // tom
       );
       // Attempt to save the design as a non-admin (in replication scenario)
       designDoc.foo = "bar";
@@ -123,14 +121,15 @@ couchTests.security_validation = function(debug) {
         user2Db.save(designDoc, {new_edits : false});
         T(false && "Can't get here. Should have thrown an error on design doc");
       } catch (e) {
-        T(e.error == "unauthorized");
-        T(user2Db.last_req.status == 401);
+        // cluster changes from 401 unauthorized to 403 forbidden
+        TEquals("forbidden", e.error);
+        TEquals(403, userDb.last_req.status);
       }
 
       // test the _session API
       var resp = userDb.request("GET", "/_session");
       var user = JSON.parse(resp.responseText).userCtx;
-      T(user.name == "Damien Katz");
+      T(user.name == "jerry");
       // test that the roles are listed properly
       TEquals(user.roles, []);
 
@@ -149,11 +148,11 @@ couchTests.security_validation = function(debug) {
               T(e.error == "forbidden");
               T(userDb.last_req.status == 403);
           }
-          // compact.
-          T(db.compact().ok);
+          // compact. - no more available on clusters (but: test is still valid w/out compaction)
+          /*T(db.compact().ok);
           T(db.last_req.status == 202);
           // compaction isn't instantaneous, loop until done
-          while (db.info().compact_running) {};
+          while (db.info().compact_running) {};*/
       }
 
       // Now attempt to update the document as a different user, Jan
@@ -167,17 +166,17 @@ couchTests.security_validation = function(debug) {
         T(user2Db.last_req.status == 401);
       }
 
-      // Now have Damien change the author to Jan
+      // Now have jerry change the author to tom
       doc = userDb.open("testdoc");
-      doc.author="Jan Lehnardt";
+      doc.author="tom";
       T(userDb.save(doc).ok);
 
-      // Now update the document as Jan
+      // Now update the document as tom
       doc = user2Db.open("testdoc");
       doc.foo = 3;
       T(user2Db.save(doc).ok);
 
-      // Damien can't delete it
+      // jerry can't delete it
       try {
         userDb.deleteDoc(doc);
         T(false && "Can't get here. Should have thrown an error 4");
@@ -206,7 +205,8 @@ couchTests.security_validation = function(debug) {
 
       // now turn on admin override
       T(db.setDbProperty("_security", {admin_override : true}).ok);
-      T(db.save(doc).ok);
+      // TODO: re-include after COUCHDB-2990
+      //T(db.save(doc).ok);
 
       // try to do something lame
       try {
@@ -221,7 +221,7 @@ couchTests.security_validation = function(debug) {
       T(user2Db.deleteDoc(doc).ok);
 
       // now test bulk docs
-      var docs = [{_id:"bahbah",author:"Damien Katz",foo:"bar"},{_id:"fahfah",foo:"baz"}];
+      var docs = [{_id:"bahbah",author:"jerry",foo:"bar"},{_id:"fahfah",foo:"baz"}];
 
       // Create the docs
       var results = db.bulkSave(docs);
@@ -235,8 +235,8 @@ couchTests.security_validation = function(debug) {
       T(db.open("fahfah") == null);
 
 
-      // now all or nothing with a failure
-      var docs = [{_id:"booboo",author:"Damien Katz",foo:"bar"},{_id:"foofoo",foo:"baz"}];
+      // now all or nothing with a failure - no more available on cluster
+/*      var docs = [{_id:"booboo",author:"Damien Katz",foo:"bar"},{_id:"foofoo",foo:"baz"}];
 
       // Create the docs
       var results = db.bulkSave(docs, {all_or_nothing:true});
@@ -245,99 +245,83 @@ couchTests.security_validation = function(debug) {
       T(results.errors[0].error == "forbidden");
       T(db.open("booboo") == null);
       T(db.open("foofoo") == null);
+*/
 
       // Now test replication
-      var AuthHeaders = {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"};
-      var host = CouchDB.host;
-      var dbPairs = [
-        {source:"" + db_name + "_a",
-          target:"" + db_name + "_b"},
-
-        {source:"" + db_name + "_a",
-          target:{url: CouchDB.protocol + host + "/" + db_name + "_b",
-                  headers: AuthHeaders}},
-
-        {source:{url:CouchDB.protocol + host + "/" + db_name + "_a",
-                 headers: AuthHeaders},
-          target:"" + db_name + "_b"},
-
-        {source:{url:CouchDB.protocol + host + "/" + db_name + "_a",
-                 headers: AuthHeaders},
-         target:{url:CouchDB.protocol + host + "/" + db_name + "_b",
-                 headers: AuthHeaders}},
-      ]
-      var adminDbA = new CouchDB("" + db_name + "_a", {"X-Couch-Full-Commit":"false"});
-      var adminDbB = new CouchDB("" + db_name + "_b", {"X-Couch-Full-Commit":"false"});
-      var dbA = new CouchDB("" + db_name + "_a",
-          {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"});
-      var dbB = new CouchDB("" + db_name + "_b",
-          {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"});
-      var xhr;
-      for (var testPair = 0; testPair < dbPairs.length; testPair++) {
-        var A = dbPairs[testPair].source
-        var B = dbPairs[testPair].target
-
-        adminDbA.deleteDb();
-        adminDbA.createDb();
-        adminDbB.deleteDb();
-        adminDbB.createDb();
-
-        // save and replicate a documents that will and will not pass our design
-        // doc validation function.
-        dbA.save({_id:"foo1",value:"a",author:"Noah Slater"});
-        dbA.save({_id:"foo2",value:"a",author:"Christopher Lenz"});
-        dbA.save({_id:"bad1",value:"a"});
-
-        T(CouchDB.replicate(A, B, {headers:AuthHeaders}).ok);
-        T(CouchDB.replicate(B, A, {headers:AuthHeaders}).ok);
-
-        T(dbA.open("foo1"));
-        T(dbB.open("foo1"));
-        T(dbA.open("foo2"));
-        T(dbB.open("foo2"));
-
-        // save the design doc to dbA
-        delete designDoc._rev; // clear rev from previous saves
-        adminDbA.save(designDoc);
-
-        // no affect on already saved docs
-        T(dbA.open("bad1"));
-
-        // Update some docs on dbB. Since the design hasn't replicated, anything
-        // is allowed.
-
-        // this edit will fail validation on replication to dbA (no author)
-        T(dbB.save({_id:"bad2",value:"a"}).ok);
-
-        // this edit will fail security on replication to dbA (wrong author
-        //  replicating the change)
-        var foo1 = dbB.open("foo1");
-        foo1.value = "b";
-        dbB.save(foo1);
-
-        // this is a legal edit
-        var foo2 = dbB.open("foo2");
-        foo2.value = "b";
-        dbB.save(foo2);
-
-        var results = CouchDB.replicate(B, A, {headers:AuthHeaders});
-
-        T(results.ok);
-
-        T(results.history[0].docs_written == 1);
-        T(results.history[0].doc_write_failures == 2);
-
-        // bad2 should not be on dbA
-        T(dbA.open("bad2") == null);
-
-        // The edit to foo1 should not have replicated.
-        T(dbA.open("foo1").value == "a");
-
-        // The edit to foo2 should have replicated.
-        T(dbA.open("foo2").value == "b");
-      }
+      var AuthHeaders = {"Authorization": "Basic c3Bpa2U6ZG9n"}; // spike
+      adminDbA = new CouchDB("" + db_name + "_a", {"X-Couch-Full-Commit":"false"});
+      adminDbB = new CouchDB("" + db_name + "_b", {"X-Couch-Full-Commit":"false"});
+      var dbA = new CouchDB("" + db_name + "_a", AuthHeaders);
+      var dbB = new CouchDB("" + db_name + "_b", AuthHeaders);
+      // looping does not really add value as the scenario is the same anyway (there's nothing 2 be gained from it)
+      var A = CouchDB.protocol + CouchDB.host + "/" + db_name + "_a";
+      var B = CouchDB.protocol + CouchDB.host + "/" + db_name + "_b";
+
+      adminDbA.deleteDb();
+      adminDbA.createDb();
+      adminDbB.deleteDb();
+      adminDbB.createDb();
+
+      // save and replicate a documents that will and will not pass our design
+      // doc validation function.
+      T(dbA.save({_id:"foo1",value:"a",author:"tom"}).ok);
+      T(dbA.save({_id:"foo2",value:"a",author:"spike"}).ok);
+      T(dbA.save({_id:"bad1",value:"a"}).ok);
+
+      T(CouchDB.replicate(A, B, {headers:AuthHeaders}).ok);
+      T(CouchDB.replicate(B, A, {headers:AuthHeaders}).ok);
+
+      T(dbA.open("foo1"));
+      T(dbB.open("foo1"));
+      T(dbA.open("foo2"));
+      T(dbB.open("foo2"));
+
+      // save the design doc to dbA
+      delete designDoc._rev; // clear rev from previous saves
+      T(adminDbA.save(designDoc).ok);
+
+      // no affect on already saved docs
+      T(dbA.open("bad1"));
+
+      // Update some docs on dbB. Since the design hasn't replicated, anything
+      // is allowed.
+
+      // this edit will fail validation on replication to dbA (no author)
+      T(dbB.save({_id:"bad2",value:"a"}).ok);
+
+      // this edit will fail security on replication to dbA (wrong author
+      //  replicating the change)
+      var foo1 = dbB.open("foo1");
+      foo1.value = "b";
+      T(dbB.save(foo1).ok);
+
+      // this is a legal edit
+      var foo2 = dbB.open("foo2");
+      foo2.value = "b";
+      T(dbB.save(foo2).ok);
+
+      var results = CouchDB.replicate({"url": B, "headers": AuthHeaders}, {"url": A, "headers": AuthHeaders}, {headers:AuthHeaders});
+      T(results.ok);
+      TEquals(1, results.history[0].docs_written);
+      TEquals(2, results.history[0].doc_write_failures);
+
+      // bad2 should not be on dbA
+      T(dbA.open("bad2") == null);
+
+      // The edit to foo1 should not have replicated.
+      T(dbA.open("foo1").value == "a");
+
+      // The edit to foo2 should have replicated.
+      T(dbA.open("foo2").value == "b");
     });
 
   // cleanup
   db.deleteDb();
+  if(adminDbA){
+    adminDbA.deleteDb();
+  }
+  if(adminDbB){
+    adminDbB.deleteDb();
+  }
+  authDb.deleteDb();
 };