workflow will be cut and the value returned immediately, this is useful to
return ``HttpReponse`` instances like ``HttpResponseRedirect``.
+----------------
+Partial Pipeline
+----------------
+
+It's possible to cut the pipeline process to return to the user asking for more
+data and resume the process later, to accomplish this add the entry
+``social_auth.backends.pipeline.misc.save_status_to_session`` (or a similar
+implementation) to the pipeline setting before any entry that returns an
+``HttpResponse`` instance::
+
+ SOCIAL_AUTH_PIPELINE = (
+ ...
+ social_auth.backends.pipeline.misc.save_status_to_session,
+ app.pipeline.redirect_to_basic_user_data_form
+ ...
+ )
+
+When it's time to resume the process just redirect the user to
+``/complete/<backend>/`` view. By default the pipeline will be resumed in the
+next entry after ``save_status_to_session`` but this can be modified by setting
+the following setting to the import path of the pipeline entry to resume
+processing::
+
+ SOCIAL_AUTH_PIPELINE_RESUME_ENTRY = <a zero based index>
+
+``save_status_to_session`` saves needed data into user session, the key can be
+defined by ``SOCIAL_AUTH_PARTIAL_PIPELINE_KEY`` which default value is
+``partial_pipeline``::
+
+ SOCIAL_AUTH_PARTIAL_PIPELINE_KEY = 'partial_pipeline'
+
+Check the `example application`_ to check a basic usage.
+
+
---------------
Deprecated bits
---------------
.. _Flickr OAuth: http://www.flickr.com/services/api/
.. _Flickr App Garden: http://www.flickr.com/services/apps/create/
.. _danielgtaylor: https://github.com/danielgtaylor
+.. _example application: https://github.com/omab/django-social-auth/blob/master/example/local_settings.py.template#L21
return ``HttpReponse`` instances like ``HttpResponseRedirect``.
+Partial Pipeline
+----------------
+
+It's possible to cut the pipeline process to return to the user asking for more
+data and resume the process later, to accomplish this add the entry
+``social_auth.backends.pipeline.misc.save_status_to_session`` (or a similar
+implementation) to the pipeline setting before any entry that returns an
+``HttpResponse`` instance::
+
+ SOCIAL_AUTH_PIPELINE = (
+ ...
+ social_auth.backends.pipeline.misc.save_status_to_session,
+ app.pipeline.redirect_to_basic_user_data_form
+ ...
+ )
+
+When it's time to resume the process just redirect the user to
+``/complete/<backend>/`` view. By default the pipeline will be resumed in the
+next entry after ``save_status_to_session`` but this can be modified by setting
+the following setting to the import path of the pipeline entry to resume
+processing::
+
+ SOCIAL_AUTH_PIPELINE_RESUME_ENTRY = <a zero based index>
+
+``save_status_to_session`` saves needed data into user session, the key can be
+defined by ``SOCIAL_AUTH_PARTIAL_PIPELINE_KEY`` which default value is
+``partial_pipeline``::
+
+ SOCIAL_AUTH_PARTIAL_PIPELINE_KEY = 'partial_pipeline'
+
+Check the `example application`_ to check a basic usage.
+
+
.. _django-social-auth: https://github.com/omab/django-social-auth
+.. _example application: https://github.com/omab/django-social-auth/blob/master/example/local_settings.py.template#L21
--- /dev/null
+from django.http import HttpResponseRedirect
+
+
+def username(request, *args, **kwargs):
+ if kwargs.get('user'):
+ username = kwargs['user'].username
+ else:
+ username = request.session.get('saved_username')
+ return { 'username': username }
+
+
+def redirect_to_form(*args, **kwargs):
+ if not kwargs['request'].session.get('saved_username') and kwargs.get('user') is None:
+ return HttpResponseRedirect('/form/')
from django.contrib.auth import logout as auth_logout
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
-from django.shortcuts import render_to_response
+from django.shortcuts import render_to_response, redirect
from django.contrib.messages.api import get_messages
from social_auth import __version__ as version
+from social_auth.utils import setting
def home(request):
@login_required
def done(request):
"""Login complete view, displays user data"""
- ctx = {'version': version,
- 'last_login': request.session.get('social_auth_last_login_backend')}
+ ctx = {
+ 'version': version,
+ 'last_login': request.session.get('social_auth_last_login_backend')
+ }
return render_to_response('done.html', ctx, RequestContext(request))
def error(request):
"""Logs out user"""
auth_logout(request)
return HttpResponseRedirect('/')
+
+
+def form(request):
+ if request.method == 'POST' and request.POST.get('username'):
+ name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline')
+ request.session['saved_username'] = request.POST['username']
+ backend = request.session[name]['backend']
+ return redirect('socialauth_complete', backend=backend)
+ return render_to_response('form.html', {}, RequestContext(request))
GITHUB_API_SECRET = ''
FOURSQUARE_CONSUMER_KEY = ''
FOURSQUARE_CONSUMER_SECRET = ''
+
+SOCIAL_AUTH_PIPELINE = (
+ 'social_auth.backends.pipeline.social.social_auth_user',
+ 'social_auth.backends.pipeline.associate.associate_by_email',
+ 'social_auth.backends.pipeline.misc.save_status_to_session',
+ 'app.pipeline.redirect_to_form',
+ 'app.pipeline.username',
+ 'social_auth.backends.pipeline.user.create_user',
+ 'social_auth.backends.pipeline.social.associate_user',
+ 'social_auth.backends.pipeline.social.load_extra_data',
+ 'social_auth.backends.pipeline.user.update_user_details',
+)
--- /dev/null
+{% extends "base.html" %}
+
+{% block heading %}User basic form{% endblock %}
+
+{% block content %}
+<form action="" method="post">
+ {% csrf_token %}
+ <p>
+ <label for="id_username">Username</label>
+ <input type="text" name="username" id="id_username" value="" />
+ </p>
+
+ <input type="submit" value="Send" />
+</form>
+{% endblock %}
from django.conf.urls.defaults import patterns, url, include
from django.contrib import admin
-from app.views import home, done, logout, error
+from app.views import home, done, logout, error, form
admin.autodiscover()
url(r'^done/$', done, name='done'),
url(r'^error/$', error, name='error'),
url(r'^logout/$', logout, name='logout'),
+ url(r'^form/$', form, name='form'),
url(r'^admin/', include(admin.site.urls)),
url(r'', include('social_auth.urls')),
)
return None
response = kwargs.get('response')
- details = self.get_user_details(response)
- uid = self.get_user_id(details, response)
- out = self.pipeline(PIPELINE, backend=self, uid=uid,
- details=details, is_new=False,
+
+ if 'pipeline_index' in kwargs:
+ details = kwargs.pop('details')
+ uid = kwargs.pop('uid')
+ is_new = kwargs.pop('is_new')
+ pipeline = PIPELINE[kwargs['pipeline_index']:]
+ else:
+ details = self.get_user_details(response)
+ uid = self.get_user_id(details, response)
+ is_new = False
+ pipeline = PIPELINE
+
+ out = self.pipeline(pipeline, backend=self, uid=uid,
+ details=details, is_new=is_new,
*args, **kwargs)
+
if not isinstance(out, dict):
return out
"""Completes loging process, must return user instance"""
raise NotImplementedError('Implement in subclass')
+ def continue_pipeline(self, *args, **kwargs):
+ """Continue previos halted pipeline"""
+ kwargs.update({ self.AUTH_BACKEND.name: True })
+ return authenticate(*args, **kwargs)
+
def auth_extra_arguments(self):
"""Return extra argumens needed on auth process, setting is per bancked
and defined by <backend name in uppercase>_AUTH_EXTRA_ARGUMENTS.
http://axschema.org/contact/email"""
return details['email']
+
# Auth classes
class GoogleAuth(OpenIdAuth):
"""Google OpenID authentication"""
from django.conf import settings
from social_auth.models import User
+from social_auth.backends import get_backend, PIPELINE
USERNAME = 'username'
--- /dev/null
+from social_auth.backends import PIPELINE
+from social_auth.utils import setting
+
+
+PIPELINE_ENTRY = 'social_auth.backends.pipeline.misc.save_status_to_session'
+
+
+def save_status_to_session(request, backend, details, response, uid,
+ *args, **kwargs):
+ """Saves current social-auth status to session."""
+ next_entry = setting('SOCIAL_AUTH_PIPELINE_RESUME_ENTRY')
+
+ try:
+ if next_entry:
+ idx = PIPELINE.index(next_entry)
+ else:
+ idx = PIPELINE.index(PIPELINE_ENTRY) + 1
+ except ValueError:
+ idx = None
+
+ name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline')
+ request.session[name] = {
+ 'backend': backend.name,
+ 'uid': uid,
+ 'details': details,
+ 'response': response,
+ 'is_new': kwargs.get('is_new', True),
+ 'next_index': idx
+ }
+ request.session.modified = True
urlpatterns = patterns('',
- url(r'^login/(?P<backend>[^/]+)/$', auth, name='socialauth_begin'),
- url(r'^complete/(?P<backend>[^/]+)/$', complete, name='socialauth_complete'),
- url(r'^associate/(?P<backend>[^/]+)/$', associate, name='socialauth_associate_begin'),
+ # authentication
+ url(r'^login/(?P<backend>[^/]+)/$', auth,
+ name='socialauth_begin'),
+ url(r'^complete/(?P<backend>[^/]+)/$', complete,
+ name='socialauth_complete'),
+
+ # association
+ url(r'^associate/(?P<backend>[^/]+)/$', associate,
+ name='socialauth_associate_begin'),
url(r'^associate/complete/(?P<backend>[^/]+)/$', associate_complete,
name='socialauth_associate_complete'),
- url(r'^disconnect/(?P<backend>[^/]+)/$', disconnect, name='socialauth_disconnect'),
- url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>[^/]+)/$', disconnect,
- name='socialauth_disconnect_individual'),
+
+ # disconnection
+ url(r'^disconnect/(?P<backend>[^/]+)/$', disconnect,
+ name='socialauth_disconnect'),
+ url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>[^/]+)/$',
+ disconnect, name='socialauth_disconnect_individual'),
)
try:
return func(request, backend, *args, **kwargs)
except Exception, e: # some error ocurred
+ if setting('DEBUG'):
+ raise
backend_name = backend.AUTH_BACKEND.name
logger.error(unicode(e), exc_info=True,
"""Complete auth process. Return authenticated user or None."""
if user and not user.is_authenticated():
user = None
- return backend.auth_complete(user=user, request=request, *args, **kwargs)
+
+ name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline')
+ if request.session.get(name):
+ data = request.session.pop(name)
+ request.session.modified = True
+ return backend.continue_pipeline(pipeline_index=data['next_index'],
+ user=user,
+ request=request,
+ uid=data['uid'],
+ details=data['details'],
+ is_new=data['is_new'],
+ response=data['response'],
+ *args, **kwargs)
+ else:
+ return backend.auth_complete(user=user, request=request, *args, **kwargs)