From c4151b7752374c0aed4bfc9f49cd8d3ff3cee501 Mon Sep 17 00:00:00 2001 From: Adolfo Fitoria Date: Tue, 18 Jun 2013 15:36:15 -0600 Subject: [PATCH] Moving away from session storage for user email confirmation process --- askbot/__init__.py | 3 +- .../0006_auto__add_useremailverifier.py | 134 ++++++++++++++++++ askbot/deps/django_authopenid/models.py | 31 ++-- askbot/deps/django_authopenid/views.py | 51 ++++--- askbot_requirements.txt | 1 + askbot_requirements_dev.txt | 1 + 6 files changed, 188 insertions(+), 33 deletions(-) create mode 100644 askbot/deps/django_authopenid/migrations/0006_auto__add_useremailverifier.py diff --git a/askbot/__init__.py b/askbot/__init__.py index 25057b43..cf529de7 100644 --- a/askbot/__init__.py +++ b/askbot/__init__.py @@ -36,7 +36,8 @@ REQUIREMENTS = { 'pytz': 'pytz', 'tinymce': 'django-tinymce==1.5.1b2', 'longerusername': 'longerusername', - 'bs4': 'beautifulsoup4' + 'bs4': 'beautifulsoup4', + 'picklefield': 'django-picklefield==0.3.0', } if platform.system() != 'Windows': diff --git a/askbot/deps/django_authopenid/migrations/0006_auto__add_useremailverifier.py b/askbot/deps/django_authopenid/migrations/0006_auto__add_useremailverifier.py new file mode 100644 index 00000000..98adeec3 --- /dev/null +++ b/askbot/deps/django_authopenid/migrations/0006_auto__add_useremailverifier.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'UserEmailVerifier' + db.create_table('django_authopenid_useremailverifier', ( + ('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)), + ('value', self.gf('picklefield.fields.PickledObjectField')()), + ('verified', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('django_authopenid', ['UserEmailVerifier']) + + + def backwards(self, orm): + # Deleting model 'UserEmailVerifier' + db.delete_table('django_authopenid_useremailverifier') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'languages': ('django.db.models.fields.CharField', [], {'default': "'es-NI'", 'max_length': '128'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'social_sharing_mode': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'twitter_access_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}), + 'twitter_handle': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'django_authopenid.association': { + 'Meta': {'object_name': 'Association'}, + 'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}), + 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issued': ('django.db.models.fields.IntegerField', [], {}), + 'lifetime': ('django.db.models.fields.IntegerField', [], {}), + 'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}), + 'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'}) + }, + 'django_authopenid.nonce': { + 'Meta': {'object_name': 'Nonce'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'salt': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {}) + }, + 'django_authopenid.userassociation': { + 'Meta': {'unique_together': "(('user', 'provider_name'), ('openid_url', 'provider_name'))", 'object_name': 'UserAssociation'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_used_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'openid_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'provider_name': ('django.db.models.fields.CharField', [], {'default': "'unknown'", 'max_length': '64'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'django_authopenid.useremailverifier': { + 'Meta': {'object_name': 'UserEmailVerifier'}, + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), + 'value': ('picklefield.fields.PickledObjectField', [], {}) + }, + 'django_authopenid.userpasswordqueue': { + 'Meta': {'object_name': 'UserPasswordQueue'}, + 'confirm_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_password': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + } + } + + complete_apps = ['django_authopenid'] diff --git a/askbot/deps/django_authopenid/models.py b/askbot/deps/django_authopenid/models.py index 31ec36f9..cf2aa676 100644 --- a/askbot/deps/django_authopenid/models.py +++ b/askbot/deps/django_authopenid/models.py @@ -3,21 +3,24 @@ from django.conf import settings from django.contrib.auth.models import User from django.db import models +from picklefield.fields import PickledObjectField + import hashlib, random, sys, os, time -__all__ = ['Nonce', 'Association', 'UserAssociation', - 'UserPasswordQueueManager', 'UserPasswordQueue'] +__all__ = ['Nonce', 'Association', 'UserAssociation', + 'UserPasswordQueueManager', 'UserPasswordQueue', + 'UserEmailVerifier'] class Nonce(models.Model): """ openid nonce """ server_url = models.CharField(max_length=255) timestamp = models.IntegerField() salt = models.CharField(max_length=40) - + def __unicode__(self): return u"Nonce: %s" % self.id - + class Association(models.Model): """ association openid url and lifetime """ server_url = models.TextField(max_length=2047) @@ -26,19 +29,19 @@ class Association(models.Model): issued = models.IntegerField() lifetime = models.IntegerField() assoc_type = models.TextField(max_length=64) - + def __unicode__(self): return u"Association: %s, %s" % (self.server_url, self.handle) class UserAssociation(models.Model): - """ - model to manage association between openid and user + """ + model to manage association between openid and user """ #todo: rename this field so that it sounds good for other methods #for exaple, for password provider this will hold password openid_url = models.CharField(blank=False, max_length=255) user = models.ForeignKey(User) - #in the future this must be turned into an + #in the future this must be turned into an #association with a Provider record #to hold things like login badge, etc provider_name = models.CharField(max_length=64, default='unknown') @@ -49,7 +52,7 @@ class UserAssociation(models.Model): ('user','provider_name'), ('openid_url', 'provider_name') ) - + def __unicode__(self): return "Openid %s with user %s" % (self.openid_url, self.user) @@ -82,3 +85,13 @@ class UserPasswordQueue(models.Model): def __unicode__(self): return self.user.username + +class UserEmailVerifier(models.Model): + '''Model that stores the required values to verify an email + address''' + key = models.CharField(max_length=255, unique=True, primary_key=True) + value = PickledObjectField() + verified = models.BooleanField(default=False) + + def __unicode__(self): + return self.key diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py index accd7a04..bde63139 100644 --- a/askbot/deps/django_authopenid/views.py +++ b/askbot/deps/django_authopenid/views.py @@ -81,7 +81,7 @@ import urllib from askbot import forms as askbot_forms from askbot.deps.django_authopenid import util from askbot.deps.django_authopenid import decorators -from askbot.deps.django_authopenid.models import UserAssociation +from askbot.deps.django_authopenid.models import UserAssociation, UserEmailVerifier from askbot.deps.django_authopenid import forms from askbot.deps.django_authopenid.backends import AuthBackend import logging @@ -1019,12 +1019,13 @@ def register(request, login_provider_name=None, user_identifier=None): cleanup_post_register_session(request) return HttpResponseRedirect(next_url) else: - request.session['username'] = username - request.session['email'] = email - key = generate_random_key() - email = request.session['email'] - send_email_key(email, key, handler_url_name='verify_email_and_register') - request.session['validation_code'] = key + email_verifier = UserEmailVerifier(key=generate_random_key()) + email_verifier.value = {'username': username, 'email': email, + 'user_identifier': user_identifier, + 'login_provider_name': login_provider_name} + email_verifier.save() + send_email_key(email, email_verifier.key, + handler_url_name='verify_email_and_register') redirect_url = reverse('verify_email_and_register') + '?next=' + next_url return HttpResponseRedirect(redirect_url) @@ -1073,14 +1074,16 @@ def verify_email_and_register(request): try: #we get here with post if button is pushed #or with "get" if emailed link is clicked - expected_code = request.session['validation_code'] - assert(presented_code == expected_code) - #create an account! - username = request.session['username'] - email = request.session['email'] - password = request.session.get('password', None) - user_identifier = request.session.get('user_identifier', None) - login_provider_name = request.session.get('login_provider_name', None) + email_verifier = UserEmailVerifier.objects.get(key=presented_code) + #verifies that the code has not been used already + assert(email_verifier.verified == False) + + username = email_verifier.value['username'] + email = email_verifier.value['email'] + password = email_verifier.value.get('password', None) + user_identifier = email_verifier.value.get('user_identifier', None) + login_provider_name = email_verifier.value.get('login_provider_name', None) + if password: user = create_authenticated_user_account( username=username, @@ -1098,7 +1101,10 @@ def verify_email_and_register(request): raise NotImplementedError() login(request, user) + email_verifier.verified = True + email_verifier.save() cleanup_post_register_session(request) + return HttpResponseRedirect(get_next_url(request)) except Exception, e: message = _( @@ -1159,14 +1165,13 @@ def signup_with_password(request): cleanup_post_register_session(request) return HttpResponseRedirect(get_next_url(request)) else: - request.session['username'] = username - request.session['email'] = email - request.session['password'] = password - #todo: generate a key and save it in the session - key = generate_random_key() - email = request.session['email'] - send_email_key(email, key, handler_url_name='verify_email_and_register') - request.session['validation_code'] = key + email_verifier = UserEmailVerifier(key=generate_random_key()) + email_verifier.value = {'username': username, + 'login_provider_name': provider_name, + 'email': email, 'password': password} + email_verifier.save() + send_email_key(email, email_verifier.key, + handler_url_name='verify_email_and_register') redirect_url = reverse('verify_email_and_register') + \ '?next=' + get_next_url(request) return HttpResponseRedirect(redirect_url) diff --git a/askbot_requirements.txt b/askbot_requirements.txt index ea9dd005..59694444 100644 --- a/askbot_requirements.txt +++ b/askbot_requirements.txt @@ -23,3 +23,4 @@ sanction django-tinymce==1.5.1b2 longerusername beautifulsoup4 +django-picklefield==0.3.0 diff --git a/askbot_requirements_dev.txt b/askbot_requirements_dev.txt index b88592cf..e5c36577 100644 --- a/askbot_requirements_dev.txt +++ b/askbot_requirements_dev.txt @@ -27,3 +27,4 @@ sanction django-tinymce longerusername beautifulsoup4 +django-picklefield==0.3.0