branch: externals/auth-source-xoauth2-plugin
commit b7bff4d391ba984ac73ae72a672dc952d27d6140
Author: Xiyue Deng <manp...@gmail.com>
Commit: Xiyue Deng <manp...@gmail.com>

    Initial commit
---
 auth-source-xoauth2-plugin.el | 123 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/auth-source-xoauth2-plugin.el b/auth-source-xoauth2-plugin.el
new file mode 100644
index 0000000000..f540b5628c
--- /dev/null
+++ b/auth-source-xoauth2-plugin.el
@@ -0,0 +1,123 @@
+;; auth-source-xoauth2-plugin.el -- authentication source plugin for xoauth2 
-*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 Xiyue Deng <manp...@gmail.com>
+
+;; Author: Xiyue Deng <manp...@gmail.com>
+;; Version: 0.1-git
+;; Package-Requires: ((emacs "28.1") (oauth2 "0.17"))
+
+;; This file is not part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package enables support for xoauth2 authentication with
+;; auth-source.
+
+;; auth-source uses the `secret' field in auth-source file as password
+;; for authentication, including xoauth2.  To decide which
+;; authentication method to use (e.g. plain password vs xoauth2), it
+;; inspects the `auth' field from the auth-source entry, and if the
+;; value is `xaouth2', it will try to gather data and get the access
+;; token for use of xoauth2 authentication; otherwise, it will fallback
+;; to the default authentication method.
+
+;; When xoauth2 authentication is enabled, it will try to get the
+;; following data from the auth-source entry: `auth-url', `token-url',
+;; `scope', `client-id', `client-secret', `redirect-uri', and optionally
+;; `state'.  These information will be used by oauth2 to retrieve the
+;; access-token.  This package uses an advice to switch the auth-source
+;; search result from the `password' to the `access-token' it got, which
+;; in turn will be used to construct the xoauth2 authentication string,
+;; currently in nnimap-login and smtpmail-try-auth-method.  To really
+;; enable xoauth2 in smtpmail, it will add 'xoauth2 to
+;; 'smtpmail-auth-supported (if it is not already in the list) using
+;; `add-to-list' so that xoauth2 is tried first.
+
+;; Note that currently the auth-source requires the searched entry must
+;; have `secret' field set in the entry, which is not necessary when
+;; using xoauth2.  Therefore in the advice it temporarily disables
+;; checking for `:secret' if set and perform the search, and check the
+;; result before returning.
+
+;;; Code:
+
+(require 'auth-source)
+(require 'cl-lib)
+(require 'oauth2)
+(require 'smtpmail)
+
+(defun auth-source-xoauth2-plugin--search-backends (orig-fun &rest args)
+  "Perform auth-source-search and set password as access-token when requested.
+The substitution only happens if one sets `auth' to `xoauth2' in
+your auth-source-entry.  It is expected that `token_url',
+`client_id', `client_secret', and `refresh_token' are properly
+set along `host', `user', and `port' (note the snake_case)."
+  (auth-source-do-trivia "Advising auth-source-search")
+  (let (check-secret)
+    (when (memq :secret (nth 5 args))
+      (auth-source-do-trivia
+       "Required fields include :secret.  As we are requesting access token to 
replace the secret, we'll temporary remove :secret from the require list and 
check that it's properly set to a valid access token later.")
+      (setf (nth 5 args) (remove :secret (nth 5 args)))
+      (setq check-secret t))
+    (let ((orig-res (apply orig-fun args))
+          res)
+      (dolist (auth-data orig-res)
+        (auth-source-do-trivia "Matched auth data: %s" (pp-to-string 
auth-data))
+        (let ((auth (plist-get auth-data :auth)))
+          (when (and auth
+                     (stringp auth)
+                     (string= auth "xoauth2"))
+            (auth-source-do-debug
+             ":auth set to `xoauth2'.  Will get access token.")
+            (let ((auth-url (plist-get auth-data :auth-url))
+                  (token-url (plist-get auth-data :token-url))
+                  (scope (plist-get auth-data :scope))
+                  (client-id (plist-get auth-data :client-id))
+                  (client-secret (plist-get auth-data :client-secret))
+                  (redirect-uri (plist-get auth-data :redirect-uri))
+                  (state (plist-get auth-data :state)))
+              (auth-source-do-trivia "Using oauth2 to auth and store token...")
+              (let ((token (oauth2-auth-and-store
+                            auth-url token-url scope client-id client-secret
+                            redirect-uri state)))
+                (auth-source-do-trivia "oauth2 token: %s" (pp-to-string token))
+                (auth-source-do-trivia "Refreshing token...")
+                (oauth2-refresh-access token)
+                (auth-source-do-trivia "oauth2 token after refresh: %s"
+                                       (pp-to-string token))
+                (let ((access-token (oauth2-token-access-token token)))
+                  (auth-source-do-trivia
+                   "Updating :secret with access-token: %s" access-token)
+                  (plist-put auth-data :secret access-token))))))
+
+        (unless (and check-secret
+                     (not (plist-get auth-data :secret)))
+          (auth-source-do-trivia "Updating auth-source-search results.")
+          (add-to-list 'res auth-data t)))
+      res)))
+
+;;;###autoload
+(defun auth-source-xoauth2-plugin-enable ()
+  "Enable auth-source-xoauth2-plugin."
+  (unless (memq 'xoauth2 smtpmail-auth-supported)
+    (add-to-list 'smtpmail-auth-supported 'xoauth2))
+
+  (advice-add 'auth-source-search-backends :around
+              #'auth-source-xoauth2-plugin--search-backends))
+
+(provide 'auth-source-xoauth2-plugin)
+
+;;; auth-source-xoauth2-plugin.el ends here

Reply via email to