[Django]-CouchDB write/read only (no edit) user

3👍

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.

Leave a comment