Commits

David Larlet committed 2de1e14 Merge

Automated merge

Comments (0)

Files changed (24)

+syntax: glob
+*.orig
+*.rej
+*~
+*.o
+*.pyc
+*.pyo
+tests/*.err
+*.swp
+store/*
+*.DS_Store
+*.beam
+*.svn 
+
+.svn
+
+syntax: regexp
+.*\#.*\#$
+

django/contrib/semantic/__init__.py

Empty file added.

django/contrib/semantic/openid/client/__init__.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.
+
+"""
+Django authentification application to *with openid using django auth contrib/.
+
+This application allow a user to connect to you website with :
+ * legacy account : username/password
+ * openid url
+"""
+
+__version__ = "0.9.4"

django/contrib/semantic/openid/client/admin.py

+# -*- coding: utf-8 -*-
+
+from django.contrib import admin
+from django.conttrib.semantic.openid.models import UserAssociation
+
+
+class UserAssociationAdmin(admin.ModelAdmin):
+    """User association admin class"""
+admin.site.register(UserAssociation, UserAssociationAdmin)

django/contrib/semantic/openid/client/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 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 ImportError:
+    from yadis import xri
+    
+from django_authopenid.util import clean_next
+
+__all__ = ['OpenidSigninForm', 'OpenidAuthForm', 'OpenidVerifyForm',
+        'OpenidRegisterForm', 'RegistrationForm', 'ChangepwForm',
+        'ChangeemailForm', 'EmailPasswordForm', 'DeleteForm',
+        'ChangeOpenidForm', 'ChangeEmailForm', 'ChangepwForm']
+
+class OpenidSigninForm(forms.Form):
+    """ signin 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):
+        """ test if openid is accepted """
+        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):
+        """ validate next """
+        if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
+            self.cleaned_data['next'] = clean_next(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):
+        """ validate username and test if it exists."""
+        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 does not exist \
+                    in our database. Please choose another."))
+            except User.MultipleObjectsReturned:
+                raise forms.ValidationError(u'There is already more than one \
+                    account registered with that username. Please try \
+                    another.')
+            return self.cleaned_data['username']
+
+    def clean_password(self):
+        """" test if password is valid for this username """
+        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 valid \
+                    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):
+        """ validate next url """
+        if 'next' in self.cleaned_data and \
+                self.cleaned_data['next'] != "":
+            self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
+            return self.cleaned_data['next']
+            
+    def get_user(self):
+        """ get authenticated user """
+        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.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, 
+        maxlength=200)), label=u'Email address')
+    
+    def clean_username(self):
+        """ test if username is valid and exist in database """
+        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']
+            except User.MultipleObjectsReturned:
+                raise forms.ValidationError(u'There is already more than one \
+                    account registered with that username. Please try \
+                    another.')
+            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']
+            except User.MultipleObjectsReturned:
+                raise forms.ValidationError(u'There is already more than one \
+                    account registered with that e-mail address. Please try \
+                    another.')
+            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 __init__(self, data=None, files=None, auto_id='id_%s',
+            prefix=None, initial=None): 
+        super(OpenidVerifyForm, self).__init__(data, files, auto_id,
+                prefix, initial)
+        self.user_cache = None
+
+    def clean_username(self):
+        """ validate username """
+        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."))
+            except User.MultipleObjectsReturned:
+                raise forms.ValidationError(u'Somehow, that username is in \
+                    use for multiple accounts. Please contact us to get this \
+                    problem resolved.')
+            return self.cleaned_data['username']
+            
+    def clean_password(self):
+        """ test if password is valid for this user """
+        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 valid \
+                    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):
+        """ get authenticated user """
+        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']
+            except User.MultipleObjectsReturned:
+                raise forms.ValidationError(u'Somehow, that username is in \
+                    use for multiple accounts. Please contact us to get this \
+                    problem resolved.')
+            raise forms.ValidationError(u'This username is already taken. \
+                    Please choose another.')
+
+    def clean_email(self):
+        """ validate if email exist in database
+        :return: raise error if it exist """
+        if 'email' in self.cleaned_data:
+            try:
+                user = User.objects.get(email = self.cleaned_data['email'])
+            except User.DoesNotExist:
+                return self.cleaned_data['email']
+            except User.MultipleObjectsReturned:
+                raise forms.ValidationError(u'There is already more than one \
+                    account registered with that e-mail address. Please try \
+                    another.')
+            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):
+        """ 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']
+    
+    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"))
+        
+        
+class ChangeemailForm(forms.Form):
+    """ change email form """
+    email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, 
+        maxlength=200)), label=u'Email address')
+    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_email(self):
+        """ check if email don't exist """
+        if 'email' in self.cleaned_data:
+            if self.user.email != self.cleaned_data['email']:
+                try:
+                    user = User.objects.get(email = self.cleaned_data['email'])
+                except User.DoesNotExist:
+                    return self.cleaned_data['email']
+                except User.MultipleObjectsReturned:
+                    raise forms.ValidationError(u'There is already more than one \
+                        account registered with that e-mail address. Please try \
+                        another.')
+                raise forms.ValidationError(u'This email is already registered \
+                    in our database. Please choose another.')
+        return self.cleaned_data['email']
+        
+
+    def clean_password(self):
+        """ check if we have to test a legacy account or not """
+        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):
+        """ check if we have to test a legacy account or not """
+        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):
+        """ get user for this username """
+        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/contrib/semantic/openid/client/middleware.py

+# -*- coding: utf-8 -*-
+from django.conttrib.semantic import mimeparse
+from django.http import HttpResponseRedirect
+from django.core.urlresolvers import reverse
+
+__all__ = ["OpenIDMiddleware"]
+
+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)
+    
+    def process_response(self, request, response):
+        if response.status_code != 200 or len(response.content) < 200:
+            return response
+        path = request.get_full_path()
+        if path == "/" and request.META.has_key('HTTP_ACCEPT') and \
+                mimeparse.best_match(['text/html', 'application/xrds+xml'], 
+                    request.META['HTTP_ACCEPT']) == 'application/xrds+xml':
+            return HttpResponseRedirect(reverse('yadis_xrdf'))
+        return response

django/contrib/semantic/openid/client/mimeparse.py

+"""MIME-Type Parser
+
+This module provides basic functions for handling mime-types. It can handle
+matching mime-types against a list of media-ranges. See section 14.1 of 
+the HTTP specification [RFC 2616] for a complete explaination.
+
+   http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+
+Contents:
+    - parse_mime_type():   Parses a mime-type into it's component parts.
+    - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
+    - quality():           Determines the quality ('q') of a mime-type when compared against a list of media-ranges.
+    - quality_parsed():    Just like quality() except the second parameter must be pre-parsed.
+    - best_match():        Choose the mime-type with the highest quality ('q') from a list of candidates. 
+"""
+
+__version__ = "0.1.1"
+__author__ = 'Joe Gregorio'
+__email__ = "joe@bitworking.org"
+__credits__ = ""
+
+def parse_mime_type(mime_type):
+    """Carves up a mime_type and returns a tuple of the
+       (type, subtype, params) where 'params' is a dictionary
+       of all the parameters for the media range.
+       For example, the media range 'application/xhtml;q=0.5' would
+       get parsed into:
+
+       ('application', 'xhtml', {'q', '0.5'})
+       """
+    parts = mime_type.split(";")
+    params = dict([tuple([s.strip() for s in param.split("=")])\
+            for param in parts[1:] ])
+    (type, subtype) = parts[0].split("/")
+    return (type.strip(), subtype.strip(), params)
+
+def parse_media_range(range):
+    """Carves up a media range and returns a tuple of the
+       (type, subtype, params) where 'params' is a dictionary
+       of all the parameters for the media range.
+       For example, the media range 'application/*;q=0.5' would
+       get parsed into:
+
+       ('application', '*', {'q', '0.5'})
+
+       In addition this function also guarantees that there 
+       is a value for 'q' in the params dictionary, filling it
+       in with a proper default if necessary.
+       """
+    (type, subtype, params) = parse_mime_type(range)
+    if not params.has_key('q') or not params['q'] or \
+            not float(params['q']) or float(params['q']) > 1\
+            or float(params['q']) < 0:
+        params['q'] = '1'
+    return (type, subtype, params)
+
+def quality_parsed(mime_type, parsed_ranges):
+    """Find the best match for a given mime_type against 
+       a list of media_ranges that have already been 
+       parsed by parse_media_range(). Returns the 
+       'q' quality parameter of the best match, 0 if no
+       match was found. This function bahaves the same as quality()
+       except that 'parsed_ranges' must be a list of
+       parsed media ranges. """
+    best_fitness = -1 
+    best_match = ""
+    best_fit_q = 0
+    (target_type, target_subtype, target_params) =\
+            parse_media_range(mime_type)
+    for (type, subtype, params) in parsed_ranges:
+        param_matches = reduce(lambda x, y: x+y, [1 for (key, value) in \
+                target_params.iteritems() if key != 'q' and \
+                params.has_key(key) and value == params[key]], 0)
+        if (type == target_type or type == '*' or target_type == '*') and \
+                (subtype == target_subtype or subtype == '*' or target_subtype == '*'):
+            fitness = (type == target_type) and 100 or 0
+            fitness += (subtype == target_subtype) and 10 or 0
+            fitness += param_matches
+            if fitness > best_fitness:
+                best_fitness = fitness
+                best_fit_q = params['q']
+            
+    return float(best_fit_q)
+    
+def quality(mime_type, ranges):
+    """Returns the quality 'q' of a mime_type when compared
+    against the media-ranges in ranges. For example:
+
+    >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
+    0.7
+    
+    """ 
+    parsed_ranges = [parse_media_range(r) for r in ranges.split(",")]
+    return quality_parsed(mime_type, parsed_ranges)
+
+def best_match(supported, header):
+    """Takes a list of supported mime-types and finds the best
+    match for all the media-ranges listed in header. The value of
+    header must be a string that conforms to the format of the 
+    HTTP Accept: header. The value of 'supported' is a list of
+    mime-types.
+    
+    >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1')
+    'text/xml'
+    """
+    parsed_header = [parse_media_range(r) for r in header.split(",")]
+    weighted_matches = [(quality_parsed(mime_type, parsed_header), mime_type)\
+            for mime_type in supported]
+    weighted_matches.sort()
+    return weighted_matches[-1][0] and weighted_matches[-1][1] or ''
+
+if __name__ == "__main__":
+    import unittest
+
+    class TestMimeParsing(unittest.TestCase):
+
+        def test_parse_media_range(self):
+            self.assert_(('application', 'xml', {'q': '1'}) == parse_media_range('application/xml;q=1'))
+            self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml'))
+            self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml;q='))
+            self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml ; q='))
+            self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=1;b=other'))
+            self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=2;b=other'))
+
+        def test_rfc_2616_example(self):
+            accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
+            self.assertEqual(1, quality("text/html;level=1", accept))
+            self.assertEqual(0.7, quality("text/html", accept))
+            self.assertEqual(0.3, quality("text/plain", accept))
+            self.assertEqual(0.5, quality("image/jpeg", accept))
+            self.assertEqual(0.4, quality("text/html;level=2", accept))
+            self.assertEqual(0.7, quality("text/html;level=3", accept))
+
+        def test_best_match(self):
+            mime_types_supported = ['application/xbel+xml', 'application/xml']
+            # direct match
+            self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml'), 'application/xbel+xml')
+            # direct match with a q parameter
+            self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml; q=1'), 'application/xbel+xml')
+            # direct match of our second choice with a q parameter
+            self.assertEqual(best_match(mime_types_supported, 'application/xml; q=1'), 'application/xml')
+            # match using a subtype wildcard
+            self.assertEqual(best_match(mime_types_supported, 'application/*; q=1'), 'application/xml')
+            # match using a type wildcard
+            self.assertEqual(best_match(mime_types_supported, '*/*'), 'application/xml')
+
+            mime_types_supported = ['application/xbel+xml', 'text/xml']
+            # match using a type versus a lower weighted subtype
+            self.assertEqual(best_match(mime_types_supported, 'text/*;q=0.5,*/*; q=0.1'), 'text/xml')
+            # fail to match anything
+            self.assertEqual(best_match(mime_types_supported, 'text/html,application/atom+xml; q=0.9'), '')
+
+        def test_support_wildcards(self):
+            mime_types_supported = ['image/*', 'application/xml']
+            # match using a type wildcard
+            self.assertEqual(best_match(mime_types_supported, 'image/png'), 'image/*')
+            # match using a wildcard for both requested and supported 
+            self.assertEqual(best_match(mime_types_supported, 'image/*'), 'image/*')
+
+    unittest.main() 

django/contrib/semantic/openid/client/models.py

+# -*- coding: utf-8 -*-
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.db import models
+
+import md5, random, sys, os, time
+
+__all__ = ['Nonce', 'Association', 'UserAssociation', 
+        'UserPasswordQueueManager', 'UserPasswordQueue']
+
+class Nonce(models.Model):
+    """ openid nonce """
+    server_url = models.CharField(max_length=255)
+    timestamp = models.IntegerField()
+    salt = models.CharField(max_length=40)
+    
+    def __unicode__(self):
+        return u"Nonce: %s" % self.id
+
+    
+class Association(models.Model):
+    """ association openid url and lifetime """
+    server_url = models.TextField(max_length=2047)
+    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 UserPasswordQueueManager(models.Manager):
+    """ manager for UserPasswordQueue object """
+    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/contrib/semantic/openid/client/urls.py

+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import patterns, url
+from django.utils.translation import ugettext as _
+
+urlpatterns = patterns('django.conttrib.semantic.openid',
+    # yadis rdf
+    url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
+     # 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/contrib/semantic/openid/client/util.py

+# -*- coding: utf-8 -*-
+from openid.store.interface import OpenIDStore
+from openid.association import Association as OIDAssociation
+from openid.extensions import sreg
+import openid.store
+
+from django.db.models.query import Q
+from django.conf import settings
+from django.http import str_to_unicode
+
+
+# needed for some linux distributions like debian
+try:
+    from openid.yadis import xri
+except:
+    from yadis import xri
+
+import time, base64, md5, operator
+import urllib
+
+from django.conttrib.semantic.openid.models import Association, Nonce
+
+__all__ = ['OpenID', 'DjangoOpenIDStore', 'from_openid_response', 'clean_next']
+
+DEFAULT_NEXT = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+def clean_next(next):
+    if next is None:
+        return DEFAULT_NEXT
+    next = str_to_unicode(urllib.unquote(next), 'utf-8')
+    next = next.strip()
+    if next.startswith('/'):
+        return next
+    return DEFAULT_NEXT
+
+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 cleanupAssociations(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):
+    """ return openid object from response """
+    issued = int(time.time())
+    sreg_resp = sreg.SRegResponse.fromSuccessResponse(openid_response) \
+            or []
+    
+    return OpenID(
+        openid_response.identity_url, issued, openid_response.signed_fields, 
+         dict(sreg_resp)
+    )

django/contrib/semantic/openid/client/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 HttpResponseRedirect, get_host
+from django.shortcuts import 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 login, logout
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.utils.encoding import smart_unicode
+from django.utils.html import escape
+from django.utils.translation import ugettext as _
+from django.contrib.sites.models import Site
+from django.utils.http import urlquote_plus
+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 ImportError:
+    from yadis import xri
+
+import re
+import urllib
+
+
+from django.conttrib.semantic.openid.util import OpenID, DjangoOpenIDStore, from_openid_response, clean_next
+from django.conttrib.semantic.openid.models import UserAssociation, UserPasswordQueue
+from django.conttrib.semantic.openid.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):
+    return get_url_host(request) + request.get_full_path()
+
+
+
+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 DiscoveryFailure:
+        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):
+    """ complete openid signin """
+    on_success = on_success or default_on_success
+    on_failure = on_failure or default_on_failure
+    
+    consumer = Consumer(request.session, DjangoOpenIDStore())
+    # make sure params are encoded in utf8
+    params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
+    openid_response = consumer.complete(params, 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 canceled')
+    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):
+    """ default action on openid signin success """
+    request.session['openid'] = from_openid_response(openid_response)
+    return HttpResponseRedirect(clean_next(request.GET.get('next')))
+
+def default_on_failure(request, message):
+    """ default failure action on signin """
+    return render('openid_failure.html', {
+        'message': message
+    })
+
+
+def not_authenticated(func):
+    """ decorator that redirect user to next page if
+    he is already logged."""
+    def decorated(request, *args, **kwargs):
+        if request.user.is_authenticated():
+            next = request.GET.get("next", "/")
+            return HttpResponseRedirect(next)
+        return func(request, *args, **kwargs)
+    return decorated
+
+@not_authenticated
+def signin(request):
+    """
+    signin page. It manage the legacy authentification (user/password) 
+    and authentification with openid.
+
+    url: /signin/
+    
+    template : openid/client/signin.htm
+    """
+
+    on_failure = signin_failure
+    next = clean_next(request.GET.get('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 = clean_next(form_signin.cleaned_data.get('next'))
+                sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
+                redirect_to = "%s%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():
+                user_ = form_auth.get_user()
+                login(request, user_)
+                next = clean_next(form_auth.cleaned_data.get('next'))
+                return HttpResponseRedirect(next)
+
+
+    return render('openid/client/signin.html', {
+        'form1': form_auth,
+        'form2': form_signin,
+        '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)
+    user_ = rel.user
+    if user_.is_active:
+        user_.backend = "django.contrib.auth.backends.ModelBackend"
+        login(request, user_)
+        
+    next = clean_next(request.GET.get('next'))
+    return HttpResponseRedirect(next)
+
+def is_association_exist(openid_url):
+    """ test if an openid is already in database """
+    is_exist = True
+    try:
+        uassoc = UserAssociation.objects.get(openid_url__exact = openid_url)
+    except:
+        is_exist = False
+    return is_exist
+
+@not_authenticated
+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 : openid/client/complete.html
+    """
+
+    is_redirect = False
+    next = clean_next(request.GET.get('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 = clean_next(form1.cleaned_data.get('next'))
+                is_redirect = True
+                tmp_pwd = User.objects.make_random_password()
+                user_ = User.objects.create_user(form1.cleaned_data['username'],
+                         form1.cleaned_data['email'], tmp_pwd)
+                
+                # make association with openid
+                uassoc = UserAssociation(openid_url=str(openid_),
+                        user_id=user_.id)
+                uassoc.save()
+                    
+                # login 
+                user_.backend = "django.contrib.auth.backends.ModelBackend"
+                login(request, user_)
+        elif 'bverify' in request.POST.keys():
+            form2 = OpenidVerifyForm(request.POST)
+            if form2.is_valid():
+                is_redirect = True
+                next = clean_next(form2.cleaned_data.get('next'))
+                user_ = form2.get_user()
+
+                uassoc = UserAssociation(openid_url=str(openid_),
+                        user_id=user_.id)
+                uassoc.save()
+                login(request, user_)
+        
+        # redirect, can redirect only if forms are valid.
+        if is_redirect:
+            return HttpResponseRedirect(next) 
+    
+    return render('openid/client/complete.html', {
+        'form1': form1,
+        'form2': form2,
+        'nickname': nickname,
+        'email': email
+    }, context_instance=RequestContext(request))
+
+def signin_failure(request, message):
+    """
+    falure with openid signin. Go back to signin page.
+
+    template : "openid/client/signin.html"
+    """
+    next = clean_next(request.GET.get('next'))
+    form_signin = OpenidSigninForm(initial={'next': next})
+    form_auth = OpenidAuthForm(initial={'next': next})
+
+    return render('openid/client/signin.html', {
+        'msg': message,
+        'form1': form_auth,
+        'form2': form_signin,
+    }, context_instance=RequestContext(request))
+
+@not_authenticated
+def signup(request):
+    """
+    signup page. Create a legacy account
+
+    url : /signup/"
+
+    templates: openid/client/signup.html, openid/client/confirm_email.txt
+    """
+    action_signin = reverse('user_signin')
+    next = clean_next(request.GET.get('next'))
+    form = RegistrationForm(initial={'next':next})
+    form_signin = OpenidSigninForm(initial={'next':next})
+    
+    if request.POST:
+        form = RegistrationForm(request.POST)
+        if form.is_valid():
+            next = clean_next(form.cleaned_data.get('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(
+                    'openid/client/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('openid/client/signup.html', {
+        'form': form,
+        'form2': form_signin,
+        }, context_instance=RequestContext(request))
+
+@login_required
+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 = clean_next(request.GET.get('next'))
+    logout(request)
+    
+    return HttpResponseRedirect(next)
+    
+def xrdf(request):
+    url_host = get_url_host(request)
+    return_to = [
+        "%s%s" % (url_host, reverse('user_complete_signin'))
+    ]
+    return render('openid/client/yadis.xrdf', { 
+        'return_to': return_to 
+        }, context_instance=RequestContext(request))
+
+@login_required
+def account_settings(request):
+    """
+    index pages to changes some basic account settings :
+     - change password
+     - change email
+     - associate a new openid
+     - delete account
+
+    url : /
+
+    template : openid/client/settings.html
+    """
+    msg = request.GET.get('msg', '')
+    is_openid = True
+
+    try:
+        uassoc = UserAssociation.objects.get(
+                user__username__exact=request.user.username
+        )
+    except:
+        is_openid = False
+
+
+    return render('openid/client/settings.html', {
+        'msg': msg,
+        'is_openid': is_openid
+        }, context_instance=RequestContext(request))
+
+@login_required
+def changepw(request):
+    """
+    change password view.
+
+    url : /changepw/
+    template: openid/client/changepw.html
+    """
+    
+    user_ = request.user
+    
+    if request.POST:
+        form = ChangepwForm(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 = ChangepwForm(user=user_)
+
+    return render('openid/client/changepw.html', {'form': form },
+                                context_instance=RequestContext(request))
+
+@login_required
+def changeemail(request):
+    """ 
+    changeemail view. It require password or openid to allow change.
+
+    url: /changeemail/
+
+    template : openid/client/changeemail.html
+    """
+    msg = request.GET.get('msg', '')
+    extension_args = {}
+    user_ = request.user
+    
+    redirect_to = get_url_host(request) + reverse('user_changeemail')
+
+    if request.POST:
+        form = ChangeemailForm(request.POST, user=user_)
+        if form.is_valid():
+            if not form.test_openid:
+                user_.email = form.cleaned_data['email']
+                user_.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': user_.email},
+                user=user_)
+    
+    return render('openid/client/changeemail.html', {
+        'form': form,
+        'msg': msg 
+        }, context_instance=RequestContext(request))
+
+
+def emailopenid_success(request, identity_url, openid_response):
+    openid_ = from_openid_response(openid_response)
+
+    user_ = request.user
+    try:
+        uassoc = UserAssociation.objects.get(
+                openid_url__exact=identity_url
+        )
+    except:
+        return emailopenid_failure(request, 
+                _("No OpenID %s found associated in our database" % identity_url))
+
+    if uassoc.user.username != request.user.username:
+        return emailopenid_failure(request, 
+                _("The OpenID %s isn't associated to current user logged in" % 
+                    identity_url))
+    
+    new_email = request.session.get('new_email', '')
+    if new_email:
+        user_.email = new_email
+        user_.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)
+ 
+@login_required
+def changeopenid(request):
+    """
+    change openid view. Allow user to change openid 
+    associated to its username.
+
+    url : /changeopenid/
+
+    template: openid/client/changeopenid.html
+    """
+
+    extension_args = {}
+    openid_url = ''
+    has_openid = True
+    msg = request.GET.get('msg', '')
+        
+    user_ = request.user
+
+    try:
+        uopenid = UserAssociation.objects.get(user=user_)
+        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=user_)
+        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=user_)
+    return render('openid/client/changeopenid.html', {
+        'form': form,
+        'has_openid': has_openid, 
+        'msg': msg 
+        }, context_instance=RequestContext(request))
+
+def changeopenid_success(request, identity_url, openid_response):
+    openid_ = from_openid_response(openid_response)
+    is_exist = True
+    try:
+        uassoc = UserAssociation.objects.get(openid_url__exact=identity_url)
+    except:
+        is_exist = False
+        
+    if not is_exist:
+        try:
+            uassoc = UserAssociation.objects.get(
+                    user__username__exact=request.user.username
+            )
+            uassoc.openid_url = identity_url
+            uassoc.save()
+        except:
+            uassoc = UserAssociation(user=request.user, 
+                    openid_url=identity_url)
+            uassoc.save()
+    elif uassoc.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 is now 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)
+  
+@login_required
+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 : openid/client/delete.html
+    """
+
+    extension_args = {}
+    
+    user_ = request.user
+
+    redirect_to = get_url_host(request) + reverse('user_delete') 
+    if request.POST:
+        form = DeleteForm(request.POST, user=user_)
+        if form.is_valid():
+            if not form.test_openid:
+                user_.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=user_)
+
+    msg = request.GET.get('msg','')
+    return render('openid/client/delete.html', {
+        'form': form, 
+        'msg': msg, 
+        }, context_instance=RequestContext(request))
+
+def deleteopenid_success(request, identity_url, openid_response):
+    openid_ = from_openid_response(openid_response)
+
+    user_ = request.user
+    try:
+        uassoc = UserAssociation.objects.get(
+                openid_url__exact=identity_url
+        )
+    except:
+        return deleteopenid_failure(request,
+                _("No OpenID %s found associated in our database" % identity_url))
+
+    if uassoc.user.username == user_.username:
+        user_.delete()
+        return signout(request)
+    else:
+        return deleteopenid_failure(request,
+                _("The OpenID %s isn't associated to current user logged in" % 
+                    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 :  openid/client/sendpw_email.txt, openid/client/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:
+                uqueue = UserPasswordQueue.objects.get(
+                        user=form.user_cache
+                )
+            except:
+                uqueue = UserPasswordQueue(
+                        user=form.user_cache
+                )
+            uqueue.new_password = new_pw
+            uqueue.confirm_key = confirm_key
+            uqueue.save()
+            # send email 
+            current_domain = Site.objects.get_current().domain
+            subject = _("Request for new password")
+            message_template = loader.get_template(
+                    'openid/client/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 address.")
+    else:
+        form = EmailPasswordForm()
+        
+    return render('openid/client/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:
+        uqueue = UserPasswordQueue.objects.get(
+                confirm_key__exact=confirm_key
+        )
+    except:
+        msg = _("Could not change password. Confirmation key '%s'\
+                is not registered." % confirm_key) 
+        redirect = "%s?msg=%s" % (
+                reverse('user_sendpw'), urlquote_plus(msg))
+        return HttpResponseRedirect(redirect)
+
+    try:
+        user_ = User.objects.get(id=uqueue.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(uqueue.new_password)
+    user_.save()
+    uqueue.delete()
+    msg = _("Password changed for %s. You may now sign in." % 
+            user_.username) 
+    redirect = "%s?msg=%s" % (reverse('user_signin'), 
+                                        urlquote_plus(msg))
+
+    return HttpResponseRedirect(redirect)

django/contrib/semantic/templates/openid/client/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 %}
+
+{% 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_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/contrib/semantic/templates/openid/client/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. 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/contrib/semantic/templates/openid/client/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="." method="post" accept-charset="utf-8">
+
+		<div id="form-row"><label for="id_oldpw">{% trans "Current 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/contrib/semantic/templates/openid/client/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>