Subject: bug#72358: 29.4; oauth2.el improvements

From: Andrew Cohen <[email-masked]>
Date: Thu, 01 Aug 2024 07:53:21 +0800

I have been using the existing oauth2.el and auth-source.el to use both
gmail and outlook (through my university) with oauth2 for several years
now (I posted a bit about it some time ago on the devel list). I
didn't need to change much to get it to work so I thought as long as the
changes by Xiyue are being considered (all of which look good to me) I
would chime in. I'm happy to provide more info about my setup and usage
if anyone is interested.

Firstly, I note that I have gmail working fine without the change in
patchset 2 (although I see nothing wrong with the change, I wonder why
it isn't necessary for me but is for Xiyue).

Secondly, there is one other important change that I have been using
which should probably be added to oauth2.el (I communicated the change
to Julien a long time ago, but he said he is no longer actively
maintaining oauth2.el): in refreshing the token the access-response is
ignored (as is the response-error). The access-response contains
information about the token expiration so its needed in order to control
when to fetch a new token. The simple patch below stores the
access-response in the appropriate slot in the token:

Lastly, a brief description of how to get things to work with
auth-source and existing code (subject to the change I mentioned above):

auth-source entries using the plstore backend allow the secret to be a
function (which is passed the whole entry plist as an argument). All
that is needed then is a function that returns the access token (which
is then used in gnus and smtpmail, both of which already work properly
with an oauth2 access-token). A simple function to check the expiration
time and fetch a new access-token if necessary (and update the new token
and expiration information) and then return the access-token is what I
use. 

So I used auth-source to create  plstore entries for  gmail and
outlook containing the oauth2 tokens, and set the secret to the
following function

(defun gnus-refresh-access (plist)
  "Return an oauth2 access-token for PLIST.
If the current token has expired, fetch, save, and return a new one."
  (cl-destructuring-bind
      (&key user host port token last-update
            (expires_in (alist-get 'expires_in
                                   (oauth2-token-access-response token)))
            (create-args
             (list :type 'plstore :create
                   '(:encrypted (token client-secret-sav)
                                :unencrypted (auth-url scope redirect-uri last-update smtp-auth))))
            &allow-other-keys) plist
    (unless (and (numberp expires_in) (numberp last-update)
                 (< (float-time) (+ last-update expires_in)))
      (message "Getting new token for %s at %s:%s" user host port)
      (setq plist (plist-put plist :secret 'gnus-refresh-access))
      (setq plist (plist-put plist :last-update (truncate (float-time))))
      ;; get a new token and update the plist
      (setq plist (plist-put plist :token (oauth2-refresh-access token)))
      ;; update auth-source---if something in the plist has changed
      ;; then no entry will be found during the search, and the
      ;; create flag will be honored.
      (apply #'auth-source-search (append plist create-args)))
    ;; return the access token
    (oauth2-token-access-token (plist-get plist :token))))

By the way, I let auth-source handle the plstore rather than
oauth2.el. It seemed simpler to have only one of them managing the store
rather than both. 

By the by the way, there are some important bugs in auth-source.el that
I have fixed in my personal tree (and a few that I haven't). I'll post
about them in a separate bug report at some point.

Best,
Andy
-- 
Andrew Cohen