]> git.parisson.com Git - django-social-auth.git/commitdiff
Add state argument to oauth2 process to secure process. Refs #386
authorMatías Aguirre <matiasaguirre@gmail.com>
Wed, 4 Jul 2012 06:38:55 +0000 (03:38 -0300)
committerMatías Aguirre <matiasaguirre@gmail.com>
Wed, 4 Jul 2012 06:38:55 +0000 (03:38 -0300)
social_auth/backends/__init__.py
social_auth/backends/contrib/github.py
social_auth/backends/contrib/instagram.py
social_auth/backends/exceptions.py

index fb439a400d74f3d9741a9bb9c856653ddcd76d7c..d70f51e66264ea7ed5a541442250851aa5586434 100644 (file)
@@ -24,6 +24,8 @@ from django.contrib.auth import authenticate
 from django.contrib.auth.backends import ModelBackend
 from django.utils import simplejson
 from django.utils.importlib import import_module
+from django.utils.crypto import constant_time_compare, get_random_string
+from django.middleware.csrf import CSRF_KEY_LENGTH
 
 from social_auth.utils import setting, log, model_to_ctype, ctype_to_model, \
                               clean_partial_pipeline
@@ -31,7 +33,8 @@ from social_auth.store import DjangoOpenIDStore
 from social_auth.backends.exceptions import StopPipeline, AuthException, \
                                             AuthFailed, AuthCanceled, \
                                             AuthUnknownError, AuthTokenError, \
-                                            AuthMissingParameter
+                                            AuthMissingParameter, \
+                                            AuthForbidden
 from social_auth.backends.utils import build_consumer_oauth_request
 
 
@@ -663,12 +666,22 @@ class BaseOAuth2(BaseOAuth):
     RESPONSE_TYPE = 'code'
     SCOPE_VAR_NAME = None
     DEFAULT_SCOPE = None
+    FORCE_STATE_CHECK = True
+
+    def csrf_token(self):
+        """Generate csrf token to include as state parameter."""
+        return get_random_string(CSRF_KEY_LENGTH)
 
     def auth_url(self):
         """Return redirect url"""
         client_id, client_secret = self.get_key_and_secret()
         args = {'client_id': client_id, 'redirect_uri': self.redirect_uri}
 
+        if self.FORCE_STATE_CHECK:
+            state = self.csrf_token()
+            args['state'] = state
+            self.request.session[self.AUTH_BACKEND.name + '_state'] = state
+
         scope = self.get_scope()
         if scope:
             args['scope'] = self.SCOPE_SEPARATOR.join(self.get_scope())
@@ -684,6 +697,13 @@ class BaseOAuth2(BaseOAuth):
             error = self.data.get('error_description') or self.data['error']
             raise AuthFailed(self, error)
 
+        if self.FORCE_STATE_CHECK:
+            if 'state' not in self.data:
+                raise AuthMissingParameter(self, 'state')
+            state = self.request.session[self.AUTH_BACKEND.name + '_state']
+            if not constant_time_compare(self.data['state'], state):
+                raise AuthForbidden(self)
+
         client_id, client_secret = self.get_key_and_secret()
         params = {'grant_type': 'authorization_code',  # request auth code
                   'code': self.data.get('code', ''),  # server response code
index 0580b2f3217ba0d7f618846830430d6de3da3ffc..af9371a89514d93cb6b3d9cb46111f6d8f8acf48 100644 (file)
@@ -53,6 +53,8 @@ class GithubAuth(BaseOAuth2):
     SCOPE_SEPARATOR = ','
     # Look at http://developer.github.com/v3/oauth/
     SCOPE_VAR_NAME = 'GITHUB_EXTENDED_PERMISSIONS'
+    # Github doesn't return the state paramenter if specified :(
+    FORCE_STATE_CHECK = False
 
     def user_data(self, access_token, *args, **kwargs):
         """Loads user data from service"""
index faa2e82cb3264f59128724cce2168cda0ce53f56..3cf1689a946edb830a56c6d804ac165f398e543f 100644 (file)
@@ -37,6 +37,7 @@ class InstagramAuth(BaseOAuth2):
     AUTH_BACKEND = InstagramBackend
     SETTINGS_KEY_NAME = 'INSTAGRAM_CLIENT_ID'
     SETTINGS_SECRET_NAME = 'INSTAGRAM_CLIENT_SECRET'
+    FORCE_STATE_CHECK = False
 
     def user_data(self, access_token, *args, **kwargs):
         """Loads user data from service"""
index 654ff224b4153b0166daab36c1d381e9f433e9d1..35f76e09f7d429e9f06841aaabeddb414bc950db 100644 (file)
@@ -24,7 +24,6 @@ class AuthException(SocialAuthBaseException):
 class AuthFailed(AuthException):
     """Auth process failed for some reason."""
     def __unicode__(self):
-
         if self.message == 'access_denied':
             return ugettext(u'Authentication process was cancelled')
         else:
@@ -60,3 +59,9 @@ class AuthMissingParameter(AuthException):
 
     def __unicode__(self):
         return u'Missing needed parameter %s' % self.parameter
+
+
+class AuthForbidden(AuthException):
+    """State parameter is incorrect."""
+    def __unicode__(self):
+        return u'Wrong state parameter given.'