aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeodor Zlatanov2011-03-09 13:39:35 +0000
committerKatsumi Yamaoka2011-03-09 13:39:35 +0000
commit733afdf4d9df952a2d06c40b067de3a62bceb26b (patch)
treeb097d2a39b6e6a8a9ce80a5f8262a09b36518cc1
parentee545c35d2e83306d50ec78a8d9173ab9011bce5 (diff)
downloademacs-733afdf4d9df952a2d06c40b067de3a62bceb26b.tar.gz
emacs-733afdf4d9df952a2d06c40b067de3a62bceb26b.zip
Merge changes made in Gnus trunk.
auth-source.el (auth-source-read-char-choice): New function to read a character choice using `dropdown-list', `read-char-choice', or `read-char'. It appends "[a/b/c] " to the prompt if the choices were '(?a ?b ?c). The `dropdown-list' support is disabled for now. Use `eval-when-compile' to load `dropdown-list'. (auth-source-netrc-saver): Use it. nnimap.el (nnimap-credentials): Keep the :save-function as the third parameter in the credentials. (nnimap-open-connection-1): Use it after a successful login. (nnimap-credentials): Add IMAP-specific user and password prompt. auth-source.el (auth-source-search): Add :require parameter, taking a list. Document it and the :save-function return token. Pass :require down. Change the CREATED message from a warning to a debug statement. (auth-source-search-backends): Pass :require down. (auth-source-netrc-search): Pass :require down. (auth-source-netrc-parse): Use :require, if it's given, as a filter. Change save prompt to indicate all modifications saved here are deletions. (auth-source-netrc-create): Take user login name as default in user prompt. Move all the save functionality to a lexically bound function under the :save-function token in the returned list. Set up clearer default prompts for user, host, port, and secret. (auth-source-netrc-saver): New function, intended to be wrapped for :save-function.
-rw-r--r--doc/misc/ChangeLog5
-rw-r--r--doc/misc/auth.texi64
-rw-r--r--lisp/gnus/ChangeLog31
-rw-r--r--lisp/gnus/auth-source.el258
-rw-r--r--lisp/gnus/nnimap.el22
5 files changed, 277 insertions, 103 deletions
diff --git a/doc/misc/ChangeLog b/doc/misc/ChangeLog
index 75674c7fd17..96a2576355a 100644
--- a/doc/misc/ChangeLog
+++ b/doc/misc/ChangeLog
@@ -1,3 +1,8 @@
12011-03-08 Teodor Zlatanov <tzz@lifelogs.com>
2
3 * auth.texi (Help for developers): Show example of using
4 `auth-source-search' with prompts and :save-function.
5
12011-03-07 Antoine Levitt <antoine.levitt@gmail.com> 62011-03-07 Antoine Levitt <antoine.levitt@gmail.com>
2 7
3 * message.texi (Message Buffers): Update default value of 8 * message.texi (Message Buffers): Update default value of
diff --git a/doc/misc/auth.texi b/doc/misc/auth.texi
index 23ac23dce5b..e16d7b49b63 100644
--- a/doc/misc/auth.texi
+++ b/doc/misc/auth.texi
@@ -131,11 +131,11 @@ library encourages this confusion by accepting both, as you'll see
131later. 131later.
132 132
133If you have problems with the search, set @code{auth-source-debug} to 133If you have problems with the search, set @code{auth-source-debug} to
134@code{t} and see what host, port, and user the library is checking in 134@code{'trivia} and see what host, port, and user the library is
135the @code{*Messages*} buffer. Ditto for any other problems, your 135checking in the @code{*Messages*} buffer. Ditto for any other
136first step is always to see what's being checked. The second step, of 136problems, your first step is always to see what's being checked. The
137course, is to write a blog entry about it and wait for the answer in 137second step, of course, is to write a blog entry about it and wait for
138the comments. 138the answer in the comments.
139 139
140You can customize the variable @code{auth-sources}. The following may 140You can customize the variable @code{auth-sources}. The following may
141be needed if you are using an older version of Emacs or if the 141be needed if you are using an older version of Emacs or if the
@@ -232,6 +232,14 @@ TODO: how does it work generally, how does secrets.el work, some examples.
232@node Help for developers 232@node Help for developers
233@chapter Help for developers 233@chapter Help for developers
234 234
235The auth-source library lets you control logging output easily.
236
237@defvar auth-source-debug
238Set this variable to 'trivia to see lots of output in *Messages*, or
239set it to a function that behaves like @code{message} to do your own
240logging.
241@end defvar
242
235The auth-source library only has a few functions for external use. 243The auth-source library only has a few functions for external use.
236 244
237@defun auth-source-search SPEC 245@defun auth-source-search SPEC
@@ -240,6 +248,52 @@ TODO: how to include docstring?
240 248
241@end defun 249@end defun
242 250
251Let's take a look at an example of using @code{auth-source-search}
252from Gnus' @code{nnimap.el}.
253
254@example
255(defun nnimap-credentials (address ports)
256 (let* ((auth-source-creation-prompts
257 '((user . "IMAP user at %h: ")
258 (secret . "IMAP password for %u@@%h: ")))
259 (found (nth 0 (auth-source-search :max 1
260 :host address
261 :port ports
262 :require '(:user :secret)
263 :create t))))
264 (if found
265 (list (plist-get found :user)
266 (let ((secret (plist-get found :secret)))
267 (if (functionp secret)
268 (funcall secret)
269 secret))
270 (plist-get found :save-function))
271 nil)))
272@end example
273
274This call requires the user and password (secret) to be in the
275results. It also requests that an entry be created if it doesn't
276exist already. While the created entry is being assembled, the shown
277prompts will be used to interact with the user. The caller can also
278pass data in @code{auth-source-creation-defaults} to supply defaults
279for any of the prompts.
280
281Note that the password needs to be evaluated if it's a function. It's
282wrapped in a function to provide some security.
283
284Later, after a successful login, @code{nnimal.el} calls the
285@code{:save-function} like so:
286
287@example
288(when (functionp (nth 2 credentials))
289 (funcall (nth 2 credentials)))
290@end example
291
292Which will work whether the @code{:save-function} was provided or not.
293@code{:save-function} will be provided only when a new entry was
294created, so this effectively says ``after a successful login, save the
295authentication information we just used, if it was newly created.''
296
243@defun auth-source-delete SPEC 297@defun auth-source-delete SPEC
244 298
245TODO: how to include docstring? 299TODO: how to include docstring?
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog
index b30cfdcfcd3..aa1f013dd35 100644
--- a/lisp/gnus/ChangeLog
+++ b/lisp/gnus/ChangeLog
@@ -1,3 +1,34 @@
12011-03-09 Teodor Zlatanov <tzz@lifelogs.com>
2
3 * auth-source.el (auth-source-read-char-choice): New function to read a
4 character choice using `dropdown-list', `read-char-choice', or
5 `read-char'. It appends "[a/b/c] " to the prompt if the choices were
6 '(?a ?b ?c). The `dropdown-list' support is disabled for now. Use
7 `eval-when-compile' to load `dropdown-list'.
8 (auth-source-netrc-saver): Use it.
9
102011-03-08 Teodor Zlatanov <tzz@lifelogs.com>
11
12 * nnimap.el (nnimap-credentials): Keep the :save-function as the third
13 parameter in the credentials.
14 (nnimap-open-connection-1): Use it after a successful login.
15 (nnimap-credentials): Add IMAP-specific user and password prompt.
16
17 * auth-source.el (auth-source-search): Add :require parameter, taking a
18 list. Document it and the :save-function return token. Pass :require
19 down. Change the CREATED message from a warning to a debug statement.
20 (auth-source-search-backends): Pass :require down.
21 (auth-source-netrc-search): Pass :require down.
22 (auth-source-netrc-parse): Use :require, if it's given, as a filter.
23 Change save prompt to indicate all modifications saved here are
24 deletions.
25 (auth-source-netrc-create): Take user login name as default in user
26 prompt. Move all the save functionality to a lexically bound function
27 under the :save-function token in the returned list. Set up clearer
28 default prompts for user, host, port, and secret.
29 (auth-source-netrc-saver): New function, intended to be wrapped for
30 :save-function.
31
12011-03-07 Lars Magne Ingebrigtsen <larsi@gnus.org> 322011-03-07 Lars Magne Ingebrigtsen <larsi@gnus.org>
2 33
3 * shr.el (shr-table-horizontal-line): Change the defaults for the table 34 * shr.el (shr-table-horizontal-line): Change the defaults for the table
diff --git a/lisp/gnus/auth-source.el b/lisp/gnus/auth-source.el
index 500de10b71c..108871974a0 100644
--- a/lisp/gnus/auth-source.el
+++ b/lisp/gnus/auth-source.el
@@ -44,7 +44,18 @@
44(require 'gnus-util) 44(require 'gnus-util)
45(require 'assoc) 45(require 'assoc)
46(eval-when-compile (require 'cl)) 46(eval-when-compile (require 'cl))
47(require 'eieio) 47(eval-when-compile (require 'dropdown-list nil t))
48(eval-and-compile
49 (or (ignore-errors (require 'eieio))
50 ;; gnus-fallback-lib/ from gnus/lisp/gnus-fallback-lib
51 (ignore-errors
52 (let ((load-path (cons (expand-file-name
53 "gnus-fallback-lib/eieio"
54 (file-name-directory (locate-library "gnus")))
55 load-path)))
56 (require 'eieio)))
57 (error
58 "eieio not found in `load-path' or gnus-fallback-lib/ directory.")))
48 59
49(autoload 'secrets-create-item "secrets") 60(autoload 'secrets-create-item "secrets")
50(autoload 'secrets-delete-item "secrets") 61(autoload 'secrets-delete-item "secrets")
@@ -286,6 +297,34 @@ If the value is not a list, symmetric encryption will be used."
286 msg)) 297 msg))
287 298
288 299
300;;; (auth-source-read-char-choice "enter choice? " '(?a ?b ?q))
301(defun auth-source-read-char-choice (prompt choices)
302 "Read one of CHOICES by `read-char-choice', or `read-char'.
303`dropdown-list' support is disabled because it doesn't work reliably.
304Only one of CHOICES will be returned. The PROMPT is augmented
305with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)."
306 (when choices
307 (let* ((prompt-choices
308 (apply 'concat (loop for c in choices
309 collect (format "%c/" c))))
310 (prompt-choices (concat "[" (substring prompt-choices 0 -1) "] "))
311 (full-prompt (concat prompt prompt-choices))
312 k)
313
314 (while (not (memq k choices))
315 (setq k (cond
316 ((and nil (featurep 'dropdown-list))
317 (let* ((blank (fill (copy-sequence prompt) ?.))
318 (dlc (cons (format "%s %c" prompt (car choices))
319 (loop for c in (cdr choices)
320 collect (format "%s %c" blank c)))))
321 (nth (dropdown-list dlc) choices)))
322 ((fboundp 'read-char-choice)
323 (read-char-choice full-prompt choices))
324 (t (message "%s" full-prompt)
325 (setq k (read-char))))))
326 k)))
327
289;; (auth-source-pick nil :host "any" :port 'imap :user "joe") 328;; (auth-source-pick nil :host "any" :port 'imap :user "joe")
290;; (auth-source-pick t :host "any" :port 'imap :user "joe") 329;; (auth-source-pick t :host "any" :port 'imap :user "joe")
291;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe") 330;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe")
@@ -393,7 +432,7 @@ parameters."
393 432
394(defun* auth-source-search (&rest spec 433(defun* auth-source-search (&rest spec
395 &key type max host user port secret 434 &key type max host user port secret
396 create delete 435 require create delete
397 &allow-other-keys) 436 &allow-other-keys)
398 "Search or modify authentication backends according to SPEC. 437 "Search or modify authentication backends according to SPEC.
399 438
@@ -487,6 +526,11 @@ should `catch' the backend-specific error as usual. Some
487backends (netrc, at least) will prompt the user rather than throw 526backends (netrc, at least) will prompt the user rather than throw
488an error. 527an error.
489 528
529:require (A B C) means that only results that contain those
530tokens will be returned. Thus for instance requiring :secret
531will ensure that any results will actually have a :secret
532property.
533
490:delete t means to delete any found entries. nil by default. 534:delete t means to delete any found entries. nil by default.
491Use `auth-source-delete' in ELisp code instead of calling 535Use `auth-source-delete' in ELisp code instead of calling
492`auth-source-search' directly with this parameter. 536`auth-source-search' directly with this parameter.
@@ -516,11 +560,17 @@ is a plist with keys :backend :host :port :user, plus any other
516keys provided by the backend (notably :secret). But note the 560keys provided by the backend (notably :secret). But note the
517exception for :max 0, which see above. 561exception for :max 0, which see above.
518 562
563The token can hold a :save-function key. If you call that, the
564user will be prompted to save the data to the backend. You can't
565request that this should happen right after creation, because
566`auth-source-search' has no way of knowing if the token is
567actually useful. So the caller must arrange to call this function.
568
519The token's :secret key can hold a function. In that case you 569The token's :secret key can hold a function. In that case you
520must call it to obtain the actual value." 570must call it to obtain the actual value."
521 (let* ((backends (mapcar 'auth-source-backend-parse auth-sources)) 571 (let* ((backends (mapcar 'auth-source-backend-parse auth-sources))
522 (max (or max 1)) 572 (max (or max 1))
523 (ignored-keys '(:create :delete :max)) 573 (ignored-keys '(:require :create :delete :max))
524 (keys (loop for i below (length spec) by 2 574 (keys (loop for i below (length spec) by 2
525 unless (memq (nth i spec) ignored-keys) 575 unless (memq (nth i spec) ignored-keys)
526 collect (nth i spec))) 576 collect (nth i spec)))
@@ -539,6 +589,10 @@ must call it to obtain the actual value."
539 (or (eq t create) (listp create)) t 589 (or (eq t create) (listp create)) t
540 "Invalid auth-source :create parameter (must be t or a list): %s %s") 590 "Invalid auth-source :create parameter (must be t or a list): %s %s")
541 591
592 (assert
593 (listp require) t
594 "Invalid auth-source :require parameter (must be a list): %s")
595
542 (setq filtered-backends (copy-sequence backends)) 596 (setq filtered-backends (copy-sequence backends))
543 (dolist (backend backends) 597 (dolist (backend backends)
544 (dolist (key keys) 598 (dolist (key keys)
@@ -562,8 +616,9 @@ must call it to obtain the actual value."
562 spec 616 spec
563 ;; to exit early 617 ;; to exit early
564 max 618 max
565 ;; create and delete 619 ;; create is always nil here
566 nil delete)) 620 nil delete
621 require))
567 622
568 (auth-source-do-debug 623 (auth-source-do-debug
569 "auth-source-search: found %d results (max %d) matching %S" 624 "auth-source-search: found %d results (max %d) matching %S"
@@ -577,9 +632,9 @@ must call it to obtain the actual value."
577 spec 632 spec
578 ;; to exit early 633 ;; to exit early
579 max 634 max
580 ;; create and delete 635 create delete
581 create delete)) 636 require))
582 (auth-source-do-warn 637 (auth-source-do-debug
583 "auth-source-search: CREATED %d results (max %d) matching %S" 638 "auth-source-search: CREATED %d results (max %d) matching %S"
584 (length found) max spec)) 639 (length found) max spec))
585 640
@@ -589,18 +644,19 @@ must call it to obtain the actual value."
589 644
590 found)) 645 found))
591 646
592(defun auth-source-search-backends (backends spec max create delete) 647(defun auth-source-search-backends (backends spec max create delete require)
593 (let (matches) 648 (let (matches)
594 (dolist (backend backends) 649 (dolist (backend backends)
595 (when (> max (length matches)) ; when we need more matches... 650 (when (> max (length matches)) ; when we need more matches...
596 (let ((bmatches (apply 651 (let* ((bmatches (apply
597 (slot-value backend 'search-function) 652 (slot-value backend 'search-function)
598 :backend backend 653 :backend backend
599 ;; note we're overriding whatever the spec 654 ;; note we're overriding whatever the spec
600 ;; has for :create and :delete 655 ;; has for :require, :create, and :delete
601 :create create 656 :require require
602 :delete delete 657 :create create
603 spec))) 658 :delete delete
659 spec)))
604 (when bmatches 660 (when bmatches
605 (auth-source-do-trivia 661 (auth-source-do-trivia
606 "auth-source-search-backend: got %d (max %d) in %s:%s matching %S" 662 "auth-source-search-backend: got %d (max %d) in %s:%s matching %S"
@@ -729,7 +785,7 @@ while \(:host t) would find all host entries."
729;;; (auth-source-netrc-parse "~/.authinfo.gpg") 785;;; (auth-source-netrc-parse "~/.authinfo.gpg")
730(defun* auth-source-netrc-parse (&rest 786(defun* auth-source-netrc-parse (&rest
731 spec 787 spec
732 &key file max host user port delete 788 &key file max host user port delete require
733 &allow-other-keys) 789 &allow-other-keys)
734 "Parse FILE and return a list of all entries in the file. 790 "Parse FILE and return a list of all entries in the file.
735Note that the MAX parameter is used so we can exit the parse early." 791Note that the MAX parameter is used so we can exit the parse early."
@@ -828,7 +884,15 @@ Note that the MAX parameter is used so we can exit the parse early."
828 (or 884 (or
829 (aget alist "port") 885 (aget alist "port")
830 (aget alist "protocol") 886 (aget alist "protocol")
831 t))) 887 t))
888 (or
889 ;; the required list of keys is nil, or
890 (null require)
891 ;; every element of require is in the normalized list
892 (let ((normalized (nth 0 (auth-source-netrc-normalize
893 (list alist)))))
894 (loop for req in require
895 always (plist-get normalized req)))))
832 (decf max) 896 (decf max)
833 (push (nreverse alist) result) 897 (push (nreverse alist) result)
834 ;; to delete a line, we just comment it out 898 ;; to delete a line, we just comment it out
@@ -853,7 +917,7 @@ Note that the MAX parameter is used so we can exit the parse early."
853 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to))) 917 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
854 918
855 ;; ask AFTER we've successfully opened the file 919 ;; ask AFTER we've successfully opened the file
856 (when (y-or-n-p (format "Save file %s? (%d modifications)" 920 (when (y-or-n-p (format "Save file %s? (%d deletions)"
857 file modified)) 921 file modified))
858 (write-region (point-min) (point-max) file nil 'silent) 922 (write-region (point-min) (point-max) file nil 'silent)
859 (auth-source-do-debug 923 (auth-source-do-debug
@@ -893,7 +957,7 @@ Note that the MAX parameter is used so we can exit the parse early."
893 957
894(defun* auth-source-netrc-search (&rest 958(defun* auth-source-netrc-search (&rest
895 spec 959 spec
896 &key backend create delete 960 &key backend require create delete
897 type max host user port 961 type max host user port
898 &allow-other-keys) 962 &allow-other-keys)
899"Given a property list SPEC, return search matches from the :backend. 963"Given a property list SPEC, return search matches from the :backend.
@@ -905,6 +969,7 @@ See `auth-source-search' for details on SPEC."
905 (let ((results (auth-source-netrc-normalize 969 (let ((results (auth-source-netrc-normalize
906 (auth-source-netrc-parse 970 (auth-source-netrc-parse
907 :max max 971 :max max
972 :require require
908 :delete delete 973 :delete delete
909 :file (oref backend source) 974 :file (oref backend source)
910 :host (or host t) 975 :host (or host t)
@@ -992,12 +1057,12 @@ See `auth-source-search' for details on SPEC."
992 (data (auth-source-netrc-element-or-first data)) 1057 (data (auth-source-netrc-element-or-first data))
993 ;; this is the default to be offered 1058 ;; this is the default to be offered
994 (given-default (aget auth-source-creation-defaults r)) 1059 (given-default (aget auth-source-creation-defaults r))
995 ;; the default supplementals are simple: for the user, 1060 ;; the default supplementals are simple:
996 ;; try (user-login-name), otherwise take given-default 1061 ;; for the user, try `given-default' and then (user-login-name);
1062 ;; otherwise take `given-default'
997 (default (cond 1063 (default (cond
998 ;; don't default the user name 1064 ((and (not given-default) (eq r 'user))
999 ;; ((and (not given-default) (eq r 'user)) 1065 (user-login-name))
1000 ;; (user-login-name))
1001 (t given-default))) 1066 (t given-default)))
1002 (printable-defaults (list 1067 (printable-defaults (list
1003 (cons 'user 1068 (cons 'user
@@ -1020,10 +1085,10 @@ See `auth-source-search' for details on SPEC."
1020 "[any port]")))) 1085 "[any port]"))))
1021 (prompt (or (aget auth-source-creation-prompts r) 1086 (prompt (or (aget auth-source-creation-prompts r)
1022 (case r 1087 (case r
1023 ('secret "%p password for user %u, host %h: ") 1088 (secret "%p password for %u@%h: ")
1024 ('user "%p user name: ") 1089 (user "%p user name for %h: ")
1025 ('host "%p host name for user %u: ") 1090 (host "%p host name for user %u: ")
1026 ('port "%p port for user %u and host %h: ")) 1091 (port "%p port for %u@%h: "))
1027 (format "Enter %s (%%u@%%h:%%p): " r))) 1092 (format "Enter %s (%%u@%%h:%%p): " r)))
1028 (prompt (auth-source-format-prompt 1093 (prompt (auth-source-format-prompt
1029 prompt 1094 prompt
@@ -1071,70 +1136,79 @@ See `auth-source-search' for details on SPEC."
1071 data)))) 1136 data))))
1072 (setq add (concat add (funcall printer))))))) 1137 (setq add (concat add (funcall printer)))))))
1073 1138
1074 (with-temp-buffer 1139 (plist-put
1075 (when (file-exists-p file) 1140 artificial
1076 (insert-file-contents file)) 1141 :save-function
1077 (when auth-source-gpg-encrypt-to 1142 (lexical-let ((file file)
1078 ;; (see bug#7487) making `epa-file-encrypt-to' local to 1143 (add add))
1079 ;; this buffer lets epa-file skip the key selection query 1144 (lambda () (auth-source-netrc-saver file add))))
1080 ;; (see the `local-variable-p' check in 1145
1081 ;; `epa-file-write-region'). 1146 (list artificial)))
1082 (unless (local-variable-p 'epa-file-encrypt-to (current-buffer)) 1147
1083 (make-local-variable 'epa-file-encrypt-to)) 1148;;(funcall (plist-get (nth 0 (auth-source-search :host '("nonesuch") :user "tzz" :port "imap" :create t :max 1)) :save-function))
1084 (if (listp auth-source-gpg-encrypt-to) 1149(defun auth-source-netrc-saver (file add)
1085 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to))) 1150 "Save a line ADD in FILE, prompting along the way.
1086 (goto-char (point-max)) 1151Respects `auth-source-save-behavior'."
1087 1152 (with-temp-buffer
1088 ;; ask AFTER we've successfully opened the file 1153 (when (file-exists-p file)
1089 (let ((prompt (format "Save auth info to file %s? %s: " 1154 (insert-file-contents file))
1090 file 1155 (when auth-source-gpg-encrypt-to
1091 "y/n/N/e/?")) 1156 ;; (see bug#7487) making `epa-file-encrypt-to' local to
1092 (done (not (eq auth-source-save-behavior 'ask))) 1157 ;; this buffer lets epa-file skip the key selection query
1093 (bufname "*auth-source Help*") 1158 ;; (see the `local-variable-p' check in
1094 k) 1159 ;; `epa-file-write-region').
1095 (while (not done) 1160 (unless (local-variable-p 'epa-file-encrypt-to (current-buffer))
1096 (message "%s" prompt) 1161 (make-local-variable 'epa-file-encrypt-to))
1097 (setq k (read-char)) 1162 (if (listp auth-source-gpg-encrypt-to)
1098 (case k 1163 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
1099 (?y (setq done t)) 1164 ;; we want the new data to be found first, so insert at beginning
1100 (?? (save-excursion 1165 (goto-char (point-min))
1101 (with-output-to-temp-buffer bufname 1166
1102 (princ 1167 ;; ask AFTER we've successfully opened the file
1103 (concat "(y)es, save\n" 1168 (let ((prompt (format "Save auth info to file %s? " file))
1104 "(n)o but use the info\n" 1169 (done (not (eq auth-source-save-behavior 'ask)))
1105 "(N)o and don't ask to save again\n" 1170 (bufname "*auth-source Help*")
1106 "(e)dit the line\n" 1171 k)
1107 "(?) for help as you can see.\n")) 1172 (while (not done)
1173 (setq k (auth-source-read-char-choice prompt '(?y ?n ?N ?e ??)))
1174 (case k
1175 (?y (setq done t))
1176 (?? (save-excursion
1177 (with-output-to-temp-buffer bufname
1178 (princ
1179 (concat "(y)es, save\n"
1180 "(n)o but use the info\n"
1181 "(N)o and don't ask to save again\n"
1182 "(e)dit the line\n"
1183 "(?) for help as you can see.\n"))
1108 (set-buffer standard-output) 1184 (set-buffer standard-output)
1109 (help-mode)))) 1185 (help-mode))))
1110 (?n (setq add "" 1186 (?n (setq add ""
1111 done t)) 1187 done t))
1112 (?N (setq add "" 1188 (?N (setq add ""
1113 done t 1189 done t
1114 auth-source-save-behavior nil)) 1190 auth-source-save-behavior nil))
1115 (?e (setq add (read-string "Line to add: " add))) 1191 (?e (setq add (read-string "Line to add: " add)))
1116 (t nil))) 1192 (t nil)))
1117 1193
1118 (when (get-buffer-window bufname) 1194 (when (get-buffer-window bufname)
1119 (delete-window (get-buffer-window bufname))) 1195 (delete-window (get-buffer-window bufname)))
1120 1196
1121 ;; make sure the info is not saved 1197 ;; make sure the info is not saved
1122 (when (null auth-source-save-behavior) 1198 (when (null auth-source-save-behavior)
1123 (setq add "")) 1199 (setq add ""))
1124 1200
1125 (when (< 0 (length add)) 1201 (when (< 0 (length add))
1126 (progn 1202 (progn
1127 (unless (bolp) 1203 (unless (bolp)
1128 (insert "\n")) 1204 (insert "\n"))
1129 (insert add "\n") 1205 (insert add "\n")
1130 (write-region (point-min) (point-max) file nil 'silent) 1206 (write-region (point-min) (point-max) file nil 'silent)
1131 (auth-source-do-warn 1207 (auth-source-do-debug
1132 "auth-source-netrc-create: wrote 1 new line to %s" 1208 "auth-source-netrc-create: wrote 1 new line to %s"
1133 file) 1209 file)
1134 nil)) 1210 (message "Saved new authentication information to %s" file)
1135 1211 nil)))))
1136 (when (eq done t)
1137 (list artificial))))))
1138 1212
1139;;; Backend specific parsing: Secrets API backend 1213;;; Backend specific parsing: Secrets API backend
1140 1214
diff --git a/lisp/gnus/nnimap.el b/lisp/gnus/nnimap.el
index 638097abd7d..e76ead515c5 100644
--- a/lisp/gnus/nnimap.el
+++ b/lisp/gnus/nnimap.el
@@ -279,16 +279,21 @@ textual parts.")
279 (current-buffer))) 279 (current-buffer)))
280 280
281(defun nnimap-credentials (address ports) 281(defun nnimap-credentials (address ports)
282 (let ((found (nth 0 (auth-source-search :max 1 282 (let* ((auth-source-creation-prompts
283 :host address 283 '((user . "IMAP user at %h: ")
284 :port ports 284 (secret . "IMAP password for %u@%h: ")))
285 :create t)))) 285 (found (nth 0 (auth-source-search :max 1
286 :host address
287 :port ports
288 :require '(:user :secret)
289 :create t))))
286 (if found 290 (if found
287 (list (plist-get found :user) 291 (list (plist-get found :user)
288 (let ((secret (plist-get found :secret))) 292 (let ((secret (plist-get found :secret)))
289 (if (functionp secret) 293 (if (functionp secret)
290 (funcall secret) 294 (funcall secret)
291 secret))) 295 secret))
296 (plist-get found :save-function))
292 nil))) 297 nil)))
293 298
294(defun nnimap-keepalive () 299(defun nnimap-keepalive ()
@@ -396,7 +401,12 @@ textual parts.")
396 (let ((nnimap-inhibit-logging t)) 401 (let ((nnimap-inhibit-logging t))
397 (setq login-result 402 (setq login-result
398 (nnimap-login (car credentials) (cadr credentials)))) 403 (nnimap-login (car credentials) (cadr credentials))))
399 (unless (car login-result) 404 (if (car login-result)
405 ;; save the credentials if a save function exists
406 ;; (such a function will only be passed if a new
407 ;; token was created)
408 (when (functionp (nth 2 credentials))
409 (funcall (nth 2 credentials)))
400 ;; If the login failed, then forget the credentials 410 ;; If the login failed, then forget the credentials
401 ;; that are now possibly cached. 411 ;; that are now possibly cached.
402 (dolist (host (list (nnoo-current-server 'nnimap) 412 (dolist (host (list (nnoo-current-server 'nnimap)