Advanced permissions with couchdb
Asked Answered
S

1

9

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.

Selfsatisfied answered 24/7, 2014 at 14:30 Comment(2)
Have you considered externalizing the authorization altogether? Look at XACML for instance.Corkboard
@DavidBrossard XACML looks nice. Thanks for sharing it.Reflex
R
8

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.

  1. Every new user that you create will go in a _users database.
  2. Within the user document you can store a list of what documents the user is allowed to view or if you are worried that a single user document will grow too big then just store a pointer to maybe a database that contains the actual list the user can view.
  3. Use the _sessions api first to authenticate the user and then to get a list of docs that the user is authorized to read or edit.
  4. Finally fetch those documents and 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 :)

Reflex answered 24/7, 2014 at 17:40 Comment(3)
Rcouch could be the way to go. ThanksSelfsatisfied
I wonder if results are cached somehow. What If I have ten thousand documents but I can only retrieve one of them? Would the function be called ten thousand times?Selfsatisfied
Over the weekend I'll try itSelfsatisfied

© 2022 - 2024 — McMap. All rights reserved.