X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/8d1463ef24fa5d4255edfcff9d1c93e73b434a87..23a624ca1d40fa9cefd7229ac6152b79278a6517:/packages/oauth2/oauth2.el diff --git a/packages/oauth2/oauth2.el b/packages/oauth2/oauth2.el index 7966e629b..180f79174 100644 --- a/packages/oauth2/oauth2.el +++ b/packages/oauth2/oauth2.el @@ -1,9 +1,9 @@ ;;; 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 -;; Version: 0.1 +;; Version: 0.10 ;; Keywords: comm ;; This file is part of GNU Emacs. @@ -26,25 +26,29 @@ ;; 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: ")) @@ -73,9 +77,10 @@ It returns the code provided by the service." 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." @@ -87,25 +92,27 @@ 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 @@ -113,19 +120,23 @@ TOKEN should be obtained with `oauth2-request-access'." 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." @@ -138,7 +149,7 @@ This allows to store the token in an unique way." (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)) @@ -153,16 +164,19 @@ This allows to store the token in an unique way." :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)))) @@ -172,21 +186,53 @@ This allows to store the token in an unique way." (if (string-match-p "\?" url) "&" "?") "access_token=" (oauth2-token-access-token token))) +(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 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-synchronously (token url) - "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-buffer (url-retrieve-synchronously - (oauth2-url-append-access-token token url)))) - (if tokens-need-renew - (oauth2-url-retrieve-synchronously (oauth2-refresh-access token) url) - url-buffer))))) +(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)