My personal dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

oauth2.el 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. ;;; oauth2.el --- OAuth 2.0 Authorization Protocol
  2. ;; Copyright (C) 2011-2013 Free Software Foundation, Inc
  3. ;; Author: Julien Danjou <julien@danjou.info>
  4. ;; Version: 0.10
  5. ;; Keywords: comm
  6. ;; Modified by Daniel Martins <daniel.tritone@gmail.com>
  7. ;; The main change is to use a simple HTTP server in order to receive
  8. ;; the Oauth2 callback instead of having the user to copy and paste
  9. ;; the code by hand.
  10. ;; This file is part of GNU Emacs.
  11. ;; GNU Emacs is free software: you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation, either version 3 of the License, or
  14. ;; (at your option) any later version.
  15. ;; GNU Emacs is distributed in the hope that it will be useful,
  16. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;; GNU General Public License for more details.
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  21. ;;; Commentary:
  22. ;; Implementation of the OAuth 2.0 draft.
  23. ;;
  24. ;; The main entry point is `oauth2-auth-and-store' which will return a token
  25. ;; structure. This token structure can be then used with
  26. ;; `oauth2-url-retrieve-synchronously' or `oauth2-url-retrieve' to retrieve
  27. ;; any data that need OAuth authentication to be accessed.
  28. ;;
  29. ;; If the token needs to be refreshed, the code handles it automatically and
  30. ;; store the new value of the access token.
  31. ;;; Code:
  32. (eval-when-compile (require 'cl))
  33. (require 'plstore)
  34. (require 'json)
  35. (require 'url-http)
  36. (defvar url-http-method nil)
  37. (defvar url-http-data nil)
  38. (defvar url-http-extra-headers nil)
  39. (defvar url-callback-function nil)
  40. (defvar url-callback-arguments nil)
  41. (defun oauth2-request-authorization (auth-url client-id &optional scope state redirect-uri)
  42. "Request OAuth authorization at AUTH-URL by launching `browse-url'.
  43. CLIENT-ID is the client id provided by the provider.
  44. It returns the code provided by the service."
  45. (browse-url (concat auth-url
  46. (if (string-match-p "\?" auth-url) "&" "?")
  47. "client_id=" (url-hexify-string client-id)
  48. "&response_type=code"
  49. "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob"))
  50. (if scope (concat "&scope=" (url-hexify-string scope)) "")
  51. (if state (concat "&state=" (url-hexify-string state)) "")))
  52. (first (split-string (shell-command-to-string
  53. (format "python %s"
  54. (locate-library "spotify_oauth2_callback_server.py"))) "\n")))
  55. (defun oauth2-request-access-parse ()
  56. "Parse the result of an OAuth request."
  57. (goto-char (point-min))
  58. (when (search-forward-regexp "^$" nil t)
  59. (json-read)))
  60. (defun oauth2-make-access-request (url data)
  61. "Make an access request to URL using DATA in POST."
  62. (let ((url-request-method "POST")
  63. (url-request-data data)
  64. (url-request-extra-headers
  65. '(("Content-Type" . "application/x-www-form-urlencoded"))))
  66. (with-current-buffer (url-retrieve-synchronously url)
  67. (let ((data (oauth2-request-access-parse)))
  68. (kill-buffer (current-buffer))
  69. data))))
  70. (defstruct oauth2-token
  71. plstore
  72. plstore-id
  73. client-id
  74. client-secret
  75. access-token
  76. refresh-token
  77. token-url
  78. access-response)
  79. (defun oauth2-request-access (token-url client-id client-secret code &optional redirect-uri)
  80. "Request OAuth access at TOKEN-URL.
  81. The CODE should be obtained with `oauth2-request-authorization'.
  82. Return an `oauth2-token' structure."
  83. (when code
  84. (let ((result
  85. (oauth2-make-access-request
  86. token-url
  87. (concat
  88. "client_id=" client-id
  89. "&client_secret=" client-secret
  90. "&code=" code
  91. "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob"))
  92. "&grant_type=authorization_code"))))
  93. (make-oauth2-token :client-id client-id
  94. :client-secret client-secret
  95. :access-token (cdr (assoc 'access_token result))
  96. :refresh-token (cdr (assoc 'refresh_token result))
  97. :token-url token-url
  98. :access-response result))))
  99. ;;;###autoload
  100. (defun oauth2-refresh-access (token)
  101. "Refresh OAuth access TOKEN.
  102. TOKEN should be obtained with `oauth2-request-access'."
  103. (setf (oauth2-token-access-token token)
  104. (cdr (assoc 'access_token
  105. (oauth2-make-access-request
  106. (oauth2-token-token-url token)
  107. (concat "client_id=" (oauth2-token-client-id token)
  108. "&client_secret=" (oauth2-token-client-secret token)
  109. "&refresh_token=" (oauth2-token-refresh-token token)
  110. "&grant_type=refresh_token")))))
  111. ;; If the token has a plstore, update it
  112. (let ((plstore (oauth2-token-plstore token)))
  113. (when plstore
  114. (plstore-put plstore (oauth2-token-plstore-id token)
  115. nil `(:access-token
  116. ,(oauth2-token-access-token token)
  117. :refresh-token
  118. ,(oauth2-token-refresh-token token)
  119. :access-response
  120. ,(oauth2-token-access-response token)
  121. ))
  122. (plstore-save plstore)))
  123. token)
  124. ;;;###autoload
  125. (defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state redirect-uri)
  126. "Authenticate application via OAuth2."
  127. (oauth2-request-access
  128. token-url
  129. client-id
  130. client-secret
  131. (oauth2-request-authorization
  132. auth-url client-id scope state redirect-uri)
  133. redirect-uri))
  134. (defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore")
  135. "File path where store OAuth tokens."
  136. :group 'oauth2
  137. :type 'file)
  138. (defun oauth2-compute-id (auth-url token-url resource-url)
  139. "Compute an unique id based on URLs.
  140. This allows to store the token in an unique way."
  141. (secure-hash 'md5 (concat auth-url token-url resource-url)))
  142. ;;;###autoload
  143. (defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret &optional redirect-uri)
  144. "Request access to a resource and store it using `plstore'."
  145. ;; We store a MD5 sum of all URL
  146. (let* ((plstore (plstore-open oauth2-token-file))
  147. (id (oauth2-compute-id auth-url token-url resource-url))
  148. (plist (cdr (plstore-get plstore id))))
  149. ;; Check if we found something matching this access
  150. (if plist
  151. ;; We did, return the token object
  152. (make-oauth2-token :plstore plstore
  153. :plstore-id id
  154. :client-id client-id
  155. :client-secret client-secret
  156. :access-token (plist-get plist :access-token)
  157. :refresh-token (plist-get plist :refresh-token)
  158. :token-url token-url
  159. :access-response (plist-get plist :access-response))
  160. (let ((token (oauth2-auth auth-url token-url
  161. client-id client-secret resource-url nil redirect-uri)))
  162. ;; Set the plstore
  163. (setf (oauth2-token-plstore token) plstore)
  164. (setf (oauth2-token-plstore-id token) id)
  165. (plstore-put plstore id nil `(:access-token
  166. ,(oauth2-token-access-token token)
  167. :refresh-token
  168. ,(oauth2-token-refresh-token token)
  169. :access-response
  170. ,(oauth2-token-access-response token)))
  171. (plstore-save plstore)
  172. token))))
  173. (defun oauth2-url-append-access-token (token url)
  174. "Append access token to URL."
  175. (concat url
  176. (if (string-match-p "\?" url) "&" "?")
  177. "access_token=" (oauth2-token-access-token token)))
  178. (defvar oauth--url-advice nil)
  179. (defvar oauth--token-data nil)
  180. ;; FIXME: We should change URL so that this can be done without an advice.
  181. (defadvice url-http-handle-authentication (around oauth-hack activate)
  182. (if (not oauth--url-advice)
  183. ad-do-it
  184. (let ((url-request-method url-http-method)
  185. (url-request-data url-http-data)
  186. (url-request-extra-headers url-http-extra-headers)))
  187. (url-retrieve-internal (oauth2-url-append-access-token
  188. (oauth2-refresh-access (car oauth--token-data))
  189. (cdr oauth--token-data))
  190. url-callback-function
  191. url-callback-arguments)
  192. ;; This is to make `url' think it's done.
  193. (when (boundp 'success) (setq success t)) ;For URL library in Emacs<24.4.
  194. (setq ad-return-value t))) ;For URL library in Emacs≥24.4.
  195. ;;;###autoload
  196. (defun oauth2-url-retrieve-synchronously (token url &optional request-method request-data request-extra-headers)
  197. "Retrieve an URL synchronously using TOKEN to access it.
  198. TOKEN can be obtained with `oauth2-auth'."
  199. (let* ((oauth--token-data (cons token url)))
  200. (let ((oauth--url-advice t) ;Activate our advice.
  201. (url-request-method request-method)
  202. (url-request-data request-data)
  203. (url-request-extra-headers request-extra-headers))
  204. (url-retrieve-synchronously
  205. (oauth2-url-append-access-token token url)))))
  206. ;;;###autoload
  207. (defun oauth2-url-retrieve (token url callback &optional
  208. cbargs
  209. request-method request-data request-extra-headers)
  210. "Retrieve an URL asynchronously using TOKEN to access it.
  211. TOKEN can be obtained with `oauth2-auth'. CALLBACK gets called with CBARGS
  212. when finished. See `url-retrieve'."
  213. ;; TODO add support for SILENT and INHIBIT-COOKIES. How to handle this in `url-http-handle-authentication'.
  214. (let* ((oauth--token-data (cons token url)))
  215. (let ((oauth--url-advice t) ;Activate our advice.
  216. (url-request-method request-method)
  217. (url-request-data request-data)
  218. (url-request-extra-headers request-extra-headers))
  219. (url-retrieve
  220. (oauth2-url-append-access-token token url)
  221. callback cbargs))))
  222. ;;;; ChangeLog:
  223. ;; 2014-01-28 Rüdiger Sonderfeld <ruediger@c-plusplus.de>
  224. ;;
  225. ;; oauth2.el: Add support for async retrieve.
  226. ;;
  227. ;; * packages/oauth2/oauth2.el (oauth--tokens-need-renew): Remove.
  228. ;; (oauth--token-data): New variable.
  229. ;; (url-http-handle-authentication): Call `url-retrieve-internal'
  230. ;; directly instead of depending on `oauth--tokens-need-renew'.
  231. ;; (oauth2-url-retrieve-synchronously): Call `url-retrieve' once.
  232. ;; (oauth2-url-retrieve): New function.
  233. ;;
  234. ;; Signed-off-by: Rüdiger Sonderfeld <ruediger@c-plusplus.de>
  235. ;; Signed-off-by: Julien Danjou <julien@danjou.info>
  236. ;;
  237. ;; 2013-07-22 Stefan Monnier <monnier@iro.umontreal.ca>
  238. ;;
  239. ;; * oauth2.el: Only require CL at compile time and avoid flet.
  240. ;; (success): Don't defvar.
  241. ;; (oauth--url-advice, oauth--tokens-need-renew): New dynbind variables.
  242. ;; (url-http-handle-authentication): Add advice.
  243. ;; (oauth2-url-retrieve-synchronously): Use the advice instead of flet.
  244. ;;
  245. ;; 2013-06-29 Julien Danjou <julien@danjou.info>
  246. ;;
  247. ;; oauth2: release 0.9, require url-http
  248. ;;
  249. ;; This is needed so that the `flet' calls doesn't restore the overriden
  250. ;; function to an unbound one.
  251. ;;
  252. ;; Signed-off-by: Julien Danjou <julien@danjou.info>
  253. ;;
  254. ;; 2012-08-01 Julien Danjou <julien@danjou.info>
  255. ;;
  256. ;; oauth2: upgrade to 0.8, add missing require on cl
  257. ;;
  258. ;; 2012-07-03 Julien Danjou <julien@danjou.info>
  259. ;;
  260. ;; oauth2: store access-reponse, bump versino to 0.7
  261. ;;
  262. ;; 2012-06-25 Julien Danjou <julien@danjou.info>
  263. ;;
  264. ;; oauth2: add redirect-uri parameter, update to 0.6
  265. ;;
  266. ;; 2012-05-29 Julien Danjou <julien@danjou.info>
  267. ;;
  268. ;; * packages/oauth2/oauth2.el: Revert fix URL double escaping, update to
  269. ;; 0.5
  270. ;;
  271. ;; 2012-05-04 Julien Danjou <julien@danjou.info>
  272. ;;
  273. ;; * packages/oauth2/oauth2.el: Don't use aget, update to 0.4
  274. ;;
  275. ;; 2012-04-19 Julien Danjou <julien@danjou.info>
  276. ;;
  277. ;; * packages/oauth2/oauth2.el: Fix URL double escaping, update to 0.3
  278. ;;
  279. ;; 2011-12-20 Julien Danjou <julien@danjou.info>
  280. ;;
  281. ;; oauth2: update version 0.2
  282. ;;
  283. ;; * oauth2: update version to 0.2
  284. ;;
  285. ;; 2011-12-20 Julien Danjou <julien@danjou.info>
  286. ;;
  287. ;; oauth2: allow to use any HTTP request type
  288. ;;
  289. ;; * oauth2: allow to use any HTTP request type
  290. ;;
  291. ;; 2011-10-08 Julien Danjou <julien@danjou.info>
  292. ;;
  293. ;; * oauth2.el: Require json.
  294. ;; Fix compilation warning with success variable from url.el.
  295. ;;
  296. ;; 2011-09-26 Julien Danjou <julien@danjou.info>
  297. ;;
  298. ;; * packages/oauth2/oauth2.el (oauth2-request-authorization): Add missing
  299. ;; calls to url-hexify-string.
  300. ;;
  301. ;; 2011-09-26 Julien Danjou <julien@danjou.info>
  302. ;;
  303. ;; * packages/oauth2/oauth2.el: Reformat to avoid long lines.
  304. ;;
  305. ;; 2011-09-23 Julien Danjou <julien@danjou.info>
  306. ;;
  307. ;; New package oauth2
  308. ;;
  309. (provide 'oauth2)
  310. ;;; oauth2.el ends here