diff options
author | sebastianro <sebastianro@apache.org> | 2016-04-15 12:37:47 +0200 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2016-04-23 12:26:44 +0200 |
commit | 17bbf26199aa9d62bee572d42007a1bf50bb9c10 (patch) | |
tree | a3b9a2eb010a883b40f8e4a033d95028b3e2708d | |
parent | 82d2eb1209f506be7034bdf0ef2644068e0f389d (diff) | |
download | couchdb-17bbf26199aa9d62bee572d42007a1bf50bb9c10.tar.gz |
Bring security validation back
-rw-r--r-- | test/javascript/tests/security_validation.js | 262 |
1 files changed, 123 insertions, 139 deletions
diff --git a/test/javascript/tests/security_validation.js b/test/javascript/tests/security_validation.js index 619ba341d..5c50ff50e 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(); }; |