Commits

Anonymous committed c0309c5

[svn r52] clean tree in view of adding tests, example and setup.py

  • Participants
  • Parent commits fd7db18

Comments (0)

Files changed (38)

__init__.py

Empty file removed.

django_authopenid/__init__.py

Empty file added.

django_authopenid/forms.py

+# -*- coding: utf-8 -*-
+"""
+ Copyright (c) 2007, 2008, Benoît Chesneau
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+     * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+
+
+from django import newforms as forms
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext as _
+from django.conf import settings
+
+import re
+
+
+# needed for some linux distributions like debian
+try:
+    from openid.yadis import xri
+except:
+    from yadis import xri
+
+
+class OpenidSigninForm(forms.Form):
+    openid_url = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs={'class': 'required openid'}))
+    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
+
+    def clean_openid_url(self):
+        if 'openid_url' in self.cleaned_data:
+            openid_url = self.cleaned_data['openid_url']
+            if xri.identifierScheme(openid_url) == 'XRI' and getattr(
+                settings, 'OPENID_DISALLOW_INAMES', False
+                ):
+                raise forms.ValidationError(_('i-names are not supported'))
+            return self.cleaned_data['openid_url']
+
+    def clean_next(self):
+        if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
+            next_url_re = re.compile('^/[-\w/]*$')
+            if not next_url_re.match(self.cleaned_data['next']):
+                raise forms.ValidationError(_('next url "%s" is invalid' % self.cleaned_data['next']))
+            return self.cleaned_data['next']
+
+
+attrs_dict = { 'class': 'required login' }
+username_re = re.compile(r'^\w+$')
+
+class OpenidAuthForm(forms.Form):
+    """ legacy account signin form """
+    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
+    username = forms.CharField(max_length=30,  widget=forms.widgets.TextInput(attrs=attrs_dict))
+    password = forms.CharField(max_length=128, widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+       
+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
+        super(OpenidAuthForm, self).__init__(data, files, auto_id, prefix, initial)
+        self.user_cache=None
+            
+    def clean_username(self):
+        if 'username' in self.cleaned_data and 'openid_url' not in self.cleaned_data:
+            if not username_re.search(self.cleaned_data['username']):
+                raise forms.ValidationError(_("Usernames can only contain letters, numbers and underscores"))
+            try:
+                user = User.objects.get(username__exact=self.cleaned_data['username'])
+            except User.DoesNotExist:
+                raise forms.ValidationError(_("This username don't exist in database. Please choose another."))
+            return self.cleaned_data['username']
+
+    def clean_password(self):
+        if 'username' in self.cleaned_data and 'password' in self.cleaned_data:
+            self.user_cache =  authenticate(username=self.cleaned_data['username'], password=self.cleaned_data['password'])
+            if self.user_cache is None:
+                raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
+            elif self.user_cache.is_active == False:
+                raise forms.ValidationError(_("This account is inactive."))
+            return self.cleaned_data['password']
+
+    def clean_next(self):
+        if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
+            next_url_re = re.compile('^/[-\w/]*$')
+            if not next_url_re.match(self.cleaned_data['next']):
+                raise forms.ValidationError(_('next url "%s" is invalid' % self.cleaned_data['next']))
+            return self.cleaned_data['next']
+            
+    def get_user(self):
+        return self.user_cache
+            
+
+class OpenidRegisterForm(forms.Form):
+    """ openid signin form """
+    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
+
+    username = forms.CharField(max_length=30, widget=forms.widgets.TextInput(attrs=attrs_dict))
+    email = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs=attrs_dict))
+    
+    def clean_username(self):
+        if 'username' in self.cleaned_data:
+            if not username_re.search(self.cleaned_data['username']):
+                raise forms.ValidationError(_("Usernames can only contain letters, numbers and underscores"))
+            try:
+                user = User.objects.get(username__exact=self.cleaned_data['username'])
+            except User.DoesNotExist:
+                return self.cleaned_data['username']
+            raise forms.ValidationError(_("This username is already taken. Please choose another."))
+            
+    def clean_email(self):
+        """For security reason one unique email in database"""
+        if 'email' in self.cleaned_data:
+            try:
+                user = User.objects.get(email=self.cleaned_data['email'])
+            except User.DoesNotExist:
+                return self.cleaned_data['email']
+            raise forms.ValidationError(_("This email is already registered in our database. Please choose another."))
+ 
+    
+class OpenidVerifyForm(forms.Form):
+    """ openid verify form (associate an openid with an account) """
+    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
+    username = forms.CharField(max_length=30, widget=forms.widgets.TextInput(attrs=attrs_dict))
+    password = forms.CharField(max_length=128, widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+     
+    def clean_username(self):
+        if 'username' in self.cleaned_data:
+            if not username_re.search(self.cleaned_data['username']):
+                raise forms.ValidationError(_("Usernames can only contain letters, numbers and underscores"))
+            try:
+                user = User.objects.get(username__exact=self.cleaned_data['username'])
+            except User.DoesNotExist:
+                raise forms.ValidationError(_("This username don't exist. Please choose another."))
+            return self.cleaned_data['username']
+            
+    def clean_password(self):
+        if 'username' in self.cleaned_data and 'password' in self.cleaned_data:
+            self.user_cache =  authenticate(username=self.cleaned_data['username'], password=self.cleaned_data['password'])
+            if self.user_cache is None:
+                raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
+            elif self.user_cache.is_active == False:
+                raise forms.ValidationError(_("This account is inactive."))
+            return self.cleaned_data['password']
+            
+    def get_user(self):
+        return self.user_cache
+
+
+attrs_dict = { 'class': 'required' }
+username_re = re.compile(r'^\w+$')
+
+class RegistrationForm(forms.Form):
+    """ legacy registration form """
+
+    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
+    username = forms.CharField(max_length=30,
+                               widget=forms.TextInput(attrs=attrs_dict),
+                               label=u'Username')
+    email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+                                                               maxlength=200)),
+                             label=u'Email address')
+    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+                                label=u'Password')
+    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+                                label=u'Password (again, to catch typos)')
+
+    def clean_username(self):
+        """
+        Validates that the username is alphanumeric and is not already
+        in use.
+        
+        """
+        if 'username' in self.cleaned_data:
+            if not username_re.search(self.cleaned_data['username']):
+                raise forms.ValidationError(u'Usernames can only contain letters, numbers and underscores')
+            try:
+                user = User.objects.get(username__exact=self.cleaned_data['username'])
+            except User.DoesNotExist:
+                return self.cleaned_data['username']
+            raise forms.ValidationError(u'This username is already taken. Please choose another.')
+
+    def clean_email(self):
+        if 'email' in self.cleaned_data:
+            try:
+                user = User.objects.get(email=self.cleaned_data['email'])
+            except:
+                return self.cleaned_data['email']
+            raise forms.ValidationError(u'This email is already registered in our database. Please choose another.')
+        return self.cleaned_data['email']
+    
+    def clean_password2(self):
+        """
+        Validates that the two password inputs match.
+        
+        """
+        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data and \
+           self.cleaned_data['password1'] == self.cleaned_data['password2']:
+            return self.cleaned_data['password2']
+        raise forms.ValidationError(u'You must type the same password each time')
+
+
+class ChangepwForm(forms.Form):
+    """ change password form """
+    oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+    def __init__(self, data=None, user=None, *args, **kwargs):
+        if user is None:
+            raise TypeError("Keyword argument 'user' must be supplied")
+        super(ChangepwForm, self).__init__(data, *args, **kwargs)
+        self.user = user
+
+    def clean_oldpw(self):
+        if not self.user.check_password(self.cleaned_data['oldpw']):
+            raise forms.ValidationError(_("Old password is wrong. Please enter a valid password."))
+        return self.cleaned_data['oldpw']
+    
+    def clean_password2(self):
+        """
+        Validates that the two password inputs match.
+        
+        """
+        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data and \
+           self.cleaned_data['password1'] == self.cleaned_data['password2']:
+            return self.cleaned_data['password2']
+        raise forms.ValidationError(_("new passwords do not match each other"))
+        
+        
+class ChangeemailForm(forms.Form):
+    """ change email form """
+    email = forms.CharField(max_length=255,widget=forms.TextInput(attrs={'class': "required validate-email" }))
+    password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
+            initial=None, user=None):
+        if user is None:
+            raise TypeError("Keyword argument 'user' must be supplied")
+        super(ChangeemailForm, self).__init__(data, files, auto_id, prefix, initial)
+        self.test_openid=False
+        self.user = user
+
+    def clean_password(self):
+        if 'password' in self.cleaned_data:
+            if not self.user.check_password(self.cleaned_data['password']):
+                self.test_openid=True
+        return self.cleaned_data['password']
+                
+class ChangeopenidForm(forms.Form):
+    """ change openid form """
+    openid_url = forms.CharField(max_length=255,widget=forms.TextInput(attrs={'class': "required" }))
+    def __init__(self, data=None, user=None, *args, **kwargs):
+        if user is None:
+            raise TypeError("Keyword argument 'user' must be supplied")
+        super(ChangeopenidForm, self).__init__(data, *args, **kwargs)
+        self.user = user
+
+class DeleteForm(forms.Form):
+    """ confirm form to delete an account """
+    confirm = forms.CharField(widget=forms.CheckboxInput(attrs=attrs_dict))
+    password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
+            initial=None, user=None):
+        super(DeleteForm, self).__init__(data, files, auto_id, prefix, initial)
+        self.test_openid=False
+        self.user = user
+
+    def clean_password(self):
+        if 'password' in self.cleaned_data:
+            if not self.user.check_password(self.cleaned_data['password']):
+                self.test_openid=True
+        return self.cleaned_data['password']
+
+
+class EmailPasswordForm(forms.Form):
+    """ send new password form """
+    username = forms.CharField(max_length=30,widget=forms.TextInput(attrs={'class': "required" }))
+
+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
+        super(EmailPasswordForm, self).__init__(data, files, auto_id, prefix, initial)
+        self.user_cache=None
+
+
+    def clean_username(self):
+        if 'username' in self.cleaned_data:
+            try:
+                self.user_cache=User.objects.get(username=self.cleaned_data['username'])
+            except:
+                raise forms.ValidationError(_("Incorrect username."))
+        return self.cleaned_data['username']

django_authopenid/middleware.py

+# -*- coding: utf-8 -*-
+class OpenIDMiddleware(object):
+    """
+    Populate request.openid. This comes either from cookie or from
+    session, depending on the presence of OPENID_USE_SESSIONS.
+    """
+    def process_request(self, request):
+        request.openid = request.session.get('openid', None)

django_authopenid/models.py

+# -*- coding: utf-8 -*-
+from django.db import models
+from django.contrib.auth.models import User
+from django.conf import settings
+
+import md5, random, sys, os, time
+
+class Nonce(models.Model):
+    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):
+    server_url = models.TextField(max_length=2047)
+    handle = models.CharField(max_length=255)
+    secret = models.TextField(max_length=255) # Stored base64 encoded
+    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 
+    """
+    openid_url = models.CharField(blank=False, max_length=255)
+    user = models.ForeignKey(User,unique=True)
+    
+    def __unicode__(self):
+        return "Openid %s with user %s" % (self.openid_url, self.user)
+        
+    class Admin:
+        pass
+
+
+class UserPasswordQueueManager(models.Manager):
+    def get_new_confirm_key(self):
+        "Returns key that isn't being used."
+        # The random module is seeded when this Apache child is created.
+        # Use SECRET_KEY as added salt.
+        while 1:
+            confirm_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest()
+            try:
+                self.get(confirm_key=confirm_key)
+            except self.model.DoesNotExist:
+                break
+        return confirm_key
+
+
+class UserPasswordQueue(models.Model):
+    """
+    model for new password queue.
+    """
+    user = models.ForeignKey(User, unique=True)
+    new_password = models.CharField(max_length=30)
+    confirm_key = models.CharField(max_length=40)
+
+    objects = UserPasswordQueueManager()
+
+
+    def __unicode__(self):
+        return self.user.username

django_authopenid/templates/authopenid/changeemail.html

+{% extends "base.html" %}
+{% load i18n %}
+
+
+
+
+{% block content %}
+<h4 class="headblue">{% trans "Account: change email" %}</h4>
+
+<p class="settings-descr">{% blocktrans %}This is where you can change the email address associated with your account. Please keep this email address up to date so we can send you a password-reset email if you request one.{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+	{% if form.email.errors %} 
+		<span class="error">{{ form.email.errors|join:", " }}</span>
+	{% endif %}
+	{% if form.password.errors %} 
+		<span class="error">{{ form.password.errors|join:", " }}</span>
+	{% endif %}
+</p>
+{% endif %}
+
+<div class="aligned">
+	<form action="{{ path }}" method="post" accept-charset="utf-8">
+
+		<div id="form-row"><label for="id_email">{% trans "Email" %}</label>{{ form.email }}</div>
+		<div id="form-row"><label for="id_password">{% trans "Password" %}</label>{{ form.password }}</div>
+
+        <p><input type="submit" value="{% trans "Change email" %}"></p>
+
+	</form>
+	</div>
+{% endblock %}

django_authopenid/templates/authopenid/changeopenid.html

+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+<h4 class="headblue">{% trans "Account: change openid url" %}</h4>
+
+<p>{% blocktrans %}This is where you can change your openid url used to connect to friendsnippets. Make sure you remember it!{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+	{% if form.openid_url.errors %} 
+		<span class="error">{{ form.openid_url.errors|join:", " }}</span>
+	{% endif %}
+
+
+</p>
+{% endif %}
+{% if msg %}
+	<p class="errors">{{ msg }}</p>
+{% endif %}
+
+<div class="aligned">
+	<form action="." method="post" accept-charset="utf-8">
+
+		<div id="form-row"><label for="id_openid_url">{% trans "OpenID url:" %}</label>{{ form.openid_url }}</div>
+        <p><input type="submit" value="{% trans "Change openid" %}"></p>
+
+	</form>
+	</div>
+{% endblock %}

django_authopenid/templates/authopenid/changepw.html

+{% extends "base.html" %}
+{% load i18n %}
+
+{% block head %}
+		
+{% endblock %}
+
+
+
+{% block content %}
+<h4 class="headblue">{% trans "Account: change password" %}</h4>
+
+<p>{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+{{ form.errors }}
+</p>
+{% endif %}
+
+<div class="aligned">
+	<form action="{{ path }}" method="post" accept-charset="utf-8">
+
+		<div id="form-row"><label for="id_oldpw">{% trans "Old password" %}</label>{{ form.oldpw }}</div>
+		<div id="form-row"><label for="id_password1">{% trans "New password" %}</label>{{ form.password1 }}</div>
+		<div id="form-row"><label for="id_password2">{% trans "New password again" %}</label>{{ form.password2 }}</div>
+        <p><input type="submit" value="{% trans "Change password" %}"></p>
+
+	</form>
+	</div>
+{% endblock %}

django_authopenid/templates/authopenid/complete.html

+{% extends "base.html" %}
+{% load i18n %}
+
+
+
+{% block head %}{% endblock %}
+
+
+{% block content %}
+
+	<div id="completetxt">
+	<h1>{% trans "Your OpenID is verified! " %}</h1>
+	{% blocktrans %}
+	<p>Your OpenID can now be associated with a new or existing membership. You can change the association later in your preferences</p>
+	{% endblocktrans %}
+	<br /><br />
+	<h2>{% trans "Associate your OpenID" %}</h2>
+	{% blocktrans %}
+	<p>If you're joining <strong>Sitename</strong>, associate your OpenID with a new account. If you're already a member, associate with your existing account.</p>
+	{% endblocktrans %}
+	</div>
+
+		
+	
+	{% if form1.errors %}
+	<p class="errors">{% trans "Please correct errors below:" %}<br />
+		{% if form1.username.errors %} 
+			<span class="error">{{ form1.username.errors|join:", " }}</span>
+		{% endif %}
+		{% if form1.email.errors %} 
+			<span class="error">{{ form1.email.errors|join:", " }}</span>
+		{% endif %}
+	</p>
+	{% endif %}
+	{% if form2.errors %}
+	<p class="errors">{% trans "Please correct errors below:" %}<br />
+		{% if form2.username.errors %} 
+			<span class="error">{{ form2.username.errors|join:", " }}</span>
+		{% endif %}
+		{% if form2.password.errors %} 
+			<span class="error">{{ form2.password.errors|join:", " }}</span>
+		{% endif %}
+	</p>
+	{% endif %}
+
+	<div class="login">
+        <form name="fregister" action="{{ action }}" method="POST">
+            {{ form.next }}
+			<fieldset>
+				<legend>{% trans "A new account" %}</legend>
+				<div class="form-row"><label for "id_username">{% trans "Screen name" %}</label><br />{{ form1.username }}</div>
+				<div class="form-row"><label for "id_email">{% trans "Email" %}</label><br />{{ form1.email }}</div>
+				<div class="submit-row"><input type="submit" name="bnewaccount" value="CREATE MY ACCOUNT"></div>
+				<hr class="clear" />
+            	</fieldset>
+		</form>
+	</div>
+
+	
+
+	<div class="login">
+		<h3></h3>
+        <form name="fverify" action="{{ action }}" method="POST">
+            {{ form.next }}
+			<fieldset>
+				<legend>{% trans "An exisiting account" %}</legend>
+				<div class="form-row"><label for "id_username">{% trans "Screen name" %}</label><br />{{ form2.username }}</div>
+				<div class="form-row"><label for "id_passwordl">{% trans "Password" %}</label><br />{{ form2.password }}</div>
+				<div class="submit-row"><input type="submit" name="bverify" value="VERIFY"></div>
+			</fieldset>
+		</form>
+	</div>
+{% endblock %}
+
+	
+

django_authopenid/templates/authopenid/confirm_email.txt

+Thank you for registering. 
+
+Your account details are:
+
+Username: {{ username }}
+Password: {{ password }}
+
+
+You could sign in  with this url:
+{{ site_url }}signin/
+
+

django_authopenid/templates/authopenid/delete.html

+{% extends "base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<h4 class="headblue">{% trans "Account: delete account" %}</h4>
+
+<p class="settings-descr">{% blocktrans %}Note: After deleting your account, anyone will be able to register this username.{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+	{% if form.confirm.errors %} 
+    <span class="error">{% trans "Check confirm box, if you want delete your account." %}</span><br />
+	{% endif %}
+	{% if form.password.errors %} 
+    <span class="error">{% trans "Password:" %} {{ form.password.errors|join:", " }}</span>
+	{% endif %}
+</p>
+{% endif %}
+{% if msg %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+    <span class="error">{{ msg }}</span>
+    </p>
+{% endif %}
+<div class="aligned">
+	<form action="." method="post" accept-charset="utf-8">
+
+        <div id="form-row"> {{ form.confirm }} {% trans "I am sure I want to delete my account." %}</div>
+        <div id="form-row"><label for="id_password">{% trans "Password/ OpenID URL" %}</label>{{ form.password }} {% trans "(required for your security)" %}</div>
+
+        <p><input type="submit" value="{% trans "delete account forever" %}"></p>
+
+	</form>
+	</div>
+{% endblock %}

django_authopenid/templates/authopenid/failure.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+  "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title>OpenID failed</title>
+</head>
+<body>
+<h1>OpenID failed</h1>
+
+<p>{{ message|escape }}</p>
+
+</body>
+</html>

django_authopenid/templates/authopenid/sendpw.html

+
+{% extends "base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<h4 class="headblue">{% trans "Account: Send a new password" %}</h4>
+
+<p class="settings-descr">{% blocktrans %}Lost your password ? Here you can ask to reset your password. Enter the username you use  and you will get a confirm mail with your new password. This new password will be activated only after you clicked on the link you will find in the email{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+	{% if form.username.errors %} 
+		<span class="error">{{ form.username.errors|join:", " }}</span>
+	{% endif %}
+</p>
+{% endif %}
+{% if msg %}
+<br /
+<span class="error">{{ msg }}</span>
+{% endif %}
+
+<div class="aligned">
+	<form action="." method="post" accept-charset="utf-8">
+		<div id="form-row"><label for="id_username">{% trans "Username" %}</label>{{ form.username }}</div>
+
+        <p><input type="submit" value="{% trans "Send new password" %}"></p>
+
+	</form>
+	</div>
+{% endblock %}

django_authopenid/templates/authopenid/sendpw_email.txt

+Someone ask to reset your password on {{ site_url }}. 
+If it's not you, please ignore this email. 
+
+Your new account details are:
+
+Username: {{ username }}
+New password: {{ password }}
+
+To confirm reset of your password go on this url:
+{{ site_url }}{{ url_confirm }}?key={{ confirm_key }}
+
+See you,
+

django_authopenid/templates/authopenid/settings.html

+{% extends "base.html" %}
+{% load i18n %}
+
+{% block head %}
+<style type="text/css" media="screen">
+	dt, dd { padding:0 0 0.35em 0; }
+	dt { float: left; width: 21ex;  }
+	dd { margin-left: 23ex;  }
+	
+	#settings-options, #settings-intro { padding: 4em 1.5em;}
+	#settings-options { min-height: 300px; border-left: 1px solid #333;}
+	
+	#settings-options h5 { font-weight: bold;}
+</style>
+{% endblock %}
+
+{% block content %}
+<div id="settings-intro">
+	
+
+
+    <h4><strong>{{ user.username }}</strong> {% trans "Settings" %}</h4>
+    {% blocktrans %}
+    <p>This is where you can make changes to your account.</p>
+
+    {% endblocktrans %}
+</div>
+
+<div id="settings-options">
+	{% if msg %}
+		<p class="error">{{ msg }}</p>
+	{% endif %}
+    <h5>{% trans "Account" %}</h5>
+	<dl>
+        <dt>» <a href="{{ settings_path }}{% trans "password/" %}">{% trans "change password" %}</a></dt>
+        <dd>{% trans "Give your  account a new password." %}</dd>
+        <dt>» <a href="{{ settings_path }}{% trans "email/" %}">{% trans "change email" %}</a></dt>
+        <dd>{% trans "Add or update the email address associated with your account." %}</dd>
+        {% if is_openid %}
+        <dt>» <a href="{{ settings_path }}{% trans "openid/" %}">{% trans "change openid url" %}</a></dt>
+        <dd>{% trans "Change openid associated to your account" %}</dd>
+        {% endif %}
+        
+        <dt>» <a href="{{ settings_path }}{% trans "delete/" %}">{% trans "delete account" %}</a></dt>
+        <dd>{% trans "Erase your username and all your data from website" %}</dd>
+	</dl>
+</div>
+{% endblock %}

django_authopenid/templates/authopenid/signin.html

+{% extends "base.html" %}
+{% load i18n %}
+
+{% block head %}
+
+
+{% endblock %}
+
+
+{% block content %}
+{% if msg %}
+<br />
+	<p class="warning">{{ msg }}</p>
+{% endif %}
+{% if form1.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+	{% if form1.username.errors %} 
+		<span class="error">{{ form1.username.errors|join:", " }}</span>
+	{% endif %}
+	{% if form1.password.errors %} 
+		<span class="error">{{ form1.password.errors|join:", " }}</span>
+	{% endif %}
+</p>
+{% endif %}
+<div class="login">
+
+    <form name="fauth" action="{{ action }}" method="post">
+        {{ form1.next }}
+	<fieldset>
+		<legend>{% trans "Sign In Using Your Account ID" %}</legend>
+		<div class="form-row"><label for="id_username">{% trans "Username:" %}</label><br />{{ form1.username }}</div>
+		<div class="form-row"><label for="id_password">{% trans "Password" %}</label><br />{{ form1.password }}</div>
+        <div class="submit-row"><input type="submit" name="blogin" value="{% trans "Sign in" %}"> </div>
+        <div>&nbsp;&nbsp;<a href="{{ sendpw_url }}">{% trans "Lost your password ?" %}</a></div>
+	</fieldset>	
+</form>
+
+<br />
+<form name="fopenid" action="{{ action }}" method="post">
+    {{ form2.next }}
+	<fieldset>
+		<legend>{% trans "Sign In Using Your OpenID" %}</legend>
+        <div class="form-row"><label for="id_openid_ul">{% trans "OpenId URL :" %}</label><br />{{ form2.openid_url }}</div>
+        <div class="submit-row "><input name="bsignin" type="submit" value="{% trans "Sign in with OPENID" %}"></div>
+		
+	</fieldset>
+</form>	
+</div>	
+{% endblock %}
+

django_authopenid/templates/authopenid/signup.html

+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+<div class="jointxt">
+    {% blocktrans %}
+    <h1>Join</h1>
+    <p>There are two ways to join: with an email + screen name, or with OpenID.<br />Enter information only for the type of sign up you want to do.</p>
+    {% endblocktrans %}
+	<br />
+	<br />
+    <h2 class="signup">{% trans "Regular Signup" %}</h2>
+    {% if form.errors %}
+    <p class="errors">{% trans "Please correct errors below:" %}<br />
+    {% if form.username.errors %} 
+    <span class="error">{{ form.username.errors|join:", " }}</span>
+    {% endif %}
+    {% if form.email.errors %} 
+    <span class="error">{{ form.email.errors|join:", " }}</span>
+    {% endif %}
+    {% if form.password2.errors %} 
+    <span class="error">{{ form.password2.errors|join:", " }}</span>
+    {% endif %}
+    </p>
+    {% endif %}
+</div>
+    <form action="{{ action }}" method="post" accept-charset="utf-8">	
+        <div class="form-row"><label for="id_username">{% trans "Choose a Screen Name:" %}</label><br />{{ form.username }}</div>
+
+        <div class="form-row"><label for="id_email">{% trans "Enter Your Email Address:" %}</label><br />{{ form.email }}</div>
+        <div class="form-row"><label for="id_password1">{% trans "Choose a Password:" %}</label><br />{{ form.password1 }}</div>
+        <div class="form-row"><label for="id_password2">{% trans "Confirm Your Password:" %}</label><br />{{ form.password2 }}</div>
+
+        <div class="submit-row"><input type="submit" value="{% trans "JOIN FREE" %}" /></div>
+        <br />
+    </form>
+<br />
+    <h2 class="signup">{% trans "OpenID Signup" %}</h2>
+    <form name="fopenid" action="{{ action_signin }}" method="post">
+        <div class="form-row">{{ form2.openid_url }}</div>
+        <div class="submit-row "><input name="bsignin" type="submit" value="{% trans "Sign in with OPENID" %}"></div>
+    </form>
+{% endblock %}

django_authopenid/urls.py

+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import patterns, url
+from django.utils.translation import ugettext as _
+
+urlpatterns = patterns('django_authopenid.views',
+     # manage account registration
+    url(r'^%s$' % _('signin/'), 'signin', name='user_signin'),
+    url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
+    url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin', name='user_complete_signin'),
+    url(r'^%s$' % _('register/'), 'register', name='user_register'),
+    url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
+    url(r'^%s$' % _('sendpw/'), 'sendpw', name='user_sendpw'),
+    url(r'^%s%s$' % (_('password/'), _('confirm/')), 'confirmchangepw', name='user_confirmchangepw'),
+
+    # manage account settings
+    url(r'^$', 'account_settings', name='user_account_settings'),
+    url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'),
+    url(r'^%s$' % _('email/'), 'changeemail', name='user_changeemail'),
+    url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
+    url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
+    
+    
+)

django_authopenid/util.py

+# -*- coding: utf-8 -*-
+from openid.store.interface import OpenIDStore
+from openid.association import Association as OIDAssociation
+from openid.association import Association
+from openid.extensions import sreg
+import openid.store
+
+from django.db.models.query import Q
+from django.conf import settings
+
+
+# needed for some linux distributions like debian
+try:
+    from openid.yadis import xri
+except:
+    from yadis import xri
+
+import time, base64, md5, operator
+
+from models import Association, Nonce
+
+class OpenID:
+    def __init__(self, openid, issued, attrs=None, sreg=None):
+        self.openid = openid
+        self.issued = issued
+        self.attrs = attrs or {}
+        self.sreg = sreg or {}
+        self.is_iname = (xri.identifierScheme(openid) == 'XRI')
+    
+    def __repr__(self):
+        return '<OpenID: %s>' % self.openid
+    
+    def __str__(self):
+        return self.openid
+
+class DjangoOpenIDStore(OpenIDStore):
+    def __init__(self):
+        self.max_nonce_age = 6 * 60 * 60 # Six hours
+    
+    def storeAssociation(self, server_url, association):
+        assoc = Association(
+            server_url = server_url,
+            handle = association.handle,
+            secret = base64.encodestring(association.secret),
+            issued = association.issued,
+            lifetime = association.issued,
+            assoc_type = association.assoc_type
+        )
+        assoc.save()
+    
+    def getAssociation(self, server_url, handle=None):
+        assocs = []
+        if handle is not None:
+            assocs = Association.objects.filter(
+                server_url = server_url, handle = handle
+            )
+        else:
+            assocs = Association.objects.filter(
+                server_url = server_url
+            )
+        if not assocs:
+            return None
+        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))
+        if not associations:
+            return None
+        return associations[-1][1]
+    
+    def removeAssociation(self, server_url, handle):
+        assocs = list(Association.objects.filter(
+            server_url = server_url, handle = handle
+        ))
+        assocs_exist = len(assocs) > 0
+        for assoc in assocs:
+            assoc.delete()
+        return assocs_exist
+
+    def useNonce(self, server_url, timestamp, salt):
+        if abs(timestamp - time.time()) > openid.store.nonce.SKEW:
+            return False
+        
+        query =[
+                Q(server_url__exact=server_url),
+                Q(timestamp__exact=timestamp),
+                Q(salt__exact=salt),
+        ]
+        try:
+            ononce = Nonce.objects.get(reduce(operator.and_, query))
+        except Nonce.DoesNotExist:
+            ononce = Nonce(
+                    server_url=server_url,
+                    timestamp=timestamp,
+                    salt=salt
+            );
+            ononce.save()
+            return True
+        
+        ononce.delete()
+
+        return False
+   
+    def cleanupNonce(self):
+        Nonce.objects.filter(timestamp<int(time.time()) - nonce.SKEW).delete()
+
+    def cleaupAssociations(self):
+        Association.objects.extra(where=['issued + lifetimeint<(%s)' % time.time()]).delete()
+
+    def getAuthKey(self):
+        # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
+        return md5.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
+    
+    def isDumb(self):
+        return False
+
+def from_openid_response(openid_response):
+    issued = int(time.time())
+    return OpenID(
+        openid_response.identity_url, issued, openid_response.signed_fields, 
+         dict(sreg.SRegResponse.fromSuccessResponse(openid_response))
+    )

django_authopenid/views.py

+# -*- coding: utf-8 -*-
+"""
+ Copyright (c) 2007, 2008, Benoît Chesneau
+ Copyright (c) 2007, Simon Willison, original work on django-openid
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+     * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+
+
+from django.http import HttpResponse, HttpResponseRedirect, get_host
+from django.shortcuts import get_object_or_404, render_to_response as render
+from django.template import RequestContext, loader, Context
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate,login,logout
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.utils.html import escape
+from django.utils.translation import ugettext as _
+from django.contrib.sites.models import Site
+from django.utils.encoding import smart_str
+from django.utils.http import urlquote_plus, urlquote
+from django.core.mail import send_mail
+
+from openid.consumer.consumer import Consumer, \
+    SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions import sreg
+# needed for some linux distributions like debian
+try:
+    from openid.yadis import xri
+except:
+    from yadis import xri
+
+import md5, re, time, urllib
+
+
+from util import OpenID, DjangoOpenIDStore, from_openid_response
+from models import UserAssociation, UserPasswordQueue
+from forms import OpenidSigninForm, OpenidAuthForm, OpenidRegisterForm, \
+        OpenidVerifyForm, RegistrationForm, ChangepwForm, ChangeemailForm, \
+        ChangeopenidForm, DeleteForm, EmailPasswordForm
+
+def get_url_host(request):
+    if request.is_secure():
+        protocol = 'https'
+    else:
+        protocol = 'http'
+    host = escape(get_host(request))
+    return '%s://%s' % (protocol, host)
+
+def get_full_url(request):
+    if request.is_secure():
+        protocol = 'https'
+    else:
+        protocol = 'http'
+    host = escape(request.META['HTTP_HOST'])
+    return get_url_host(request) + request.get_full_path()
+
+next_url_re = re.compile('^/[-\w/]+$')
+
+def is_valid_next_url(next):
+    # When we allow this:
+    #   /openid/?next=/welcome/
+    # For security reasons we want to restrict the next= bit to being a local 
+    # path, not a complete URL.
+    return bool(next_url_re.match(next))
+
+def ask_openid(request, openid_url, redirect_to, on_failure=None, sreg_request=None):
+    """ basic function to ask openid and return response """
+
+    on_failure = on_failure or signin_failure
+    
+    trust_root = getattr(
+        settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
+    )
+    if xri.identifierScheme(openid_url) == 'XRI' and getattr(
+            settings, 'OPENID_DISALLOW_INAMES', False
+    ):
+        msg = _("i-names are not supported")
+        return on_failure(request,msg)
+    consumer = Consumer(request.session, DjangoOpenIDStore())
+    try:
+        auth_request = consumer.begin(openid_url)
+    except:
+        msg =_("The OpenID %s was invalid" % openid_url)
+        return on_failure(request,msg)
+
+    if sreg_request:
+        auth_request.addExtension(sreg_request)
+    redirect_url = auth_request.redirectURL(trust_root, redirect_to)
+    return HttpResponseRedirect(redirect_url)
+
+def complete(request, on_success=None, on_failure=None, return_to=None):
+    on_success = on_success or default_on_success
+    on_failure = on_failure or default_on_failure
+    
+    consumer = Consumer(request.session, DjangoOpenIDStore())
+    openid_response = consumer.complete(dict(request.GET.items()), return_to)
+    
+    if openid_response.status == SUCCESS:
+        return on_success(request, openid_response.identity_url, openid_response)
+    elif openid_response.status == CANCEL:
+        return on_failure(request, 'The request was cancelled')
+    elif openid_response.status == FAILURE:
+        return on_failure(request, openid_response.message)
+    elif openid_response.status == SETUP_NEEDED:
+        return on_failure(request, 'Setup needed')
+    else:
+        assert False, "Bad openid status: %s" % openid_response.status
+
+def default_on_success(request, identity_url, openid_response):
+    request.session['openid']=from_openid_response(openid_response)
+    
+    next = request.GET.get('next', '').strip()
+    if not next or not is_valid_next_url(next):
+        next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+    
+    return HttpResponseRedirect(next)
+
+def default_on_failure(request, message):
+    return render('openid_failure.html', {
+        'message': message
+    })
+
+
+def signin(request):
+    """
+    signin page. It manage the legacy authentification (user/password) 
+    and authentification with openid.
+
+    url: /signin/
+    
+    template : authopenid/signin.htm
+    """
+
+    on_failure = signin_failure
+    next = ''
+
+
+    if request.GET.get('next') and is_valid_next_url(request.GET['next']):
+        next = request.GET.get('next', '').strip()
+    if not next or not is_valid_next_url(next):
+        next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+
+    if request.user.is_authenticated():
+        return HttpResponseRedirect(next)
+
+
+    form_signin = OpenidSigninForm(initial={'next':next})
+    form_auth = OpenidAuthForm(initial={'next':next})
+
+    if request.POST:   
+        if 'bsignin' in request.POST.keys():
+            form_signin = OpenidSigninForm(request.POST)
+            if form_signin.is_valid():
+                next = form_signin.cleaned_data['next']
+                if not next:
+                    next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+
+                sreg_req = sreg.SRegRequest(optional=['nickname','email'])
+                redirect_to = "%s?%s" % (
+                        get_url_host(request) + reverse('user_complete_signin'), 
+                        urllib.urlencode({'next':next}))
+
+                return ask_openid(request, 
+                        form_signin.cleaned_data['openid_url'], 
+                        redirect_to, 
+                        on_failure=signin_failure, 
+                        sreg_request=sreg_req)
+
+        elif 'blogin' in request.POST.keys():
+            # perform normal django authentification
+            form_auth = OpenidAuthForm(request.POST)
+            if form_auth.is_valid():
+                u = form_auth.get_user()
+                login(request, u)
+
+                next = form_auth.cleaned_data['next']
+                if not next:
+                    next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+                return HttpResponseRedirect(next)
+
+
+    return render('authopenid/signin.html', {
+        'form1': form_auth,
+        'form2': form_signin,
+        'action': request.path,
+        'msg':  request.GET.get('msg',''),
+        'sendpw_url': reverse('user_sendpw'),
+    }, context_instance=RequestContext(request))
+
+def complete_signin(request):
+    """ in case of complete signin with openid """
+    return complete(request, signin_success, signin_failure, get_url_host(request) + reverse('user_complete_signin'))
+
+
+def signin_success(request, identity_url, openid_response):
+    """
+    openid signin success.
+
+    If the openid is already registered, the user is redirected to 
+    url set par next or in settings with OPENID_REDIRECT_NEXT variable.
+    If none of these urls are set user is redirectd to /.
+
+    if openid isn't registered user is redirected to register page.
+    """
+
+    openid=from_openid_response(openid_response)
+    request.session['openid']=openid
+
+    try:
+        rel = UserAssociation.objects.get(openid_url__exact=str(openid))
+    except:
+        # try to register this new user
+        return register(request)
+    u = rel.user
+    if u.is_active:
+        u.backend = "django.contrib.auth.backends.ModelBackend"
+        login(request,u)
+
+    next = request.GET.get('next', '').strip()
+    if not next or not is_valid_next_url(next):
+        next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+    
+    return HttpResponseRedirect(next)
+
+def is_association_exist(openid_url):
+    """ test if an openid is already in database """
+    is_exist=True
+    try:
+        o=UserAssociation.objects.get(openid_url__exact=openid_url)
+    except:
+        is_exist=False
+    return is_exist
+
+def register(request):
+    """
+    register an openid.
+
+    If user is already a member he can associate its openid with 
+    its account.
+
+    A new account could also be created and automaticaly associated
+    to the openid.
+
+    url : /complete/
+
+    template : authopenid/complete.html
+    """
+
+    is_redirect = False
+    next = request.GET.get('next', '').strip()
+    if not next or not is_valid_next_url(next):
+        next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+
+
+    openid = request.session.get('openid', None)
+    if not openid:
+         return HttpResponseRedirect(reverse('user_signin') + next)
+
+    nickname = openid.sreg.get('nickname', '')
+    email = openid.sreg.get('email', '')
+    
+    form1 = OpenidRegisterForm(initial={
+        'next': next,
+        'username': nickname,
+        'email': email,
+    }) 
+    form2 = OpenidVerifyForm(initial={
+        'next': next,
+        'username': nickname,
+    })
+    
+    if request.POST:
+        just_completed=False
+        if 'bnewaccount' in request.POST.keys():
+            form1 = OpenidRegisterForm(request.POST)
+            if form1.is_valid():
+                next = form1.cleaned_data['next']
+                if not next:
+                    next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+                is_redirect = True
+                tmp_pwd = User.objects.make_random_password()
+                u = User.objects.create_user(form1.cleaned_data['username'],form1.cleaned_data['email'], tmp_pwd)
+                
+                # make association with openid
+                ua = UserAssociation(openid_url=str(openid),user_id=u.id)
+                ua.save()
+                    
+                # login 
+                u.backend = "django.contrib.auth.backends.ModelBackend"
+                login(request, u)
+        elif 'bverify' in request.POST.keys():
+            form2 = OpenidVerifyForm(request.POST)
+            if form2.is_valid():
+                is_redirect = True
+                next = form2.cleaned_data['next']
+                if not next:
+                    next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+                u = form2.get_user()
+
+                ua = UserAssociation(openid_url=str(openid),user_id=u.id)
+                ua.save()
+                login(request, u)
+        
+        # redirect, can redirect only if forms are valid.
+        if is_redirect:
+            return HttpResponseRedirect(next)
+    
+    
+    
+    return render('authopenid/complete.html', {
+        'form1': form1,
+        'form2': form2,
+        'action': reverse('user_register'),
+        'nickname': nickname,
+        'email': email
+    }, context_instance=RequestContext(request))
+
+def signin_failure(request, message):
+    """
+    falure with openid signin. Go back to signin page.
+
+    template : "authopenid/signin.html"
+    """
+    next = request.REQUEST.get('next', '')
+
+    form_signin = OpenidSigninForm(initial={'next':next})
+    form_auth = OpenidAuthForm(initial={'next':next})
+
+    return render('authopenid/signin.html', {
+        'msg': message,
+        'form1': form_auth,
+        'form2': form_signin,
+    }, context_instance=RequestContext(request))
+
+
+def signup(request):
+    """
+    signup page. Create a legacy account
+
+    url : /signup/"
+
+    templates: authopenid/signup.html, authopenid/confirm_email.txt
+    """
+    action_signin = reverse('user_signin')
+
+    next = request.GET.get('next', '')
+    if not next or not is_valid_next_url(next):
+        next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+
+    form = RegistrationForm(initial={'next':next})
+    form_signin = OpenidSigninForm(initial={'next':next})
+
+    if request.user.is_authenticated():
+        return HttpResponseRedirect(next)
+    
+    if request.POST:
+        form = RegistrationForm(request.POST)
+        if form.is_valid():
+
+            next = form.cleaned_data.get('next', '')
+            if not next or not is_valid_next_url(next):
+                next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+
+            user = User.objects.create_user(form.cleaned_data['username'],form.cleaned_data['email'], form.cleaned_data['password1'])
+           
+            user.backend = "django.contrib.auth.backends.ModelBackend"
+            login(request, user)
+            
+            # send email
+            current_domain = Site.objects.get_current().domain
+            subject = _("Welcome")
+            message_template = loader.get_template('authopenid/confirm_email.txt')
+            message_context = Context({ 'site_url': 'http://%s/' % current_domain,
+                                        'username': form.cleaned_data['username'],
+                                        'password': form.cleaned_data['password1'] })
+            message = message_template.render(message_context)
+            send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
+            
+            return HttpResponseRedirect(next)
+    
+    return render('authopenid/signup.html', {
+        'form': form,
+        'form2': form_signin,
+        'action': request.path,
+        'action_signin': action_signin,
+        },context_instance=RequestContext(request))
+
+def signout(request):
+    """
+    signout from the website. Remove openid from session and kill it.
+
+    url : /signout/"
+    """
+    try:
+        del request.session['openid']
+    except KeyError:
+        pass
+    next = request.GET.get('next', '/')
+    if not is_valid_next_url(next):
+        next = '/'
+
+    logout(request)
+    
+    return HttpResponseRedirect(next)
+signout = login_required(signout)
+
+def account_settings(request):
+    """
+    index pages to changes some basic account settings :
+     - change password
+     - change email
+     - associate a new openid
+     - delete account
+
+    url : /
+
+    template : authopenid/settings.html
+    """
+    msg = request.GET.get('msg', '')
+    is_openid = True
+
+    try:
+        o=UserAssociation.objects.get(user__username__exact=request.user.username)
+    except:
+        is_openid = False
+
+
+    return render('authopenid/settings.html',
+            {'msg': msg, 'settings_path': request.path, 'is_openid': is_openid},
+            context_instance=RequestContext(request))
+account_settings = login_required(account_settings)
+
+def changepw(request):
+    """
+    change password view.
+
+    url : /changepw/
+    template: authopenid/changepw.html
+    """
+    
+    u = request.user
+    
+    if request.POST:
+        form = ChangepwForm(request.POST, user=u)
+        if form.is_valid():
+            u.set_password(form.cleaned_data['password1'])
+            u.save()
+            msg=_("Password changed.") 
+            redirect="%s?msg=%s" % (reverse('user_account_settings'),urlquote_plus(msg))
+            return HttpResponseRedirect(redirect)
+    else:
+        form=ChangepwForm(user=u)
+
+    return render('authopenid/changepw.html', {'form': form },
+                                context_instance=RequestContext(request))
+changepw = login_required(changepw)
+
+def changeemail(request):
+    """ 
+    changeemail view. It require password or openid to allow change.
+
+    url: /changeemail/
+
+    template : authopenid/changeemail.html
+    """
+
+    extension_args = {}
+ 
+    u = request.user
+    
+    redirect_to = get_url_host(request) + reverse('user_changeemail')
+
+    if request.POST:
+        form = ChangeemailForm(request.POST, user=u)
+        if form.is_valid():
+            if not form.test_openid:
+                u.email = form.cleaned_data['email']
+                u.save()
+                msg=_("Email changed.") 
+                redirect="%s?msg=%s" % (reverse('user_account_settings'),urlquote_plus(msg))
+                return HttpResponseRedirect(redirect)
+            else:
+                request.session['new_email'] = form.cleaned_data['email']
+                return ask_openid(request, form.cleaned_data['password'], redirect_to, on_failure=emailopenid_failure)    
+    elif not request.POST and 'openid.mode' in request.GET:
+        return complete(request, emailopenid_success, emailopenid_failure, redirect_to) 
+    else:
+        form = ChangeemailForm(initial={'email': u.email}, user=u)
+    
+    return render('authopenid/changeemail.html', 
+            {'form': form }, context_instance=RequestContext(request))
+changeemail = login_required(changeemail)
+
+def emailopenid_success(request, identity_url, openid_response):
+    openid=from_openid_response(openid_response)
+
+    u = request.user
+    try:
+        o=UserAssociation.objects.get(openid_url__exact=identity_url)
+    except:
+        return emailopenid_failure(request, _("No openid % associated in our database" % identity_url))
+
+    if o.user.username != request.user.username:
+        return emailopenid_failure(request, _("The openid %s isn't associated to current logged user" % identity_url))
+    
+    new_email=request.session.get('new_email', '')
+    if new_email:
+        u.email=new_email
+        u.save()
+        del request.session['new_email']
+    msg=_("Email Changed.")
+
+    redirect="%s?msg=%s" % (reverse('user_account_settings'),urlquote_plus(msg))
+    return HttpResponseRedirect(redirect)
+    
+
+def emailopenid_failure(request, message):
+    redirect_to="%s?msg=%s" % (reverse('user_changeemail'), urlquote_plus(message))
+    return HttpResponseRedirect(redirect_to)
+ 
+
+def changeopenid(request):
+    """
+    change openid view. Allow user to change openid associated to its username.
+
+    url : /changeopenid/
+
+    template: authopenid/changeopenid.html
+    """
+
+    extension_args = {}
+    openid_url=''
+    has_openid=True
+    msg = request.GET.get('msg', '')
+        
+    u = request.user
+
+    try:
+        uopenid=UserAssociation.objects.get(user=u)
+        openid_url = uopenid.openid_url
+    except:
+        has_openid=False
+    
+    redirect_to = get_url_host(request) + reverse('user_changeopenid')
+    if request.POST and has_openid:
+        form=ChangeopenidForm(request.POST, user=u)
+        if form.is_valid():
+            return ask_openid(request, form.cleaned_data['openid_url'], redirect_to, on_failure=changeopenid_failure)
+    elif not request.POST and has_openid:
+        if 'openid.mode' in request.GET:
+            return complete(request, changeopenid_success, changeopenid_failure, redirect_to)    
+
+    form = ChangeopenidForm(initial={'openid_url': openid_url }, user=u)
+    return render('authopenid/changeopenid.html', {'form': form,
+        'has_openid': has_openid, 'msg': msg }, context_instance=RequestContext(request))
+changeopenid = login_required(changeopenid)
+
+def changeopenid_success(request, identity_url, openid_response):
+    openid=from_openid_response(openid_response)
+    is_exist=True
+    try:
+        o=UserAssociation.objects.get(openid_url__exact=identity_url)
+    except:
+        is_exist=False
+        
+    if not is_exist:
+        try:
+            o=UserAssociation.objects.get(user__username__exact=request.user.username)
+            o.openid_url=identity_url
+            o.save()
+        except:
+            o=UserAssociation(user=request.user,openid_url=identity_url)
+            o.save()
+    elif o.user.username != request.user.username:
+        return changeopenid_failure(request, _('This openid is already associated with another account.'))
+
+    request.session['openids'] = []
+    request.session['openids'].append(openid)
+
+    msg=_("Openid %s associated with your account." % identity_url) 
+    redirect="%s?msg=%s" % (reverse('user_account_settings'), urlquote_plus(msg))
+    return HttpResponseRedirect(redirect)
+    
+
+def changeopenid_failure(request, message):
+    redirect_to="%s?msg=%s" % (reverse('user_changeopenid'), urlquote_plus(message))
+    return HttpResponseRedirect(redirect_to)
+  
+
+def delete(request):
+    """
+    delete view. Allow user to delete its account. Password/openid are required to 
+    confirm it. He should also check the confirm checkbox.
+
+    url : /delete
+
+    template : authopenid/delete.html
+    """
+
+    extension_args={}
+    
+    u = request.user
+
+    redirect_to = get_url_host(request) + reverse('user_delete') 
+    if request.POST:
+        form = DeleteForm(request.POST, user=u)
+        if form.is_valid():
+            if not form.test_openid:
+                u.delete() 
+                return signout(request)
+            else:
+                return ask_openid(request, form.cleaned_data['password'], redirect_to, on_failure=deleteopenid_failure)
+    elif not request.POST and 'openid.mode' in request.GET:
+        return complete(request, deleteopenid_success, deleteopenid_failure, redirect_to) 
+    
+    form = DeleteForm(user=u)
+
+    msg = request.GET.get('msg','')
+    return render('authopenid/delete.html', {'form': form, 'msg': msg, },
+                                        context_instance=RequestContext(request))
+delete = login_required(delete)
+
+def deleteopenid_success(request, identity_url, openid_response):
+    openid=from_openid_response(openid_response)
+
+    u = request.user
+    try:
+        o=UserAssociation.objects.get(openid_url__exact=identity_url)
+    except:
+        return deleteopenid_failure(request, _("No openid % associated in our database" % identity_url))
+
+    if o.user.username == request.user.username:
+        u.delete()
+        return signout(request)
+    else:
+        return deleteopenid_failure(request, _("The openid %s isn't associated to current logged user" % identity_url))
+    
+    msg=_("Account deleted.") 
+    redirect="/?msg=%s" % (urlquote_plus(msg))
+    return HttpResponseRedirect(redirect)
+    
+
+def deleteopenid_failure(request, message):
+    redirect_to="%s?msg=%s" % (reverse('user_delete'), urlquote_plus(message))
+    return HttpResponseRedirect(redirect_to)
+
+
+def sendpw(request):
+    """
+    send a new password to the user. It return a mail with 
+    a new pasword and a confirm link in. To activate the 
+    new password, the user should click on confirm link.
+
+    url : /sendpw/
+
+    templates :  authopenid/sendpw_email.txt, authopenid/sendpw.html
+    """
+
+    msg = request.GET.get('msg','')
+    if request.POST:
+        form = EmailPasswordForm(request.POST)
+        if form.is_valid():
+            new_pw = User.objects.make_random_password()
+            confirm_key = UserPasswordQueue.objects.get_new_confirm_key()
+            try:
+                q=UserPasswordQueue.objects.get(user=form.user_cache)
+            except:
+                q=UserPasswordQueue(user=form.user_cache)
+            q.new_password=new_pw
+            q.confirm_key = confirm_key
+            q.save()
+            # send email 
+            current_domain = Site.objects.get_current().domain
+            subject = _("Request for a new password")
+            message_template = loader.get_template('authopenid/sendpw_email.txt')
+            message_context = Context({ 'site_url': 'http://%s' % current_domain,
+                'confirm_key': confirm_key,
+                'username': form.user_cache.username,
+                'password': new_pw,
+                'url_confirm': reverse('user_confirmchangepw'),
+            })
+            message = message_template.render(message_context)
+            send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [form.user_cache.email])
+            msg=_("A new password has been sent to your email")
+    else:
+        form = EmailPasswordForm()
+        
+    return render('authopenid/sendpw.html', {'form': form,
+            'msg': msg, },
+            context_instance=RequestContext(request))
+
+
+def confirmchangepw(request):
+    """
+    view to set new password when the user click on confirm link
+    in its mail. Basically it check if the confirm key exist, then
+    replace old password with new password and remove confirm
+    ley from the queue. Then it redirect the user to signin
+    page.
+
+    url : /sendpw/confirm/?key
+
+    """
+
+
+    confirm_key = request.GET.get('key', '')
+    if not confirm_key:
+        return HttpResponseRedirect('/')
+
+    try:
+        q = UserPasswordQueue.objects.get(confirm_key__exact=confirm_key)
+    except:
+        msg=_("Can not change password. Confirmation key '%s' isn't registered." % confirm_key) 
+        redirect="%s?msg=%s" % (reverse('user_sendpw'),urlquote_plus(msg))
+        return HttpResponseRedirect(redirect)
+
+    try:
+        user = User.objects.get(id=q.user.id)
+    except:
+        msg=_("Can not change password. User don't exist anymore in our database.") 
+        redirect="%s?msg=%s" % (reverse('user_sendpw'),urlquote_plus(msg))
+        return HttpResponseRedirect(redirect)
+
+    user.set_password(q.new_password)
+    user.save()
+    q.delete()
+    msg=_("Password changed for %s. You could now sign-in" % user.username) 
+    redirect="%s?msg=%s" % (reverse('user_signin'), 
+                                        urlquote_plus(msg))
+
+    return HttpResponseRedirect(redirect)

forms.py

-# -*- coding: utf-8 -*-
-"""
- Copyright (c) 2007, 2008, Benoît Chesneau
-
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-     * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-
-
-from django import newforms as forms
-from django.contrib.auth.models import User
-from django.contrib.auth import authenticate
-from django.utils.translation import ugettext as _
-from django.conf import settings
-
-import re
-
-
-# needed for some linux distributions like debian
-try:
-    from openid.yadis import xri
-except:
-    from yadis import xri
-
-
-class OpenidSigninForm(forms.Form):
-    openid_url = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs={'class': 'required openid'}))
-    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
-
-    def clean_openid_url(self):
-        if 'openid_url' in self.cleaned_data:
-            openid_url = self.cleaned_data['openid_url']
-            if xri.identifierScheme(openid_url) == 'XRI' and getattr(
-                settings, 'OPENID_DISALLOW_INAMES', False
-                ):
-                raise forms.ValidationError(_('i-names are not supported'))
-            return self.cleaned_data['openid_url']
-
-    def clean_next(self):
-        if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
-            next_url_re = re.compile('^/[-\w/]*$')
-            if not next_url_re.match(self.cleaned_data['next']):
-                raise forms.ValidationError(_('next url "%s" is invalid' % self.cleaned_data['next']))
-            return self.cleaned_data['next']
-
-
-attrs_dict = { 'class': 'required login' }
-username_re = re.compile(r'^\w+$')
-
-class OpenidAuthForm(forms.Form):
-    """ legacy account signin form """
-    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
-    username = forms.CharField(max_length=30,  widget=forms.widgets.TextInput(attrs=attrs_dict))
-    password = forms.CharField(max_length=128, widget=forms.widgets.PasswordInput(attrs=attrs_dict))
-       
-    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
-        super(OpenidAuthForm, self).__init__(data, files, auto_id, prefix, initial)
-        self.user_cache=None
-            
-    def clean_username(self):
-        if 'username' in self.cleaned_data and 'openid_url' not in self.cleaned_data:
-            if not username_re.search(self.cleaned_data['username']):
-                raise forms.ValidationError(_("Usernames can only contain letters, numbers and underscores"))
-            try:
-                user = User.objects.get(username__exact=self.cleaned_data['username'])
-            except User.DoesNotExist:
-                raise forms.ValidationError(_("This username don't exist in database. Please choose another."))
-            return self.cleaned_data['username']
-
-    def clean_password(self):
-        if 'username' in self.cleaned_data and 'password' in self.cleaned_data:
-            self.user_cache =  authenticate(username=self.cleaned_data['username'], password=self.cleaned_data['password'])
-            if self.user_cache is None:
-                raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
-            elif self.user_cache.is_active == False:
-                raise forms.ValidationError(_("This account is inactive."))
-            return self.cleaned_data['password']
-
-    def clean_next(self):
-        if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
-            next_url_re = re.compile('^/[-\w/]*$')
-            if not next_url_re.match(self.cleaned_data['next']):
-                raise forms.ValidationError(_('next url "%s" is invalid' % self.cleaned_data['next']))
-            return self.cleaned_data['next']
-            
-    def get_user(self):
-        return self.user_cache
-            
-
-class OpenidRegisterForm(forms.Form):
-    """ openid signin form """
-    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
-
-    username = forms.CharField(max_length=30, widget=forms.widgets.TextInput(attrs=attrs_dict))
-    email = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs=attrs_dict))
-    
-    def clean_username(self):
-        if 'username' in self.cleaned_data:
-            if not username_re.search(self.cleaned_data['username']):
-                raise forms.ValidationError(_("Usernames can only contain letters, numbers and underscores"))
-            try:
-                user = User.objects.get(username__exact=self.cleaned_data['username'])
-            except User.DoesNotExist:
-                return self.cleaned_data['username']
-            raise forms.ValidationError(_("This username is already taken. Please choose another."))
-            
-    def clean_email(self):
-        """For security reason one unique email in database"""
-        if 'email' in self.cleaned_data:
-            try:
-                user = User.objects.get(email=self.cleaned_data['email'])
-            except User.DoesNotExist:
-                return self.cleaned_data['email']
-            raise forms.ValidationError(_("This email is already registered in our database. Please choose another."))
- 
-    
-class OpenidVerifyForm(forms.Form):
-    """ openid verify form (associate an openid with an account) """
-    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
-    username = forms.CharField(max_length=30, widget=forms.widgets.TextInput(attrs=attrs_dict))
-    password = forms.CharField(max_length=128, widget=forms.widgets.PasswordInput(attrs=attrs_dict))
-     
-    def clean_username(self):
-        if 'username' in self.cleaned_data:
-            if not username_re.search(self.cleaned_data['username']):
-                raise forms.ValidationError(_("Usernames can only contain letters, numbers and underscores"))
-            try:
-                user = User.objects.get(username__exact=self.cleaned_data['username'])
-            except User.DoesNotExist:
-                raise forms.ValidationError(_("This username don't exist. Please choose another."))
-            return self.cleaned_data['username']
-            
-    def clean_password(self):
-        if 'username' in self.cleaned_data and 'password' in self.cleaned_data:
-            self.user_cache =  authenticate(username=self.cleaned_data['username'], password=self.cleaned_data['password'])
-            if self.user_cache is None:
-                raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
-            elif self.user_cache.is_active == False:
-                raise forms.ValidationError(_("This account is inactive."))
-            return self.cleaned_data['password']
-            
-    def get_user(self):
-        return self.user_cache
-
-
-attrs_dict = { 'class': 'required' }
-username_re = re.compile(r'^\w+$')
-
-class RegistrationForm(forms.Form):
-    """ legacy registration form """
-
-    next = forms.CharField(max_length=255,widget=forms.HiddenInput(), required=False)
-    username = forms.CharField(max_length=30,
-                               widget=forms.TextInput(attrs=attrs_dict),
-                               label=u'Username')
-    email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
-                                                               maxlength=200)),
-                             label=u'Email address')
-    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
-                                label=u'Password')
-    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
-                                label=u'Password (again, to catch typos)')
-
-    def clean_username(self):
-        """
-        Validates that the username is alphanumeric and is not already
-        in use.
-        
-        """
-        if 'username' in self.cleaned_data:
-            if not username_re.search(self.cleaned_data['username']):
-                raise forms.ValidationError(u'Usernames can only contain letters, numbers and underscores')
-            try:
-                user = User.objects.get(username__exact=self.cleaned_data['username'])
-            except User.DoesNotExist:
-                return self.cleaned_data['username']
-            raise forms.ValidationError(u'This username is already taken. Please choose another.')
-
-    def clean_email(self):
-        if 'email' in self.cleaned_data:
-            try:
-                user = User.objects.get(email=self.cleaned_data['email'])
-            except:
-                return self.cleaned_data['email']
-            raise forms.ValidationError(u'This email is already registered in our database. Please choose another.')
-        return self.cleaned_data['email']
-    
-    def clean_password2(self):
-        """
-        Validates that the two password inputs match.
-        
-        """
-        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data and \
-           self.cleaned_data['password1'] == self.cleaned_data['password2']:
-            return self.cleaned_data['password2']
-        raise forms.ValidationError(u'You must type the same password each time')
-
-
-class ChangepwForm(forms.Form):
-    """ change password form """
-    oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
-    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
-    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
-
-    def __init__(self, data=None, user=None, *args, **kwargs):
-        if user is None:
-            raise TypeError("Keyword argument 'user' must be supplied")
-        super(ChangepwForm, self).__init__(data, *args, **kwargs)
-        self.user = user
-