blob: 4994958fcabfb9cb59db1bcba53284612589e57b [file] [log] [blame]
// 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.skip = true;
couchTests.replicator_db_security = function(debug) {
var reset_dbs = function(dbs) {
dbs.forEach(function(db) {
db.deleteDb();
try { db.createDb() } catch (e) {};
});
};
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) {
return new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
});
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()
{
reset_dbs(dbs);
// _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();
// 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);
});
};
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();
};