CouchDB write/read only (no edit) user

1.5k Views Asked by At

Toolchain/frameworks

I'm using django==2.1.3 and python-cloudant==2.1.3 and running CouchDB ver. 2.2.0, and pretty much doing all of my setup/configuration through Fauxton. I like to think that I know my way around python/django in general, and I'm testing this approach in a small little project to see how it works

Problem Description

Suppose I have a fairly simple CRUD application with just 1 model:

class Asset(models.Model):
    asset_id = models.CharField(max_length=32)
    asset_name = models.CharField(max_length=32)

and I have a view that I use to create the asset

class CreateAssetView(views.View):
    def get(self, request, *args, **kwargs):
        #some code here


    def post(self, request, *args, **kwargs):
        #some code here|
        #log request data into database
        client = CouchDB('myusername', 'mypassword', url='http://127.0.0.1:5984', connect=True)
        db = client['assets']

        log_data = {'view_name': self.view_name, 'post_data': post_data,'user': request.user.username,
                    'time': str(timezone.now())}
        db.create_document(log_data)
        return render(...)

I understand that I should be doing the logging portion using a middleware (which I plan to) and probably just use django's CreateView in that case, I'm doing this approach for now just during early development.

What I'm having a problem wrapping my head around is creating a user with myusername and mypassword that has the permissions to:

  1. Write new documents
  2. Read old documents
  3. not edit already created documents

I could even settle for 1 and 3 only (and only use admin to read). I spent a little bit of time playing around with Fauxton's interface for permissions, but I can only basically create a user and assign a role (couldn't even get to assigning a password :/)

clarification

The Asset is not a CouchDB document, that's a normal SQL model, I only want to dump the logs with post data to CouchDB

Any help/gudiance/documentation pointers would be really appreciated

1

There are 1 best solutions below

2
On BEST ANSWER

Overview

Couchdb has one overriding level of administrator setup in configuration instead of setup in the _users database and assigned the _admin permission to prevent any possibility of being locked out.

Each individual database then has a rough level security policy of 2 levels:

  1. admins
  2. members

specified via:

  1. names
  2. roles

making 4 fields.

These levels control access slightly differently for the 2 types of documents a db can contain:

  1. id: _design/* - Design documents can contain functions that will be executed in some context
  2. id: other - Normal documents are just normal data

Both levels of database access have read access to all documents in the database, but admins have write access to _design documents. Write access to normal documents is usually given to all users granted any access to the db, but can be limited by validate design documents.

To Summarize

The process for setting a unique security policy up is:

  1. Experience the provided validate design document as a consumer while setting up _users.
  2. Setup a new database and its basic security giving your users member access.
  3. Add a design doc to the new database with a validate function that restricts member write access.

1 Setting up _users entries

Add a role to a user

As an admin add role: ["logger"] to a user's doc and save it, note that this must be done by admin due to this part of the default _users design document:

        // DB: _users doc: _design/_auth
        function(newDoc, oldDoc, userCtx, secObj) {
        ..
        if (oldRoles.length !== newRoles.length) {
            throw({forbidden: 'Only _admin may edit roles'});
        }

Change the password for the user.

Either the admin or the user can change their password by setting password:"mynewpassword" in their document (which couchdb will transform into a hashed/salted password during the save process). This works for a user since they can add/modify fields aside from their name and roles, as long as a user is editing their own doc:

        // DB: _users doc: _design/_auth
        function(newDoc, oldDoc, userCtx, secObj) {
        ..
        if (userCtx.name !== newDoc.name) {
            throw({
                forbidden: 'You may only update your own user document.'
            });
        }
        // then checks that they don't modify roles

You could repeat this process with a user you assign an adminlogger role to create a delegated administrator that you assign permissions to can reconfigure a database or you can continue to use the couchdb admin with its _admin role for all administration.

2 Setup a new database and its basic security

Create a db named logger assign the logger a security policy:

{
    "admins": {
        "names": [

        ],
        "roles": [
            "adminlogger"
        ]
    },
    "members": {
        "names": [

        ],
        "roles": [
            "logger"
        ]
    }
}

3. Create a new validate design document in the new db

As either _admin user or a user with the adminlogger role create a new validate design doc by copying the _users design document, removing the _rev and modifying the function:

// DB: logger doc: _design/auth
function(newDoc, oldDoc, userCtx, secObj) {
     // Don't let non-admins write a pre-existing document:
     if (!is_server_or_database_admin()) {
          if (!!oldDoc) {
              throw({
                forbidden: 'You may not update existing documents.'
            });
          }
     }
     // Where the function to define admins can be copied verbatim from the doc:
     var is_server_or_database_admin = function(userCtx, secObj) {
        // see if the user is a server admin
        if(userCtx.roles.indexOf('_admin') !== -1) {
            return true; // a server admin
        }

        // see if the user a database admin specified by name
        if(secObj && secObj.admins && secObj.admins.names) {
            if(secObj.admins.names.indexOf(userCtx.name) !== -1) {
                return true; // database admin
            }
        }

        // see if the user a database admin specified by role
        if(secObj && secObj.admins && secObj.admins.roles) {
            var db_roles = secObj.admins.roles;
            for(var idx = 0; idx < userCtx.roles.length; idx++) {
                var user_role = userCtx.roles[idx];
                if(db_roles.indexOf(user_role) !== -1) {
                    return true; // role matches!
                }
            }
        }

        return false; // default to no admin
    }
}    

If you followed these steps then the user you gave the logger role in step 1 can run your code to write new documents only in logger database configured in steps 2 and 3.