]> jfr.im git - z_archive/twitter.git/blame - twitter/oauth.py
bandaid unicode/str.encode-related crash bug
[z_archive/twitter.git] / twitter / oauth.py
CommitLineData
a6a7f763
MV
1"""
2Visit the Twitter developer page and create a new application:
3
4 https://dev.twitter.com/apps/new
5
6This will get you a CONSUMER_KEY and CONSUMER_SECRET.
7
8When users run your application they have to authenticate your app
9with their Twitter account. A few HTTP calls to twitter are required
10to do this. Please see the twitter.oauth_dance module to see how this
11is done. If you are making a command-line app, you can use the
12oauth_dance() function directly.
13
14Performing the "oauth dance" gets you an ouath token and oauth secret
15that authenticate the user with Twitter. You should save these for
16later so that the user doesn't have to do the oauth dance again.
17
18read_token_file and write_token_file are utility methods to read and
19write OAuth token and secret key values. The values are stored as
20strings in the file. Not terribly exciting.
21
22Finally, you can use the OAuth authenticator to connect to Twitter. In
23code it all goes like this::
3930cc7b 24
d4f3123e
MV
25 from twitter import *
26
a6a7f763
MV
27 MY_TWITTER_CREDS = os.path.expanduser('~/.my_app_credentials')
28 if not os.path.exists(MY_TWITTER_CREDS):
29 oauth_dance("My App Name", CONSUMER_KEY, CONSUMER_SECRET,
30 MY_TWITTER_CREDS)
31
32 oauth_token, oauth_secret = read_token_file(MY_TWITTER_CREDS)
33
34 twitter = Twitter(auth=OAuth(
35 oauth_token, oauth_token_secret, CONSUMER_KEY, CONSUMER_SECRET))
36
37 # Now work with Twitter
9319fd3a 38 twitter.statuses.update(status='Hello, world!')
a6a7f763
MV
39
40"""
41
42from __future__ import print_function
568331a9 43
568331a9 44from random import getrandbits
4073437d
BS
45from time import time
46
d100f654 47from .util import PY_3_OR_HIGHER
3930cc7b
MV
48
49try:
50 import urllib.parse as urllib_parse
aeabd5b8 51 from urllib.parse import urlencode
3930cc7b
MV
52except ImportError:
53 import urllib2 as urllib_parse
aeabd5b8 54 from urllib import urlencode
3930cc7b 55
568331a9
MH
56import hashlib
57import hmac
a2855195 58import base64
568331a9 59
5722b73f 60from .auth import Auth, MissingCredentialsError
3930cc7b
MV
61
62
1b31d642 63def write_token_file(filename, oauth_token, oauth_token_secret):
d828f28d
MV
64 """
65 Write a token file to hold the oauth token and oauth token secret.
66 """
1b31d642 67 oauth_file = open(filename, 'w')
f7e63802
MV
68 print(oauth_token, file=oauth_file)
69 print(oauth_token_secret, file=oauth_file)
1b31d642
MV
70 oauth_file.close()
71
72def read_token_file(filename):
d828f28d
MV
73 """
74 Read a token file and return the oauth token and oauth token secret.
75 """
1b31d642
MV
76 f = open(filename)
77 return f.readline().strip(), f.readline().strip()
78
79
568331a9 80class OAuth(Auth):
1cc9ab0b
MV
81 """
82 An OAuth authenticator.
83 """
568331a9 84 def __init__(self, token, token_secret, consumer_key, consumer_secret):
1cc9ab0b
MV
85 """
86 Create the authenticator. If you are in the initial stages of
87 the OAuth dance and don't yet have a token or token_secret,
88 pass empty strings for these params.
89 """
568331a9
MH
90 self.token = token
91 self.token_secret = token_secret
92 self.consumer_key = consumer_key
93 self.consumer_secret = consumer_secret
94
5722b73f
JS
95 if token_secret is None or consumer_secret is None:
96 raise MissingCredentialsError(
97 'You must supply strings for token_secret and consumer_secret, not None.')
98
568331a9
MH
99 def encode_params(self, base_url, method, params):
100 params = params.copy()
101
6c527e72
MV
102 if self.token:
103 params['oauth_token'] = self.token
104
568331a9
MH
105 params['oauth_consumer_key'] = self.consumer_key
106 params['oauth_signature_method'] = 'HMAC-SHA1'
107 params['oauth_version'] = '1.0'
108 params['oauth_timestamp'] = str(int(time()))
109 params['oauth_nonce'] = str(getrandbits(64))
110
f7e63802 111 enc_params = urlencode_noplus(sorted(params.items()))
568331a9 112
d8a0b4a2 113 key = self.consumer_secret + "&" + urllib_parse.quote(self.token_secret, safe='~')
568331a9
MH
114
115 message = '&'.join(
d8a0b4a2 116 urllib_parse.quote(i, safe='~') for i in [method.upper(), base_url, enc_params])
d828f28d 117
a2855195
MV
118 signature = (base64.b64encode(hmac.new(
119 key.encode('ascii'), message.encode('ascii'), hashlib.sha1)
120 .digest()))
d8a0b4a2 121 return enc_params + "&" + "oauth_signature=" + urllib_parse.quote(signature, safe='~')
568331a9
MH
122
123 def generate_headers(self):
124 return {}
125
126# apparently contrary to the HTTP RFCs, spaces in arguments must be encoded as
127# %20 rather than '+' when constructing an OAuth signature (and therefore
128# also in the request itself.)
129# So here is a specialized version which does exactly that.
13f259cf 130# In Python2, since there is no safe option for urlencode, we force it by hand
568331a9 131def urlencode_noplus(query):
d100f654 132 if not PY_3_OR_HIGHER:
aeabd5b8 133 new_query = []
13f259cf 134 TILDE = '____TILDE-PYTHON-TWITTER____'
aeabd5b8 135 for k,v in query:
136 if type(k) is unicode: k = k.encode('utf-8')
d290f579 137 k = str(k).replace("~", TILDE)
aeabd5b8 138 if type(v) is unicode: v = v.encode('utf-8')
d290f579 139 v = str(v).replace("~", TILDE)
aeabd5b8 140 new_query.append((k, v))
141 query = new_query
13f259cf 142 return urlencode(query).replace(TILDE, "~").replace("+", "%20")
be5f32da 143
13f259cf 144 return urlencode(query, safe='~').replace("+", "%20")