branch: externals/url-http-oauth
commit fcb592965458196b4e0982b0c3fc3fd6a4bbc16e
Author: Thomas Fitzsimmons <fitz...@fitzsim.org>
Commit: Thomas Fitzsimmons <fitz...@fitzsim.org>

    Fix auth-source lookup conflicts and config cdrs
    
    * url-http-oauth.el (url-http-oauth-interpose): Change url to
    urls.  Assume conses, not lists in url-settings.
    (url-http-oauth-uninterpose): Likewise.
    (url-http-no-retry): Define special variable.
    (url-http-oauth-auth-source-search): Do not warn on zero results.
    (url-http-oauth-encode-scope): New function.
    (url-http-oauth-auth-info-password): Likewise.
    (url-http-oauth-json-parse-buffer): Likewise.
    (url-http-oauth-get-access-token-grant): Assume conses, not lists
    in url-settings.  Do not set auth-source-do-cache to nil.
    Simplify url-http-oauth-auth-source-search call.  Encode scope
    with no spaces for authinfo storage.  Use
    url-http-oauth-json-parse-buffer instead of json-parse-buffer
    (url-http-oauth-authorization-url): Assume conses, not lists in
    url-settings.  Apply list instead of using macro for
    url-build-query-string argument.
    (url-http-oauth-get-bearer): Use dummy "BEARER" user name.
    Simplify call to auth-source-search.
---
 url-http-oauth.el | 135 +++++++++++++++++++++++++++++++++---------------------
 1 file changed, 83 insertions(+), 52 deletions(-)

diff --git a/url-http-oauth.el b/url-http-oauth.el
index 40e7b4e72f..1591a0eaa6 100644
--- a/url-http-oauth.el
+++ b/url-http-oauth.el
@@ -35,6 +35,7 @@
 (require 'url-auth)
 (require 'url-http)
 (require 'url-util)
+(require 'json)
 
 (defvar url-http-oauth--interposed nil
   "A hash table mapping URL strings to lists of OAuth 2.0 settings.")
@@ -80,24 +81,30 @@ CLIENT-SECRET-METHOD is the symbol `prompt' if a client 
secret is
 required, nil otherwise."
   (unless url-http-oauth--interposed
     (setq url-http-oauth--interposed (make-hash-table :test #'equal)))
-  (let* ((url (cadr (assoc "url" url-settings)))
-         (key (url-http-oauth-url-string url))
+  (let* ((urls (cdr (assoc "urls" url-settings)))
          (client-secret-method
-          (cadr (assoc "client-secret-method" url-settings))))
+          (cdr (assoc "client-secret-method" url-settings))))
     (unless (or (eq client-secret-method 'prompt) (eq client-secret-method 
nil))
       (error "Unrecognized client-secret-method value"))
-    (puthash key url-settings url-http-oauth--interposed)))
+    (dolist (url urls)
+      (puthash (url-http-oauth-url-string url) url-settings
+               url-http-oauth--interposed))))
 
 (defun url-http-oauth-uninterpose (url-settings)
   "Arrange for Emacs not to use OAuth 2.0 when accessing URL in URL-SETTINGS.
 This function does the opposite of `url-http-oauth-interpose'."
   (when url-http-oauth--interposed
-    (let* ((url (cadr (assoc "url" url-settings)))
-           (key (url-http-oauth-url-string url)))
-      (remhash key url-http-oauth--interposed))))
+    (let* ((urls (cdr (assoc "urls" url-settings))))
+      (dolist (url urls)
+        (remhash (url-http-oauth-url-string url)
+                 url-http-oauth--interposed)))))
 
 (defvar url-http-response-status)
 (defvar auth-source-creation-prompts)
+;; FIXME: if anything goes wrong during the authentication steps,
+;; `url-http-end-of-document-sentinel' calls back into
+;; `url-oauth-auth' somehow.  Maybe `url-http-no-retry' can help here?
+(defvar url-http-no-retry)
 
 (defun url-http-oauth-port (url)
   "Return port of URL object.
@@ -110,8 +117,8 @@ Assume an HTTPS URL that does not specify a port uses 443."
 (defun url-http-oauth-auth-source-search (&rest spec)
   "Like `auth-source-search' but search for all of SPEC in all backends.
 Filter out nil spec entries prior to searching."
-  (let* ((auth-source-do-cache nil)
-         (all (apply #'auth-source-search :max 5001 spec)) ; no :max 'all
+  (let* ((auth-source-do-cache nil) ; do not cache nil result.
+         (all (apply #'auth-source-search :max 5001 spec)) ; hmm, no :max 'all.
          (spec (cl-loop for i below (length spec) by 2
                         unless (null (nth (1+ i) spec))
                         collect (nth i spec)
@@ -120,40 +127,56 @@ Filter out nil spec entries prior to searching."
          (result (cl-loop for entry in all
                           when (auth-source-specmatchp spec entry)
                           collect entry)))
-    (unless (eq (length result) 1)
+    (unless (or (eq (length result) 0)
+                (eq (length result) 1))
       (warn "url-http-oauth-auth-source-search produced multiple results for 
%s"
             spec))
     result))
 
+(defun url-http-oauth-encode-scope (scope)
+  "Replace spaces in SCOPE with plus signs."
+  (replace-regexp-in-string " " "+" scope))
+
+;; Backport of `auth-info-password'.
+(defun url-http-oauth-auth-info-password (auth-info)
+  "Return the :secret password from the AUTH-INFO."
+  (let ((secret (plist-get auth-info :secret)))
+    (if (functionp secret)
+        (funcall secret)
+      secret)))
+
+;; Backport (roughly) of `json-parse-buffer'.
+(defun url-http-oauth-json-parse-buffer ()
+  "See `json-parse-buffer'."
+  (let ((json-object-type 'hash-table))
+    (json-read-from-string
+     (buffer-substring (point) (point-max)))))
+
 (defun url-http-oauth-get-access-token-grant (url code)
   "Get an access token for URL using CODE."
   (let* ((url-request-method "POST")
          (url-settings (url-http-oauth-settings url))
          (access-token-object
           (url-http-oauth-url-object
-           (cadr (assoc "access-token-endpoint" url-settings))))
-         (client-identifier (cadr (assoc "client-identifier" url-settings)))
-         (scope (cadr (assoc "scope" url-settings)))
-         (client-secret-method (cadr (assoc "client-secret-method"
-                                            url-settings)))
+           (cdr (assoc "access-token-endpoint" url-settings))))
+         (client-identifier (cdr (assoc "client-identifier" url-settings)))
+         (scope (cdr (assoc "scope" url-settings)))
+         (client-secret-method (cdr (assoc "client-secret-method"
+                                           url-settings)))
          (auth-result
           (when client-secret-method
             (car (let* ((auth-source-creation-prompts
-                         '((secret . "Client secret for %u at %h")))
-                        ;; Do not cache nil result.
-                        (auth-source-do-cache nil)
+                         '((secret . "Client secret for %u at %h: ")))
                         (spec (list :user client-identifier
                                     :host (url-host access-token-object)
                                     :port (url-http-oauth-port
                                            access-token-object)
                                     :path (url-filename access-token-object)
-                                    :scope scope))
-                        (existing-entry
-                         (apply #'url-http-oauth-auth-source-search spec)))
-                   (or existing-entry
-                       (apply #'auth-source-search
-                              :create '(path scope) spec))))))
-         (client-secret (auth-info-password auth-result))
+                                    :scope
+                                    (url-http-oauth-encode-scope scope))))
+                   (or (apply #'url-http-oauth-auth-source-search spec)
+                       (apply #'auth-source-search :create '(path scope) 
spec))))))
+         (client-secret (url-http-oauth-auth-info-password auth-result))
          (save-function (plist-get auth-result :save-function))
          (authorization (when client-secret
                           (concat
@@ -176,7 +199,7 @@ Filter out nil spec entries prior to searching."
           (progn
             (goto-char (point-min))
             (re-search-forward "\n\n")
-            (let* ((grant (json-parse-buffer))
+            (let* ((grant (url-http-oauth-json-parse-buffer))
                    (type (gethash "token_type" grant)))
               (unless (equal type "bearer" )
                 (error "Unrecognized token type %s for %s at %s" type
@@ -206,14 +229,16 @@ The time is in seconds since the epoch."
 
 (defun url-http-oauth-authorization-url (url-settings)
   "Return the authorization URL for URL-SETTINGS."
-  (let ((base (cadr (assoc "authorization-endpoint" url-settings)))
+  (let ((base (cdr (assoc "authorization-endpoint" url-settings)))
         (client
-         (list "client_id" (cadr (assoc "client-identifier" url-settings))))
+         (list "client_id" (cdr (assoc "client-identifier" url-settings))))
         (response-type (list "response_type" "code"))
-        (scope (assoc "scope" url-settings))
-        (extra (cadr (assoc "authorization-extra-arguments" url-settings))))
+        (scope (list "scope" (cdr (assoc "scope" url-settings))))
+        (extra (mapcar (lambda (entry)
+                         (list (car entry) (cdr entry)))
+                (cdr (assoc "authorization-extra-arguments" url-settings)))))
     (concat base "?" (url-build-query-string
-                      `(,client ,response-type ,scope ,@extra)))))
+                      (apply #'list client response-type scope extra)))))
 
 (defun url-http-oauth-get-bearer (url)
   "Prompt the user with the authorization endpoint for URL.
@@ -222,12 +247,12 @@ URL is a parsed object."
          (url-settings (url-http-oauth-settings url))
          (path-and-query (url-path-and-query url))
          (path (car path-and-query))
-         (scope (cadr (assoc "scope" url-settings)))
-         (bearer-current (auth-info-password
+         (scope (cdr (assoc "scope" url-settings)))
+         (bearer-current (url-http-oauth-auth-info-password
                           (car
                            (let ((auth-source-do-cache nil))
                              (url-http-oauth-auth-source-search
-                              :user (url-user url)
+                              :user "BEARER"
                               :host (url-host url)
                               :port (url-http-oauth-port url)
                               :path path
@@ -244,24 +269,30 @@ URL is a parsed object."
                 (url-http-oauth-extract-authorization-code response-url))
                (grant (url-http-oauth-get-access-token-grant url code))
                (bearer-retrieved (gethash "access_token" grant))
-               (spec (list :user (or (url-user url) "")
-                           :host (url-host url)
-                           :port (url-http-oauth-port url)
-                           :path path
-                           :scope (if (string= (gethash "scope" grant)
-                                               scope)
-                                      scope
-                                    (error
-                                     (concat "url-http-oauth:"
-                                             " Returned scope did not"
-                                             " match requested scope")))
-                           :expiry (url-http-oauth-expiry-string grant)
-                           :secret bearer-retrieved))
-               (auth-result
-                (unless (apply #'url-http-oauth-auth-source-search spec)
-                  (let ((auth-source-do-cache nil))
-                    (apply #'auth-source-search
-                           :create '(path scope expiry) spec))))
+               (auth-result (auth-source-search
+                             :create '(path scope expiry)
+                             ;; If :user is nil, then
+                             ;; (auth-source-search :create ...) will
+                             ;; find the client-identifier username.
+                             ;; :user isn't used for bearer tokens
+                             ;; anyway, so use this dummy name to
+                             ;; differentiate the bearer token
+                             ;; authinfo line from the
+                             ;; client-identifier/client-secret
+                             ;; authinfo line.
+                             :user "BEARER"
+                             :host (url-host url)
+                             :port (url-http-oauth-port url)
+                             :path path
+                             :scope (if (string= (gethash "scope" grant)
+                                                 scope)
+                                        (url-http-oauth-encode-scope scope)
+                                      (error
+                                       (concat "url-http-oauth:"
+                                               " Returned scope did not"
+                                               " match requested scope")))
+                             :expiry (url-http-oauth-expiry-string grant)
+                             :secret bearer-retrieved))
                (save-function (plist-get (car auth-result) :save-function)))
           ;; Success; save bearer.
           (when (functionp save-function)

Reply via email to