summaryrefslogtreecommitdiff
path: root/rel/overlay/share/www/script/test/security_validation.js
diff options
context:
space:
mode:
Diffstat (limited to 'rel/overlay/share/www/script/test/security_validation.js')
-rw-r--r--rel/overlay/share/www/script/test/security_validation.js329
1 files changed, 329 insertions, 0 deletions
diff --git a/rel/overlay/share/www/script/test/security_validation.js b/rel/overlay/share/www/script/test/security_validation.js
new file mode 100644
index 00000000..e0ab17d6
--- /dev/null
+++ b/rel/overlay/share/www/script/test/security_validation.js
@@ -0,0 +1,329 @@
+// 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.
+ 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);
+ }
+
+ // 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: "http://" + host + "/test_suite_db_b",
+ headers: AuthHeaders}},
+
+ {source:{url:"http://" + host + "/test_suite_db_a",
+ headers: AuthHeaders},
+ target:"test_suite_db_b"},
+
+ {source:{url:"http://" + host + "/test_suite_db_a",
+ headers: AuthHeaders},
+ target:{url:"http://" + 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");
+ }
+ });
+};