Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Database-level security.
This patch builds on the DB-admins feature to store lists of database admin and reader names and roles, as well as a security object which can be used for configuration in validation functions.

git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@905436 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
jchris committed Feb 1, 2010
1 parent 29ad970 commit 282198254eaeb92b58e3b1ce4674a4871c07072b
Showing 8 changed files with 228 additions and 4 deletions.
@@ -117,6 +117,7 @@
$("#toolbar button.add").click(page.newDocument);
$("#toolbar button.compact").click(page.compactAndCleanup);
$("#toolbar button.delete").click(page.deleteDatabase);
$("#toolbar button.security").click(page.databaseSecurity);

$('#jumpto input').suggest(function(text, callback) {
page.db.allDocs({
@@ -161,6 +162,7 @@ <h1>
</div>
<ul id="toolbar">
<li><button class="add">New Document</button></li>
<li><button class="security">Security…</button></li>
<li><button class="compact">Compact &amp; Cleanup…</button></li>
<li><button class="delete">Delete Database…</button></li>
</ul>
@@ -0,0 +1,50 @@
<!--
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.
-->
<form action="" method="post">
<h2>Admins and Readers</h2>
<p class="help">
Each database contains lists of admins and readers.
Admins and readers are each defined by <tt>names</tt> and <tt>roles</tt>, which are lists of strings. For example, if the readers is defined by <tt>names ["jane", "mike"]</tt> and roles <tt>["bbq"]</tt> then anyone with a <tt>"bbq"</tt> role can read the database. Yummy!
</p>
<fieldset>
<h3>Admins</h3>
<p class="help">Database admins can update design documents and edit the readers list.</p>
<table summary=""><tbody><tr>
<th><label>Names:</label></th>
<td><input type="text" name="admin_names" size="40"></td>
</tr><tr>
<th><label>Roles:</label></th>
<td><input type="text" name="admin_roles" size="40"></td>
</tr>
</tbody></table>
</fieldset>
<fieldset>
<h3>Readers</h3>
<p class="help">Database readers can access the database. If no readers are defined, the database is public. When readers are defined, only they may read or write to the database.</p>
<table summary=""><tbody><tr>
<th><label>Names:</label></th>
<td><input type="text" name="reader_names" size="40"></td>
</tr><tr>
<th><label>Roles:</label></th>
<td><input type="text" name="reader_roles" size="40"></td>
</tr>
</tbody></table>

</fieldset>
<div class="buttons">
<button type="submit">Update</button>
<button type="button" class="cancel">Cancel</button>
</div>
</form>
@@ -63,6 +63,7 @@ loadScript("script/oauth.js");
loadScript("script/sha1.js");
loadTest("oauth.js");
loadTest("purge.js");
loadTest("reader_acl.js");
loadTest("recreate_doc.js");
loadTest("reduce.js");
loadTest("reduce_builtin.js");
@@ -178,6 +178,32 @@
}
});
}

this.databaseSecurity = function() {
$.showDialog("dialog/_database_security.html", {
load : function(d) {
["admin", "reader"].forEach(function(key) {
db.getDbProperty("_"+key+"s", {
success : function(r) {
$("input[name="+key+"_names]",d).val(JSON.stringify(r.names||[]));
$("input[name="+key+"_roles]",d).val(JSON.stringify(r.roles||[]));
}
});
});
},
// maybe this should be 2 forms
submit: function(data, callback) {
["admin", "reader"].forEach(function(key) {
var new_value = {
names : JSON.parse(data[key+"_names"]),
roles : JSON.parse(data[key+"_roles"])
};
db.setDbProperty("_"+key+"s", new_value);
});
callback();
}
});
}

this.populateViewEditor = function() {
if (viewName.match(/^_design\//)) {
@@ -371,6 +371,25 @@
},
options, "An error occurred accessing the view"
);
},
getDbProperty: function(propName, options, ajaxOptions) {
ajax({url: this.uri + propName + encodeOptions(options)},
options,
"The property could not be retrieved",
ajaxOptions
);
},

setDbProperty: function(propName, propValue, options, ajaxOptions) {
ajax({
type: "PUT",
url: this.uri + propName + encodeOptions(options),
data : JSON.stringify(propValue)
},
options,
"The property could not be updated",
ajaxOptions
);
}
};
},
@@ -0,0 +1,95 @@
// 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.reader_acl = function(debug) {
// this tests read access control

var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
var secretDb = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
function testFun() {
try {
usersDb.deleteDb();
usersDb.createDb();
secretDb.deleteDb();
secretDb.createDb();

// create a user with top-secret-clearance
var jchrisUserDoc = CouchDB.prepareUserDoc({
name: "jchris@apache.org",
roles : ["top-secret"]
}, "funnybone");
T(usersDb.save(jchrisUserDoc).ok);

T(CouchDB.session().userCtx.name == null);

// set secret db to be read controlled
T(secretDb.save({_id:"baz",foo:"bar"}).ok);
T(secretDb.open("baz").foo == "bar");

T(secretDb.setDbProperty("_readers", {
roles : ["super-secret-club"],
names : ["joe","barb"]}).ok);
// can't read it as jchris
T(CouchDB.login("jchris@apache.org", "funnybone").ok);
T(CouchDB.session().userCtx.name == "jchris@apache.org");

try {
secretDb.open("baz");
T(false && "can't open a doc from a secret db") ;
} catch(e) {
T(true)
}

CouchDB.logout();

// admin now adds the top-secret role to the db's readers
T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);

T(secretDb.setDbProperty("_readers", {
roles : ["super-secret-club", "top-secret"],
names : ["joe","barb"]}).ok);

// now top-secret users can read it
T(secretDb.open("baz").foo == "bar");
T(CouchDB.login("jchris@apache.org", "funnybone").ok);
T(secretDb.open("baz").foo == "bar");

CouchDB.logout();

// can't set non string reader names or roles
try {
T(!secretDb.setDbProperty("_readers", {
roles : ["super-secret-club", {"top-secret":"awesome"}],
names : ["joe","barb"]}).ok);
T(false && "only string roles");
} catch (e) {}

try {
T(!secretDb.setDbProperty("_readers", {
roles : ["super-secret-club", "top-secret"],
names : ["joe",22]}).ok);
T(false && "only string names");
} catch (e) {}
} finally {
CouchDB.logout();
}
}

run_on_modified_server(
[{section: "httpd",
key: "authentication_handlers",
value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"},
{section: "couch_httpd_auth",
key: "authentication_db", value: "test_suite_users"}],
testFun
);
}
@@ -69,7 +69,13 @@ couchTests.security_validation = function(debug) {
var designDoc = {
_id:"_design/test",
language: "javascript",
validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) {
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:
@@ -99,11 +105,11 @@ couchTests.security_validation = function(debug) {
}

// set user as the admin
T(db.setDbProperty("_admins", ["Damien Katz"]).ok);
T(db.setDbProperty("_admins", {names : ["Damien Katz"]}).ok);

T(userDb.save(designDoc).ok);

// test the _whoami endpoint
// test the _session API
var resp = userDb.request("GET", "/_session");
var user = JSON.parse(resp.responseText).userCtx;
T(user.name == "Damien Katz");
@@ -158,6 +164,31 @@ couchTests.security_validation = function(debug) {
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);

// go back to normal
T(db.setDbProperty("_security", {admin_override : false}).ok);

// Now delete document
T(user2Db.deleteDoc(doc).ok);
@@ -188,7 +219,6 @@ couchTests.security_validation = function(debug) {
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;
@@ -236,6 +236,7 @@ body.fullwidth #wrap { margin-right: 0; }
#toolbar button:hover { background-position: 2px -30px; color: #000; }
#toolbar button:active { background-position: 2px -62px; color: #000; }
#toolbar button.add { background-image: url(../image/add.png); }
#toolbar button.security { background-image: url(../image/compact.png); }
#toolbar button.compact { background-image: url(../image/compact.png); }
#toolbar button.delete { background-image: url(../image/delete.png); }
#toolbar button.load { background-image: url(../image/load.png); }

0 comments on commit 2821982

Please sign in to comment.