Commits

offline committed 154fb8f

openid basic functionality implemented

  • Participants
  • Parent commits 2f8edfd

Comments (0)

Files changed (10)

File openauth/admin.py

 from django.contrib import admin
-from openauth.models import Profile, Confirmation
+from openauth.models import Profile, Confirmation, OpenID
 
 
 class ProfileAdmin(admin.ModelAdmin):
 class ConfirmationAdmin(admin.ModelAdmin):
     pass
 
+class OpenIDAdmin(admin.ModelAdmin):
+    list_display = ['user', 'url']
+
+
+admin.site.register(OpenID, OpenIDAdmin)
 admin.site.register(Profile, ProfileAdmin)
 admin.site.register(Confirmation, ConfirmationAdmin)
 

File openauth/backends.py

+from django.contrib.auth.models import User
+
+from openauth.models import OpenID
+
+class OpenIDBackend(object):
+    def authenticate(self, openid_url=None):
+        if openid_url:
+            try:
+                user = OpenID.objects.get(url=openid_url).user
+                return user
+            except OpenID.DoesNotExist:
+                return None
+        else:
+            return None
+
+    def get_user(self, user_id):
+        try:
+            return User.objects.get(pk=user_id)
+        except User.DoesNotExist:
+            return None

File openauth/forms.py

 from annoying.decorators import autostrip
 
 import openauth
-from openauth.models import Confirmation
+from openauth.utils import uri_to_username
+from openauth.models import Confirmation, OpenID
 
 
 
 LoginForm = autostrip(LoginForm)
 
 
+
+class OpenIDLoginForm(forms.Form):
+    openid_url = forms.CharField(u'OpenID URL', widget=forms.TextInput())
+
+    def __init__(self, *args, **kwargs):
+        self.post = kwargs.pop('post', {})
+        kwargs['auto_id'] = '%s'
+        super(OpenIDLoginForm, self).__init__(*args, **kwargs)
+
+
+class OpenIDRegistrationForm(forms.Form):
+    def __init__(self, *args, **kwargs):
+
+        if openauth.settings.OPENID_REQUIRE_EMAIL:
+            self.base_fields['email'] = User._meta.get_field('email').formfield(required=True)
+        if openauth.settings.OPENID_REQUIRE_USERNAME:
+            self.base_fields['username'] = username = forms.CharField()
+        self.openid_url = kwargs.pop('openid_url')
+        super(self.__class__, self).__init__(*args, **kwargs)
+
+    def clean_email(self):
+        if 'email' in self.cleaned_data:
+            email = self.cleaned_data['email']
+            try:
+                User.objects.get(email=email)
+            except User.DoesNotExist:
+                return email
+            else:
+                raise forms.ValidationError(u'This email is already registered')
+
+    def clean_username(self):
+        if 'username' in self.cleaned_data:
+            username = self.cleaned_data['username']
+            #if not re.search(r'^[a-z0-9_]+$', username, re.I):
+                #raise forms.ValidationError('Restricted symbols in username')
+            try:
+                User.objects.get(username=username)
+            except User.DoesNotExist:
+                return username
+            else:
+                raise forms.ValidationError(u'This username is already registered')
+
+    def clean(self):
+        if openauth.settings.OPENID_REQUIRE_USERNAME:
+            self.cleaned_data['username'] = uri_to_username(self.openid_url)
+        if openauth.settings.OPENID_REQUIRE_EMAIL:
+            self.cleaned_data['email'] = ''
+        return self.cleaned_data
+
+    def save(self):
+        user = User.objects.create_user(self.cleaned_data['username'],
+                                        self.cleaned_data['email'])
+        user.is_active = False
+        user.save()
+        OpenID(user=user, url=self.openid_url).save()
+        return user
+
+
+
 class NewPasswordForm(forms.Form):
     """
     Form for changing user's password.

File openauth/models.py

         return "%s: %s" % (self.user.email, self.key)
 
 
+
+
+
+class OpenID(models.Model):
+    user = models.ForeignKey(User)
+    url = models.CharField(u'OpenID URL', max_length=255, unique=True)
+
+    def __unicode__(self):
+        return self.url
+

File openauth/settings.py

 
 LOGIN_URL = get_config("LOGIN_URL", "/auth/login/")
 LOGIN_REDIRECT_URL = get_config("LOGIN_REDIRECT_URL", "/auth/user/")
-AUTHENTICATION_TYPE = get_config("AUTHENTICATION_TYPE", ["username"])
+AUTHENTICATION_TYPE = get_config("AUTHENTICATION_TYPE", ["username", "openid"])
 
 USERNAME_REGEX = get_config("USERNAME_REGEX", re.compile(r"[a-z0-9][_a-z0-9]*[a-z0-9]$", re.I))
 USERNAME_MIN_LENGTH = get_config("ACCOUNT_USERNAME_MIN_LENGTH", 3)
 PASSWORD_MIN_LENGTH = get_config("ACCOUNT_PASSWORD_MIN_LENGTH", 1)
 PASSWORD_MAX_LENGTH = get_config("ACCOUNT_PASSWORD_MAX_LENGTH", 30)
 PASSWORD_FIELD_ATTRIBUTES = get_config("PASSWORD_FIELD_ATTRIBUTES", {})
+
+
+#OpenID settings
+
+OPENID_REQUIRE_EMAIL = get_config("OPENID_REQUIRE_EMAIL", True)
+OPENID_REQUIRE_USERNAME = get_config("OPENID_REQUIRE_USERNAME", True)
+
+SREG_FIELDS = ['nickname', 'email', 'fullname', 'dob', 'gender',
+                'postcode', 'country', 'language', 'timezone']
+
+PROFILE_DETAILS = set(get_config('AUTH_OPENID_PROFILE_DETAILS', []))
+for detail in list(PROFILE_DETAILS):
+    if not detail in SREG_FIELDS:
+        PROFILE_DETAILS.remove(detail)
+
+PROFILE_DETAILS_MAPPING = get_config('AUTH_OPENID_PROFILE_DETAILS_MAPPING', {'nickname': 'username'})
+
+
+# This dict contains mapping of SREG fields to AX uris
+# http://www.axschema.org/types/
+AX_URIS = {
+    'nickname': 'http://axschema.org/namePerson/friendly',
+    'email': 'http://axschema.org/contact/email',
+    'fullname': 'http://axschema.org/namePerson',
+    'dob': 'http://axschema.org/birthDate',
+    'gender': 'http://axschema.org/person/gender',
+    'postcode': 'http://axschema.org/contact/postalCode/home',
+    'country': 'http://axschema.org/contact/country/home',
+    'language': 'http://axschema.org/pref/language',
+    'timezone': 'http://axschema.org/pref/timezone',
+}

File openauth/templates/openauth/openid_login.html

+{% extends 'base.html' %}
+
+{% block content %}
+<script type="text/javascript">
+function customAuth(provider) {
+    var form = $$('.openid-auth-form');
+    if (provider == 'google') {
+        $('openid_url').set('value', 'https://www.google.com/accounts/o8/id')
+        form.submit();
+    } else {
+        alert('unknown provider');
+    }
+}
+</script>
+<h2>Авторизация через OpenID</h2>
+{% if error %}
+<div class="error">{{ error }}</div>
+{% endif %}
+<form action="." method="post">
+    {{ form.as_table }}
+    <p>Or with <input type="button" name="auth_google" onclick="customAuth('google')" value="Google"/></p>
+    <p><input type="submit" value="Continue"/></p>
+</form>
+{% endblock %}

File openauth/templates/openauth/openid_registration.html

+{% extends 'base.html' %}
+{% load i18n %}
+
+{% block content %}
+<h2>{% trans "Registration with OpenID: last step" %}</h2>
+<p><label>{% trans "OpenID URL" %}:</label> {{ openid_url }}</p>
+<form method="post">
+    <formfield>
+        <legend>Регистрация через OpenID</legend>
+        <div class="wide">{{ form.as_p }}</div>
+        <p><input type="submit" value="{% trans "Submit" %}"/></p>
+    </formfield>
+</form>
+{% endblock %}

File openauth/urls.py

 urlpatterns = patterns('',
     url(r'^registration/$', views.registration, name='openauth-registration'),
     url(r'^login/$', views.login, name='openauth-login'),
+    url(r'^openid-login/$', views.openid_login, name='openauth-openid-login'),
+    url(r'^openid-complete/$', views.openid_complete, name='openauth-openid-complete'),
+    url(r'^openid-registration/$', views.openid_registration, name='openauth-openid-registration'),
     url(r'^logout/$', views.logout, name='openauth-logout'),
     url(r'^reset_password/$', views.reset_password, name='openauth-reset-password'),
     url(r'^change_password/$', views.change_password, name='openauth-change-password'),

File openauth/utils.py

+import re
 from functools import wraps
 
 from django.shortcuts import redirect
             return redirect(next)
         return view_func(request, *args, **kwargs)
     return wrapper
+
+
+def uri_to_username(uri):
+    return re.sub('r[^0-9a-z]', '_', uri)
+

File openauth/views.py

+import pickle
+
+from openid.consumer import consumer, discover
+from openid.extensions import sreg 
+from openid.extensions import ax
+from openid import oidutil
+
+
 from django.contrib.auth.models import User
 from django.core.urlresolvers import reverse
 from django.contrib import auth 
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, redirect
 from django.utils.translation import ugettext as _
 
 from annoying.decorators import render_to
 
 import openauth
-from openauth.forms import ResetPasswordForm, NewPasswordForm, LoginForm, EditUserForm
+from openauth.forms import ResetPasswordForm, NewPasswordForm, LoginForm, \
+     EditUserForm, OpenIDLoginForm, OpenIDRegistrationForm
 from openauth.utils import email_template, build_redirect_url, str_to_class, \
     generate_hash, activation_required, message
 from openauth.models import Confirmation
 
 @render_to('openauth/login.html')
 def login(request):
-
     if request.user.is_authenticated():
         return message(request, _('You are already authenticated'), 'notice', '/')
     if request.POST:
     return {'form': form}
 
 
+@render_to('openauth/openid_login.html')
+def openid_login(request, provider=None):
+    """
+    If OpenID url was submitted then start OpenID authentication
+    else display OpenID authentication form
+    """
+
+    post = request.POST.copy()
+    initial = {}
+    if provider == 'google':
+        google_uri = 'https://www.google.com/accounts/o8/id'
+        post['openid_url'] = google_uri
+        initial['openid_url'] = google_uri
+
+        request.method = 'POST'
+        request.POST = post
+    
+    if provider == 'yahoo':
+        google_uri = 'http://yahoo.com'
+        post['openid_url'] = google_uri
+        initial['openid_url'] = google_uri
+
+        request.method = 'POST'
+        request.POST = post
+
+    if 'POST' == request.method:
+        form = OpenIDLoginForm(post, post=request.POST)
+    else:
+        form = OpenIDLoginForm(initial=initial)
+    error = ''
+    
+    request.session['login_redirect_url'] = request.GET.get('next')
+
+    if form.is_valid():
+        openid_url = form.cleaned_data['openid_url']
+        return_to = request.build_absolute_uri(reverse('openauth-openid-complete'))
+        request.session['openid_return_to'] = return_to
+        client = consumer.Consumer(request.session, None)
+        try:
+            auth_req = client.begin(openid_url)
+            if openauth.settings.PROFILE_DETAILS:
+                auth_req.addExtension(sreg.SRegRequest(required=openauth.settings.PROFILE_DETAILS))
+
+                ax_msg = ax.FetchRequest()
+                for detail in openauth.settings.PROFILE_DETAILS:
+                    ax_msg.add(ax.AttrInfo(openauth.settings.AX_URIS[detail], required=True))
+                auth_req.addExtension(ax_msg)
+
+            redirect_url = auth_req.redirectURL(realm='http://' + request.get_host(),
+                                                return_to=return_to)
+            return redirect(redirect_url)
+
+        except discover.DiscoveryFailure, ex:
+            error = _('Could not find OpenID server')
+            form.errors['openid_url'] = [error]
+
+    return {'form': form, 'error': error}
+
+
+
+def openid_complete(request):
+    """
+    Complete OpenID authorization process.
+    If OpenID URL was successfuly authenticated:
+     * if user with such URL exists then login as this user
+     * if no user with such URL exists then redirect to registration form
+     * if no user with such URL exists and current user is authenticated then
+       assign OpenID url to this user
+    """
+
+    registration_redirect = openauth.settings.LOGIN_REDIRECT_URL
+
+    error = ''
+    client = consumer.Consumer(request.session, None)
+
+    args = dict(request.GET.items())
+    if 'POST' == request.method:
+        args.update(request.POST)
+    resp = client.complete(args, request.session.get('openid_return_to'))
+
+    try:
+        del request.session['openid_return_to']
+    except KeyError:
+        pass
+    if resp.status == consumer.CANCEL:
+        error = u'You have cancelled OpenID authorization'
+    elif resp.status == consumer.FAILURE:
+        error = u'OpenID authorization failed. Reason: %s' % resp.message
+    elif resp.status == consumer.SUCCESS:
+        if request.user.is_authenticated():
+            try:
+                user = OpenID.objects.get(url=resp.identity_url).user
+            except OpenID.DoesNotExist:
+                pass
+            else:
+                return message(u'This url already used by %s' % user.username)
+            OpenID(user=request.user, url=resp.identity_url).save()
+            return redirect(registration_redirect)
+        else:
+            user = auth.authenticate(openid_url=resp.identity_url)
+            if user:
+                if user.is_active:
+                    auth.login(request, user)
+                    error = u'You have saccessfully authorized via OpenID'
+                    redirect_url = build_redirect_url(request, openauth.settings.LOGIN_REDIRECT_URL)
+                    return redirect(redirect_url)
+                else:
+                    request.flash['notice'] = 'Your account is not active. Please activate it.'
+                    return redirect("/")
+            else:
+                request.session['authenticated_openid_url'] = resp.identity_url
+                sreg_resp = sreg.SRegResponse.fromSuccessResponse(resp)
+                request.session['sreg_resp'] = pickle.dumps(sreg_resp)
+
+                ax_resp = ax.FetchResponse.fromSuccessResponse(resp)
+                request.session['ax_resp'] = pickle.dumps(ax_resp)
+                return redirect(reverse('openauth-openid-registration'))
+    else:
+        error = 'OpenID authorization failed'
+    form = OpenIDLoginForm()
+    return {'form': form, 'error': error} 
+
+
+@render_to('openauth/openid_registration.html')
+def openid_registration(request):
+    """                                                
+    Handle registration new user with given openid_url in session
+    """                                                          
+    openid_url = request.session.get('authenticated_openid_url') 
+
+    if not openid_url:
+        return redirect(reverse('openauth-openid-login'))
+
+    kwargs = {'openid_url': openid_url}
+
+    if 'POST' == request.method:
+        form = OpenIDRegistrationForm(request.POST, **kwargs)
+    else:                                        
+        details = {}                             
+
+        def get_name(name):
+            return openauth.settings.PROFILE_DETAILS_MAPPING.get(name, name)
+
+        if 'sreg_resp' in request.session:
+            sreg_resp = pickle.loads(request.session['sreg_resp'])
+            if sreg_resp:
+                for detail in openauth.settings.PROFILE_DETAILS:
+                    details[get_name(detail)] = sreg_resp.get(detail, '')
+
+        if 'ax_resp' in request.session:
+            ax_resp = pickle.loads(request.session['ax_resp'])
+            if ax_resp:
+                for detail in openauth.settings.PROFILE_DETAILS:
+                    if not details.get(detail):
+                        details[get_name(detail)] = ax_resp.getSingle(openauth.settings.AX_URIS[detail], '')
+        form = OpenIDRegistrationForm(initial=details, **kwargs)
+
+    if form.is_valid():
+        user = form.save()
+        activation = Confirmation(user=user, key=generate_hash())
+        activation.save()
+        url = request.build_absolute_uri(reverse('openauth-confirm'))
+        url += '?key=%s' % activation.key
+        params = {'domain': 'localhost:8000', 'email': user.email, 'url': url}
+        email_template(user.email, 'openauth/mail/registration.txt', **params)
+        request.flash['notice'] = "You have successfully registered. Check your inbox for email with activation link."
+        del request.session['authenticated_openid_url']
+        redirect_url = build_redirect_url(request, reverse('openauth-show-user', args=[user.id]))
+        return redirect(redirect_url)
+
+    return {'form': form, 'openid_url': openid_url}
+
+
+
 @activation_required
 @render_to('openauth/change_password.html')
 def change_password(request):