PageRenderTime 166ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/libgreader/auth.py

https://github.com/askedrelic/libgreader
Python | 387 lines | 262 code | 46 blank | 79 comment | 37 complexity | 8c662265bb14e186ccd60d5c5e77a586 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. import requests
  3. from requests.compat import urlencode, urlparse
  4. # import urllib2
  5. import time
  6. try:
  7. import json
  8. except:
  9. # Python 2.6 support
  10. import simplejson as json
  11. try:
  12. import oauth2 as oauth
  13. has_oauth = True
  14. except:
  15. has_oauth = False
  16. try:
  17. import httplib2
  18. has_httplib2 = True
  19. except:
  20. has_httplib2 = False
  21. from .googlereader import GoogleReader
  22. from .url import ReaderUrl
  23. def toUnicode(obj, encoding='utf-8'):
  24. return obj
  25. # if isinstance(obj, basestring):
  26. # if not isinstance(obj, unicode):
  27. # obj = unicode(obj, encoding)
  28. # return obj
  29. class AuthenticationMethod(object):
  30. """
  31. Defines an interface for authentication methods, must have a get method
  32. make this abstract?
  33. 1. auth on setup
  34. 2. need to have GET method
  35. """
  36. def __init__(self):
  37. self.client = "libgreader" #@todo: is this needed?
  38. def getParameters(self, extraargs=None):
  39. parameters = {'ck':time.time(), 'client':self.client}
  40. if extraargs:
  41. parameters.update(extraargs)
  42. return urlencode(parameters)
  43. def postParameters(self, post=None):
  44. return post
  45. class ClientAuthMethod(AuthenticationMethod):
  46. """
  47. Auth type which requires a valid Google Reader username and password
  48. """
  49. CLIENT_URL = 'https://www.google.com/accounts/ClientLogin'
  50. def __init__(self, username, password):
  51. super(ClientAuthMethod, self).__init__()
  52. self.username = username
  53. self.password = password
  54. self.auth_token = self._getAuth()
  55. self.token = self._getToken()
  56. def postParameters(self, post=None):
  57. post.update({'T': self.token})
  58. return super(ClientAuthMethod, self).postParameters(post)
  59. def get(self, url, parameters=None):
  60. """
  61. Convenience method for requesting to google with proper cookies/params.
  62. """
  63. getString = self.getParameters(parameters)
  64. headers = {'Authorization':'GoogleLogin auth=%s' % self.auth_token}
  65. req = requests.get(url + "?" + getString, headers=headers)
  66. return req.text
  67. def post(self, url, postParameters=None, urlParameters=None):
  68. """
  69. Convenience method for requesting to google with proper cookies/params.
  70. """
  71. if urlParameters:
  72. url = url + "?" + self.getParameters(urlParameters)
  73. headers = {'Authorization':'GoogleLogin auth=%s' % self.auth_token,
  74. 'Content-Type': 'application/x-www-form-urlencoded'
  75. }
  76. postString = self.postParameters(postParameters)
  77. req = requests.post(url, data=postString, headers=headers)
  78. return req.text
  79. def _getAuth(self):
  80. """
  81. Main step in authorizing with Reader.
  82. Sends request to Google ClientAuthMethod URL which returns an Auth token.
  83. Returns Auth token or raises IOError on error.
  84. """
  85. parameters = {
  86. 'service' : 'reader',
  87. 'Email' : self.username,
  88. 'Passwd' : self.password,
  89. 'accountType' : 'GOOGLE'}
  90. req = requests.post(ClientAuthMethod.CLIENT_URL, data=parameters)
  91. if req.status_code != 200:
  92. raise IOError("Error getting the Auth token, have you entered a"
  93. "correct username and password?")
  94. data = req.text
  95. #Strip newline and non token text.
  96. token_dict = dict(x.split('=') for x in data.split('\n') if x)
  97. return token_dict["Auth"]
  98. def _getToken(self):
  99. """
  100. Second step in authorizing with Reader.
  101. Sends authorized request to Reader token URL and returns a token value.
  102. Returns token or raises IOError on error.
  103. """
  104. headers = {'Authorization':'GoogleLogin auth=%s' % self.auth_token}
  105. req = requests.get(ReaderUrl.API_URL + 'token', headers=headers)
  106. if req.status_code != 200:
  107. raise IOError("Error getting the Reader token.")
  108. return req.content
  109. class OAuthMethod(AuthenticationMethod):
  110. """
  111. Loose wrapper around OAuth2 lib. Kinda awkward.
  112. """
  113. GOOGLE_URL = 'https://www.google.com/accounts/'
  114. REQUEST_TOKEN_URL = (GOOGLE_URL + 'OAuthGetRequestToken?scope=%s' %
  115. ReaderUrl.READER_BASE_URL)
  116. AUTHORIZE_URL = GOOGLE_URL + 'OAuthAuthorizeToken'
  117. ACCESS_TOKEN_URL = GOOGLE_URL + 'OAuthGetAccessToken'
  118. def __init__(self, consumer_key, consumer_secret):
  119. if not has_oauth:
  120. raise ImportError("No module named oauth2")
  121. super(OAuthMethod, self).__init__()
  122. self.oauth_key = consumer_key
  123. self.oauth_secret = consumer_secret
  124. self.consumer = oauth.Consumer(self.oauth_key, self.oauth_secret)
  125. self.authorized_client = None
  126. self.token_key = None
  127. self.token_secret = None
  128. self.callback = None
  129. self.username = "OAuth"
  130. def setCallback(self, callback_url):
  131. self.callback = '&oauth_callback=%s' % callback_url
  132. def setRequestToken(self):
  133. # Step 1: Get a request token. This is a temporary token that is used for
  134. # having the user authorize an access token and to sign the request to obtain
  135. # said access token.
  136. client = oauth.Client(self.consumer)
  137. if not self.callback:
  138. resp, content = client.request(OAuthMethod.REQUEST_TOKEN_URL)
  139. else:
  140. resp, content = client.request(OAuthMethod.REQUEST_TOKEN_URL + self.callback)
  141. if int(resp['status']) != 200:
  142. raise IOError("Error setting Request Token")
  143. token_dict = dict(urlparse.parse_qsl(content))
  144. self.token_key = token_dict['oauth_token']
  145. self.token_secret = token_dict['oauth_token_secret']
  146. def setAndGetRequestToken(self):
  147. self.setRequestToken()
  148. return (self.token_key, self.token_secret)
  149. def buildAuthUrl(self, token_key=None):
  150. if not token_key:
  151. token_key = self.token_key
  152. #return auth url for user to click or redirect to
  153. return "%s?oauth_token=%s" % (OAuthMethod.AUTHORIZE_URL, token_key)
  154. def setAccessToken(self):
  155. self.setAccessTokenFromCallback(self.token_key, self.token_secret, None)
  156. def setAccessTokenFromCallback(self, token_key, token_secret, verifier):
  157. token = oauth.Token(token_key, token_secret)
  158. #step 2 depends on callback
  159. if verifier:
  160. token.set_verifier(verifier)
  161. client = oauth.Client(self.consumer, token)
  162. resp, content = client.request(OAuthMethod.ACCESS_TOKEN_URL, "POST")
  163. if int(resp['status']) != 200:
  164. raise IOError("Error setting Access Token")
  165. access_token = dict(urlparse.parse_qsl(content))
  166. #created Authorized client using access tokens
  167. self.authFromAccessToken(access_token['oauth_token'],
  168. access_token['oauth_token_secret'])
  169. def authFromAccessToken(self, oauth_token, oauth_token_secret):
  170. self.token_key = oauth_token
  171. self.token_secret = oauth_token_secret
  172. token = oauth.Token(oauth_token,oauth_token_secret)
  173. self.authorized_client = oauth.Client(self.consumer, token)
  174. def getAccessToken(self):
  175. return (self.token_key, self.token_secret)
  176. def get(self, url, parameters=None):
  177. if self.authorized_client:
  178. getString = self.getParameters(parameters)
  179. #can't pass in urllib2 Request object here?
  180. resp, content = self.authorized_client.request(url + "?" + getString)
  181. return toUnicode(content)
  182. else:
  183. raise IOError("No authorized client available.")
  184. def post(self, url, postParameters=None, urlParameters=None):
  185. """
  186. Convenience method for requesting to google with proper cookies/params.
  187. """
  188. if self.authorized_client:
  189. if urlParameters:
  190. getString = self.getParameters(urlParameters)
  191. req = urllib2.Request(url + "?" + getString)
  192. else:
  193. req = urllib2.Request(url)
  194. postString = self.postParameters(postParameters)
  195. resp,content = self.authorized_client.request(req, method="POST", body=postString)
  196. return toUnicode(content)
  197. else:
  198. raise IOError("No authorized client available.")
  199. class OAuth2Method(AuthenticationMethod):
  200. '''
  201. Google OAuth2 base method.
  202. '''
  203. GOOGLE_URL = 'https://accounts.google.com'
  204. AUTHORIZATION_URL = GOOGLE_URL + '/o/oauth2/auth'
  205. ACCESS_TOKEN_URL = GOOGLE_URL + '/o/oauth2/token'
  206. SCOPE = [
  207. 'https://www.googleapis.com/auth/userinfo.email',
  208. 'https://www.googleapis.com/auth/userinfo.profile',
  209. 'https://www.google.com/reader/api/',
  210. ]
  211. def __init__(self, client_id, client_secret):
  212. super(OAuth2Method, self).__init__()
  213. self.client_id = client_id
  214. self.client_secret = client_secret
  215. self.authorized_client = None
  216. self.code = None
  217. self.access_token = None
  218. self.action_token = None
  219. self.redirect_uri = None
  220. self.username = "OAuth2"
  221. def setRedirectUri(self, redirect_uri):
  222. self.redirect_uri = redirect_uri
  223. def buildAuthUrl(self):
  224. args = {
  225. 'client_id': self.client_id,
  226. 'redirect_uri': self.redirect_uri,
  227. 'scope': ' '.join(self.SCOPE),
  228. 'response_type': 'code',
  229. }
  230. return self.AUTHORIZATION_URL + '?' + urlencode(args)
  231. def setActionToken(self):
  232. '''
  233. Get action to prevent XSRF attacks
  234. http://code.google.com/p/google-reader-api/wiki/ActionToken
  235. TODO: mask token expiring? handle regenerating?
  236. '''
  237. self.action_token = self.get(ReaderUrl.ACTION_TOKEN_URL)
  238. def setAccessToken(self):
  239. params = {
  240. 'grant_type': 'authorization_code', # request auth code
  241. 'code': self.code, # server response code
  242. 'client_id': self.client_id,
  243. 'client_secret': self.client_secret,
  244. 'redirect_uri': self.redirect_uri
  245. }
  246. headers = {'Content-Type': 'application/x-www-form-urlencoded'}
  247. request = requests.post(self.ACCESS_TOKEN_URL, data=params,
  248. headers=headers)
  249. if request.status_code != 200:
  250. raise IOError('Error getting Access Token')
  251. response = request.json()
  252. if 'access_token' not in response:
  253. raise IOError('Error getting Access Token')
  254. else:
  255. self.authFromAccessToken(response['access_token'])
  256. def authFromAccessToken(self, access_token):
  257. self.access_token = access_token
  258. def get(self, url, parameters=None):
  259. """
  260. Convenience method for requesting to google with proper cookies/params.
  261. """
  262. if not self.access_token:
  263. raise IOError("No authorized client available.")
  264. if parameters is None:
  265. parameters = {}
  266. parameters.update({'access_token': self.access_token, 'alt': 'json'})
  267. request = requests.get(url + '?' + self.getParameters(parameters))
  268. if request.status_code != 200:
  269. return None
  270. else:
  271. return toUnicode(request.text)
  272. def post(self, url, postParameters=None, urlParameters=None):
  273. """
  274. Convenience method for requesting to google with proper cookies/params.
  275. """
  276. if not self.access_token:
  277. raise IOError("No authorized client available.")
  278. if not self.action_token:
  279. raise IOError("Need to generate action token.")
  280. if urlParameters is None:
  281. urlParameters = {}
  282. headers = {'Authorization': 'Bearer ' + self.access_token,
  283. 'Content-Type': 'application/x-www-form-urlencoded'}
  284. postParameters.update({'T':self.action_token})
  285. request = requests.post(url + '?' + self.getParameters(urlParameters),
  286. data=postParameters, headers=headers)
  287. if request.status_code != 200:
  288. return None
  289. else:
  290. return toUnicode(request.text)
  291. class GAPDecoratorAuthMethod(AuthenticationMethod):
  292. """
  293. An adapter to work with Google API for Python OAuth2 wrapper.
  294. Especially useful when deploying to Google AppEngine.
  295. """
  296. def __init__(self, credentials):
  297. """
  298. Initialize auth method with existing credentials.
  299. Args:
  300. credentials: OAuth2 credentials obtained via GAP OAuth2 library.
  301. """
  302. if not has_httplib2:
  303. raise ImportError("No module named httplib2")
  304. super(GAPDecoratorAuthMethod, self).__init__()
  305. self._http = None
  306. self._credentials = credentials
  307. self._action_token = None
  308. def _setupHttp(self):
  309. """
  310. Setup an HTTP session authorized by OAuth2.
  311. """
  312. if self._http == None:
  313. http = httplib2.Http()
  314. self._http = self._credentials.authorize(http)
  315. def get(self, url, parameters=None):
  316. """
  317. Implement libgreader's interface for authenticated GET request
  318. """
  319. if self._http == None:
  320. self._setupHttp()
  321. uri = url + "?" + self.getParameters(parameters)
  322. response, content = self._http.request(uri, "GET")
  323. return content
  324. def post(self, url, postParameters=None, urlParameters=None):
  325. """
  326. Implement libgreader's interface for authenticated POST request
  327. """
  328. if self._action_token == None:
  329. self._action_token = self.get(ReaderUrl.ACTION_TOKEN_URL)
  330. if self._http == None:
  331. self._setupHttp()
  332. uri = url + "?" + self.getParameters(urlParameters)
  333. postParameters.update({'T':self._action_token})
  334. body = self.postParameters(postParameters)
  335. response, content = self._http.request(uri, "POST", body=body)
  336. return content