diff options
author | Robert Newson <rnewson@apache.org> | 2011-05-17 11:15:14 +0000 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2011-05-17 11:15:14 +0000 |
commit | e8e4b0d293021fe90326a85828f3cfb087bf18b7 (patch) | |
tree | 986f544eac623ec23b769b36828894f93a173aa3 /1.1.x/share/www/script/test/security_validation.js | |
parent | da6a5322b0b8084f434752060caa8be214c6f4fa (diff) |
tagging 1.1.0
git-svn-id: https://svn.apache.org/repos/asf/couchdb/tags/1.1.0@1104149 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to '1.1.x/share/www/script/test/security_validation.js')
-rw-r--r-- | 1.1.x/share/www/script/test/security_validation.js | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/1.1.x/share/www/script/test/security_validation.js b/1.1.x/share/www/script/test/security_validation.js new file mode 100644 index 00000000..42aa11c9 --- /dev/null +++ b/1.1.x/share/www/script/test/security_validation.js @@ -0,0 +1,336 @@ +// 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.security_validation = function(debug) { + // 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 = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + 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"}], + + function () { + // try saving document using the wrong credentials + var wrongPasswordDb = new CouchDB("test_suite_db", + {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"} + ); + + try { + wrongPasswordDb.save({foo:1,author:"Damien Katz"}); + T(false && "Can't get here. Should have thrown an error 1"); + } catch (e) { + T(e.error == "unauthorized"); + T(wrongPasswordDb.last_req.status == 401); + } + + // test force basic login + var resp = wrongPasswordDb.request("GET", "/_session?basic=true"); + var err = JSON.parse(resp.responseText); + T(err.error == "unauthorized"); + T(resp.status == 401); + + // Create the design doc that will run custom validation code + var designDoc = { + _id:"_design/test", + language: "javascript", + validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx, secObj) { + if (secObj.admin_override) { + if (userCtx.roles.indexOf('_admin') != -1) { + // user is admin, they can do anything + return true; + } + } + // docs should have an author field. + if (!newDoc._deleted && !newDoc.author) { + throw {forbidden: + "Documents must have an author field"}; + } + if (oldDoc && oldDoc.author != userCtx.name) { + throw {unauthorized: + "You are not the author of this document. You jerk."}; + } + }).toString() + ")" + } + + // Save a document normally + var userDb = new CouchDB("test_suite_db", + {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:pecan pie"} + ); + + T(userDb.save({_id:"testdoc", foo:1, author:"Damien Katz"}).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); + } + + // set user as the admin + T(db.setSecObj({ + admins : {names : ["Damien Katz"]} + }).ok); + + T(userDb.save(designDoc).ok); + + var user2Db = new CouchDB("test_suite_db", + {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"} + ); + // Attempt to save the design as a non-admin (in replication scenario) + try { + 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); + } + + // test the _session API + var resp = userDb.request("GET", "/_session"); + var user = JSON.parse(resp.responseText).userCtx; + T(user.name == "Damien Katz"); + // test that the roles are listed properly + TEquals(user.roles, []); + + + // update the document + var doc = userDb.open("testdoc"); + doc.foo=2; + T(userDb.save(doc).ok); + + // Save a document that's missing an author field (before and after compaction) + for (var i=0; i<2; i++) { + try { + userDb.save({foo:1}); + T(false && "Can't get here. Should have thrown an error 2"); + } catch (e) { + T(e.error == "forbidden"); + T(userDb.last_req.status == 403); + } + // compact. + T(db.compact().ok); + T(db.last_req.status == 202); + // compaction isn't instantaneous, loop until done + while (db.info().compact_running) {}; + } + + // Now attempt to update the document as a different user, Jan + var doc = user2Db.open("testdoc"); + doc.foo=3; + try { + user2Db.save(doc); + T(false && "Can't get here. Should have thrown an error 3"); + } catch (e) { + T(e.error == "unauthorized"); + T(user2Db.last_req.status == 401); + } + + // Now have Damien change the author to Jan + doc = userDb.open("testdoc"); + doc.author="Jan Lehnardt"; + T(userDb.save(doc).ok); + + // Now update the document as Jan + doc = user2Db.open("testdoc"); + doc.foo = 3; + T(user2Db.save(doc).ok); + + // Damien can't delete it + try { + userDb.deleteDoc(doc); + T(false && "Can't get here. Should have thrown an error 4"); + } catch (e) { + T(e.error == "unauthorized"); + T(userDb.last_req.status == 401); + } + + // admin must save with author field unless admin override + var resp = db.request("GET", "/_session"); + var user = JSON.parse(resp.responseText).userCtx; + T(user.name == null); + // test that we are admin + TEquals(user.roles, ["_admin"]); + + // can't save the doc even though we are admin + var doc = db.open("testdoc"); + doc.foo=3; + try { + db.save(doc); + T(false && "Can't get here. Should have thrown an error 3"); + } catch (e) { + T(e.error == "unauthorized"); + T(db.last_req.status == 401); + } + + // now turn on admin override + T(db.setDbProperty("_security", {admin_override : true}).ok); + T(db.save(doc).ok); + + // try to do something lame + try { + db.setDbProperty("_security", ["foo"]); + T(false && "can't do this"); + } catch(e) {} + + // go back to normal + T(db.setDbProperty("_security", {admin_override : false}).ok); + + // Now delete document + T(user2Db.deleteDoc(doc).ok); + + // now test bulk docs + var docs = [{_id:"bahbah",author:"Damien Katz",foo:"bar"},{_id:"fahfah",foo:"baz"}]; + + // Create the docs + var results = db.bulkSave(docs); + + T(results[0].rev) + T(results[0].error == undefined) + T(results[1].rev === undefined) + T(results[1].error == "forbidden") + + T(db.open("bahbah")); + 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"}]; + + // Create the docs + var results = db.bulkSave(docs, {all_or_nothing:true}); + + T(results.errors.length == 1); + 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:"test_suite_db_a", + target:"test_suite_db_b"}, + + {source:"test_suite_db_a", + target:{url: CouchDB.protocol + host + "/test_suite_db_b", + headers: AuthHeaders}}, + + {source:{url:CouchDB.protocol + host + "/test_suite_db_a", + headers: AuthHeaders}, + target:"test_suite_db_b"}, + + {source:{url:CouchDB.protocol + host + "/test_suite_db_a", + headers: AuthHeaders}, + target:{url:CouchDB.protocol + host + "/test_suite_db_b", + headers: AuthHeaders}}, + ] + var adminDbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + var adminDbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + var dbA = new CouchDB("test_suite_db_a", + {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}); + var dbB = new CouchDB("test_suite_db_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"); + } + }); +}; |