Code Monkey home page Code Monkey logo

rauth's Introduction

Rauth

A simple Python OAuth 1.0/a, OAuth 2.0, and Ofly consumer library built on top of Requests.

build status

Features

  • Supports OAuth 1.0/a, 2.0 and Ofly
  • Service wrappers for convenient connection initialization
  • Authenticated session objects providing nifty things like keep-alive
  • Well tested (100% coverage)
  • Built on Requests (v1.x)

Installation

To install:

$ pip install rauth

Or if you must:

$ easy_install rauth

Example Usage

Let's get a user's Twitter timeline. Start by creating a service container object:

from rauth import OAuth1Service

# Get a real consumer key & secret from https://dev.twitter.com/apps/new
twitter = OAuth1Service(
    name='twitter',
    consumer_key='J8MoJG4bQ9gcmGh8H7XhMg',
    consumer_secret='7WAscbSy65GmiVOvMU5EBYn5z80fhQkcFWSLMJJu4',
    request_token_url='https://api.twitter.com/oauth/request_token',
    access_token_url='https://api.twitter.com/oauth/access_token',
    authorize_url='https://api.twitter.com/oauth/authorize',
    base_url='https://api.twitter.com/1.1/')

Then get an OAuth 1.0 request token:

request_token, request_token_secret = twitter.get_request_token()

Go through the authentication flow. Since our example is a simple console application, Twitter will give you a PIN to enter.

authorize_url = twitter.get_authorize_url(request_token)

print 'Visit this URL in your browser: ' + authorize_url
pin = raw_input('Enter PIN from browser: ')  # `input` if using Python 3!

Exchange the authorized request token for an authenticated OAuth1Session:

session = twitter.get_auth_session(request_token,
                                   request_token_secret,
                                   method='POST',
                                   data={'oauth_verifier': pin})

And now we can fetch our Twitter timeline!

params = {'include_rts': 1,  # Include retweets
          'count': 10}       # 10 tweets

r = session.get('statuses/home_timeline.json', params=params)

for i, tweet in enumerate(r.json(), 1):
    handle = tweet['user']['screen_name']
    text = tweet['text']
    print(u'{0}. @{1} - {2}'.format(i, handle, text))

Here's the full example: examples/twitter-timeline-cli.py.

Documentation

The Sphinx-compiled documentation is available here: http://readthedocs.org/docs/rauth/en/latest/

Contribution

Anyone who would like to contribute to the project is more than welcome. Basically there's just a few steps to getting started:

  1. Fork this repo
  2. Make your changes and write a test for them
  3. Add yourself to the AUTHORS file and submit a pull request!

Note: Before you make a pull request, please run make check. If your code passes then you should be good to go! Requirements for running tests are in requirements-dev@<python-version>.txt. You may also want to run tox to ensure that nothing broke in other supported environments, e.g. Python 3.

Copyright and License

Rauth is Copyright (c) 2013 litl, LLC and licensed under the MIT license. See the LICENSE file for full details.

rauth's People

Contributors

02strich avatar appsforartists avatar barberj avatar bbigras avatar candeira avatar domino14 avatar jmcvetta avatar joelverhagen avatar joeshaw avatar jonathan-deprizio avatar kbriggs avatar kennethkoontz avatar leftium avatar marekrei avatar maxcountryman avatar montylounge avatar mwarkentin avatar neogenix avatar netdude78 avatar nyov avatar ptpt avatar randomir avatar sashahart avatar sjl avatar ta2xeo avatar takluyver avatar tristan avatar xevix avatar xsleonard avatar yoloseem avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rauth's Issues

Auto Refresh Access Token

Not sure if this is outside the scope of this project, but I know that the requests library has sessions, and that rauth has an OAuthHook to avoid using the service wrapper. It would be great if the service wrapper could remember the grant_type that is used, and when it tries to make a call using its access token and gets a 400 status code with "invalid_token" response (per OAuth2 Bearer Token spec), it goes through the same flow done initially to refresh it, or if it got a refresh token originally, to use that to get a new access token.

This is useful so that someone can just use the library and make calls without worrying about refreshing tokens manually.

CSRF protection

As detailed here and here, the default implementation of OAuth2 is vulnerable to CSRF attacks whereby a malicious host can compromise the developer's site by grabbing a valid code from his own Facebook/Google/etc. account and tricking the victim into GETting it. This would enable the malicious user to use his own Facebook/Google/etc. account to login to the developer's site as the victim.

The solution is to send a known value in the state parameter and assert that it comes back as expected.

It seems to me like this is something we should fix at the library level. Here's one potential solution, but I'm interested in hearing others.

  1. When initializing an OAuth2Service, the developer passes in a CSRF_SECRET_KEY (or None to disable rauth's CSRF protection).
  2. When the developer calls get_authorize_url, he passes in the current user's ID (or None if the user is anonymous).
  3. If rauth receives an ID, it salts it with CSRF_SECRET_KEY, hashes it, and concatenates it onto the state param:
&state=__csrf__%3D95078fdf5cde38c1ca1694389cda1460;original_state_here
  1. Just before get_access_token returns, it salts and hashes the current user's ID, and compares the results to __csrf__.
    4a) If the hashes match, the __csrf__ string is removed from the state and it is passed unmolested back to the developer.
    4b) If the hashes don't match, return {'error': 'csrf_validation_failed'} to the developer.

I'm new to both the rauth project and OAuth2 implementation in general. I'd love to know if I'm off-base here. If not, I'd be happy to add support.

Please critique.

Linkedin Share POST call

A call to the Linkedin API https://api.linkedin.com/people/~/shares for sharing requires to send a literal JSON in the body.

With rauth if I pass a dictionary to the "data" argument the dictionary is converted into a key value pair seperated by ampersands, however if I assign the return value of "json.dump(body_data)" to the "data" argument then the body is empty.

I would appreciate if someone can shed some light on how to send a literal JSON string in the body.

How do I check in every request?

I have a session created using the rauth 0.5.3 which I save in my session. I'd like to check / do somethings on every request made using that session. Is it possible? If yes, how do I accomplish that?

Thanks.
Khan

OAuth1Session request wrapper eats some POST data

From #66.
The issue seems to be when sending a POST request with JSON encoded data,
which gets wrongly parsed by parse_qsl() in session.py.

The result is that data = {'category_id': '8'} gets transformed to category_id=8 in the POST body and json.dumps( {'category_id': '8'} ) gets completely lost ({} or None depending on Requests version)

Removing session.py lines 142,143 fixed the issue, maybe it should to check the Content-Type?

We should remove rauth doc from pythonhosted?

http://pythonhosted.org/rauth/

I think this is outdated.
I was using this because it came up as one of the top doc search result.

response =             service.get_access_token(method='GET'
                             request_token=request_token,
                             request_token_secret=request_token_secret)

This part threw me off but luckily my vim has doc reader i was able to figured out.

Thanks for helping me on SO today.

PlainText Signature

It seems that rauth currently does not support plain-text signatures. Is it in the works? I could help if it is not currently being worked on.

Python 3 support

This has come up before, see #26 for instance. Unfortunately we were unable to merge that patch given the breaking changes in 0.5.0.

However I would like to revisit this. There seems to be interest out there, e.g. omab/python-social-auth#3.

So I would like to propose that we slate this for the 0.6.0 release and extend an invitation to whomever might be willing to feel free to hack on this. We would gladly accept pull requests and it might even be possible to revisit #26 and base a patch on that.

I will try to work on this as I find free time, but that probably excludes the immediate future.

OAuth2 get_access_token fails if run instantly after getting callback code

For whatever reason I can not seem to get an access token from get_access_token if i call it right away when I get the code in the callback GET request.

Not sure if this is a bug in rauth or a flaw in my understanding of OAuth2

Here is a simple Flask example:

from flask import Flask, request, redirect, url_for
from rauth.service import OAuth1Service, OAuth2Service

github = OAuth2Service(
    name='github',
    consumer_key='GITHUB_CONSUMER_KEY',
    consumer_secret='GITHUB_CONSUMER_SECRET',
    access_token_url='https://github.com/loginoauth/access_token',
    authorize_url='https://github.com/login/oauth/authorize',
)

app = Flask(__name__)
app.debug = True

@app.route('/')
def index():
    code = request.args.get('code',None)
    if code:
        print 'CODE: ',code
        access_token = github.get_access_token(
            code=code,
        ).content['access_token']
        # The above does not contain access_token and results in a KeyError
        print 'ACCESS_TOKEN: ',access_token
    else:
        return redirect(url_for('login'))

@app.route('/login')
def login():
    authorize_url = github.get_authorize_url()
    print authorize_url
    return redirect(authorize_url)

if __name__ == '__main__':
    app.run()

Implicit, default API endpoint and token

Most API calls will reuse the same base URL and token. It would be nice to set default values for them once and remove the redundant clutter from each API call. (DRY: Don't repeat yourself^^) For example:

facebook = OAuth2Service(...)
facebook.base_url = BASE_URL
facebook.access_token = ACCESS_TOKEN

profile = facebook.get('me').content
friends = facebook.get('me/friends').content
feed = facebook.get('me/feed').content

Instead of:

facebook = OAuth2Service(...)

profile = facebook.get(BASE_URL + 'me', params=dict(access_token=ACCESS_TOKEN)).content
friends = facebook.get(BASE_URL + 'me/friends', params=dict(access_token=ACCESS_TOKEN)).content
feed = facebook.get(BASE_URL + 'me/feed', params=dict(access_token=ACCESS_TOKEN)).content

Of course, the default values could be easily overridden by supplying an absolute URL and/or access_token in params

rauth won't install with pip if requests is not already installed

rauth won't install with pip if requests is not already installed.

It may be because setup.py do import rauth (which try to import requests (by doing from .service import OAuth1Service, OAuth2Service, OflyService)) to get rauth.__version__.

(tv) C:\Users\bruno\git\tv>pip install rauth
Downloading/unpacking rauth
  Downloading rauth-0.4.13.tar.gz
  Running setup.py egg_info for package rauth
    Traceback (most recent call last):
      File "<string>", line 14, in <module>
      File "C:\Users\bruno\git\tv\build\rauth\setup.py", line 3, in <module>
        import rauth
      File "rauth\__init__.py", line 12, in <module>
        from .service import OAuth1Service, OAuth2Service, OflyService
      File "rauth\service.py", line 8, in <module>
        import requests
    ImportError: No module named requests
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 14, in <module>

  File "C:\Users\bruno\git\tv\build\rauth\setup.py", line 3, in <module>

    import rauth

  File "rauth\__init__.py", line 12, in <module>

    from .service import OAuth1Service, OAuth2Service, OflyService

  File "rauth\service.py", line 8, in <module>

    import requests

ImportError: No module named requests

----------------------------------------
Command python setup.py egg_info failed with error code 1 in C:\Users\bruno\git\
tv\build\rauth
Storing complete log in C:\Users\bruno\AppData\Roaming\pip\pip.log

(tv) C:\Users\bruno\git\tv\wsgi\testpyramid>

Some providers return data in JSON format

Hello!

When you request the access_token from Google, the application throws an KeyError('access_token',) exception. That's because Google returns the data in JSON format. See https://developers.google.com/accounts/docs/OAuth2WebServer#handlingtheresponse
My code now looks like:

r = google.get_raw_access_token(data=data)
token_data = json.loads(r.content)
session = google.get_session(token_data["access_token"])

instead of:

session = google.get_auth_session(data=data)

Maybe add a way to tell the library that it should decode the response using json.loads instead of urlparse.parse_qsl?

Alexei

Allow use of HTTPS with verify=False

I would love to use rauth but cannot because the HTTPS server I'm accessing doesn't have a verifiable SSL cert. I need to be able to tell requests to not verify the SSL cert.

OAuth2Service: 'get_access_token' breaks

This is my first time creating issues, so pardon for not knowing the exact way to submit the request.

I am connecting to a Muel server for authentication and at the step where I call "get_access_token" function, the code breaks. However if I call the "get_raw_access_token" function and use "json.loads" and then try to get the "access_token" key, everything works great.

At the moment I am having to extend that class and override the "get_access_token" function in my extended class, that I'd like to just use it from the base class. Thanks again for looking into this.

Khan

How do you save the session object ?

after you've authenticated, how do you save the session object, so you don't have to authenticate each time (i.e. when running a REST call from the console app)? Is it possible to simply create the session object from the consumer key/secret and the access key/secret? What if I already have those 4 pieces of information, how do I create the session object, so I don't need to do this OAuth dance? Thanks!

Saving get_session to request.session error

I get the following when using this code with the OAuth2 get_session. This seems to work fine if i use get_auth_session(), but due to the raw JSON response back from an API, i am using the get_session() separately. It works fine if I don't set it to the request.session. Any thoughts?

Steve

Code:
self.request.session['oauth_session'] = self.auth_service.get_session(token=token)

Error:
'OAuth2Session' object has no attribute 'attrs'
/Library/Python/2.7/site-packages/requests/sessions.py in getstate Line 397
return dict((attr, getattr(self, attr, None)) for attr in self.attrs)

Allow requests 1.2.0 or unforce the version

For some reasons the requests version is forced to 1.1.0 and I need to use requests 1.2.0.

It would be nice if the version is not forced at all, or in a more permissive way (ie >= 1.1.0).

Thanks,

Christophe

Foursquare expects 'oauth_token' instead of 'access_token'

Using the following fails as Fourquare expects the access_token to be provided as 'oauth_token'

profile = service.request(
    'GET',
    'https://api.foursquare.com/v2/users/self' ,
    access_token=token
).content

Currently I am using the following dirty hack which works:

profile = service.request(
    'GET',
    'https://api.foursquare.com/v2/users/self?oauth_token=%s' % token,
    access_token=token,
).content

Really I think this is more Fourquare's fault for not using the same argument name as everyone else... however a nicer workaround or argument option in rauth would be nice.

Perhaps just add and 'oauth_token' option to request() that does the same thing as 'access_token' and require one of them, but not both?

rauth uses wrong request URI, causes missing consumer key error (?)

I'm trying to access the fatsecret API. I've successfully accessed this same API with the python-fatsecret library from here https://bitbucket.org/fmoo/python-fatsecret I'm using the exact same consumer key with rauth as I did with python-fatsecret but rauth is giving me the following error in get_request_token: (I added some print statements in service.py)
"response <Response [400]>
response.content Missing required parameter: oauth_consumer_key"

but I'm definitely instantiating OAuth1Service with a consumer key(and I've triple checked that it is correct).
I ran wireshark while getting a request token using rauth and python-fatsecret and noticed these differences:

rauth: OAuth Realm="http://www.fatsecret.com/" and request_URI=/oauth_request_token

python-fatsecret: OAuth Realm="" and request_URI = http://www.fatsecret.com/oauth/request_token?oauth_nonce=77769080&oauth_timestamp=1339285156&oauth_consumer_key=my_key_was_here&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_signature=8a3mb2veTNIPkW6kNWr8J2pyxQ4%3D&oauth_callback=oob

Any hints on why rauth is doing a GET on the wrong URI(or am I misunderstanding what's going on)? I just pulled from the repo today, so I'm using rauth 0.4.6.

Default request headers should be matched case-insensitive

From #66.
The headers keywords ('Content-Type' especially here) must be matched exactly (case sensitive) to replace the default values.
They should be filtered in a case-insensitive way.
Otherwise they get added as duplicates, or in the instance of sending a POST with body where the script assumes content-type urlencoded (GET), chokes on some body content (e.g. JSON data)

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/usr/lib/python2.7/dist-packages/IPython/utils/py3compat.pyc in execfile(fname, *where)
    176             else:
    177                 filename = fname
--> 178             __builtin__.execfile(filename, *where)

[...]/rauth.git/magento-test2.py in <module>()
    133 #    data=json.dumps(payload),
    134     headers=headers,
--> 135     data=payload,
    136     #access_token=access_token,
    137     #access_token_secret=access_token_secret

[...]/rauth.git/rauth/session.pyc in request(self, method, url, header_auth, realm, **req_kwargs)
    161                                 url,
    162                                 oauth_params,
--> 163                                 req_kwargs)
    164 
    165         if header_auth:

[...]/rauth.git/rauth/oauth.pyc in sign(self, consumer_secret, access_token_secret, method, url, oauth_params, req_kwargs)
    132 
    133         oauth_params = \
--> 134             self._normalize_request_parameters(oauth_params, req_kwargs)
    135         parameters = map(self._escape, [method, url, oauth_params])
    136 

[...]/rauth.git/rauth/oauth.pyc in _normalize_request_parameters(self, oauth_params, req_kwargs)
     75         if 'Content-Type' in headers and \
     76                 headers['Content-Type'] == FORM_URLENCODED:
---> 77             for k, v in data.items():
     78                 normalized += [(k, v)]
     79 

AttributeError: 'str' object has no attribute 'items'

where data = payload = json.dumps({'test':'me'}) and headers = {'content-type': 'application/json'} in a POST request

duplicate params in GET request w/o header auth

I am writing an example program for webauth to hit Twitter and pull down a timeline. That code is currently here:

https://gist.github.com/3815bf48c5bb563a7f5b

When the last request has header_auth=False things fail. Looking at the response from Twitter, it says:

{"error":"Could not authenticate with OAuth.","request":"\/1\/statuses\/home_timeline.json?count=10&oauth_version=1.0&oauth_signature=HRJYrmLO%2BngsbvHG4iumQ1fVe44%3D&oauth_token=5344992-AWVC6whTXSwOcyiE3LSW6J1K9usjQBM5jeNSfKeW5s&oauth_nonce=2bec3417d487342f3460570c22648e9883351eaa&oauth_timestamp=1331846018&oauth_verifier=&oauth_consumer_key=J8MoJG4bQ9gcmGh8H7XhMg&oauth_signature_method=HMAC-SHA1&include_rts=1&count=10&include_rts=1"}

Notice that count and include_rts are included twice.

I'm not 100% sure that's the problem, and I haven't been able to figure out where in the code that is set, but Twitter is probably not wrong.

If I set header_auth=True things work.

OAuth 1.0 requests issues

I'm having some issues in 0.5 with OAuth 1.0

The default oauth_callback got dropped?
Ok, I got that working again by adding params={'oauth_callback': 'oob'} into service.get_request_token() for my specific API.

Some more trouble was session.request() when trying to set a content-type header:
Unless it's named exactly 'Content-Type' (e.g. application/json) the request will add it instead of replacing the default 'Content-Type': 'application/x-www-form-urlencoded', which is strange because it should be case insensitive.

The real problem I can't figure out though is, that a session.post() even with setting header_auth=True will silently eat any data=payload if it's json encoded:
r = session.post(url,data=json.dumps({'status':'ok'})
will have an empty r.request.data.

And on another note, I was missing some error reporting if the server response is something other than 200 in the authentication stage.
I had to hack some 'print's into the source to catch server responses like oauth_problem=parameter_absent&oauth_parameters_absent=oauth_verifier or oauth_problem=parameter_rejected&message=oauth_callback which only failed with "obscure" lines like

  File "[...]/rauth.git/rauth/service.py", line 191, in get_request_token
    return data['oauth_token'], data['oauth_token_secret']
KeyError: 'oauth_token'

Pickling OAuth1Service

Perhaps I'm missing some fundamental part of rauth (or perhaps oauth...) however I don't seem to be able to pickle OAuth1Service for later use.

Is there a way of 'reusing' the session created using OAuth1Service.get_auth_session?

Essentially I'd like to pickle the oauth session so I can reuse it across short lived http requests.

As it seems rather unlikely that I'd be the first person wanting to do this, perhaps I've missed it in the docs or it isn't documented?

Make test command more resilient to missing tooling

As an OS packager/porter, I'm not necessarily interested in pep/flake style development testing (though I will fix things and report upstream if I can).

While these of course are highly relevant to the author for people contributing code back to the project and for QA'ing PR's, I'm mostly interested in assuring quality for our end users by ensuring the test suite passes, and reporting failures back

Accordingly, I'd like to see the test command fail gracefully and continue in the face of ImportErrors for all the relevant steps in the test process. This also saves you (us) from having to create a special 'tests-only' command, which runs nothing but the tests (and feels a little backward)

Ideally, a special Makefile to coordinate these things is not necessary in the medium term, and rauth can use standard pythonic command classes to do everything it needs.

oauth params replaces the params

If the "header_auth" argument is set to false, the oauth params replaces the params. This is done in hook.py at line 127. Is there a specific reason for doing this?

OAuth2 terminology for client ID/secret is wrong

Reading over the OAuth 2 spec (several versions, including 12, 16, and the latest, 31) I found that rauth is using old OAuth 1.0 terminology for some things in the OAuth2Service object. Namely, consumer_key should be client_id and consumer_secret should be client_secret. (See section 2.3.1)

Maybe we should switch to the correct names with backward compatibility for the other names with a DeprecationWarning?

rauth 0.5.4 fails against requests 1.2.3

havent done any investigation, but when I pip installed requests==1.1, it started working again


  File "/Users/rbp/Projects/tengahdb/default/lib/python2.7/site-packages/rauth/service.py", line 212, in get_request_token
    r = self.get_raw_request_token(method=method, **kwargs)
  File "/Users/rbp/Projects/tengahdb/default/lib/python2.7/site-packages/rauth/service.py", line 186, in get_raw_request_token
    return session.request(method, self.request_token_url, **kwargs)
  File "/Users/rbp/Projects/tengahdb/default/lib/python2.7/site-packages/rauth/session.py", line 136, in request
    req_kwargs['headers'] = CaseInsensitiveDict(req_kwargs['headers'])
  File "/Users/rbp/Projects/tengahdb/default/lib/python2.7/site-packages/rauth/utils.py", line 55, in __init__
    return super(CaseInsensitiveDict, self).__init__(lowered_d)
  File "/Users/rbp/Projects/tengahdb/default/lib/python2.7/site-packages/requests/structures.py", line 69, in __init__
    self.update(data, **kwargs)
  File "/Users/rbp/Projects/tengahdb/default/lib/python2.7/site-packages/rauth/utils.py", line 75, in update
    self._clear_lower_keys()
AttributeError: 'CaseInsensitiveDict' object has no attribute '_clear_lower_keys'

Github example fails with an exception

When I run the Github example I get the following traceback:

Traceback (most recent call last):
  File ".\test.py", line 29, in <module>
    access_token=access_token).content
  File "C:\Users\user\github-api-proxy\lib\site-packages\rauth\service.py", line 57, in get
    return self.request('GET', url, **kwargs)
  File "C:\Users\user\github-api-proxy\lib\site-packages\rauth\service.py", line 399, in request
    response = self.session.request(method, uri, **kwargs)
TypeError: request() got an unexpected keyword argument 'access_token'

This looks like it shouldn't happen. I'm running Python 2.7 on windows.

Split out core OAuth logic

Currently we are moving steadily towards decoupling the OAuth logic from the client logic. Eventually I think the ideal would be to completely split apart these two layers of logic such that we can provide an entirely generic solution that might be used by clients and servers.

The OAuth 1.0a signing logic is battle-tested in a way most libraries are not; it actually works with dozens of providers and is used daily. Similarly the OAuth 2.0 logic shares this quality and is relatively simple anyway.

I think that moving this logic into two modules, oauth1.py and oauth2.py is probably the best approach, to begin with. Eventually we could even provide an entirely independent package. The goals here should be 1) simple API 2) practical implementation 3) and of course, well tested.

Most of the work is already done to provide the basis for this. The nuanced bit of this is probably deciding what additions servers might require. But because the signing procedure works in practice, servers may use this to validate and verify OAuth requests (even today this is possible, albeit it means pulling in Requests which is unnecessary from a server point of view). The rest of a server implementation is fairly domain specific, and we've seen other libraries stumble over this, trying to make a one-size-fits-all solution that frankly ends up being quite a mess.

I'm opening this up for discussion. Thoughts, ideas, comments are all appreciated.

oauth_callback

There is no support for a callback url. Making some services incompatible with this.

Problem with binary data

I am writing a Smugmug photo uploader using rauth (nice library, thank you). However, I have encountered a problem.

The API specifies I attach the image as the main body:

data = open('image.jpg', 'rb').read()
response = smugmug.put('http://upload.smugmug.com/image.jpg',
   data=data,
   access_token=access_token,
   access_token_secret=access_token_secret,
   header_auth = True,
   headers={'X-Smug-AlbumID':str(album_id), 'X-Smug-Version':'1.2.2', 'X-Smug-ResponseType':'JSON'})

This mostly does not work and Smugmug responds that the signature is invalid. However, it somehow worked with one tiny (5x5) sample image, so I dug into the code to find the problem. Apparently in SignatureMethod._normalize_request_parameters(), the binary data gets detected as a string, and this causes it to be passed to the parse_qsl() method. Now for most images, the parse_qsl() method returns some kind of re-encoded version of the binary data, but for the tiny sample image it returns nothing, [ ].

Apparently Smugmug doesn't want the data to be included in the signature, and if parse_qsl() returns nothing, it is correctly excluded and everything works. For other images, the bindary data gets included and the signature is wrong.

If I add

normalized = []

right after

normalized = parse_qsl(request.data)

then uploading works.

Is Smugmug wrong to exclude the data? Or is rauth wrong to include it (in such a form at least)? Checking the OAuth specification, it says:

The request parameters are collected, sorted and concatenated into a normalized string:
   Parameters in the OAuth HTTP Authorization header excluding the realm parameter.
   Parameters in the HTTP POST request body (with a content-type of application/x-www-form-urlencoded).
   HTTP GET parameters added to the URLs in the query part (as defined by [RFC3986] section 3).

Technically, it doesn't say anything about the body/data of a PUT request. However, Smugmug specification allows POST as well and that has the same symptoms.

OAuth2Service.access_token should be automatically set by get_access_token

pprint(runkeeper.get_access_token(data = {'code': '7ff5d79fb7094253acc61244c897182a', 'redirect_uri': 'http://localhost:8080/oauth/runkeeper/set_token/'}).content)
>>> {u'access_token': u'56558399c20d878f84fd3c87c98dd55e', u'token_type': u'Bearer'}
print runkeeper.access_token
>>> None

Shouldn't get_access_token set self.access_token? I'd be happy to make this change.

issue with Stripe connect when using rauth 0.5

I got the following error using 0.5:

File "/app/.heroku/python/lib/python2.7/site-packages/rauth/service.py", line 441, in get_access_token
return parse_utf8_qsl(r.content)['access_token']

KeyError: 'access_token'

I switched back to 0.4.17 and it is working fine again.

This is using https://stripe.com/docs/connect/oauth

perhaps another issue with the provider returning JSON (saw this happened in another issue relating to google oauth)

if service returns a list (instead of dict), exception setting _unparsed_response

Requesting statuses/home_timeline.json from Twitter returns a list. This causes things to blow up when returning a json-parsed object and setting _unparsed_response on it.

Traceback (most recent call last):
  File "examples/twitter-stream.py", line 43, in <module>
    params=params)
  File "/Users/joe/src/litl/webauth/webauth/service.py", line 458, in request
    _response['_unparsed_response'] = response
TypeError: list indices must be integers, not str

I'm beginning to think that maybe we shouldn't return a dict but instead an object similar to Flask's request object or Requests' response object, from which we can get either the raw data or a parsed representation.

OAuth2 Flows

Hey. I'm glad to have found a nice OAuth2 lib on top of requests. Is there a plan to support other grant_type than 'authorization_code' such as 'client_credentials'? Or is there a nice way to do this already that I didn't find?

OAuth2 Example Needed

I got Twitter working perfectly using the OAuth1 example as a guide, but my attempts at using OAuth2 to connect to the Github API have not been successful.

My attempts so far:

gthub = OAuth2Service(
    name='github',
    consumer_key='mykey',
    consumer_secret='mysecret',
    access_token_url='https://github.com/loginoauth/access_token',
    authorize_url='https://github.com/login/oauth/authorize',
)

authorize_url = github.get_authorize_url()

This URL is opened client side, authorization is given, and then it redirects to http://myapplication.com/?code=somecode

From there I catch 'code' and attempt to get an access_token

oauth_token = github.get_access_token(
    code=somecode,
    grant_type='authorization_code',
    redirect_uri='http://myapplication.com',
)
print(oauth_token.content)

This however just dumps a bunch of Github HTML with no sign of any tokens.

I am sure I am probably doing something wrong.

Perhaps an example that uses the Github OAuth2 API or some other OAuth2 service might help?

OAuth2Service - bug with docs or code

The docs for the OAuth2Service state that to get the authorize url, you do the following:

# the return URL is used to validate the request
url = service.get_authorize_url(redirect_uri='http://example.com/',
                                params={'response_type': 'code'})

However, this actually encodes the entire params dict into the url. Here's the offending method:

https://github.com/litl/rauth/blob/master/rauth/service.py#L393

    def get_authorize_url(self, **params):
        '''
        Returns a formatted authorize URL.

        :param \*\*params: Additional keyworded arguments to be added to the
            URL querystring.
        :type \*\*params: dict
        '''

        params.update({'client_id': self.client_id})
        return self.authorize_url + '?' + urlencode(params)

Setting oauth_verifier when POSTing in the get_access_token phase of oauth 1.0a

How does one set the oauth_verifier parameter when POSTing at the get_access_token phase of oauth 1.0a ?

The twiiter rauth example shows the oauth_verifier being defined in 'params' using a GET call.
Defining oauth_verifier as a 'data' parameter using POST does not work.

I see that verifier is set to None in the OAuthObject class, https://github.com/litl/rauth/blob/master/rauth/oauth.py#L16 , but I see no way of assigning it a value.

oauth_verifier seems to default to '' as a result of code logic in the oauth_params function in hook.py https://github.com/litl/rauth/blob/master/rauth/hook.py#L145

Should there be a block of code added to the __call__ function in hook.py to have oauth_verifier handled in the same way as oauth_callback https://github.com/litl/rauth/blob/master/rauth/hook.py#L95 ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.