Skip to content

Commit

Permalink
Disconnect pipeline, move details/uid extraction to pipeline methods …
Browse files Browse the repository at this point in the history
…too. Refactor pipeline run code
  • Loading branch information
omab committed Sep 8, 2013
1 parent 31861f7 commit ef7b22d
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 52 deletions.
41 changes: 27 additions & 14 deletions social/actions.py
@@ -1,6 +1,6 @@
from social.p3 import quote
from social.utils import sanitize_redirect, user_is_authenticated, \
user_is_active
user_is_active, partial_pipeline_data


def do_auth(strategy, redirect_name='next'):
Expand Down Expand Up @@ -39,16 +39,12 @@ def do_complete(strategy, login, user=None, redirect_name='next',
login_error_url = strategy.setting('LOGIN_ERROR_URL') or \
strategy.setting('LOGIN_URL')

partial = strategy.session_get('partial_pipeline', None)
partial = partial_pipeline_data(strategy, user, *args, **kwargs)
if partial is not None:
idx, backend, xargs, xkwargs = strategy.from_session(partial)
idx, backend, xargs, xkwargs = partial
if backend == strategy.backend_name:
kwargs = kwargs.copy()
kwargs.setdefault('user', user)
kwargs.setdefault('request', strategy.request)
kwargs.update(xkwargs)
user = strategy.continue_pipeline(pipeline_index=idx,
*xargs, **kwargs)
*xargs, **xkwargs)
else:
strategy.clean_partial_pipeline()
user = strategy.complete(user=user, request=strategy.request,
Expand Down Expand Up @@ -96,9 +92,26 @@ def do_complete(strategy, login, user=None, redirect_name='next',
return strategy.redirect(url)


def do_disconnect(strategy, user, association_id=None, redirect_name='next'):
strategy.disconnect(user=user, association_id=association_id)
data = strategy.request_data()
return strategy.redirect(data.get(redirect_name, '') or
strategy.setting('DISCONNECT_REDIRECT_URL') or
strategy.setting('LOGIN_REDIRECT_URL'))
def do_disconnect(strategy, user, association_id=None, redirect_name='next',
*args, **kwargs):
partial = partial_pipeline_data(strategy, user, *args, **kwargs)
if partial is not None:
idx, backend, xargs, xkwargs = partial
if backend == strategy.backend_name:
out = strategy.disconnect(pipeline_index=idx, user=user,
association_id=association_id,
*args, **kwargs)
else:
strategy.clean_partial_pipeline()
out = strategy.disconnect(user=user, association_id=association_id,
*args, **kwargs)
else:
out = strategy.disconnect(user=user, association_id=association_id,
*args, **kwargs)
if not isinstance(out, dict):
return out
else:
data = strategy.request_data()
return strategy.redirect(data.get(redirect_name, '') or
strategy.setting('DISCONNECT_REDIRECT_URL') or
strategy.setting('LOGIN_REDIRECT_URL'))
41 changes: 23 additions & 18 deletions social/backends/base.py
Expand Up @@ -12,7 +12,6 @@
from requests import request

from social.utils import module_member, parse_qs
from social.exceptions import AuthForbidden


class BaseAuth(object):
Expand Down Expand Up @@ -67,23 +66,33 @@ def authenticate(self, *args, **kwargs):
self.redirect_uri = self.redirect_uri or kwargs.get('redirect_uri')
self.data = self.strategy.request_data()
pipeline = self.strategy.get_pipeline()

kwargs.setdefault('is_new', False)
if 'pipeline_index' in kwargs:
return self.pipeline(pipeline[kwargs['pipeline_index']:],
*args, **kwargs)
else:
details = self.get_user_details(kwargs['response'])
uid = self.get_user_id(details, kwargs['response'])
if not self.auth_allowed(kwargs['response'], details):
raise AuthForbidden(self)
return self.pipeline(pipeline, details=details, uid=uid,
is_new=False, *args, **kwargs)
pipeline = pipeline[kwargs['pipeline_index']:]
return self.pipeline(pipeline, *args, **kwargs)

def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs):
kwargs['strategy'] = self.strategy
out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs)
if not isinstance(out, dict):
return out
user = out.get('user')
if user:
user.social_user = out.get('social')
user.is_new = out.get('is_new')
return user

def disconnect(self, *args, **kwargs):
pipeline = self.strategy.get_disconnect_pipeline()
if 'pipeline_index' in kwargs:
pipeline = pipeline[kwargs['pipeline_index']:]
kwargs['name'] = self.strategy.backend.name
kwargs['user_storage'] = self.strategy.storage.user
return self.run_pipeline(pipeline, *args, **kwargs)

def run_pipeline(self, pipeline, pipeline_index=0, *args, **kwargs):
out = kwargs.copy()
out.pop(self.name, None)
out.setdefault('strategy', self.strategy)
out.setdefault('backend', out.pop(self.name, None) or self)

for idx, name in enumerate(pipeline):
out['pipeline_index'] = pipeline_index + idx
Expand All @@ -92,12 +101,8 @@ def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs):
if not isinstance(result, dict):
return result
out.update(result)
user = out['user']
if user:
user.social_user = out['social']
user.is_new = out['is_new']
self.strategy.clean_partial_pipeline()
return user
return out

def extra_data(self, user, uid, response, details):
"""Return default blank user extra data"""
Expand Down
31 changes: 31 additions & 0 deletions social/pipeline/disconnect.py
@@ -0,0 +1,31 @@
from social.exceptions import NotAllowedToDisconnect


def allowed_to_disconnect(strategy, user, name, user_storage,
association_id=None, *args, **kwargs):
if not user_storage.allowed_to_disconnect(user, name, association_id):
raise NotAllowedToDisconnect()


def get_entries(strategy, user, name, user_storage, association_id=None,
*args, **kwargs):
return {
'entries': user_storage.get_social_auth_for_user(
user, name, association_id
)
}


def revoke_tokens(strategy, entries, *args, **kwargs):
revoke_tokens = strategy.setting('REVOKE_TOKENS_ON_DISCONNECT', False)
if revoke_tokens:
for entry in entries:
if 'access_token' in entry.extra_data:
backend = entry.get_backend(strategy)(strategy)
backend.revoke_token(entry.extra_data['access_token'],
entry.uid)


def disconnect(strategy, entries, user_storage, *args, **kwargs):
for entry in entries:
user_storage.disconnect(entry)
16 changes: 15 additions & 1 deletion social/pipeline/social_auth.py
@@ -1,4 +1,18 @@
from social.exceptions import AuthAlreadyAssociated, AuthException
from social.exceptions import AuthAlreadyAssociated, AuthException, \
AuthForbidden


def social_details(strategy, response, *args, **kwargs):
return {'details': strategy.backend.get_user_details(response)}


def social_uid(strategy, details, response, *args, **kwargs):
return {'uid': strategy.backend.get_user_id(details, response)}


def auth_allowed(strategy, details, response, *args, **kwargs):
if not strategy.backend.auth_allowed(response, details):
raise AuthForbidden(strategy.backend)


def social_user(strategy, uid, user=None, *args, **kwargs):
Expand Down
37 changes: 18 additions & 19 deletions social/strategies/base.py
Expand Up @@ -74,21 +74,11 @@ def complete(self, *args, **kwargs):
def continue_pipeline(self, *args, **kwargs):
return self.backend.continue_pipeline(*args, **kwargs)

def disconnect(self, user, association_id=None):
name = self.backend.name
user_storage = self.storage.user
revoke_token = self.setting('REVOKE_TOKENS_ON_DISCONNECT', False)
if user_storage.allowed_to_disconnect(user, name, association_id):
entries = user_storage.get_social_auth_for_user(user, name,
association_id)
for entry in entries:
if revoke_token:
backend = entry.get_backend(self)(self)
backend.revoke_token(entry.extra_data['access_token'],
entry.uid)
user_storage.disconnect(entry)
else:
raise NotAllowedToDisconnect()
def disconnect(self, user, association_id=None, *args, **kwargs):
return self.backend.disconnect(
user=user, association_id=association_id,
*args, **kwargs
)

def authenticate(self, *args, **kwargs):
kwargs['strategy'] = self
Expand All @@ -115,8 +105,7 @@ def to_session_value(self, val):
def from_session_value(self, val):
return val

def to_session(self, next, backend, storage, request=None,
*args, **kwargs):
def to_session(self, next, backend, request=None, *args, **kwargs):
return {
'next': next,
'backend': backend.name,
Expand All @@ -134,14 +123,17 @@ def from_session(self, session):
for key, val in session['kwargs'].items())
)

def clean_partial_pipeline(self):
self.session_pop('partial_pipeline')
def clean_partial_pipeline(self, name='partial_pipeline'):
self.session_pop(name)

def openid_store(self):
return OpenIdStore(self)

def get_pipeline(self):
return self.setting('PIPELINE', (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
# 'social.pipeline.social_auth.associate_by_email',
Expand All @@ -150,6 +142,13 @@ def get_pipeline(self):
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details'))

def get_disconnect_pipeline(self):
return self.setting('DISCONNECT_PIPELINE', (
'social.pipeline.disconnect.allowed_to_disconnect',
'social.pipeline.disconnect.get_entries',
'social.pipeline.disconnect.revoke_tokens',
'social.pipeline.disconnect.disconnect'))

def random_string(self, length=12, chars=ALLOWED_CHARS):
# Implementation borrowed from django 1.4
try:
Expand Down
11 changes: 11 additions & 0 deletions social/utils.py
Expand Up @@ -119,3 +119,14 @@ def drop_lists(value):
val = six.text_type(val, 'utf-8')
out[key] = val
return out


def partial_pipeline_data(strategy, user, *args, **kwargs):
partial = strategy.session_get('partial_pipeline', None)
if partial is not None:
idx, backend, xargs, xkwargs = strategy.from_session(partial)
kwargs = kwargs.copy()
kwargs.setdefault('user', user)
kwargs.setdefault('request', strategy.request)
kwargs.update(xkwargs)
return idx, backend, xargs, xkwargs
3 changes: 3 additions & 0 deletions tests/actions/actions_tests.py
Expand Up @@ -139,6 +139,9 @@ def do_login_with_partial_pipeline(self, before_complete=None):
'social.backends.github.GithubOAuth2',
),
'SOCIAL_AUTH_PIPELINE': (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.partial.save_status_to_session',
'tests.pipeline.ask_for_password',
'social.pipeline.social_auth.social_user',
Expand Down
36 changes: 36 additions & 0 deletions tests/actions/disconnect_test.py
@@ -1,7 +1,11 @@
import requests

from sure import expect
from httpretty import HTTPretty

from social.actions import do_disconnect
from social.exceptions import NotAllowedToDisconnect
from social.utils import parse_qs

from tests.models import User
from tests.actions.actions_tests import BaseActionTest
Expand All @@ -21,3 +25,35 @@ def test_disconnect(self):
user.password = 'password'
do_disconnect(self.strategy, user)
expect(len(user.social)).to.equal(0)

def test_disconnect_with_partial_pipeline(self):
self.strategy.set_settings({
'SOCIAL_AUTH_DISCONNECT_PIPELINE': (
'social.pipeline.partial.save_status_to_session',
'tests.pipeline.ask_for_password',
'tests.pipeline.set_password',
'social.pipeline.disconnect.allowed_to_disconnect',
'social.pipeline.disconnect.get_entries',
'social.pipeline.disconnect.revoke_tokens',
'social.pipeline.disconnect.disconnect'
)
})
self.do_login()
user = User.get(self.expected_username)
redirect = do_disconnect(self.strategy, user)

url = self.strategy.build_absolute_uri('/password')
expect(redirect.url).to.equal(url)
HTTPretty.register_uri(HTTPretty.GET, redirect.url, status=200,
body='foobar')
HTTPretty.register_uri(HTTPretty.POST, redirect.url, status=200)

password = 'foobar'
requests.get(url)
requests.post(url, data={'password': password})
data = parse_qs(HTTPretty.last_request.body)
expect(data['password']).to.equal(password)
self.strategy.session_set('password', data['password'])

redirect = do_disconnect(self.strategy, user)
expect(len(user.social)).to.equal(0)
3 changes: 3 additions & 0 deletions tests/actions/login_test.py
Expand Up @@ -52,6 +52,9 @@ def test_invalid_user(self):
self.strategy.set_settings({
'SOCIAL_AUTH_LOGIN_ERROR_URL': '/error',
'SOCIAL_AUTH_PIPELINE': (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.user.create_user',
Expand Down
3 changes: 3 additions & 0 deletions tests/base.py
Expand Up @@ -36,6 +36,9 @@ def do_login(self):
def pipeline_settings(self):
self.strategy.set_settings({
'SOCIAL_AUTH_PIPELINE': (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.partial.save_status_to_session',
'tests.pipeline.ask_for_password',
'tests.pipeline.ask_for_slug',
Expand Down
3 changes: 3 additions & 0 deletions tests/strategy.py
Expand Up @@ -85,6 +85,9 @@ def authenticate(self, *args, **kwargs):

def get_pipeline(self):
return self.setting('PIPELINE', (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.social_auth.associate_by_email',
Expand Down

0 comments on commit ef7b22d

Please sign in to comment.