File: auth-source-xoauth2-plugin.el

package info (click to toggle)
emacs-auth-source-xoauth2-plugin 0.3.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 240 kB
  • sloc: lisp: 162; makefile: 2
file content (235 lines) | stat: -rw-r--r-- 11,213 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
;;; auth-source-xoauth2-plugin.el --- Authentication source plugin for xoauth2 -*- lexical-binding: t -*-

;; Copyright (C) 2024-2025  Free Software Foundation, Inc.

;; Author: Xiyue Deng <manphiz@gmail.com>
;; Homepage: https://gitlab.com/manphiz/auth-source-xoauth2-plugin
;; Version: 0.3.1
;; Package-Requires: ((emacs "28.1") (oauth2 "0.18"))

;; 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:

;; An auth-source plugin to enable xoauth2 support.

;; This package provides a global minor mode for enabling xoauth2 in
;; auth-source.  Once adding information required for xoauth2 authentication in
;; your auth-source file and enabling the global minor mode, one can
;; authenticate through xoauth2 to supported services, e.g. Gmail, etc.

;; See README.org for a more detailed introduction and usages.

;;; Code:

(require 'auth-source)
(require 'cl-lib)
(require 'map)
(require 'oauth2)
(require 'org)
(require 'smtpmail)

(defvar auth-source-xoauth2-plugin-predefined-issuers
  '(thunderbird
    (google
     ( :client-id "406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com"
       :client-secret "kSmqreRr0qwBWJgbf5Y-PjSU"
       :auth-url "https://accounts.google.com/o/oauth2/auth"
       :token-url "https://www.googleapis.com/oauth2/v3/token"
       :redirect-uri "http://localhost/"
       :scope "https://mail.google.com/"
       :use-pkce "true" )
     microsoft
     ( :client-id "9e5f94bc-e8a4-4e73-b8be-63364c29d753"
       ;; :client-secret ""
       :auth-url "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
       :token-url "https://login.microsoftonline.com/common/oauth2/v2.0/token"
       :redirect-uri "http://localhost"
       :scope "https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access"
       :use-pkce "true" ))))

(defvar auth-source-xoauth2-plugin-default-predefined-source "thunderbird"
  "The default predefined issuers provider.")

(defun auth-source-xoauth2-plugin--get-predefined-credentials (source provider)
  "Helper function to get the predefined credentials of PROVIDER from SOURCE."
  (plist-get (plist-get auth-source-xoauth2-plugin-predefined-issuers
                        (intern source))
             (intern provider)))

(defun auth-source-xoauth2-plugin--search-backends (orig-fun &rest args)
  "Perform `auth-source-search' and set password as access-token when requested.
Calls ORIG-FUN which would be `auth-source-search-backends' with
ARGS to get the auth-source-entry.  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 "[xoauth2-plugin] Advising auth-source-search")
  (let (check-secret)
    (when (memq :secret (nth 5 args))
      (auth-source-do-trivia
       (concat "[xoauth2-plugin] 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 "[xoauth2-plugin] Matched auth data: %s"
                               (pp-to-string auth-data))
        (let ((auth (plist-get auth-data :auth))
              (user (plist-get auth-data :user)))
          (when (and (equal auth "xoauth2")
                     ;; When sending mails, some auth-source query results from
                     ;; some smtpmail authentication methods don't contain the
                     ;; :user field (meanwhile queries from Gnus seems to always
                     ;; include :user).  When using predefined provider
                     ;; credentials, only the :user field is different to
                     ;; distinguish among different accounts, which is
                     ;; unfortunately missing in certain cases.  Fortunately,
                     ;; smtpmail may set smtpmail-smtp-user to the user value
                     ;; when X-Message-SMTP-Method is properly set.  Therefore
                     ;; additionally, assuming X-Message-SMTP-Method is set
                     ;; correctly, we need to check whether smtpmail-smtp-user
                     ;; is the same as :user to be sure.
                     (if smtpmail-smtp-user
                         (progn
                           (auth-source-do-trivia
                            "[xoauth2-plugin] user: %s, smtpmail-smtp-user: %s"
                            user smtpmail-smtp-user)
                           (string= smtpmail-smtp-user user))
                       t))
            (auth-source-do-debug
             (concat "[xoauth2-plugin] account \"%s\" has :auth set to "
                     "`xoauth2'.  Will get access token.")
             user)
            (map-let (:auth-source-xoauth2-predefined-service
                      (:auth-source-xoauth2-predefined-source
                       auth-source-xoauth2-predefined-source
                       auth-source-xoauth2-plugin-default-predefined-source))
                auth-data
              (when auth-source-xoauth2-predefined-service
                (auth-source-do-trivia
                 (concat "[xoauth2-plugin] Using service \"%s\" with "
                         "credentials provided by source \"%s\"")
                 auth-source-xoauth2-predefined-service
                 auth-source-xoauth2-predefined-source)
                (setq auth-data
                      (org-combine-plists
                       auth-data
                       (auth-source-xoauth2-plugin--get-predefined-credentials
                        auth-source-xoauth2-predefined-source
                        auth-source-xoauth2-predefined-service)))))
            (map-let (:host
                      :user
                      :auth-url
                      :token-url
                      :scope
                      :client-id
                      :client-secret
                      :redirect-uri
                      :state
                      :use-pkce)
                auth-data
              (auth-source-do-debug
               "[xoauth2-plugin] 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 user host use-pkce)))
                (auth-source-do-trivia "[xoauth2-plugin] oauth2 token: %s"
                                       (pp-to-string token))
                (auth-source-do-debug "[xoauth2-plugin] Refreshing token...")
                (oauth2-refresh-access token host)
                (auth-source-do-debug "[xoauth2-plugin] Refresh successful.")
                (auth-source-do-trivia
                 "[xoauth2-plugin] 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)
                  (setq auth-data
                        (plist-put auth-data :secret access-token))
                  ;; Fill fields that may help 3rd party usage,
                  ;; e.g. offlineimap.
                  (setq auth-data
                        (plist-put auth-data :auth-url auth-url))
                  (setq auth-data
                        (plist-put auth-data :token-url token-url))
                  (setq auth-data
                        (plist-put auth-data :client-id client-id))
                  (setq auth-data
                        (plist-put auth-data :client-secret client-secret))
                  (setq auth-data
                        (plist-put auth-data :access-token
                                   (oauth2-token-access-token token)))
                  (setq auth-data
                        (plist-put auth-data :refresh-token
                                   (oauth2-token-refresh-token token))))))))

        (auth-source-do-debug "[xoauth2-plugin] auth-data after processing: %s"
                              (pp-to-string auth-data))
        (unless (and check-secret
                     (not (plist-get auth-data :secret)))
          (auth-source-do-debug
           "[xoauth2-plugin] Updating auth-source-search results.")
          (push auth-data res)))
      res)))

(defvar auth-source-xoauth2-plugin--enabled-xoauth2-by-us nil
  "Non-nil means `smtpmail-auth-supported' was set by us.")

(defun auth-source-xoauth2-plugin--enable ()
  "Enable auth-source-xoauth2-plugin."
  (unless (memq 'xoauth2 smtpmail-auth-supported)
    ;; smtpmail considers smtp request with a return value less than 400 to be
    ;; successful, but for Gmail when an xoauth2 request fails it returns 334
    ;; server challenge, and waiting for a subsequent request with the correct
    ;; credentials which will never happen.  Putting 'xoauth2 as the last entry
    ;; in smtpmail-auth-supported so that it is tried last.  See also
    ;; https://debbugs.gnu.org/78366.
    (add-to-list 'smtpmail-auth-supported 'xoauth2 t)
    (setq auth-source-xoauth2-plugin--enabled-xoauth2-by-us t))

  (advice-add #'auth-source-search-backends :around
              #'auth-source-xoauth2-plugin--search-backends))

(defun auth-source-xoauth2-plugin--disable ()
  "Disable auth-source-xoauth2-plugin."
  (when (and auth-source-xoauth2-plugin--enabled-xoauth2-by-us
             (memq 'xoauth2 smtpmail-auth-supported))
    (setq smtpmail-auth-supported (delq 'xoauth2 smtpmail-auth-supported))
    (setq auth-source-xoauth2-plugin--enabled-xoauth2-by-us nil))

  (advice-remove #'auth-source-search-backends
                 #'auth-source-xoauth2-plugin--search-backends))

;;;###autoload
(define-minor-mode auth-source-xoauth2-plugin-mode
  "Toggle auth-source-xoauth2-plugin-mode.
Enable auth-source-xoauth2-plugin-mode to use xoauth2
authentications for emails."
  :global t
  :group 'auth-source
  (if auth-source-xoauth2-plugin-mode
      (auth-source-xoauth2-plugin--enable)
    (auth-source-xoauth2-plugin--disable)))

(provide 'auth-source-xoauth2-plugin)

;;; auth-source-xoauth2-plugin.el ends here