omab / django-social-auth

Django social authentication made simple
https://groups.google.com/group/django-social-auth
BSD 3-Clause "New" or "Revised" License
2.65k stars 760 forks source link

Interface to sign requests by OAuth 1.0 #338

Closed andrusha closed 12 years ago

andrusha commented 12 years ago

Here is the thing. I want to sign my API requests by OAuth access_token. It is easy to do with OAuth 2.0 - you can simply add ?access_token parameter to url. But it isn't so for OAuth 1.0. What I currently do is:

from social_auth.models import UserSocialAuth
from social_auth.backends.contrib.yahoo import YahooOAuth
from oauth2 import Token

def view(request, user_id):
    oauth_info = UserSocialAuth.objects.filter(provider='yahoo-oauth').get(user=User.objects.get(id=user_id))
    token      = Token.from_string(oauth_info.tokens['access_token'])
    oauth      = YahooOAuth(request, '/')
    request    = oauth.oauth_request(token, 'api.yahoo.com/something')
    response   = oauth.fetch_response(request)
    return simplejson.loads(response)

social_auth.backends.BaseAuth is tied up to django's HttpRequest, therefore I can't use it outside of django views. It would be good to have a simpler way to sign api requests.

omab commented 12 years ago

Can you prepare a pull-request with such util? a good place would be social_auth/utils.py IMO.

andrusha commented 12 years ago

I'm not sure how to properly decouple BaseAuth from HttpRequest. Any tips on it?

omab commented 12 years ago

@andrusha, from that last change, you will be able to do:

from social_auth.backends.yahoo import YahooAuth
from social_auth.backends.utils import consumer_oauth_url_request

# response will be the json evaluated
response = consumer_oauth_url_request(YahooAuth, 'api.yahoo.com/something', user_id, '/')

I didn't tested against any API, just checked that the auth workflow was still working OK, so please review.

andrusha commented 12 years ago

Here what I got:


Environment:

Request Method: GET
Request URL: http://localhost:8000/accounts/new_association/

Django Version: 1.4
Python Version: 2.7.1
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'grappelli.dashboard',
 'grappelli',
 'django.contrib.admin',
 'south',
 'storages',
 'taggit',
 'compressor',
 'debug_toolbar',
 'django_openid_auth',
 'mailer',
 'emailconfirmation',
 'metron',
 'sorl.thumbnail',
 'social_auth',
 'apps.movies',
 'apps.girls',
 'apps.galleries',
 'apps.reviews',
 'apps.accounts')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.gzip.GZipMiddleware',
 'django.middleware.http.ConditionalGetMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware')

Traceback:
File "/Library/Python/2.7/site-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/Users/andrew/Projects/jporn/apps/accounts/views.py" in new_association
  36.     response = consumer_oauth_url_request(YahooOAuth, url, request.user.id, '/')
File "/Library/Python/2.7/site-packages/django_social_auth-0.6.9-py2.7.egg/social_auth/backends/utils.py" in consumer_oauth_url_request
  18.     oauth_info = user.social_auth.objects.filter(

Exception Type: AttributeError at /accounts/new_association/
Exception Value: 'RelatedManager' object has no attribute 'objects'

Also, API might not necessary return JSON, it is better to leave to user decide how to parse result.

omab commented 12 years ago

Related manager fixed. JSON parsing is on by default, but doing json=False will disable it and return the raw data, I think it's a good option for that utility since json is the usual preferred return value.

andrusha commented 12 years ago

Now this:


Environment:

Request Method: GET
Request URL: http://localhost:8000/accounts/new_association/

Django Version: 1.4
Python Version: 2.7.1
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'grappelli.dashboard',
 'grappelli',
 'django.contrib.admin',
 'south',
 'storages',
 'taggit',
 'compressor',
 'debug_toolbar',
 'django_openid_auth',
 'mailer',
 'emailconfirmation',
 'metron',
 'sorl.thumbnail',
 'social_auth',
 'apps.movies',
 'apps.girls',
 'apps.galleries',
 'apps.reviews',
 'apps.accounts')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.gzip.GZipMiddleware',
 'django.middleware.http.ConditionalGetMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware')

Traceback:
File "/Library/Python/2.7/site-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/Users/andrew/Projects/jporn/apps/accounts/views.py" in new_association
  36.     response = consumer_oauth_url_request(YahooOAuth, url, request.user.id, '/')
File "/Library/Python/2.7/site-packages/django_social_auth-0.6.9-py2.7.egg/social_auth/backends/utils.py" in consumer_oauth_url_request
  24.     response = '\n'.join(urlopen(request.to_url()).readlines())
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py" in urlopen
  126.     return _opener.open(url, data, timeout)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py" in open
  400.             response = meth(req, response)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py" in http_response
  513.                 'http', request, response, code, msg, hdrs)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py" in error
  438.             return self._call_chain(*args)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py" in _call_chain
  372.             result = func(*args)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py" in http_error_default
  521.         raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)

Exception Type: HTTPError at /accounts/new_association/
Exception Value: HTTP Error 401: Authorization Required

Local vars for def new_association(request):

Variable    Value
url 
'http://social.yahooapis.com/v1/me/guid?format=json'

request 
"<WSGIRequest\npath:/accounts/new_association/,\nGET:<QueryDict: {}>,\nPOST:<QueryDict: {}>,\nCOOKIES:{'csrftoken': '32aa49dce295521d54a533c8ec2fcb13',\n 'djdt': 'hide',\n 'sessionid': '7920c10f3f657c2ffd52b04937009104'},\nMETA:{'Apple_PubSub_Socket_Render': '/tmp/launch-70PsQc/Render',\n 'Apple_Ubiquity_Message': '/tmp/launch-UH9cai/Apple_Ubiquity_Message',\n 'COMMAND_MODE': 'unix2003',\n 'CONTENT_LENGTH': '',\n 'CONTENT_TYPE': 'text/plain',\n 'CSRF_COOKIE': '32aa49dce295521d54a533c8ec2fcb13',\n 'DISPLAY': '/tmp/launch-1MXkMy/org.x:0',\n 'DJANGO_SETTINGS_MODULE': 'settings',\n 'EDITOR': 'vim',\n 'GATEWAY_INTERFACE': 'CGI/1.1',\n 'GREP_COLOR': '1;32',\n 'GREP_OPTIONS': '--color=auto',\n 'HOME': '/Users/andrew',\n 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n 'HTTP_ACCEPT_CHARSET': 'UTF-8,*;q=0.5',\n 'HTTP_ACCEPT_ENCODING': 'gzip,deflate,sdch',\n 'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.8,ru;q=0.6',\n 'HTTP_CACHE_CONTROL': 'max-age=0',\n 'HTTP_CONNECTION': 'keep-alive',\n 'HTTP_COOKIE': 'djdt=hide; csrftoken=32aa49dce295521d54a533c8ec2fcb13; sessionid=7920c10f3f657c2ffd52b04937009104',\n 'HTTP_HOST': 'localhost:8000',\n 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.168 Safari/535.19',\n 'LC_CTYPE': '',\n 'LOGNAME': 'andrew',\n 'LSCOLORS': 'Gxfxcxdxbxegedabagacad',\n 'OLDPWD': '/Users/andrew/Projects/django-social-auth',\n 'PAGER': 'less',\n 'PATH': '/Users/andrew/.rbenv/shims:/Users/andrew/.rbenv/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin',\n 'PATH_INFO': u'/accounts/new_association/',\n 'PWD': '/Users/andrew/Projects/jporn',\n 'QUERY_STRING': '',\n 'REMOTE_ADDR': '127.0.0.1',\n 'REMOTE_HOST': '',\n 'REQUEST_METHOD': 'GET',\n 'RUN_MAIN': 'true',\n 'SCRIPT_NAME': u'',\n 'SERVER_NAME': '1.0.0.127.in-addr.arpa',\n 'SERVER_PORT': '8000',\n 'SERVER_PROTOCOL': 'HTTP/1.1',\n 'SERVER_SOFTWARE': 'WSGIServer/0.1 Python/2.7.1',\n 'SHELL': '/bin/zsh',\n 'SHLVL': '1',\n 'SSH_AUTH_SOCK': '/tmp/launch-vRDOvT/Listeners',\n 'TERM': 'xterm-256color',\n 'TERM_PROGRAM': 'Apple_Terminal',\n 'TERM_PROGRAM_VERSION': '303',\n 'TERM_SESSION_ID': 'E710B9E4-62CD-4E41-86DB-AD3C2054DC28',\n 'TMPDIR': '/var/folders/cw/mjt358f94w3g4vn2cfh9kn1w0000gn/T/',\n 'TZ': 'Asia/Tokyo',\n 'USER': 'andrew',\n 'VERSIONER_PYTHON_PREFER_32_BIT': 'no',\n 'VERSIONER_PYTHON_VERSION': '2.7',\n 'ZSH': '/Users/andrew/.oh-my-zsh',\n 'ZSH_THEME': 'nicoulaj',\n '_': '/Users/andrew/Projects/jporn/./manage.py',\n '__CF_USER_TEXT_ENCODING': '0x1F5:0:0',\n 'vcs_info_msg_0_': '.',\n 'vcs_info_msg_1_': 'jporn/git/master !',\n 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x10db18270>,\n 'wsgi.file_wrapper': <class wsgiref.util.FileWrapper at 0x10e576c80>,\n 'wsgi.input': <socket._fileobject object at 0x10f3ec650>,\n 'wsgi.multiprocess': False,\n 'wsgi.multithread': True,\n 'wsgi.run_once': False,\n 'wsgi.url_scheme': 'http',\n 'wsgi.version': (1, 0)}>"

consumer_oauth_url_request  
<function consumer_oauth_url_request at 0x10ed2db90>

YahooOAuth  
<class 'social_auth.backends.contrib.yahoo.YahooOAuth'>

Local vars for consumer_oauth_url_request:

url 
'http://social.yahooapis.com/v1/me/guid?format=json'

request 
{'oauth_body_hash': '2jmj7l5rSw0yVb/vlWAYkK/YBwk=',
 u'oauth_callback': u'/',
 u'oauth_consumer_key': u'dj0yJmk9WlRtRnRhM2xVY3U3JmQ9WVdrOVFtMWtXVFJYTjJNbWNHbzlNVE0xT1RBME16RTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD0wYg--',
 u'oauth_nonce': u'77014517',
 'oauth_signature': 'hw1TFS7Atmr0+zgCwOM7AQDLO3Y=',
 'oauth_signature_method': 'HMAC-SHA1',
 u'oauth_timestamp': u'1336474599',
 u'oauth_token': u'A=nqLHgcfZlSac4U3xPAg6DswllHAYvhskWZ.WMiawKbypE4apxOQxz4YW3d5n3mirIkQyKO5es5_rIRwGe6YRF8nPDkXt1BzSamb2sTQj9Jq3kWcLhNCbgaoofdLcsKyhiw4oh16XX1jjzIPClUw_jb0m0kKSZCnM9R02GC99pdelPdNVK2QGvwvCV..Dtn7oDVtE9wo.aTsvTNy8L0gOKxFw6xf0v19D9FCU4COCYZDeHOSWIH5IA5kawgFRnLLbi7rFE0d8kosfiL_Y1b5.VoaI7fbqRPSYMIACUFQmqazPkiDxAbpMGT_bIXcz5I693FojYwywpCMhVi9WRb4VJYkgxw5usdCxJS1.1opC34NKkA4Y.ZKrmR_shdeW0Xh5hllzw56UB_Ek4Qwq94lBeZuZ2hAaG64Q8KrJlh3vHXo.HnxyJDFd2pMyR6Te8Byr.5vCDEuEessskvm8srlywwnb83wddLzE2uI1xUKskGsroqBy.iZgnAm_rrkX6RYf1j..Z2FPx1AtTmkbbNvzQnZJGFOyoon4JtV01VPSp2_rp8KClAeP10.r8Ho.p8BlTIzZsp8lDn7m.1V9NlIH6Hzq29zOz93GupEkop12hHnyNo01xzOYIDXoQ0GxwEUCC4rCbelNYH2b6ydtSwwOO0m38cwVjSZhclTwWjqd2Bmv7lMlvsgXECS9oRtNabHj9r34lsQO3H7kdYEGN3UHeOI.CziNsaHdbYJ3Rx9ETvZ3h76y7FlNadKQG0fPrKsbg3oQtcVgXYG3tfKQG1mPeGJKfAus2_Oomg49TRIYFoCmYr1sT0x3wqrFSbW9Z5K2JRM2k6fma1k-',
 u'oauth_version': u'1.0'}

token   
<oauth2.Token object at 0x10f2d9cd0>

json    
True

redirect_uri    
'/'

user    
<User: wolfon>

oauth_info  
<UserSocialAuth: wolfon>

user_or_id  
12

backend 
<class 'social_auth.backends.contrib.yahoo.YahooOAuth'>

Looks like it wasn't signed properly.

omab commented 12 years ago

I've made an small change that fixes the retrieval of the UserSocialAuth instance, then I did a test and it worked correctly, I was able to get the response from http://social.yahooapis.com/v1/me/guid?format=json with my freshly generated access_token. Do you have the needed scopes configured in Yahoo app panel? I think Social Directory is the one needed to access the /v1/me/guid.

andrusha commented 12 years ago

Thanks, works well now.