Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #30 from truemped/usermgmt

Adding support for CouchDB user management
  • Loading branch information...
commit 440102dc2ba307c82040a44316f907e16da0de1a 2 parents 70df226 + 0d74f08
@nailor nailor authored
Showing with 194 additions and 0 deletions.
  1. +147 −0 test/test_usermgmt.py
  2. +47 −0 trombi/client.py
View
147 test/test_usermgmt.py
@@ -0,0 +1,147 @@
+#
+# 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], update_callback)
+ ioloop.start()
+
+ eq(userdoc[1]['roles'], ['test'])
+
+ def update_passwd_callback(doc):
+ assert not doc.error
+ userdoc.append(doc)
+ ioloop.stop()
+
+ s.update_user_password('updatetest', 'test2', update_passwd_callback)
+ ioloop.start()
+
+ eq(userdoc[1]['salt'], userdoc[2]['salt'])
+ eq(userdoc[1]['password_sha'] != userdoc[2]['password_sha'], True)
+
+
+@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
47 trombi/client.py
@@ -24,6 +24,8 @@
"""Asynchronous CouchDB client"""
import functools
+from hashlib import sha1
+import uuid
import logging
import re
import collections
@@ -257,6 +259,51 @@ def _really_callback(response):
_really_callback,
)
+ def add_user(self, name, password, callback, doc=None):
+ userdb = Database(self, '_users')
+
+ if not doc:
+ doc = {}
+
+ doc['type'] = 'user'
+ if 'roles' not in doc:
+ doc['roles'] = []
+
+ doc['name'] = name
+ doc['salt'] = str(uuid.uuid4())
+ doc['password_sha'] = sha1(password + doc['salt']).hexdigest()
+
+ if '_id' not in doc:
+ doc['_id'] = 'org.couchdb.user:%s' % name
+
+ userdb.set(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, callback):
+ userdb = Database(self, '_users')
+ userdb.set(user_doc, callback)
+
+ def update_user_password(self, username, password, callback):
+ def _really_callback(user_doc):
+ if user_doc.error:
+ callback(user_doc)
+ user_doc['password_sha'] = sha1(password + user_doc['salt']).hexdigest()
+ self.update_user(user_doc, callback)
+
+ self.get_user(username, _really_callback)
+
+ def delete_user(self, user_doc, callback):
+ userdb = Database(self, '_users')
+ userdb.delete(user_doc, callback)
+
class Database(TrombiObject):
def __init__(self, server, name):
Please sign in to comment.
Something went wrong with that request. Please try again.