Skip to content

Commit

Permalink
Improve http error handling on auth_complete/do_auth. Fixes #304
Browse files Browse the repository at this point in the history
  • Loading branch information
omab committed Apr 4, 2015
1 parent 8aab809 commit 7dd4273
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 96 deletions.
25 changes: 8 additions & 17 deletions social/backends/beats.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
"""
import base64

from requests import HTTPError

from social.exceptions import AuthCanceled, AuthUnknownError
from social.utils import handle_http_errors
from social.backends.oauth import BaseOAuth2


Expand All @@ -30,23 +28,16 @@ def auth_headers(self):
))
}

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
self.process_error(self.data)
try:
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
data=self.auth_complete_params(self.validate_state()),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
except KeyError:
raise AuthUnknownError(self)
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
data=self.auth_complete_params(self.validate_state()),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
self.process_error(response)
# mashery wraps in jsonrpc
if response.get('jsonrpc', None):
Expand Down
3 changes: 2 additions & 1 deletion social/backends/facebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import base64
import hashlib

from social.utils import parse_qs, constant_time_compare
from social.utils import parse_qs, constant_time_compare, handle_http_errors
from social.backends.oauth import BaseOAuth2
from social.exceptions import AuthException, AuthCanceled, AuthUnknownError, \
AuthMissingParameter
Expand Down Expand Up @@ -62,6 +62,7 @@ def process_error(self, data):
raise AuthCanceled(self, data.get('error_message') or
data.get('error_code'))

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
self.process_error(self.data)
Expand Down
24 changes: 9 additions & 15 deletions social/backends/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
Google OpenId, OAuth2, OAuth1, Google+ Sign-in backends, docs at:
http://psa.matiasaguirre.net/docs/backends/google.html
"""
from requests import HTTPError

from social.utils import handle_http_errors
from social.backends.open_id import OpenIdAuth, OpenIdConnectAuth
from social.backends.oauth import BaseOAuth2, BaseOAuth1
from social.exceptions import AuthMissingParameter, AuthCanceled
from social.exceptions import AuthMissingParameter


class BaseGoogleAuth(object):
Expand Down Expand Up @@ -135,6 +134,7 @@ def auth_complete_params(self, state=None):
params['redirect_uri'] = 'postmessage'
return params

@handle_http_errors
def auth_complete(self, *args, **kwargs):
if 'access_token' in self.data and 'code' not in self.data:
raise AuthMissingParameter(self, 'access_token or code')
Expand All @@ -147,18 +147,12 @@ def auth_complete(self, *args, **kwargs):
params={'access_token': token}
))

try:
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
data=self.auth_complete_params(),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
data=self.auth_complete_params(),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
self.process_error(response)
return self.do_auth(response['access_token'], response=response,
*args, **kwargs)
Expand Down
23 changes: 8 additions & 15 deletions social/backends/jawbone.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Jawbone OAuth2 backend, docs at:
http://psa.matiasaguirre.net/docs/backends/jawbone.html
"""
from requests import HTTPError
from social.utils import handle_http_errors
from social.backends.oauth import BaseOAuth2
from social.exceptions import AuthCanceled, AuthUnknownError

Expand Down Expand Up @@ -62,23 +62,16 @@ def auth_complete_params(self, state=None):
'client_secret': client_secret,
}

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
self.process_error(self.data)
try:
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
params=self.auth_complete_params(self.validate_state()),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
except KeyError:
raise AuthUnknownError(self)
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
params=self.auth_complete_params(self.validate_state()),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
self.process_error(response)
return self.do_auth(response['access_token'], response=response,
*args, **kwargs)
2 changes: 2 additions & 0 deletions social/backends/lastfm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import hashlib

from social.utils import handle_http_errors
from social.backends.base import BaseAuth


Expand All @@ -21,6 +22,7 @@ class LastFmAuth(BaseAuth):
def auth_url(self):
return self.AUTH_URL.format(api_key=self.setting('KEY'))

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes login process, must return user instance"""
key, secret = self.get_key_and_secret()
Expand Down
35 changes: 12 additions & 23 deletions social/backends/oauth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import six

from requests import HTTPError
from requests_oauthlib import OAuth1
from oauthlib.oauth1 import SIGNATURE_TYPE_AUTH_HEADER

from social.p3 import urlencode, unquote
from social.utils import url_add_parameters, parse_qs
from social.utils import url_add_parameters, parse_qs, handle_http_errors
from social.exceptions import AuthFailed, AuthCanceled, AuthUnknownError, \
AuthMissingParameter, AuthStateMissing, \
AuthStateForbidden, AuthTokenError
Expand Down Expand Up @@ -170,21 +169,17 @@ def process_error(self, data):
raise AuthCanceled(self, 'User refused the access')
raise AuthUnknownError(self, 'Error was ' + data['oauth_problem'])

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Return user, might be logged in"""
# Multiple unauthorized tokens are supported (see #521)
self.process_error(self.data)
self.validate_state()
token = self.get_unauthorized_token()
try:
access_token = self.access_token(token)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
access_token = self.access_token(token)
return self.do_auth(access_token, *args, **kwargs)

@handle_http_errors
def do_auth(self, access_token, *args, **kwargs):
"""Finish the auth process once the access_token was retrieved"""
if not isinstance(access_token, dict):
Expand Down Expand Up @@ -356,28 +351,22 @@ def process_error(self, data):
elif 'denied' in data:
raise AuthCanceled(self, data['denied'])

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
state = self.validate_state()
self.process_error(self.data)
try:
response = self.request_access_token(
self.access_token_url(),
data=self.auth_complete_params(state),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
except KeyError:
raise AuthUnknownError(self)
response = self.request_access_token(
self.access_token_url(),
data=self.auth_complete_params(state),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
self.process_error(response)
return self.do_auth(response['access_token'], response=response,
*args, **kwargs)

@handle_http_errors
def do_auth(self, access_token, *args, **kwargs):
"""Finish the auth process once the access_token was retrieved"""
data = self.user_data(access_token, *args, **kwargs)
Expand Down
2 changes: 2 additions & 0 deletions social/backends/persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Mozilla Persona authentication backend, docs at:
http://psa.matiasaguirre.net/docs/backends/persona.html
"""
from social.utils import handle_http_errors
from social.backends.base import BaseAuth
from social.exceptions import AuthFailed, AuthMissingParameter

Expand Down Expand Up @@ -33,6 +34,7 @@ def extra_data(self, user, uid, response, details):
return {'audience': response['audience'],
'issuer': response['issuer']}

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
if 'assertion' not in self.data:
Expand Down
2 changes: 2 additions & 0 deletions social/backends/pocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
http://psa.matiasaguirre.net/docs/backends/pocket.html
"""
from social.backends.base import BaseAuth
from social.utils import handle_http_errors


class PocketAuth(BaseAuth):
Expand Down Expand Up @@ -33,6 +34,7 @@ def auth_url(self):
bits = (self.AUTHORIZATION_URL, token, self.redirect_uri)
return '%s?request_token=%s&redirect_uri=%s' % bits

@handle_http_errors
def auth_complete(self, *args, **kwargs):
data = {
'consumer_key': self.setting('POCKET_CONSUMER_KEY'),
Expand Down
9 changes: 2 additions & 7 deletions social/backends/shopify.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import imp
import six

from requests import HTTPError

from social.utils import handle_http_errors
from social.backends.oauth import BaseOAuth2
from social.exceptions import AuthFailed, AuthCanceled

Expand Down Expand Up @@ -61,6 +60,7 @@ def auth_url(self):
redirect_uri=redirect_uri
)

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes login process, must return user instance"""
self.process_error(self.data)
Expand All @@ -73,11 +73,6 @@ def auth_complete(self, *args, **kwargs):
access_token = shopify_session.token
except self.shopifyAPI.ValidationException:
raise AuthCanceled(self)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
else:
if not access_token:
raise AuthFailed(self, 'Authentication Failed')
Expand Down
26 changes: 9 additions & 17 deletions social/backends/yahoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
Yahoo OpenId, OAuth1 and OAuth2 backends, docs at:
http://psa.matiasaguirre.net/docs/backends/yahoo.html
"""
from requests import HTTPError
from requests.auth import HTTPBasicAuth

from social.utils import handle_http_errors
from social.backends.open_id import OpenIdAuth
from social.backends.oauth import BaseOAuth2, BaseOAuth1
from social.exceptions import AuthCanceled, AuthUnknownError


class YahooOpenId(OpenIdAuth):
Expand Down Expand Up @@ -113,24 +112,17 @@ def user_data(self, access_token, *args, **kwargs):
'Authorization': 'Bearer {0}'.format(access_token)
}, method='GET')['profile']

@handle_http_errors
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
self.process_error(self.data)
try:
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
auth=HTTPBasicAuth(*self.get_key_and_secret()),
data=self.auth_complete_params(self.validate_state()),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
except HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(self)
else:
raise
except KeyError:
raise AuthUnknownError(self)
response = self.request_access_token(
self.ACCESS_TOKEN_URL,
auth=HTTPBasicAuth(*self.get_key_and_secret()),
data=self.auth_complete_params(self.validate_state()),
headers=self.auth_headers(),
method=self.ACCESS_TOKEN_METHOD
)
self.process_error(response)
return self.do_auth(response['access_token'], response=response,
*args, **kwargs)
Expand Down
6 changes: 6 additions & 0 deletions social/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ def __str__(self):
return 'Your credentials aren\'t allowed'


class AuthUnreachableProvider(AuthException):
"""Cannot reach the provider"""
def __str__(self):
return 'The authentication provider could not be reached'


class InvalidEmail(AuthException):
def __str__(self):
return 'Email couldn\'t be validated'
20 changes: 19 additions & 1 deletion social/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import sys
import unicodedata
import collections
import six
import functools

import six
import requests
import social

from social.exceptions import AuthCanceled, AuthUnreachableProvider
from social.p3 import urlparse, urlunparse, urlencode, \
parse_qs as battery_parse_qs

Expand Down Expand Up @@ -187,3 +190,18 @@ def setting_url(backend, *names):
value = backend.setting(name)
if is_url(value):
return value


def handle_http_errors(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except requests.HTTPError as err:
if err.response.status_code == 400:
raise AuthCanceled(args[0])
elif err.response.status_code == 503:
raise AuthUnreachableProvider(args[0])
else:
raise
return wrapper

0 comments on commit 7dd4273

Please sign in to comment.