From 905db51cf3705b2b0fdbff321571c14ebd48254a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mat=C3=ADas=20Aguirre?= Date: Wed, 18 Apr 2012 13:29:12 -0300 Subject: [PATCH] Deprecate /associate/ URLs in favor of /login/ and /complete/ with user.is_authenticated() check. Closes #319 --- README.rst | 13 +++++++ doc/deprecated.rst | 12 ++++++ example/templates/done.html | 10 ++--- social_auth/decorators.py | 58 +++++++++++++++++++++++++++++ social_auth/urls.py | 11 +++--- social_auth/views.py | 73 ++++--------------------------------- 6 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 social_auth/decorators.py diff --git a/README.rst b/README.rst index 5fe7f11..e515169 100644 --- a/README.rst +++ b/README.rst @@ -494,6 +494,7 @@ The following settings are deprecated in favor of pipeline functions. SOCIAL_AUTH_DEFAULT_USERNAME SOCIAL_AUTH_UUID_LENGTH SOCIAL_AUTH_USERNAME_FIXER + SOCIAL_AUTH_ASSOCIATE_URL_NAME - User creation setting should be avoided and remove the entry ``create_user`` from pipeline instead:: @@ -515,6 +516,18 @@ The following settings are deprecated in favor of pipeline functions. SOCIAL_AUTH_ASSOCIATE_BY_MAIL +- Associate URLs are deprecated since the login ones can handle the case, this + avoids issues where providers check the redirect URI and redirects to the + configured value in the application. So, from now on a single entry point is + recommended being:: + + //login// + + And to complete the process:: + + //complete// + + Usage example ------------- diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 60d3ff6..9efafca 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -10,6 +10,7 @@ The following settings are deprecated in favor of pipeline functions. SOCIAL_AUTH_DEFAULT_USERNAME SOCIAL_AUTH_UUID_LENGTH SOCIAL_AUTH_USERNAME_FIXER + SOCIAL_AUTH_ASSOCIATE_URL_NAME - User creation setting should be avoided and remove the entry ``create_user`` from pipeline instead:: @@ -30,3 +31,14 @@ The following settings are deprecated in favor of pipeline functions. ``associate_by_email`` pipeline entry instead of using this setting:: SOCIAL_AUTH_ASSOCIATE_BY_MAIL + +- Associate URLs are deprecated since the login ones can handle the case, this + avoids issues where providers check the redirect URI and redirects to the + configured value in the application. So, from now on a single entry point is + recommended being:: + + //login// + + And to complete the process:: + + //complete// diff --git a/example/templates/done.html b/example/templates/done.html index 2ef4e0d..c21d950 100644 --- a/example/templates/done.html +++ b/example/templates/done.html @@ -37,14 +37,14 @@

Associate new OAuth credentials:

Associate new OAuth2 credentials:

@@ -53,10 +53,10 @@ {% for name in social_auth.backends.openid %}
  • {% if name != "livejournal" and name != "openid" %} - {{ name|title }} + {{ name|title }} {% else %} {% if name == "livejournal" %} -
    {% csrf_token %} + {% csrf_token %}
    @@ -65,7 +65,7 @@ {% else %} {% if name == "openid" %} -
    {% csrf_token %} + {% csrf_token %}
    diff --git a/social_auth/decorators.py b/social_auth/decorators.py new file mode 100644 index 0000000..cf10eb8 --- /dev/null +++ b/social_auth/decorators.py @@ -0,0 +1,58 @@ +from functools import wraps + +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect, HttpResponseServerError +from django.utils.importlib import import_module + +from social_auth.backends import get_backend +from social_auth.utils import setting, log, backend_setting + + +LOGIN_ERROR_URL = setting('LOGIN_ERROR_URL', setting('LOGIN_URL')) +RAISE_EXCEPTIONS = setting('SOCIAL_AUTH_RAISE_EXCEPTIONS', setting('DEBUG')) +PROCESS_EXCEPTIONS = setting('SOCIAL_AUTH_PROCESS_EXCEPTIONS', + 'social_auth.utils.log_exceptions_to_messages') + + +def dsa_view(redirect_name=None): + """Decorate djangos-social-auth views. Will check and retrieve backend + or return HttpResponseServerError if backend is not found. + + redirect_name parameter is used to build redirect URL used by backend. + """ + def dec(func): + @wraps(func) + def wrapper(request, backend, *args, **kwargs): + if redirect_name: + redirect = reverse(redirect_name, args=(backend,)) + else: + redirect = request.path + backend = get_backend(backend, request, redirect) + + if not backend: + return HttpResponseServerError('Incorrect authentication ' + \ + 'service') + + try: + return func(request, backend, *args, **kwargs) + except Exception, e: # some error ocurred + if RAISE_EXCEPTIONS: + raise + log('error', unicode(e), exc_info=True, extra={ + 'request': request + }) + + mod, func_name = PROCESS_EXCEPTIONS.rsplit('.', 1) + try: + process = getattr(import_module(mod), func_name, + lambda *args: None) + except ImportError: + pass + else: + process(request, backend, e) + + url = backend_setting(backend, 'SOCIAL_AUTH_BACKEND_ERROR_URL', + LOGIN_ERROR_URL) + return HttpResponseRedirect(url) + return wrapper + return dec diff --git a/social_auth/urls.py b/social_auth/urls.py index 3d41d3c..512aae5 100644 --- a/social_auth/urls.py +++ b/social_auth/urls.py @@ -1,8 +1,7 @@ """URLs module""" from django.conf.urls.defaults import patterns, url -from social_auth.views import auth, complete, associate, associate_complete, \ - disconnect +from social_auth.views import auth, complete, disconnect urlpatterns = patterns('', @@ -12,10 +11,12 @@ urlpatterns = patterns('', url(r'^complete/(?P[^/]+)/$', complete, name='socialauth_complete'), - # association - url(r'^associate/(?P[^/]+)/$', associate, + # XXX: Deprecated, this URLs are deprecated, instead use the login and + # complete ones directly, they will differentiate the user intention + # by checking it's authenticated status association. + url(r'^associate/(?P[^/]+)/$', auth, name='socialauth_associate_begin'), - url(r'^associate/complete/(?P[^/]+)/$', associate_complete, + url(r'^associate/complete/(?P[^/]+)/$', complete, name='socialauth_associate_complete'), # disconnection diff --git a/social_auth/views.py b/social_auth/views.py index 45808a8..13bd330 100644 --- a/social_auth/views.py +++ b/social_auth/views.py @@ -5,72 +5,20 @@ Notes: on third party providers that (if using POST) won't be sending csrf token back. """ -from functools import wraps - -from django.http import HttpResponseRedirect, HttpResponse, \ - HttpResponseServerError -from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect, HttpResponse from django.contrib.auth import login, REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required from django.contrib import messages -from django.utils.importlib import import_module from django.views.decorators.csrf import csrf_exempt -from social_auth.backends import get_backend -from social_auth.utils import sanitize_redirect, setting, log, \ +from social_auth.utils import sanitize_redirect, setting, \ backend_setting, clean_partial_pipeline +from social_auth.decorators import dsa_view DEFAULT_REDIRECT = setting('SOCIAL_AUTH_LOGIN_REDIRECT_URL') or \ setting('LOGIN_REDIRECT_URL') LOGIN_ERROR_URL = setting('LOGIN_ERROR_URL', setting('LOGIN_URL')) -RAISE_EXCEPTIONS = setting('SOCIAL_AUTH_RAISE_EXCEPTIONS', setting('DEBUG')) -PROCESS_EXCEPTIONS = setting('SOCIAL_AUTH_PROCESS_EXCEPTIONS', - 'social_auth.utils.log_exceptions_to_messages') - - -def dsa_view(redirect_name=None): - """Decorate djangos-social-auth views. Will check and retrieve backend - or return HttpResponseServerError if backend is not found. - - redirect_name parameter is used to build redirect URL used by backend. - """ - def dec(func): - @wraps(func) - def wrapper(request, backend, *args, **kwargs): - if redirect_name: - redirect = reverse(redirect_name, args=(backend,)) - else: - redirect = request.path - backend = get_backend(backend, request, redirect) - - if not backend: - return HttpResponseServerError('Incorrect authentication ' + \ - 'service') - - try: - return func(request, backend, *args, **kwargs) - except Exception, e: # some error ocurred - if RAISE_EXCEPTIONS: - raise - log('error', unicode(e), exc_info=True, extra={ - 'request': request - }) - - mod, func_name = PROCESS_EXCEPTIONS.rsplit('.', 1) - try: - process = getattr(import_module(mod), func_name, - lambda *args: None) - except ImportError: - pass - else: - process(request, backend, e) - - url = backend_setting(backend, 'SOCIAL_AUTH_BACKEND_ERROR_URL', - LOGIN_ERROR_URL) - return HttpResponseRedirect(url) - return wrapper - return dec @dsa_view(setting('SOCIAL_AUTH_COMPLETE_URL_NAME', 'socialauth_complete')) @@ -84,20 +32,13 @@ def auth(request, backend): def complete(request, backend, *args, **kwargs): """Authentication complete view, override this view if transaction management doesn't suit your needs.""" - return complete_process(request, backend, *args, **kwargs) - - -@login_required -@dsa_view(setting('SOCIAL_AUTH_ASSOCIATE_URL_NAME', - 'socialauth_associate_complete')) -def associate(request, backend): - """Authentication starting process""" - return auth_process(request, backend) + if request.user.is_authenticated(): + return associate_complete(request, backend, *args, **kwargs) + else: + return complete_process(request, backend, *args, **kwargs) -@csrf_exempt @login_required -@dsa_view() def associate_complete(request, backend, *args, **kwargs): """Authentication complete process""" # pop redirect value before the session is trashed on login() -- 2.39.5