We have a couchapp application with multiple users and a complex system of permissions. Our models are of two kinds: Foo and bar.
Users have admin access to their own Foo and Bar, and can be given permission to see, change and delete other people's Foo and bar.
Example:
User Sabrina has these models:
Foo {
_id: 1
}
Foo {
_id: 2
}
Bar {
_id:1
}
Bar {
_id:2
}
Of course the real models are larger documents.
She wants to give Giulia read access to her Foos, and read and write access to her first bar. She also wants Giulia not to be able to see her second Bar.
How can we model this kind of permissions in couchdb?
This is the solution we are using, but it seems a lot complex and we wonder if there's a simpler one:
We have a selection of roles:
{username}:admin
: can read, write, delete everything on every database related to the user
{username}:foos:read
: can read every document in the foos database related to the user
{username}:foos:write
: can write every document in the foos database related to the user
{username}:{bar}:read
: can read the Bar database related to the user
{username}:{bar}:write
: can write the Bar database related to the user
When Sabrina register to the app, we create a new sabrina-foos
database, and we give to the user Sabrina the role sabrina:admin
.
The sabrina-foos
database is created with a _security
document granting access to roles sabrina:admin
, sabrina:foos:read
, sabrina:foos:write
.
The sabrina-foos
database is created with a validation function which allows write access to the roles sabrina:admin
, sabrina:foos:write
.
When Sabrina decides to let Giulia see her foos, we give Giulia the role sabrina:foos:read
When Sabrina creates a new Bar called 'Bar 1', we create a new sabrina-bar_1
database.
The sabrina-bar_1
database is created with a _security document granting access to roles sabrina:admin
, sabrina:bar_1:read
, sabrina:bar_1:write
The sabrina-bar_1
database is created with a validation function which allows write access to the roles sabrina:admin
, sabrina:bar_1:write
.
Of course, being this a CouchApp, the creation of databases and editing of user models is handled by a Node Process.
CouchDB 3.0 follows many of the security practices of the old school, SQL databases. You must supply an admin password upon installation, and all newly created databases are accessible only to server admin users by default, instead of world-readable and world-writeable. CouchDB 3.0 also adds more granular user roles.
Features of CouchDBReplication: It provides the simplest form of replication and no other database is so simple to replicate. Document Storage: It is a NoSQL database that follows document storage where each field is uniquely named and contains values of various data types such as text, number, Boolean, lists, etc.
Couchdb does not have the concept of collections. However, you can achieve similar results using type identifiers on your documents in conjunction with Couchdb views. When you save a document in Couchdb add a field that specifies the type.
Your design is good. From your question it looks like you want document level authentication. couchdb offers no protection on individual documents so the only choice left is to partition the documents by databases and set read and write permissions on them.
There are two alternatives. The easiest one is to use rcouch's validate docs on read. I am not too sure but I think in couch db 2.0 all of rouch's features have been merged so if you are willing to wait a bit you can use couchdb 2.0 (it should be out any time now!) instead.
Another method is to do what you are doing but in a _users database. In a _users
database you can
create users and authenticate them with _sessions
api. So here is how it will work.
_users
database. _sessions
api first to authenticate the user and then to get a list of docs that the user is authorized
to read or edit. show
it to the user. The advantage of this method is that you will need a minimum of 2 and at most 3 http queries. One to authenticate and get a pointer to the list. Second to get the actual list of documents to be fetched. Third to fetch those documents. In return your architecture is greatly simplified.
A very cool property of _users
database is that you can increase the auth cache size in the config to hold the _user
objects in the memory so the access times will be very fast.
What If I have ten thousand documents but I can only retrieve one of them? Would the function be called ten thousand times?
I don't have rcouch installed at the moment but there is an easy way to test this by logging:-
function(doc, userCtx) {
log("function called");
if ((typeof doc.name !== 'undefined') && (doc.name != userCtx.name)) {
throw({unauthorized: userCtx.name + ' cannnot read ' + doc._id});
}
}
The log function will print a log message in the log files and also on the console couchdb is running on so you can see for yourself how many times the update function is being called. Would be nice if you could share the results :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With