Commits

offline committed fd8acce

it works now

Comments (0)

Files changed (9)

publicauth/backends/__init__.py

-from django.shortcuts import render_to_response, redirect
 from django.conf import settings as global_settings
-from django.utils.translation import ugettext as _
-from django.contrib.auth.models import User
-from django.template import RequestContext
 from django.contrib import messages
 from django.contrib import auth
-from django.http import Http404
 
 from annoying.exceptions import Redirect
 
 class BaseBackend(object):
 
     PROFILE_MAPPING = property(lambda self: getattr(global_settings, "%s_PROFILE_MAPPING" % self.provider.upper(), {}))
-    EXTRA_FORM = property(lambda self: getattr(global_settings, "%s_EXTRA_FORM" % self.provider.upper(), 'publicauth.forms.ExtraForm'))
 
     def __init__(self, provider):
         self.provider = provider
-        self._identity = None
-
-    def set_identity(self, identity):
-        self._identity = identity
-
-    def get_identity(self):
-        return self._identity
+        self.identity = None
 
     def begin(self, request, data):
         raise NotImplementedError
 
-    def complete(self, request):
-        """
-        Login user if its identity already exists,
-        Create new user in case that no extra fields are required
-        and user with such identity doesnt exists.
-        Merge accounts if user already logged in and identity is new.
-        """
+    def complete(self, request, response):
+        raise NotImplementedError
+
+    def validate(self, request, data):
+        raise NotImplementedError
+
+    def get_extra_data(self, response):
         raise NotImplementedError
     
-    def attach_account(self, request, user):
+    def merge_accounts(self, request):
         """
         Attach PublicID account to regular django 
-        account and then login user.
+        account and then redirect user. In this situation
+        user dont have to fill extra fields because he filled
+        them when first account (request.user) was created.
+
+        Note that self.indentity must be already set in this stage by
+        validate_response function. 
         """
-        PublicID.objects.create(user=user, identity=self.get_identity(), provider=self.provider)
+        # create new public ID record in database
+        # and attach it to request.user account.
+        publicid = PublicID()
+        publicid.user = request.user 
+        publicid.identity = self.identity 
+        publicid.provider = self.provider
+        publicid.save()
+
+        # show nice message to user.
         messages.add_message(request, messages.SUCCESS, lang.ACCOUNTS_MERGED)
-        auth.authenticate(identity=self.get_identity(), provider=self.provider)
-        auth.login(request, user)
+        # redirect user.
         raise Redirect(global_settings.LOGIN_REDIRECT_URL)
 
     def login_user(self, request):
-        if settings.PUBLICID_ACTIVATION_REQUIRED and not request.user.is_active:
+        """
+        Try to login user by public identity.
+        Do nothing in case of failure.
+        """
+        # only actavted users can login if activation required.
+        if settings.PUBLICAUTH_ACTIVATION_REQUIRED and not request.user.is_active:
             messages.add_message(request, messages.ERROR, lang.NOT_ACTIVATED)
-            raise Redirect("/")
+            raise Redirect(global_settings.LOGIN_REDIRECT_URL)
 
-        user = auth.authenticate(identity=self.get_identity(), provider=self.provider)
+        # authenticate and redirect user.
+        user = auth.authenticate(identity=self.identity, provider=self.provider)
         if user:
             messages.add_message(request, messages.SUCCESS, lang.SUCCESSFULLY_AUTHENTICATED)
             auth.login(request, user)
-            raise Redirect(global_settings.LOGIN_REDIRECT_URL)
 
-    def set_profile_fields(self, request, extra):
+            try:
+                redirect_url = request.session['next_url']
+                del request.session['next_url']
+            except KeyError:
+                redirect_url = global_settings.LOGIN_REDIRECT_URL
+            raise Redirect(redirect_url)
+
+    def fill_extra_fields(self, request, extra):
         """
         Try to fetch extra data from provider, if this data is enough
-        to validate EXTRA_FORM then call save method of form class and 
-        login user.
+        to validate settings.EXTRA_FORM then call save method of form 
+        class and login the user.
+
+        The extra parameter can be some complex object
+        this is why we use method function 'extra_data' to
+        extract data from this object.
+
+        Also we need to create a dictionary with remapped 
+        keys from profile mapping settings.
         """
         data = {}
-
         for field in self.PROFILE_MAPPING:
             data.update(self.extract_data(extra, field))
 
-        form = str_to_class(self.EXTRA_FORM)(data, fields=self.PROFILE_MAPPING)
+        form = str_to_class(settings.EXTRA_FORM)(data, fields=self.PROFILE_MAPPING)
         if form.is_valid():
-            form.save(self.get_identity(), self.provider)
-            user = auth.authenticate(identity=self.get_identity(), provider=self.provider)
-            if user:
-                auth.login(request, user)
-                try:
-                    redirect_url = request.session['next_url']
-                    del request.session['next_url']
-                except KeyError:
-                    redirect_url = global_settings.LOGIN_REDIRECT_URL
-                raise Redirect(redirect_url)
+            form.save(self.identity, self.provider)
+            self.login_user(request)
         else:
             return data
 
-    def extract_data(self, data, field):
+    def extract_data(self, extra, field):
+        """
+        If extra isnt standart python dictionary
+        you need to implement this method to retrive
+        values from this object.
+        """
         try:
-            return {self.PROFILE_MAPPING[field][0]: data.get(field, '')}
+            return {self.PROFILE_MAPPING[field][0]: extra.get(field, '')}
         except AttributeError:
             return {self.PROFILE_MAPPING[field][0]: ''}
 
-    def validate_response(self, request):
-        pass
 
-
-    def extra(self, request):
-        """
-        Handle registration of new user with extra data for profile
-        """
-        try:
-            identity = request.session['identity']
-        except KeyError:
-            raise Http404
-
-        if request.method == "POST":
-            form = str_to_class(self.EXTRA_FORM)(request.POST)
-            if form.is_valid():
-                user = form.save(identity, self.provider)
-                del request.session['identity']
-                user = auth.authenticate(identity=identity, provider=self.provider)
-                if user:
-                    auth.login(request, user)
-                    next_url = request.session['next_url']
-                    del request.session['next_url']
-                    return redirect(next_url)
-        else:
-            initial = request.session['extra']
-            form = str_to_class(self.EXTRA_FORM)(initial=initial)
-
-        return render_to_response(     
-                                    "publicauth/extra.html", 
-                                    {'form': form}, 
-                                    context_instance=RequestContext(request)
-                                )
-

publicauth/backends/facebook.py

 from __future__ import absolute_import 
 
-from django.utils.translation import ugettext as _
 from django.contrib import messages
-from django.contrib import auth
 
 from annoying.exceptions import Redirect
 
 from publicauth.backends import BaseBackend
-from publicauth import settings
 from publicauth import lang
 
 
 class FacebookBackend(BaseBackend):
     
-    def validate_response(self, request):
+    def validate(self, request, data):
         if not request.facebook.validate_cookie_signature(request.COOKIES):
-            messages.add_message(request, messages.SUCCESS, lang.FACEBOOK_INVALID_RESPONSE)
+            messages.add_message(request, messages.ERROR, lang.FACEBOOK_INVALID_RESPONSE)
             raise Redirect('publicauth-login')
         else:
             uid = request.facebook.api_key
-            self.set_identity(int(request.COOKIES.get('%s_user' % uid)))
+            self.identity = request.COOKIES.get('%s_user' % uid)
+            return request.facebook
 
     def complete(self, request, response):
+        data = self.fill_extra_fields(request, self.get_extra_data(response))
+        request.session['extra'] = data
+        request.session['identity'] = self.identity
+        raise Redirect('publicauth-extra', 'facebook')
+
+    def get_extra_data(self, response):
         extra_fields = [i for i in self.PROFILE_MAPPING]
-        extra = request.facebook.users.getInfo([self.get_identity()], extra_fields)[0]
-        self.set_profile_fields(request, extra)
-
-        request.session['identity'] = self.get_identity()
-
-        raise Redirect('publicauth-social-extra', 'facebook')
-
+        return response.users.getInfo([self.identity], extra_fields)[0]

publicauth/backends/google.py

     def get_extra_data(self, resp):
         return FetchResponse.fromSuccessResponse(resp)
 
-    def extract_data(self, data, field):
+    def extract_data(self, extra, field):
         try:
-            return {self.PROFILE_MAPPING[field][0]: data.getSingle(settings.AX_URIS[field], '')}
+            return {self.PROFILE_MAPPING[field][0]: extra.getSingle(settings.AX_URIS[field], '')}
         except:
-            return {self.PROFILE_MAPPING[field]: ''}
+            return {self.PROFILE_MAPPING[field][0]: ''}
 

publicauth/backends/openid.py

             messages.add_message(request, messages.ERROR, _('Could not find OpenID server'))
             raise Redirect('publicauth-login')
 
-    def validate_response(self, request):
+    def validate(self, request, data):
         """
         Validate response from OpenID server.
         Set identity in case of successfull validation.
         """
         client = consumer.Consumer(request.session, None)
-        data = request.GET.copy()
-        data.update(request.POST)
 
         try:
             resp = client.complete(data, request.session['openid_return_to'])
         except KeyError:
             messages.add_message(request, messages.ERROR, lang.INVALID_RESPONSE_FROM_OPENID)
-            raise Redirect('publicauth-begin')
+            raise Redirect('publicauth-login')
         if resp.status == consumer.CANCEL:
             messages.add_message(request, messages.WARNING, lang.OPENID_CANCELED)
-            raise Redirect('publicauth-begin')
+            raise Redirect('publicauth-login')
         elif resp.status == consumer.FAILURE:
             messages.add_message(request, messages.ERROR, lang.OPENID_FAILED % resp.message)
-            raise Redirect('publicauth-begin')
+            raise Redirect('publicauth-login')
         elif resp.status == consumer.SUCCESS:
-            self.set_identity(resp.identity_url)
+            self.identity = resp.identity_url
             del request.session['openid_return_to']
             return resp
 
-    def complete(self, request):
-        response = self.validate_response(request)
-        extra = self.get_extra_data(response)
-        data = self.set_profile_fields(request, extra)
+    def complete(self, request, response):
+        data = self.fill_extra_fields(request, self.get_extra_data(response))
         request.session['extra'] = data
-        request.session['identity'] = self.get_identity()
+        request.session['identity'] = self.identity
         return redirect('publicauth-extra', self.provider)
 
-    def get_extra_data(self, resp):
-        return SRegResponse.fromSuccessResponse(resp)
+    def get_extra_data(self, response):
+        return SRegResponse.fromSuccessResponse(response)
 

publicauth/backends/twitter.py

 from oauth.oauth import OAuthConsumer, OAuthToken, OAuthRequest, OAuthSignatureMethod_HMAC_SHA1
 
 from django.conf import settings as global_settings
-from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.contrib import auth
 
 from annoying.exceptions import Redirect
 
-from publicauth import settings
 from publicauth.backends import BaseBackend
-from publicauth.models import PublicID
 
 
 class TwitterBackend(BaseBackend):
     AUTHORIZE_URL = property(lambda self: getattr(global_settings, "%s_AUTHORIZE_URL" % self.provider.upper()))
 
     def begin(self, request, data):
+        """
+        Try to get Request Token from OAuth Provider and 
+        redirect user to provider's site for approval.
+        """
         consumer = OAuthConsumer(self.CONSUMER_KEY, self.CONSUMER_SECRET)
         signature_method = OAuthSignatureMethod_HMAC_SHA1()
-        callback = request.build_absolute_uri(reverse('publicauth-social-complete', args=[self.provider]))
+        callback = request.build_absolute_uri(reverse('publicauth-complete', args=[self.provider]))
         oauth_req = OAuthRequest.from_consumer_and_token(consumer, callback=callback, http_url=self.REQUEST_TOKEN_URL)
         oauth_req.sign_request(signature_method, consumer, None)
         response = urllib.urlopen(oauth_req.to_url()).read()
         raise Redirect(oauth_req.to_url())
         
 
-    def complete(self, request, response):
+    def validate(self, request, data):
         signature_method = OAuthSignatureMethod_HMAC_SHA1()
-        data = request.GET.copy()
         consumer = OAuthConsumer(self.CONSUMER_KEY, self.CONSUMER_SECRET)
         oauth_token = data['oauth_token']
         oauth_verifier = data['oauth_verifier']
         oauth_req.set_parameter('oauth_verifier', oauth_verifier)
         oauth_req.sign_request(signature_method, consumer, None)
         response = urllib.urlopen(oauth_req.to_url()).read()
-        self.set_identity(urlparse.parse_qs(response, keep_blank_values=False)['oauth_token'][0])
-        user = auth.authenticate(identity=self.get_identity(), provider=self.provider)
+        self.identity = urlparse.parse_qs(response, keep_blank_values=False)['oauth_token'][0]
+        return response
 
-        extra = self.get_extra_data(response)
-        data = self.set_profile_fields(request, extra)
+    def complete(self, request, response):
+        data = self.fill_extra_fields(request, self.get_extra_data(response))
         request.session['extra'] = data
-        request.session['identity'] = self.get_identity()
-        raise Redirect('publicauth-social-extra', self.provider)
+        request.session['identity'] = self.identity
+        raise Redirect('publicauth-extra', self.provider)
 
     def get_extra_data(self, response):
         return urlparse.parse_qs(response, keep_blank_values=False)
 
-    def extract_data(self, data, field):
+    def extract_data(self, extra, field):
         try:
-            return {self.PROFILE_MAPPING[field][0]: data.get(field, '')[0]}
+            return {self.PROFILE_MAPPING[field][0]: extra.get(field, '')[0]}
         except IndexError:
             return {self.PROFILE_MAPPING[field][0]: ''}
             

publicauth/forms.py

 
     def save(self, identity, provider):
         user = User.objects.create(username=self.cleaned_data['username'])
-        if settings.PUBLICID_ACTIVATION_REQUIRED:
+        if settings.PUBLICAUTH_ACTIVATION_REQUIRED:
             user.is_active = False
         user.save()
         PublicID.objects.create(user=user, identity=identity, provider=provider)

publicauth/settings.py

 
 REGISTRATION_DISABLED_REDIRECT = getattr(settings, "REGISTRATION_DISABLED_REDIRECT", "/")
 
-PUBLICID_ACTIVATION_REQUIRED = getattr(settings, "PUBLICID_ACTIVATION_REQUIRED", False)
+PUBLICAUTH_ACTIVATION_REQUIRED = getattr(settings, "PUBLICAUTH_ACTIVATION_REQUIRED", False)
+
+EXTRA_FORM = getattr(settings, "PUBLICAUTH_EXTRA_FORM", "publicauth.forms.ExtraForm")

publicauth/templates/publicauth/login.html

 {% extends 'base.html' %}
 
 {% block content %}
-<form action="{% url publicauth-begin 'openid' %}?next=request.GET.next" method="post" id="openid_login">
+<form action="{% url publicauth-begin 'openid' %}?next={{ request.GET.next }}" method="post" id="openid_login">
     Openid URL
     <input type="text" name="openid_url" />
     <p><input type="submit" value="Continue"/></p>
 </form>
 
-<a href="{% url publicauth-begin 'google' %}?openid_url=https://www.google.com/accounts/o8/id&next=request.GET.next">Google</a>
-<a href="{% url publicauth-begin 'twitter' %}?next=request.GET.next">Twitter</a>
+<a href="{% url publicauth-begin 'google' %}?openid_url=https://www.google.com/accounts/o8/id&next={{ request.GET.next }}">Google</a>
+<a href="{% url publicauth-begin 'twitter' %}?next={{ request.GET.next }}">Twitter</a>
 
 
 <a href="#" onclick="return fb_login();" id="facebook">Login with facebook</a>
 
     function fb_login() {
         FB.Connect.requireSession(function(test) {
-            window.location = "{% url publicauth-complete 'facebook' %}?next=request.GET.next";
+            window.location = "{% url publicauth-complete 'facebook' %}?next={{ request.GET.next }}";
         });
         return false;
     }

publicauth/views.py

 
 from publicauth.utils import str_to_class, get_backend
 from publicauth import settings
+from publicauth import lang
 
 
 def logout(request):
     auth.logout(request)
-    messages.add_message(request, messages.SUCCESS, publicauth.lang.SUCCESS_LOGOUT)
-    return redirect("/")
+    messages.add_message(request, messages.SUCCESS, lang.SUCCESS_LOGOUT)
+    return redirect(global_settings.LOGOUT_URL)
 
 
 def begin(request, provider):
     data = request.GET.copy()
     data.update(request.POST)
 
-    request.session['next_url'] = request.GET.get("next") or global_settings.LOGIN_REDIRECT_URL
+    # store url to where user will be redirected 
+    # after successfull authentication.
+    request.session['next_url'] = request.GET.get("next") or \
+                                    global_settings.LOGIN_REDIRECT_URL
         
     # start the authentication process 
     backend = get_backend(provider)
-    backend.begin(request, data)
-
-    return {}
+    return backend.begin(request, data)
 
 
 def complete(request, provider):
     """
-    Complete PublicID authorization process.
-    If PublicID was successfuly authenticated:
-     * if no user with such ID exists and current user is authenticated then
-       assign PublicID to this user.
-     * if user with such ID exists and current user is anonimouse then login 
-       as this user.
-     * if no user with such ID exists and user must fill extra fields, 
-       redirect him to registration form.
+    After first step of public authentication, we must validate the response. 
+    If everything is ok, we must do the following:
+    1. If user is already authenticated:
+        a. Try to login him again (strange variation but we must take it to account).
+        b. Create new PublicID record in database.
+        c. Merge authenticated account with newly created PublicID record.
+        d. Redirect user to 'next' url stored in session.
+    2. If user is anonymouse:
+        a. Try to log him by identity and redirect to 'next' url.
+        b. Create new  PublicID record in database.
+        c. Try to automaticaly fill all extra fields with information returned form 
+           server. If successfull, login the user and redirect to 'next' url.
+        d. Redirect user to extra page where he can fill all extra fields by hand.
     """
+    # merge data from POST and GET methods
+    data = request.GET.copy()
+    data.update(request.POST)
+
     backend = get_backend(provider)
-    response = backend.validate_response(request)
+    response = backend.validate(request, data)
+
     if request.user.is_authenticated():
         backend.login_user(request)
-        backend.attach_account(request, request.user)
+        backend.merge_accounts(request)
     else:
         backend.login_user(request)
         if not settings.REGISTRATION_ALLOWED:
             messages.add_message(request, messages.WARNING, lang.REGISTRATION_DISABLED)
             return redirect(settings.REGISTRATION_DISABLED_REDIRECT)
-    backend.complete(request, response)
+
+    return backend.complete(request, response)
 
 
 @render_to('publicauth/extra.html')
         raise Http404
 
     if request.method == "POST":
-        form = str_to_class(settings.PUBLICID_EXTRA_FORM)(request.POST)
+        form = str_to_class(settings.EXTRA_FORM)(request.POST)
         if form.is_valid():
             user = form.save(identity, provider)
             del request.session['identity']
                 return redirect(next_url)
     else:
         initial = request.session['extra']
-        form = str_to_class(settings.PUBLICID_EXTRA_FORM)(initial=initial)
+        form = str_to_class(settings.EXTRA_FORM)(initial=initial)
 
     return {'form': form}