diff options
Diffstat (limited to 'forum')
23 files changed, 523 insertions, 113 deletions
diff --git a/forum/authentication/base.py b/forum/authentication/base.py index 08534adc..99005866 100755 --- a/forum/authentication/base.py +++ b/forum/authentication/base.py @@ -33,6 +33,10 @@ class ConsumerTemplateContext(object): extra_css = [] show_to_logged_in_user = True + @classmethod + def readable_key(cls, key): + return key.key + class InvalidAuthentication(Exception): def __init__(self, message): self.message = message diff --git a/forum/authentication/forms.py b/forum/authentication/forms.py index 019c85f3..7fa06b01 100755 --- a/forum/authentication/forms.py +++ b/forum/authentication/forms.py @@ -1,7 +1,8 @@ -from forum.utils.forms import NextUrlField, UserNameField, UserEmailField -from forum.models import EmailFeedSetting, Question +from forum.utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm +from forum.models import EmailFeedSetting, Question, User from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext as _ +from django.utils.safestring import mark_safe from django import forms from forum.forms import EditUserEmailFeedsForm import logging @@ -11,6 +12,29 @@ class SimpleRegistrationForm(forms.Form): username = UserNameField() email = UserEmailField() +class TemporaryLoginRequestForm(forms.Form): + def __init__(self, data=None): + super(TemporaryLoginRequestForm, self).__init__(data) + self.user_cache = None + + email = forms.EmailField( + required=True, + label=_("Your account email"), + error_messages={ + 'required': _("You cannot leave this field blank"), + 'invalid': _('please enter a valid email address'), + } + ) + + def clean_email(self): + try: + user = User.objects.get(email=self.cleaned_data['email']) + except: + raise forms.ValidationError(_("Sorry, but this email is not on our database.")) + + self.user_cache = user + return self.cleaned_data['email'] + class SimpleEmailSubscribeForm(forms.Form): SIMPLE_SUBSCRIBE_CHOICES = ( @@ -29,3 +53,21 @@ class SimpleEmailSubscribeForm(forms.Form): else: email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL) email_settings_form.save(user,save_unbound=True) + +class ChangePasswordForm(SetPasswordForm): + """ change password form """ + oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}), + label=mark_safe(_('Current password'))) + + def __init__(self, data=None, user=None, *args, **kwargs): + if user is None: + raise TypeError("Keyword argument 'user' must be supplied") + super(ChangePasswordForm, self).__init__(data, *args, **kwargs) + self.user = user + + def clean_oldpw(self): + """ test old password """ + if not self.user.check_password(self.cleaned_data['oldpw']): + raise forms.ValidationError(_("Old password is incorrect. \ + Please enter the correct password.")) + return self.cleaned_data['oldpw'] diff --git a/forum/context.py b/forum/context.py new file mode 100644 index 00000000..b33a1760 --- /dev/null +++ b/forum/context.py @@ -0,0 +1,47 @@ +from django.conf import settings +def application_settings(context): + my_settings = { + 'APP_TITLE' : settings.APP_TITLE, + 'APP_SHORT_NAME' : settings.APP_SHORT_NAME, + 'APP_URL' : settings.APP_URL, + 'APP_KEYWORDS' : settings.APP_KEYWORDS, + 'APP_DESCRIPTION' : settings.APP_DESCRIPTION, + 'APP_INTRO' : settings.APP_INTRO, + 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION, + 'FEEDBACK_SITE_URL': settings.FEEDBACK_SITE_URL, + 'FORUM_SCRIPT_ALIAS': settings.FORUM_SCRIPT_ALIAS, + 'LANGUAGE_CODE': settings.LANGUAGE_CODE, + 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE, + 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY, + 'WIKI_ON':settings.WIKI_ON, + 'USE_EXTERNAL_LEGACY_LOGIN':settings.USE_EXTERNAL_LEGACY_LOGIN, + 'RESOURCE_REVISION':settings.RESOURCE_REVISION, + 'OSQA_SKIN':settings.OSQA_DEFAULT_SKIN, + } + return {'settings':my_settings} + +def auth_processor(request): + """ + Returns context variables required by apps that use Django's authentication + system. + + If there is no 'user' attribute in the request, uses AnonymousUser (from + django.contrib.auth). + """ + if hasattr(request, 'user'): + user = request.user + if user.is_authenticated(): + messages = user.message_set.all() + else: + messages = None + else: + from django.contrib.auth.models import AnonymousUser + user = AnonymousUser() + messages = None + + from django.core.context_processors import PermWrapper + return { + 'user': user, + 'messages': messages, + 'perms': PermWrapper(user), + } diff --git a/forum/forms.py b/forum/forms.py index 5d4fb516..2260bfe5 100755 --- a/forum/forms.py +++ b/forum/forms.py @@ -261,25 +261,7 @@ class TagFilterSelectionForm(forms.ModelForm): if before != after: return True return False - - -class ChangePasswordForm(SetPasswordForm): - """ change password form """ - oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}), - label=mark_safe(_('Current password'))) - - def __init__(self, data=None, user=None, *args, **kwargs): - if user is None: - raise TypeError("Keyword argument 'user' must be supplied") - super(ChangePasswordForm, self).__init__(data, *args, **kwargs) - self.user = user - - def clean_oldpw(self): - """ test old password """ - if not self.user.check_password(self.cleaned_data['oldpw']): - raise forms.ValidationError(_("Old password is incorrect. \ - Please enter the correct password.")) - return self.cleaned_data['oldpw'] + class EditUserEmailFeedsForm(forms.Form): WN = (('w',_('weekly')),('n',_('no email'))) diff --git a/forum/models/__init__.py b/forum/models/__init__.py index db66a227..fee52447 100755 --- a/forum/models/__init__.py +++ b/forum/models/__init__.py @@ -2,7 +2,7 @@ from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion from answer import Answer, AnonymousAnswer, AnswerRevision from tag import Tag, MarkedTag from meta import Vote, Comment, FlaggedItem -from user import Activity, AnonymousEmail, EmailFeedSetting, AuthKeyUserAssociation +from user import Activity, ValidationHash, EmailFeedSetting, AuthKeyUserAssociation from repute import Badge, Award, Repute import re @@ -34,6 +34,9 @@ def user_get_q_sel_email_feed_frequency(self): #print 'have freq=%s' % feed_setting.frequency return feed_setting.frequency +def user_get_absolute_url(self): + return "/users/%d/%s/" % (self.id, (self.username)) + User.add_to_class('is_approved', models.BooleanField(default=False)) User.add_to_class('email_isvalid', models.BooleanField(default=False)) User.add_to_class('email_key', models.CharField(max_length=32, null=True)) @@ -68,6 +71,7 @@ User.add_to_class('tag_filter_setting', default='ignored' ) ) +User.add_to_class('get_absolute_url', user_get_absolute_url) # custom signal tags_updated = django.dispatch.Signal(providing_args=["question"]) @@ -294,7 +298,6 @@ def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs) aa.publish(user) #signal for User modle save changes - pre_save.connect(calculate_gravatar_hash, sender=User) post_save.connect(record_ask_event, sender=Question) post_save.connect(record_answer_event, sender=Answer) @@ -338,7 +341,7 @@ Repute = Repute Activity = Activity EmailFeedSetting = EmailFeedSetting -AnonymousEmail = AnonymousEmail +ValidationHash = ValidationHash AuthKeyUserAssociation = AuthKeyUserAssociation __all__ = [ @@ -364,7 +367,7 @@ __all__ = [ 'Activity', 'EmailFeedSetting', - 'AnonymousEmail', + 'ValidationHash', 'AuthKeyUserAssociation', 'User' diff --git a/forum/models/user.py b/forum/models/user.py index bed6f55e..ff0af7fa 100755 --- a/forum/models/user.py +++ b/forum/models/user.py @@ -1,6 +1,9 @@ from base import * from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User +from hashlib import md5 +import string +from random import Random from django.utils.translation import ugettext as _ @@ -57,19 +60,72 @@ class EmailFeedSetting(models.Model): class Meta: app_label = 'forum' -class AnonymousEmail(models.Model): - #validation key, if used - key = models.CharField(max_length=32) - email = models.EmailField(null=False,unique=True) - isvalid = models.BooleanField(default=False) +from forum.utils.time import one_day_from_now + +class ValidationHashManager(models.Manager): + def _generate_md5_hash(self, user, type, hash_data, seed): + return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest() + + def create_new(self, user, type, hash_data=[], expiration=None): + seed = ''.join(Random().sample(string.letters+string.digits, 12)) + hash = self._generate_md5_hash(user, type, hash_data, seed) + + obj = ValidationHash(hash_code=hash, seed=seed, user=user, type=type) + + if expiration is not None: + obj.expiration = expiration + + try: + obj.save() + except: + return None + + return obj + + def validate(self, hash, user, type, hash_data=[]): + try: + obj = self.get(hash_code=hash) + except: + return False + + if obj.type != type: + return False + + if obj.user != user: + return False + + valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed)) + + if valid: + if obj.expiration < datetime.datetime.now(): + obj.delete() + return False + else: + obj.delete() + return True + + return False + +class ValidationHash(models.Model): + hash_code = models.CharField(max_length=256,unique=True) + seed = models.CharField(max_length=12) + expiration = models.DateTimeField(default=one_day_from_now) + type = models.CharField(max_length=12) + user = models.ForeignKey(User) + + objects = ValidationHashManager() class Meta: + unique_together = ('user', 'type') app_label = 'forum' + def __str__(self): + return self.hash_code + class AuthKeyUserAssociation(models.Model): key = models.CharField(max_length=255,null=False,unique=True) provider = models.CharField(max_length=64) - user = models.ForeignKey(User) + user = models.ForeignKey(User, related_name="auth_keys") added_at = models.DateTimeField(default=datetime.datetime.now) class Meta: diff --git a/forum/skins/default/templates/auth/auth_settings.html b/forum/skins/default/templates/auth/auth_settings.html new file mode 100755 index 00000000..051fb6ba --- /dev/null +++ b/forum/skins/default/templates/auth/auth_settings.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +<!-- changepw.html --> +{% load i18n %} +{% block head %}{% endblock %} +{% block title %}{% spaceless %}{% trans "Authentication settings" %}{% endspaceless %}{% endblock %} +{% block content %} +<div class="headNormal">{% trans "Authentication settings" %}</div> +{% if auth_keys %} + <p class="message">{% blocktrans %}These are the external authentication providers currently associated with your account.{% endblocktrans %}</p> + <div> + {% for key in auth_keys %} + <p>{{ key.name }} (<a href="{% url user_remove_external_provider id=key.id %}">{% trans "remove" %}</a>)</p> + {% endfor %} + </div> +{% endif %} +{% if not auth_keys %} + <p class="message">{% blocktrans %}You currently have no external authentication provider associated with your account.{% endblocktrans %}</p> +{% endif %} +<input type="button" class="submit" value="{% trans "Add new provider" %}" onclick="window.location='{% url user_add_external_provider %}'" /> +{% if has_password %} + <p class="message">{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p> +{% endif %} +{% if not has_password %} + <p class="message">{% blocktrans %}You can set up a password for your account, so you can login using standard username and password!{% endblocktrans %}</p> +{% endif %} +<div class="aligned"> + <form action="" method="post" accept-charset="utf-8"> + <ul id="changepw-form" class="form-horizontal-rows"> + {{form.as_ul}} + </ul> + <div class="submit-row"><input type="submit" class="submit" value="{% if has_password %}{% trans "Change password" %}{% endif %}{% if not has_password %}{% trans "Create password" %}{% endif %}" /></div> + </form> + </div> +{% endblock %} +<!-- end changepw.html --> diff --git a/forum/skins/default/templates/auth/email_validation.html b/forum/skins/default/templates/auth/email_validation.html new file mode 100755 index 00000000..a4126a69 --- /dev/null +++ b/forum/skins/default/templates/auth/email_validation.html @@ -0,0 +1,20 @@ +{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "To make use of the Forum, please follow the link below:" %}</p>
+
+ <a href="{% fullurl auth_validate_email user=user.id,code=validation_code %}">{% fullurl auth_validate_email user=user.id,code=validation_code %}</a>
+
+ <p>{% trans "Following the link above will help us verify your email address." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
diff --git a/forum/skins/default/templates/auth/signin.html b/forum/skins/default/templates/auth/signin.html index d4ee9fc1..478de4a8 100755 --- a/forum/skins/default/templates/auth/signin.html +++ b/forum/skins/default/templates/auth/signin.html @@ -73,12 +73,16 @@ <input type="hidden" class="validate_email" name="validate_email" value="yes" />
</form>
{% for provider in stackitem_providers %}
- <h3 class="or_label">Or...</h3>
+ <h3 class="or_label">{% trans 'Or...' %}</h3>
<form class="signin_form" method="POST" action="{% url auth_provider_signin provider=provider.id %}">
{% include provider.stack_item_template %}
<input type="hidden" class="validate_email" name="validate_email" value="yes" />
</form>
{% endfor %}
+ <h3 class="or_label">{% trans 'Or...' %}</h3>
+ <fieldset>
+ {% trans 'Click' %} <a href="{% url auth_local_register %}">here</a> {% trans "if you're having troubles signing in." %}
+ </fieldset>
<script type="text/html" id="simple_form_template">
<fieldset id="slot_form">
<p id="provider_name_slot">{% trans 'Enter your ' %}%%YOUR_WHAT%%</p>
diff --git a/forum/skins/default/templates/auth/temp_login_email.html b/forum/skins/default/templates/auth/temp_login_email.html new file mode 100755 index 00000000..063608fe --- /dev/null +++ b/forum/skins/default/templates/auth/temp_login_email.html @@ -0,0 +1,20 @@ +{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "You're seeing this because someone requested a temporary login link" %}</p>
+
+ <a href="{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}">{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}</a>
+
+ <p>{% trans "Following the link above will give you access to your account." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
diff --git a/forum/skins/default/templates/auth/temp_login_request.html b/forum/skins/default/templates/auth/temp_login_request.html new file mode 100755 index 00000000..772f18fb --- /dev/null +++ b/forum/skins/default/templates/auth/temp_login_request.html @@ -0,0 +1,28 @@ +{% extends "base.html" %}
+
+{% load i18n %}
+{% block head %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Request temporary login key" %}{% endspaceless %}{% endblock %}
+{% block content %}
+<div class="headNormal">{% trans "Account: request temporary login key" %}</div>
+<p class="message">{% blocktrans %}
+ If you're experiencing problems accessing your account, or if you forgot your password,
+ here you can request a temporary login key. Fill out your account email and we'll send you a temporary access link that
+ will enable you to access your account. This token is valid only once and for a limited period of time.
+ {% endblocktrans %}</p>
+<div class="aligned">
+ {% if form.errors %}
+ <ul class="errorlist">
+ {% for error in form.errors %}
+ <li>{{ error }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ <form action="" method="post" accept-charset="utf-8">
+ <ul id="changepw-form" class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <div class="submit-row"><input type="submit" class="submit" value="{% trans "Send link" %}" /></div>
+ </form>
+ </div>
+{% endblock %}
\ No newline at end of file diff --git a/forum/skins/default/templates/base.html b/forum/skins/default/templates/base.html index 3a1848ef..884551e3 100755 --- a/forum/skins/default/templates/base.html +++ b/forum/skins/default/templates/base.html @@ -3,7 +3,7 @@ {% load extra_filters %} {% load extra_tags %} {% load i18n %} -<html xmlns="http://www.w3.org/1999/xhtml"{% if fb_api_key %} xmlns:fb="http://www.facebook.com/2008/fbml"{% endif %}> +<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title> {% spaceless %} diff --git a/forum/skins/default/templates/changepw.html b/forum/skins/default/templates/changepw.html deleted file mode 100755 index 7b4cf801..00000000 --- a/forum/skins/default/templates/changepw.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} -<!-- changepw.html --> -{% load i18n %} -{% block head %}{% endblock %} -{% block title %}{% spaceless %}{% trans "Change password" %}{% endspaceless %}{% endblock %} -{% block content %} -<div class="headNormal">{% trans "Account: change password" %}</div> -<p class="message">{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p> -<div class="aligned"> - <form action="" method="post" accept-charset="utf-8"> - <ul id="changepw-form" class="form-horizontal-rows"> - {{form.as_ul}} - </ul> - <div class="submit-row"><input type="submit" class="submit" value="{% trans "Change password" %}" /></div> - </form> - </div> -{% endblock %} -<!-- end changepw.html --> diff --git a/forum/skins/default/templates/email_base.html b/forum/skins/default/templates/email_base.html new file mode 100755 index 00000000..b74741e3 --- /dev/null +++ b/forum/skins/default/templates/email_base.html @@ -0,0 +1,26 @@ +{% load extra_filters %}
+{% load extra_tags %}
+{% load i18n %}
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link href="{% fullmedia "/media/style/style.css" %}" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <a href="{% fullurl index %}">
+ <img src="{% fullmedia "/media/images/logo.png" %}" title="{% trans "home" %}" alt="{{settings.APP_TITLE}} logo"/>
+ </a>
+ <br />
+ <p>{{ settings.APP_TITLE }}</p>
+ <br /><br />
+ <div id="wrapper">
+ <div id="room">
+ <div id="CALeft">
+ {% block content%}
+ {% endblock%}
+ </div>
+ </div>
+ <div class="spacer3"></div>
+ </div>
+ </body>
+</html>
\ No newline at end of file diff --git a/forum/skins/default/templates/user_info.html b/forum/skins/default/templates/user_info.html index 0ce4e77a..b0fd246a 100755 --- a/forum/skins/default/templates/user_info.html +++ b/forum/skins/default/templates/user_info.html @@ -46,12 +46,8 @@ {% endif %} {% separator %} {% ifequal request.user view_user %} - <a href="{% url auth_signin %}">add authentication method</a> + <a href="{% url user_authsettings %}">authentication settings</a> {% endifequal %} - {% separator %} - {% if request.user.has_usable_password %} - <a href="{% url user_changepw %}">change password</a> - {% endif %} {% endjoinitems %} </td> </tr> diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 26c52b8d..8fa01629 100755 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -13,6 +13,7 @@ from forum.models import Question, Answer, QuestionRevision, AnswerRevision from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.conf import settings +from django.template.defaulttags import url as default_url from forum import skins register = template.Library() @@ -355,3 +356,27 @@ def blockmedia(parser,token): if next.contents == 'endblockmedia': break return BlockMediaUrlNode(nodelist) + +class FullUrlNode(template.Node): + def __init__(self, default_node): + self.default_node = default_node + + def render(self, context): + domain = settings.APP_URL + protocol = getattr(settings, "PROTOCOL", "http") + path = self.default_node.render(context) + return "%s://%s%s" % (protocol, domain, path) + +@register.tag(name='fullurl') +def fullurl(parser, token): + default_node = default_url(parser, token) + return FullUrlNode(default_node) + +@register.simple_tag +def fullmedia(url): + domain = settings.APP_URL + protocol = getattr(settings, "PROTOCOL", "http") + path = media(url) + return "%s://%s%s" % (protocol, domain, path) + + diff --git a/forum/urls.py b/forum/urls.py index f81bad69..59693be7 100755 --- a/forum/urls.py +++ b/forum/urls.py @@ -76,7 +76,7 @@ urlpatterns = patterns('', url(r'^%s$' % _('users/'),app.users.users, name='users'), url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.users.moderate_user, name='moderate_user'), url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.users.edit_user, name='edit_user'), - url(r'^%s(?P<id>\d+)//*' % _('users/'), app.users.user, name='user'), + url(r'^%s(?P<id>\d+)/(?P<slug>\w+)/$' % _('users/'), app.users.user, name='user'), url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'), url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'), url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'), @@ -96,10 +96,15 @@ urlpatterns = patterns('', url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('signin/')), app.auth.prepare_provider_signin, name='auth_provider_signin'), url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('done/')), app.auth.process_provider_signin, name='auth_provider_done'), url(r'^%s%s$' % (_('account/'), _('register/')), app.auth.external_register, name='auth_external_register'), + url(r'^%s%s(?P<user>\d+)/(?P<code>.+)/$' % (_('account/'), _('validate/')), app.auth.validate_email, name="auth_validate_email"), + url(r'^%s%s$' % (_('account/'), _('tempsignin/')), app.auth.request_temp_login, name="auth_request_tempsignin"), + url(r'^%s%s(?P<user>\d+)/(?P<code>.+)/$' % (_('account/'), _('tempsignin/')), app.auth.temp_signin, name="auth_tempsignin"), + url(r'^%s%s$' % (_('account/'), _('authsettings/')), app.auth.auth_settings, name='user_authsettings'), + url(r'^%s%s(?P<id>\d+)/%s$' % (_('account/'), _('providers/'), _('remove/')), app.auth.remove_external_provider, name='user_remove_external_provider'), + url(r'^%s%s%s$' % (_('account/'), _('providers/'), _('add/')), app.auth.signin_page, name='user_add_external_provider'), - url(r'^%s%s$' % (_('account/'), _('password/')), app.users.changepw, name='user_changepw'), #url(r'^%s%s%s$' % (_('accounts/'), _('password/'), _('confirm/')), app.user.confirmchangepw, name='user_confirmchangepw'), - url(r'^%s$' % _('account/'), app.users.account_settings, name='user_account_settings'), + #url(r'^%s$' % _('account/'), app.users.account_settings, name='user_account_settings'), #url(r'^%s$' % _('delete/'), app.users.delete, name='user_delete'), ) diff --git a/forum/user_messages/context_processors.py b/forum/user_messages/context_processors.py index 2bf26269..5f7b857c 100755 --- a/forum/user_messages/context_processors.py +++ b/forum/user_messages/context_processors.py @@ -17,6 +17,9 @@ def user_messages (request): #if request.user.is_authenticated(): #else: # messages = LazyMessages(request) + #import inspect + #print inspect.stack()[1] + #print messages return { 'user_messages': messages } class LazyMessages (StrAndUnicode): diff --git a/forum/utils/email.py b/forum/utils/email.py new file mode 100755 index 00000000..a6ea1087 --- /dev/null +++ b/forum/utils/email.py @@ -0,0 +1,21 @@ +from django.core.mail import send_mail, EmailMultiAlternatives
+from django.conf import settings
+from django.template import loader, Context
+from django.utils.html import strip_tags
+from threading import Thread
+
+def send_email(subject, recipients, template, context={}, sender=settings.DEFAULT_FROM_EMAIL, txt_template=None):
+ context['settings'] = settings
+ html_body = loader.get_template(template).render(Context(context))
+
+ if txt_template is None:
+ txt_body = strip_tags(html_body)
+ else:
+ txt_body = loader.get_template(txt_template).render(Context(context))
+
+ msg = EmailMultiAlternatives(subject, txt_body, sender, recipients)
+ msg.attach_alternative(html_body, "text/html")
+
+ thread = Thread(target=EmailMultiAlternatives.send, args=[msg])
+ thread.setDaemon(True)
+ thread.start()
diff --git a/forum/utils/forms.py b/forum/utils/forms.py index c54056ca..c8305c7c 100755 --- a/forum/utils/forms.py +++ b/forum/utils/forms.py @@ -133,6 +133,10 @@ class SetPasswordForm(forms.Form): error_messages={'required':_('please, retype your password'), 'nomatch':_('sorry, entered passwords did not match, please try again')}, ) + + def __init__(self, data=None, user=None, *args, **kwargs): + super(SetPasswordForm, self).__init__(data, *args, **kwargs) + def clean_password2(self): """ Validates that the two password inputs match. diff --git a/forum/utils/time.py b/forum/utils/time.py new file mode 100755 index 00000000..5fd15e24 --- /dev/null +++ b/forum/utils/time.py @@ -0,0 +1,4 @@ +import datetime
+
+def one_day_from_now():
+ return datetime.datetime.now() + datetime.timedelta(days=1)
diff --git a/forum/views/auth.py b/forum/views/auth.py index d885440b..74f7af4b 100644 --- a/forum/views/auth.py +++ b/forum/views/auth.py @@ -1,17 +1,21 @@ -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.core.urlresolvers import reverse from django.contrib.auth.models import User -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, Http404 from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ +from django.utils.http import urlquote_plus from django.contrib.auth.decorators import login_required from django.contrib.auth import login, logout from django.http import get_host import types +import datetime -from forum.models import AuthKeyUserAssociation -from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm +from forum.models import AuthKeyUserAssociation, ValidationHash +from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm, \ + TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm +from forum.utils.email import send_email from forum.authentication.base import InvalidAuthentication from forum.authentication import AUTH_PROVIDERS @@ -105,7 +109,9 @@ def process_provider_signin(request, provider): except: uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider) uassoc.save() - request.session['auth_error'] = _("These new credentials are now associated with your account.") + request.user.message_set.create(message=_('The new credentials are now associated with your account')) + return HttpResponseRedirect(reverse('user_authsettings')) + return HttpResponseRedirect(reverse('auth_signin')) else: if isinstance(assoc_key, (type, User)): @@ -128,12 +134,15 @@ def external_register(request): email_feeds_form = SimpleEmailSubscribeForm(request.POST) if (form1.is_valid() and email_feeds_form.is_valid()): - tmp_pwd = User.objects.make_random_password() - user_ = User.objects.create_user(form1.cleaned_data['username'], - form1.cleaned_data['email'], tmp_pwd) - + user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email']) + user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email'] user_.set_unusable_password() + user_.save() + + if not user_.email_isvalid: + send_validation_email(user_) + try: assoc_key = request.session['assoc_key'] auth_provider = request.session['auth_provider'] @@ -148,7 +157,11 @@ def external_register(request): del request.session['assoc_key'] del request.session['auth_provider'] - return login_and_forward(request, user_) + + if user_.email_isvalid: + return login_and_forward(request, user_) + else: + return HttpResponseRedirect(reverse('index')) else: provider_class = AUTH_PROVIDERS[request.session['auth_provider']].consumer user_data = provider_class.get_user_data(request.session['assoc_key']) @@ -159,6 +172,9 @@ def external_register(request): if not email: email = request.session.get('auth_email_request', '') + if email: + request.session['auth_validated_email'] = email + form1 = SimpleRegistrationForm(initial={ 'next': '/', 'username': username, @@ -176,6 +192,119 @@ def external_register(request): 'gravatar_faq_url':reverse('faq') + '#gravatar', }, context_instance=RequestContext(request)) +def request_temp_login(request): + if request.method == 'POST': + form = TemporaryLoginRequestForm(request.POST) + + if form.is_valid(): + user = form.user_cache + + try: + hash = get_object_or_404(ValidationHash, user=user, type='templogin') + if hash.expiration < datetime.datetime.now(): + hash.delete() + return request_temp_login(request) + except: + hash = ValidationHash.objects.create_new(user, 'templogin', [user.id]) + + send_email(_("Temporary login link"), [user.email], "auth/temp_login_email.html", { + 'temp_login_code': hash, + 'user': user + }) + + return HttpResponseRedirect(reverse('index')) + else: + form = TemporaryLoginRequestForm() + + return render_to_response( + 'auth/temp_login_request.html', {'form': form}, + context_instance=RequestContext(request)) + +def temp_signin(request, user, code): + user = get_object_or_404(User, id=user) + + if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])): + print user.get_absolute_url() + return login_and_forward(request, user, reverse('user_authsettings'), + _("You are logged in with a temporary access key, please take the time to fix your issue with authentication.")) + else: + raise Http404() + +def send_validation_email(user): + hash = ValidationHash.objects.create_new(user, 'email', [user.email]) + send_email(_("Email Validation"), [user.email], "auth/email_validation.html", { + 'validation_code': hash, + 'user': user + }) + +def validate_email(request, user, code): + user = get_object_or_404(User, id=user) + + if (ValidationHash.objects.validate(code, user, 'email', [user.email])): + user.email_isvalid = True + user.save() + return login_and_forward(request, user, None, _("Thank you, your email is now validated.")) + else: + raise Http404() + +@login_required +def auth_settings(request): + """ + change password view. + + url : /changepw/ + template: authopenid/changepw.html + """ + user_ = request.user + auth_keys = user_.auth_keys.all() + + if user_.has_usable_password(): + FormClass = ChangePasswordForm + else: + FormClass = SetPasswordForm + + if request.POST: + form = FormClass(request.POST, user=user_) + if form.is_valid(): + if user_.has_usable_password(): + request.user.message_set.create(message=_("Your password was changed")) + else: + request.user.message_set.create(message=_("New password set")) + + user_.set_password(form.cleaned_data['password1']) + user_.save() + return HttpResponseRedirect(reverse('user_authsettings')) + else: + form = FormClass(user=user_) + + auth_keys_list = [] + + for k in auth_keys: + provider = AUTH_PROVIDERS.get(k.provider, None) + + if provider is not None: + name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k)) + else: + from forum.authentication.base import ConsumerTemplateContext + "unknown: %s" % ConsumerTemplateContext.readable_key(k) + + auth_keys_list.append({ + 'name': name, + 'id': k.id + }) + + return render_to_response('auth/auth_settings.html', { + 'form': form, + 'has_password': user_.has_usable_password(), + 'auth_keys': auth_keys_list, + }, context_instance=RequestContext(request)) + +def remove_external_provider(request, id): + association = get_object_or_404(AuthKeyUserAssociation, id=id) + request.user.message_set.create(message=_("You removed the association with %s") % association.provider) + association.delete() + return HttpResponseRedirect(reverse('user_authsettings')) + def newquestion_signin_action(user): question = Question.objects.filter(author=user).order_by('-added_at')[0] return question.get_absolute_url() @@ -189,7 +318,7 @@ POST_SIGNIN_ACTIONS = { 'newanswer': newanswer_signin_action, } -def login_and_forward(request, user): +def login_and_forward(request, user, forward=None, message=None): old_session = request.session.session_key user.backend = "django.contrib.auth.backends.ModelBackend" login(request, user) @@ -197,19 +326,24 @@ def login_and_forward(request, user): from forum.models import user_logged_in user_logged_in.send(user=user,session_key=old_session,sender=None) - signin_action = request.session.get('on_signin_action', None) - if not signin_action: - redirect = request.session.get('on_signin_url', None) + if not forward: + signin_action = request.session.get('on_signin_action', None) + if not signin_action: + forward = request.session.get('on_signin_url', None) - if not redirect: - redirect = reverse('index') - else: - try: - redirect = POST_SIGNIN_ACTIONS[signin_action](user) - except: - redirect = reverse('index') + if not forward: + forward = reverse('index') + else: + try: + forward = POST_SIGNIN_ACTIONS[signin_action](user) + except: + forward = reverse('index') + + if message is None: + message = _("Welcome back %s, you are now logged in") % user.username - return HttpResponseRedirect(redirect) + request.user.message_set.create(message=message) + return HttpResponseRedirect(forward) @login_required def signout(request): diff --git a/forum/views/users.py b/forum/views/users.py index baa8090b..ff92803c 100755 --- a/forum/views/users.py +++ b/forum/views/users.py @@ -947,44 +947,13 @@ USER_TEMPLATE_VIEWS = ( ) ) -def user(request, id): +def user(request, id, slug=None): sort = request.GET.get('sort', 'stats') user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0]) from forum.views import users func = user_view.view_func return func(request, id, user_view) - -@login_required -def changepw(request): - """ - change password view. - - url : /changepw/ - template: authopenid/changepw.html - """ - logging.debug('') - user_ = request.user - - if not user_.has_usable_password(): - raise Http404 - - if request.POST: - form = ChangePasswordForm(request.POST, user=user_) - if form.is_valid(): - user_.set_password(form.cleaned_data['password1']) - user_.save() - msg = _("Password changed.") - redirect = "%s?msg=%s" % ( - reverse('user_account_settings'), - urlquote_plus(msg)) - return HttpResponseRedirect(redirect) - else: - form = ChangePasswordForm(user=user_) - - return render_to_response('changepw.html', {'form': form }, - context_instance=RequestContext(request)) - @login_required def account_settings(request): """ |