diff --git a/django_couchdb_utils/openid_consumer/_design/views/issued_lifetime_view/map.js b/django_couchdb_utils/openid_consumer/_design/views/issued_lifetime_view/map.js new file mode 100644 index 0000000..7b7fe3b --- /dev/null +++ b/django_couchdb_utils/openid_consumer/_design/views/issued_lifetime_view/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.doc_type == 'Association') { + emit(doc.issued + doc.lifetime, doc); + } +} diff --git a/django_couchdb_utils/openid_consumer/_design/views/openid_view/map.js b/django_couchdb_utils/openid_consumer/_design/views/openid_view/map.js new file mode 100644 index 0000000..d6d109d --- /dev/null +++ b/django_couchdb_utils/openid_consumer/_design/views/openid_view/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.doc_type == 'UserOpenidAssociation') { + emit(doc.openid, doc); + } +} diff --git a/django_couchdb_utils/openid_consumer/_design/views/timestamp_view/map.js b/django_couchdb_utils/openid_consumer/_design/views/timestamp_view/map.js new file mode 100644 index 0000000..cbbd42a --- /dev/null +++ b/django_couchdb_utils/openid_consumer/_design/views/timestamp_view/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.doc_type == 'Nonce') { + emit(doc.timestamp, doc); + } +} diff --git a/django_couchdb_utils/openid_consumer/_design/views/url_handle_view/map.js b/django_couchdb_utils/openid_consumer/_design/views/url_handle_view/map.js new file mode 100644 index 0000000..be30559 --- /dev/null +++ b/django_couchdb_utils/openid_consumer/_design/views/url_handle_view/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.doc_type == 'Association') { + emit([doc.server_url, doc.handle], doc); + } +} diff --git a/django_couchdb_utils/openid_consumer/_design/views/url_timestamp_salt_view/map.js b/django_couchdb_utils/openid_consumer/_design/views/url_timestamp_salt_view/map.js new file mode 100644 index 0000000..51bd4df --- /dev/null +++ b/django_couchdb_utils/openid_consumer/_design/views/url_timestamp_salt_view/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.doc_type == 'Nonce') { + emit([doc.server_url, doc.timestamp, doc.salt], doc); + } +} diff --git a/django_couchdb_utils/openid_consumer/_design/views/url_view/map.js b/django_couchdb_utils/openid_consumer/_design/views/url_view/map.js new file mode 100644 index 0000000..4bb5ef3 --- /dev/null +++ b/django_couchdb_utils/openid_consumer/_design/views/url_view/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.doc_type == 'Association') { + emit(doc.server_url, doc); + } +} diff --git a/django_couchdb_utils/openid_consumer/consumer.py b/django_couchdb_utils/openid_consumer/consumer.py index 26d9b6c..cbfbd53 100644 --- a/django_couchdb_utils/openid_consumer/consumer.py +++ b/django_couchdb_utils/openid_consumer/consumer.py @@ -1,15 +1,17 @@ -import couchdb, datetime +import datetime from django.conf import settings from openid.consumer import consumer from django_openid.consumer import signed -from openid_consumer.models import server_uri, DB_PREFIX from django_openid.auth import AuthConsumer as DjangoOpenidAuthConsumer # I know this long naming sucks! But couldn't think of anything better... from django_openid.consumer import Consumer as DjangoOpenidConsumer, \ LoginConsumer as DjangoOpenidLoginConsumer, \ SessionConsumer as DjangoOpenidSessionConsumer, \ CookieConsumer as DjangoOpenidCookieConsumer -from openid_consumer.models import DjangoCouchDBOpenIDStore, get_or_create, get_values +from .models import DjangoCouchDBOpenIDStore, UserOpenidAssociation +from django_couchdb_utils.auth.models import User +from django.contrib.auth import login +from couchdbkit.exceptions import ResourceNotFound class Consumer(DjangoOpenidConsumer): def get_consumer(self, request, session_store): @@ -25,18 +27,12 @@ class CookieConsumer(LoginConsumer, DjangoOpenidCookieConsumer): pass class AuthConsumer(SessionConsumer, DjangoOpenidAuthConsumer): - def __init__(self): - auth_db_prefix = getattr(settings, "COUCHDB_AUTH_PREFIX", "") - self.auth_db = get_or_create(server_uri, "%s%s" %(auth_db_prefix, "auth")) - super(AuthConsumer, self).__init__() - def user_can_login(self, request, user): "Over-ride for things like user bans or account e-mail validation" return user.is_active def log_in_user(self, request, user): - from auth import login - user.backend = 'auth.backends.CouchDBAuthBackend' + user.backend = 'django_couchdb_utils.auth.backends.CouchDBAuthBackend' login(request, user) def do_associate(self, request): @@ -49,13 +45,13 @@ def do_associate(self, request): except signed.BadSignature: return self.show_error(request, self.csrf_failed_message) # Associate openid with their account, if it isn't already - temp_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, "user_openid")) - if not len(get_values(temp_db.view('openid_view/all', key = openid))): - from openid_consumer.models import UserOpenidAssociation + if not len(UserOpenidAssociation.view('%s/openid_view' % UserOpenidAssociation._meta.app_label, + key = openid), include_docs=True): uoa = UserOpenidAssociation(user_id = request.user.id, openid = openid, created = datetime.datetime.now()) - uoa.store(temp_db) + uoa["temp"] = True + uoa.store() return self.show_associate_done(request, openid) return self.show_error(request, 'Should POST to here') @@ -77,19 +73,19 @@ def do_associations(self, request): message = self.associate_tampering_message else: # It matches! Delete the OpenID relationship - temp_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, "user_openid")) - from openid_consumer.models import UserOpenidAssociation - rows = get_values(temp_db.view('openid_view/all', key=todelete['openid'])) - temp_db.delete(rows[0]) - message = self.association_deleted_message % ( - todelete['openid'] - ) + row = UserOpenidAssociation.view('%s/openid_view' % UserOpenidAssociation._meta.app_label, + key=todelete['openid'], include_docs=True).first() + if row.temp == True: + row.delete() + message = self.association_deleted_message % ( + todelete['openid'] + ) except signed.BadSignature: message = self.associate_tampering_message # We construct a button to delete each existing association openids = [] - temp_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, "user_openid")) - for association in get_values(temp_db.view('openid_view/all')): + for association in UserOpenidAssociation.view('%s/openid_view' % UserOpenidAssociation._meta.app_label, + include_docs=True): openids.append({ 'openid': association['openid'], 'button': signed.dumps({ @@ -109,22 +105,19 @@ def do_associations(self, request): def lookup_openid(self, request, identity_url): - from auth.models import User - temp_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, "user_openid")) - try: - openid = get_values(temp_db.view('openid_view/all', key=identity_url))[0] - except IndexError: - return [] - return [User.load(self.auth_db, openid['user_id']),] + openid = UserOpenidAssociation.view('%s/openid_view' % UserOpenidAssociation._meta.app_label, + key=identity_url, include_docs=True).first() + if openid: + return User.view('%s/users_by_username' % User._meta.app_label, + key=openid['user_id'], include_docs=True).all() def lookup_users_by_email(self, email): - return get_values(self.auth_db.view('auth_email/all', key=email)) + return User.view('%s/users_by_email' % User._meta.app_label, + key=email, include_docs=True).first() def lookup_user_by_username(self, username): - try: - return get_values(self.auth_db.view('auth_id/all', key=username)) - except IndexError: - return None + return User.view('%s/users_by_username' % User._meta.app_label, + key=username, include_docs=True).first() def lookup_user_by_id(self, id): return self.lookup_user_by_username(id) diff --git a/django_couchdb_utils/openid_consumer/forms.py b/django_couchdb_utils/openid_consumer/forms.py index 7bbf4ba..951b342 100644 --- a/django_couchdb_utils/openid_consumer/forms.py +++ b/django_couchdb_utils/openid_consumer/forms.py @@ -1,24 +1,24 @@ from django import forms -from auth.models import User, DB_PREFIX -from openid_consumer.models import server_uri, get_or_create, get_values +from django_couchdb_utils.auth.models import User from django_openid.forms import RegistrationForm as DjangoOpenidRegistrationForm, \ RegistrationFormPasswordConfirm as DjangoOpenidRegistrationFormPasswordConfirm +from couchdbkit.exceptions import ResourceNotFound class RegistrationForm(DjangoOpenidRegistrationForm): def __init__(self, *args, **kwargs): super(RegistrationForm, self).__init__(*args, **kwargs) - self.auth_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, "auth")) - User.id_view.sync(self.auth_db) - User.email_view.sync(self.auth_db) - User.is_active_view.sync(self.auth_db) def save(self): user = User(**self.cleaned_data) - return user.store(self.auth_db) + return user.store() def clean_email(self): email = self.cleaned_data.get('email', '') - if self.no_duplicate_emails and len(get_values(self.auth_db.view('auth_email/all', key = email))) > 0: + try: + email_count = User.view('%s/users_by_email' % User._meta.app_label, key = email).count() + except ResourceNotFound: + email_count = 0 + if self.no_duplicate_emails and email_count > 0: raise forms.ValidationError, self.duplicate_email_error return email diff --git a/django_couchdb_utils/openid_consumer/models.py b/django_couchdb_utils/openid_consumer/models.py index 3326954..cf8f181 100644 --- a/django_couchdb_utils/openid_consumer/models.py +++ b/django_couchdb_utils/openid_consumer/models.py @@ -1,88 +1,39 @@ -from auth.models import User -from couchdb.schema import * -from couchdb.schema import View from django.conf import settings -from couchdb import Server, Database +from couchdbkit.ext.django.schema import * import time, base64, openid.store, urlparse -from couchdb.client import PreconditionFailed from django_openid.models import DjangoOpenIDStore from django.utils.hashcompat import md5_constructor from openid.association import Association as OIDAssociation - -DEFAULT_COUCHDB_HOST = "http://127.0.0.1:5984" -server_uri = getattr(settings, 'COUCHDB_HOST', DEFAULT_COUCHDB_HOST) -DB_PREFIX = getattr(settings, 'COUCHDB_OPENID_PREFIX', '') -openid_db_uri = getattr(settings, 'COUCHDB_OPENID_DB', '%s%s' %(DB_PREFIX, 'openid')) - -def get_or_create(server_uri, db_name): - server = Server(server_uri) - try: - db = server.create(db_name) - except PreconditionFailed, e: - if not e.message[0] == 'file_exists': - raise e - # Database seems to exist. Let's just use it - db = Database(urlparse.urljoin(server_uri, db_name)) - return db - -def get_values(db_view_result): - return [i['value'] for i in db_view_result.rows] - +from couchdbkit.exceptions import ResourceNotFound class UserOpenidAssociation(Document): - user_id = TextField() - openid = TextField() - created = DateTimeField() + user_id = StringProperty() + openid = StringProperty() + created = DateTimeProperty() - openid_view = View('openid_view', - '''function (doc) { emit(doc.openid, doc); }''', - name='all') + class Meta: + app_label = "django_couchdb_utils_openid_consumer" class Nonce(Document): - server_url = TextField() - timestamp = IntegerField() - salt = TextField() + server_url = StringProperty() + timestamp = IntegerProperty() + salt = StringProperty() - timestamp_view = View('timestamp_view', - '''function (doc) { emit(doc.timestamp, doc); }''', - name='all') - url_timestamp_salt_view = View('url_timestamp_salt_view', - '''function (doc) { emit([doc.server_url, doc.timestamp, doc.salt], doc); }''', - name='all') + class Meta: + app_label = "django_couchdb_utils_openid_consumer" class Association(Document): - server_url = TextField() - handle = TextField() - secret = TextField() # Stored base64 encoded - issued = IntegerField() - lifetime = IntegerField() - assoc_type = TextField() + server_url = StringProperty() + handle = StringProperty() + secret = StringProperty() # Stored base64 encoded + issued = IntegerProperty() + lifetime = IntegerProperty() + assoc_type = StringProperty() - url_handle_view = View('url_handle_view', - '''function (doc) { emit([doc.server_url, doc.handle], doc); }''', - name='all') - url_view = View('url_view', - '''function (doc) { emit(doc.server_url, doc); }''', - name='all') - issued_lifetime_view = View('issued_lifetime_view', - '''function (doc) { emit(doc.issued+doc.lifetime, doc); } ''', - name='all') + class Meta: + app_label = "django_couchdb_utils_openid_consumer" class DjangoCouchDBOpenIDStore(DjangoOpenIDStore): - def __init__(self): - # This constructor argument is specific only to - # the couchdb store. It accepts a couchdb db - # instance - self.nonce_db = get_or_create(server_uri, "%s_nonce" %openid_db_uri) - self.assoc_db = get_or_create(server_uri, "%s_assoc" %openid_db_uri) - self.user_openid_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, "user_openid")) - UserOpenidAssociation.openid_view.sync(self.user_openid_db) - Nonce.timestamp_view.sync(self.nonce_db) - Nonce.url_timestamp_salt_view.sync(self.nonce_db) - Association.url_handle_view.sync(self.assoc_db) - Association.url_view.sync(self.assoc_db) - Association.issued_lifetime_view.sync(self.assoc_db) - def storeAssociation(self, server_url, association): assoc = Association( server_url = server_url, @@ -92,62 +43,70 @@ def storeAssociation(self, server_url, association): lifetime = association.issued, assoc_type = association.assoc_type ) - assoc.store(self.assoc_db) + assoc.store() def getAssociation(self, server_url, handle=None): assocs = [] if handle is not None: - assocs = get_values(self.assoc_db.view('url_handle_view/all', key=[server_url, handle])) + assocs = Association.view('%s/url_handle_view' % Association._meta.app_label, + key=[server_url, handle], include_docs=True).all() else: - assocs = get_values(self.assoc_db.view('url_view/all', key=server_url)) - if not assocs: - return None + assocs = Association.view('%s/url_view' % Association._meta.app_label, + key=server_url, include_docs=True).all() associations = [] - for assoc in assocs: - association = OIDAssociation( - assoc['handle'], base64.decodestring(assoc['secret']), assoc['issued'], - assoc['lifetime'], assoc['assoc_type'] - ) - if association.getExpiresIn() == 0: - self.removeAssociation(server_url, assoc.handle) - else: - associations.append((association.issued, association)) + try: + for assoc in assocs: + association = OIDAssociation( + assoc['handle'], base64.decodestring(assoc['secret']), assoc['issued'], + assoc['lifetime'], assoc['assoc_type'] + ) + if association.getExpiresIn() == 0: + self.removeAssociation(server_url, assoc.handle) + else: + associations.append((association.issued, association)) + except ResourceNotFound: + pass if not associations: return None return associations[-1][1] def removeAssociation(self, server_url, handle): - assocs = get_values(self.assoc_db.view('url_handle_view/all', key=[server_url, handle])) - assocs_exist = len(assocs) > 0 + try: + assocs = Association.view('%s/url_handle_view' % Association._meta.app_label, + key=[server_url, handle], include_docs=True).all() + except ResourceNotFound: + assocs = [] for assoc in assocs: - self.assoc_db.delete(assoc) - return assocs_exist + assoc.delete() + return len(assocs) def useNonce(self, server_url, timestamp, salt): # Has nonce expired? if abs(timestamp - time.time()) > openid.store.nonce.SKEW: return False - try: - nonce = get_values(self.nonce_db.view('url_timestamp_salt_view/all', - key=[server_url, timestamp, salt]))[0] - except IndexError: + nonce = Nonce.view('%s/url_timestamp_salt_view' % Nonce._meta.app_label, + key=[server_url, timestamp, salt], include_docs=True).first() + if not nonce: nonce = Nonce( server_url = server_url, timestamp = timestamp, salt = salt ) - nonce.store(self.nonce_db) + nonce.store() return True - self.nonce_db.delete(nonce) + if nonce: + nonce.delete() return False def cleanupNonce(self): max_key_val = time.time() - openid.store.nonce.SKEW - nonces = get_values(self.nonce_db.view('timestamp_view/all', endkey=max_key_val)) + nonces = Nonce.view('%s/timestamp_view' % Nonce._meta.app_label, + endkey=max_key_val, include_docs=True) for nonce in nonces: - self.nonce_db.delete(nonce) + nonce.delete() def cleaupAssociations(self): - assocs = get_values(self.assoc_db.view('issued_lifetime_view/all', endkey=time.time())) + assocs = Association.view('%s/issued_lifetime_view' % Association._meta.app_label, + endkey=time.time(), include_docs=True) for assoc in assocs: - self.assoc_db.delete(assoc) + assoc.delete() diff --git a/django_couchdb_utils/openid_consumer/registration.py b/django_couchdb_utils/openid_consumer/registration.py index 8806e3a..9c14384 100644 --- a/django_couchdb_utils/openid_consumer/registration.py +++ b/django_couchdb_utils/openid_consumer/registration.py @@ -1,50 +1,56 @@ -from auth.models import User +from django_couchdb_utils.auth.models import User from datetime import datetime from django.http import Http404 from django_openid import signed from django.conf import settings -from openid_consumer.consumer import AuthConsumer -from openid_consumer.forms import RegistrationFormPasswordConfirm -from openid_consumer.models import server_uri, DB_PREFIX, get_values, get_or_create +from .consumer import AuthConsumer +from .forms import RegistrationFormPasswordConfirm +from .models import UserOpenidAssociation from django_openid.registration import RegistrationConsumer as DjangoOpenIDRegistrationConsumer +from couchdbkit.exceptions import ResourceNotFound class RegistrationConsumer(AuthConsumer, DjangoOpenIDRegistrationConsumer): RegistrationForm = RegistrationFormPasswordConfirm def user_is_unconfirmed(self, user): - return len(get_values(self.auth_db.view('auth_is_active/all', key=[user['_id'], False]))) + count = 0 + try: + count = User.view('%s/users_by_username' % User._meta.app_label, + key=user.username, include_docs=True).count() + except ResourceNotFound: + return False + if count: + return True + return False def mark_user_confirmed(self, user): user.is_active = True - return user.store(self.auth_db) + return user.store() def mark_user_unconfirmed(self, user): user.is_active = False - user.store(self.auth_db) + return user.store() def create_user(self, request, data, openid=None): user = User( - id = data['username'], + username = data['username'], first_name = data.get('first_name', ''), last_name = data.get('last_name', ''), email = data.get('email', ''), ) - user.store(self.auth_db) # Set OpenID, if one has been associated if openid: - from openid_consumer.models import UserOpenidAssociation - temp_db = get_or_create(server_uri, "%s%s" %(DB_PREFIX, 'user_openid')) - uoa = UserOpenidAssociation(user_id = user.id, + uoa = UserOpenidAssociation(user_id = user.username, openid = openid, created = datetime.now()) - uoa.store(temp_db) + uoa.store() # Set password, if one has been specified password = data.get('password') if password: user.set_password(password) else: user.set_unusable_password() - user.store(self.auth_db) + user.store() return user def suggest_nickname(self, nickname): @@ -53,7 +59,15 @@ def suggest_nickname(self, nickname): return '' original_nickname = nickname suffix = None - while len(self.auth_db.view('auth_id/all', key=nickname).rows): + username_exists = True + while username_exists: + try: + username_exists = User.view('%s/users_by_username' % User._meta.app_label, + key=nickname, include_docs=True).count() + except ResourceNotFound: + username_exists = False + if not username_exists: + break if suffix is None: suffix = 1 else: @@ -76,7 +90,7 @@ def do_password(self, request): if form.is_valid(): u = request.user u.set_password(form.cleaned_data['password']) - u.store(self.auth_db) + u.store() return self.show_password_has_been_set(request) else: form = ChangePasswordForm(request.user) @@ -101,17 +115,21 @@ def do_c(self, request, token = ''): ) # Only line change compared with django-openid user_id = value - try: - user = self.lookup_user_by_id(user_id)[0] - except IndexError: # Maybe the user was deleted? + user = self.lookup_user_by_id(user_id) + if not user: # Maybe the user was deleted? return self.show_error(request, self.r_user_not_found_message) # Check user is NOT active but IS in the correct group if self.user_is_unconfirmed(user): # Confirm them - user = User.load(self.auth_db, user['_id']) - self.mark_user_confirmed(user) - self.log_in_user(request, user) + try: + user = User.view('%s/users_by_username' % User._meta.app_label, + key=user.username, include_docs=True).first() + except ResourceNotFound: + user = None + if user: + self.mark_user_confirmed(user) + self.log_in_user(request, user) return self.on_registration_complete(request) else: return self.show_error(request, self.c_already_confirmed_message)