Permalink
Browse files

Adding support for CouchDB user management

You may call either method on the `trombi.Server` instance:

- add_user(name, password, callback, doc=None)
    Add a user with a name, password and a callback. Optionally
    add the default user doc with the doc arg

- get_user(name, callback)
    Return the user's document

- update_user(user_doc, password, callback)
    Update a user's document. If you want the password not to be
    changed, use `None` as value.

- delete_user(user_doc, callback)
    Simply delete a user document
  • Loading branch information...
1 parent b6e778b commit 88fe785e45aef5f5663ad591ea4a2dfccc3485fd Daniel Truemper committed Oct 19, 2011
Showing with 180 additions and 0 deletions.
  1. +136 −0 test/test_usermgmt.py
  2. +44 −0 trombi/client.py
View
@@ -0,0 +1,136 @@
+#
+# Copyright (c) 2011 Daniel Truemper truemped@googlemail.com
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+
+from __future__ import with_statement
+
+from datetime import datetime
+import hashlib
+import sys
+
+from nose.tools import eq_ as eq
+from .couch_util import setup as setup, teardown, with_couchdb
+from .util import with_ioloop, DatetimeEncoder
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+try:
+ # Python 3
+ from urllib.request import urlopen
+ from urllib.error import HTTPError
+except ImportError:
+ # Python 2
+ from urllib2 import urlopen
+ from urllib2 import HTTPError
+
+import trombi
+import trombi.errors
+
+
+@with_ioloop
+@with_couchdb
+def test_add_user(baseurl, ioloop):
+ s = trombi.Server(baseurl, io_loop=ioloop)
+
+ def callback(doc):
+ assert not doc.error
+ ioloop.stop()
+
+ s.add_user('test', 'test', callback)
+ ioloop.start()
+
+
+@with_ioloop
+@with_couchdb
+def test_get_user(baseurl, ioloop):
+ s = trombi.Server(baseurl, io_loop=ioloop)
+
+ def callback(doc):
+ assert not doc.error
+ ioloop.stop()
+
+ s.add_user('get_test', 'test', callback)
+ ioloop.start()
+
+ user = []
+ def callback(doc):
+ assert not doc.error
+ user.append(doc)
+ ioloop.stop()
+
+ s.get_user('get_test', callback)
+ ioloop.start()
+
+ eq(True, isinstance(user[0], trombi.Document))
+
+@with_ioloop
+@with_couchdb
+def test_update_user(baseurl, ioloop):
+ s = trombi.Server(baseurl, io_loop=ioloop)
+ userdoc = []
+
+ def add_callback(doc):
+ assert not doc.error
+ userdoc.append(doc)
+ ioloop.stop()
+
+ s.add_user('updatetest', 'test', add_callback)
+ ioloop.start()
+
+ def update_callback(doc):
+ assert not doc.error
+ userdoc.append(doc)
+ ioloop.stop()
+
+ userdoc[0]['roles'].append('test')
+ s.update_user(userdoc[0], None, update_callback)
+ ioloop.start()
+
+ eq(userdoc[1]['roles'], ['test'])
+
+
+@with_ioloop
+@with_couchdb
+def test_delete_user(baseurl, ioloop):
+ s = trombi.Server(baseurl, io_loop=ioloop)
+ user = []
+
+ def add_callback(doc):
+ assert not doc.error
+ user.append(doc)
+ ioloop.stop()
+
+ s.add_user('deletetest', 'test', add_callback)
+ ioloop.start()
+
+ def delete_callback(db):
+ assert not db.error
+ assert isinstance(db, trombi.Database)
+ ioloop.stop()
+
+ s.delete_user(user[0], delete_callback)
+ ioloop.start()
View
@@ -24,6 +24,8 @@
"""Asynchronous CouchDB client"""
import functools
+from hashlib import sha1
+import uuid
import logging
import re
import collections
@@ -257,6 +259,48 @@ def _really_callback(response):
_really_callback,
)
+ def add_user(self, name, password, callback, doc=None):
+ userdb = Database(self, '_users')
+
+ if doc and not isinstance(doc, Document):
+ doc = Document(userdb, doc)
+ elif not doc:
+ doc = Document(userdb, {})
+
+ doc['type'] = 'user'
+ if 'roles' not in doc:
+ doc['roles'] = []
+
+ if 'name' not in doc:
+ doc['name'] = name
+
+ if 'salt' not in doc:
+ doc['salt'] = str(uuid.uuid4())
+
+ if password:
+ doc['password_sha'] = sha1(password + doc['salt']).hexdigest()
+
+ if not name.startswith('org.couchdb.user:'):
+ name = 'org.couchdb.user:%s' % name
+
+ userdb.set(name, doc, callback)
+
+ def get_user(self, name, callback, attachments=False):
+ userdb = Database(self, '_users')
+
+ doc_id = name
+ if not name.startswith('org.couchdb.user:'):
+ doc_id = 'org.couchdb.user:%s' % name
+
+ userdb.get(doc_id, callback, attachments=attachments)
+
+ def update_user(self, user_doc, password, callback):
+ self.add_user(user_doc['name'], password, callback, doc=user_doc)
+
+ def delete_user(self, user_doc, callback):
+ userdb = Database(self, '_users')
+ userdb.delete(user_doc, callback)
+
class Database(TrombiObject):
def __init__(self, server, name):

0 comments on commit 88fe785

Please sign in to comment.