;;; oauth2.el --- OAuth 2.0 Authorization Protocol
-;; Copyright (C) 2011 Free Software Foundation, Inc
+;; Copyright (C) 2011-2013 Free Software Foundation, Inc
;; Author: Julien Danjou <julien@danjou.info>
-;; Version: 0.1
+;; Version: 0.10
;; Keywords: comm
;; This file is part of GNU Emacs.
;; Implementation of the OAuth 2.0 draft.
;;
;; The main entry point is `oauth2-auth-and-store' which will return a token
-;; structure. This token structure can be then used with
-;; `oauth2-url-retrieve-synchronously' to retrieve any data that need OAuth
-;; authentication to be accessed.
+;; structure. This token structure can be then used with
+;; `oauth2-url-retrieve-synchronously' or `oauth2-url-retrieve' to retrieve
+;; any data that need OAuth authentication to be accessed.
;;
;; If the token needs to be refreshed, the code handles it automatically and
;; store the new value of the access token.
;;; Code:
+(eval-when-compile (require 'cl))
(require 'plstore)
(require 'json)
+(require 'url-http)
-(defun oauth2-request-authorization (auth-url client-id &optional scope state)
+(defun oauth2-request-authorization (auth-url client-id &optional scope state redirect-uri)
"Request OAuth authorization at AUTH-URL by launching `browse-url'.
CLIENT-ID is the client id provided by the provider.
It returns the code provided by the service."
(browse-url (concat auth-url
(if (string-match-p "\?" auth-url) "&" "?")
"client_id=" (url-hexify-string client-id)
- "&response_type=code&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
+ "&response_type=code"
+ "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob"))
(if scope (concat "&scope=" (url-hexify-string scope)) "")
(if state (concat "&state=" (url-hexify-string state)) "")))
(read-string "Enter the code your browser displayed: "))
client-secret
access-token
refresh-token
- token-url)
+ token-url
+ access-response)
-(defun oauth2-request-access (token-url client-id client-secret code)
+(defun oauth2-request-access (token-url client-id client-secret code &optional redirect-uri)
"Request OAuth access at TOKEN-URL.
The CODE should be obtained with `oauth2-request-authorization'.
Return an `oauth2-token' structure."
"client_id=" client-id
"&client_secret=" client-secret
"&code=" code
- "&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code"))))
+ "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob"))
+ "&grant_type=authorization_code"))))
(make-oauth2-token :client-id client-id
:client-secret client-secret
- :access-token (aget result 'access_token)
- :refresh-token (aget result 'refresh_token)
- :token-url token-url))))
+ :access-token (cdr (assoc 'access_token result))
+ :refresh-token (cdr (assoc 'refresh_token result))
+ :token-url token-url
+ :access-response result))))
;;;###autoload
(defun oauth2-refresh-access (token)
"Refresh OAuth access TOKEN.
TOKEN should be obtained with `oauth2-request-access'."
(setf (oauth2-token-access-token token)
- (aget (oauth2-make-access-request
- (oauth2-token-token-url token)
- (concat "client_id=" (oauth2-token-client-id token)
- "&client_secret=" (oauth2-token-client-secret token)
- "&refresh_token=" (oauth2-token-refresh-token token)
- "&grant_type=refresh_token"))
- 'access_token))
+ (cdr (assoc 'access_token
+ (oauth2-make-access-request
+ (oauth2-token-token-url token)
+ (concat "client_id=" (oauth2-token-client-id token)
+ "&client_secret=" (oauth2-token-client-secret token)
+ "&refresh_token=" (oauth2-token-refresh-token token)
+ "&grant_type=refresh_token")))))
;; If the token has a plstore, update it
(let ((plstore (oauth2-token-plstore token)))
(when plstore
nil `(:access-token
,(oauth2-token-access-token token)
:refresh-token
- ,(oauth2-token-refresh-token token)))
+ ,(oauth2-token-refresh-token token)
+ :access-response
+ ,(oauth2-token-access-response token)
+ ))
(plstore-save plstore)))
token)
;;;###autoload
-(defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state)
+(defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state redirect-uri)
"Authenticate application via OAuth2."
(oauth2-request-access
token-url
client-id
client-secret
(oauth2-request-authorization
- auth-url client-id scope state)))
+ auth-url client-id scope state redirect-uri)
+ redirect-uri))
(defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore")
"File path where store OAuth tokens."
(secure-hash 'md5 (concat auth-url token-url resource-url)))
;;;###autoload
-(defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret)
+(defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret &optional redirect-uri)
"Request access to a resource and store it using `plstore'."
;; We store a MD5 sum of all URL
(let* ((plstore (plstore-open oauth2-token-file))
:client-secret client-secret
:access-token (plist-get plist :access-token)
:refresh-token (plist-get plist :refresh-token)
- :token-url token-url)
+ :token-url token-url
+ :access-response (plist-get plist :access-response))
(let ((token (oauth2-auth auth-url token-url
- client-id client-secret resource-url)))
+ client-id client-secret resource-url nil redirect-uri)))
;; Set the plstore
(setf (oauth2-token-plstore token) plstore)
(setf (oauth2-token-plstore-id token) id)
(plstore-put plstore id nil `(:access-token
,(oauth2-token-access-token token)
:refresh-token
- ,(oauth2-token-refresh-token token)))
+ ,(oauth2-token-refresh-token token)
+ :access-response
+ ,(oauth2-token-access-response token)))
(plstore-save plstore)
token))))
(if (string-match-p "\?" url) "&" "?")
"access_token=" (oauth2-token-access-token token)))
-;; Local variable from `url'
-;; defined here to avoid compile warning
-(defvar success)
+(defvar oauth--url-advice nil)
+(defvar oauth--token-data)
+
+;; FIXME: We should change URL so that this can be done without an advice.
+(defadvice url-http-handle-authentication (around oauth-hack activate)
+ (if (not oauth--url-advice)
+ ad-do-it
+ (let ((url-request-method url-http-method)
+ (url-request-data url-http-data)
+ (url-request-extra-headers url-http-extra-headers)))
+ (url-retrieve-internal (oauth2-url-append-access-token
+ (oauth2-refresh-access (car oauth--token-data))
+ (cdr oauth--token-data))
+ url-callback-function
+ url-callback-arguments)
+ ;; This is to make `url' think it's done.
+ (when (boundp 'success) (setq success t)) ;For URL library in Emacs<24.4.
+ (setq ad-return-value t))) ;For URL library in Emacsā„24.4.
;;;###autoload
(defun oauth2-url-retrieve-synchronously (token url &optional request-method request-data request-extra-headers)
- "Retrieve an URL synchronously using TOKENS to access it.
-TOKENS can be obtained with `oauth2-auth'."
- (let (tokens-need-renew)
- (flet ((url-http-handle-authentication (proxy)
- (setq tokens-need-renew t)
- ;; This is to make `url' think
- ;; it's done.
- (setq success t)))
- (let ((url-request-method request-method)
- (url-request-data request-data)
- (url-request-extra-headers request-extra-headers)
- (url-buffer))
- (setq url-buffer (url-retrieve-synchronously
- (oauth2-url-append-access-token token url)))
- (if tokens-need-renew
- (oauth2-url-retrieve-synchronously (oauth2-refresh-access token) url request-method request-data request-extra-headers)
- url-buffer)))))
+ "Retrieve an URL synchronously using TOKEN to access it.
+TOKEN can be obtained with `oauth2-auth'."
+ (let* ((oauth--token-data (cons token url)))
+ (let ((oauth--url-advice t) ;Activate our advice.
+ (url-request-method request-method)
+ (url-request-data request-data)
+ (url-request-extra-headers request-extra-headers))
+ (url-retrieve-synchronously
+ (oauth2-url-append-access-token token url)))))
+
+;;;###autoload
+(defun oauth2-url-retrieve (token url callback &optional
+ cbargs
+ request-method request-data request-extra-headers)
+ "Retrieve an URL asynchronously using TOKEN to access it.
+TOKEN can be obtained with `oauth2-auth'. CALLBACK gets called with CBARGS
+when finished. See `url-retrieve'."
+ ;; TODO add support for SILENT and INHIBIT-COOKIES. How to handle this in `url-http-handle-authentication'.
+ (let* ((oauth--token-data (cons token url)))
+ (let ((oauth--url-advice t) ;Activate our advice.
+ (url-request-method request-method)
+ (url-request-data request-data)
+ (url-request-extra-headers request-extra-headers))
+ (url-retrieve
+ (oauth2-url-append-access-token token url)
+ callback cbargs))))
(provide 'oauth2)