Simplify erc-sasl's auth-source API

* doc/misc/erc.texi: Revise descriptions in SASL chapter to reflect
simplified auth-source options.

* lisp/erc/erc-sasl.el (erc-sasl-password,
erc-sasl-auth-source-function): Revise doc strings.
(erc-sasl-auth-source-password-as-host): New function to serve as
more useful choice for option `erc-sasl-auth-source-function'.
(erc-sasl--read-password): Promote auth-source to pole position, above
an explicit string and `:password'.

* test/lisp/erc/erc-sasl-tests.el (erc-sasl--read-password--basic):
Massage tests to conform to simplified `erc-sasl-password'
API.  (Bug#29108.)
This commit is contained in:
F. Jason Park 2022-11-23 21:31:19 -08:00 committed by Amin Bandali
parent 83b9496a19
commit 00de296d1b
3 changed files with 98 additions and 57 deletions

View file

@ -1055,17 +1055,10 @@ borrowing that parameter for its own uses, thus allowing you to call
@code{erc-tls} with @code{:password} set to your NickServ password. @code{erc-tls} with @code{:password} set to your NickServ password.
You can also set this to a nonemtpy string, and ERC will send that You can also set this to a nonemtpy string, and ERC will send that
when needed, no questions asked. If you instead give a non-@code{nil} when needed, no questions asked. Or, if you'd rather use auth-source,
symbol (other than @code{:password}), like @samp{Libera.Chat}, ERC set @code{erc-sasl-auth-source-function} to a function, and ERC will
will use it for the @code{:host} field in an auth-source query. perform an auth-source query instead. As last resort in all cases,
Actually, the same goes for when this option is @code{nil} but an ERC will prompt you for input.
explicit session ID is already on file (@pxref{Network Identifier}).
For all such queries, ERC specifies the resolved value of
@code{erc-sasl-user} for the @code{:user} (@code{:login}) param. Keep
in mind that none of this matters unless
@code{erc-sasl-auth-source-function} holds a function, and it's
@code{nil} by default. As a last resort, ERC will prompt you for
input.
Lastly, if your mechanism is @code{ecdsa-nist256p-challenge}, this Lastly, if your mechanism is @code{ecdsa-nist256p-challenge}, this
option should instead hold the file name of your key. option should instead hold the file name of your key.
@ -1075,7 +1068,23 @@ option should instead hold the file name of your key.
This is nearly identical to the other ERC @samp{auth-source} function This is nearly identical to the other ERC @samp{auth-source} function
options (@pxref{ERC auth-source functions}) except that the default options (@pxref{ERC auth-source functions}) except that the default
value here is @code{nil}, meaning you have to set it to something like value here is @code{nil}, meaning you have to set it to something like
@code{erc-auth-source-search} for queries to be performed. @code{erc-auth-source-search} for queries to be performed. For
convenience, this module provides the following as a possible value:
@defun erc-sasl-auth-source-password-as-host &rest plist
Setting @code{erc-sasl-auth-source-function} to this function tells
ERC to use @code{erc-sasl-password} for the @code{:host} field when
querying auth-source, even if its value is the default
@code{:password}, in which case ERC knows to ``resolve'' it to
@code{erc-session-password} and use that as long as it's
non-@code{nil}. Otherwise, ERC just defers to
@code{erc-auth-source-search} to determine the @code{:host}, along
with everything else.
@end defun
As long as this option specifies a function, ERC will pass it the
``resolved'' value of @code{erc-sasl-user} for the auth-source
@code{:user} param.
@end defopt @end defopt
@defopt erc-sasl-authzid @defopt erc-sasl-authzid
@ -1143,10 +1152,11 @@ machine Example.Net login aph-bot password sesame
(erc-tls :server "irc.libera.chat" :port 6697 (erc-tls :server "irc.libera.chat" :port 6697
:client-certificate t))) :client-certificate t)))
('example ('example
(let ((erc-sasl-auth-source-function #'erc-auth-source-search) (let ((erc-sasl-auth-source-function
(erc-sasl-password 'Example.Net)) #'erc-sasl-auth-source-password-as-host))
(erc-tls :server "irc.example.net" :port 6697 (erc-tls :server "irc.example.net" :port 6697
:user "alyssa"))))) :user "alyssa"
:password "Example.Net")))))
@end lisp @end lisp
You've started storing your credentials with auth-source and have You've started storing your credentials with auth-source and have

View file

@ -77,15 +77,14 @@ version is used."
(defcustom erc-sasl-password :password (defcustom erc-sasl-password :password
"Optional account password to send when authenticating. "Optional account password to send when authenticating.
When the value is a string, ERC will use it unconditionally for When `erc-sasl-auth-source-function' is a function, ERC will
most mechanisms. Likewise with `:password', except ERC will attempt an auth-source query and prompt for input if it fails.
instead use the \"session password\" on file, which often Otherwise, when the value is a nonempty string, ERC will use it
originates from the entry-point commands `erc' or `erc-tls'. unconditionally for most mechanisms. Likewise with `:password',
Otherwise, when `erc-sasl-auth-source-function' is a function, except ERC will instead use the \"session password\" on file, if
ERC will attempt an auth-source query, possibly using a non-nil any, which often originates from the entry-point commands `erc'
symbol for the suggested `:host' parameter if set as this or `erc-tls'. As with auth-source, ERC will prompt for input as
option's value or passed as an `:id' to `erc-tls'. Failing that, a fallback.
ERC will prompt for input.
Note that, with `:password', ERC will forgo sending a traditional Note that, with `:password', ERC will forgo sending a traditional
server password via the IRC \"PASS\" command. Also, when server password via the IRC \"PASS\" command. Also, when
@ -95,15 +94,18 @@ option should hold the file name of the key."
(defcustom erc-sasl-auth-source-function nil (defcustom erc-sasl-auth-source-function nil
"Function to query auth-source for an SASL password. "Function to query auth-source for an SASL password.
Called with keyword params known to `auth-source-search', which If provided, this function should expect to be called with any
includes `erc-sasl-user' for the `:user' field and number of keyword params known to `auth-source-search', even
`erc-sasl-password' for the `:host' field, when the latter option though ERC itself only specifies `:user' paired with a
is a non-nil, non-keyword symbol. In return, ERC expects a \"resolved\" `erc-sasl-user' value. When calling this function,
string to send as the SASL password, or nil, to move on to the ERC binds all options defined in this library, such as
next approach, as described in the doc string for the option `erc-sasl-password', to their values from entry-point invocation.
`erc-sasl-password'. See info node `(erc) Connecting' for In return, ERC expects a string to send as the SASL password, or
details on ERC's auth-source integration." nil, in which case, ERC will prompt the for input. See info
:type '(choice (function-item erc-auth-source-search) node `(erc) Connecting' for details on ERC's auth-source
integration."
:type '(choice (function-item erc-sasl-auth-source-password-as-host)
(function-item erc-auth-source-search)
(const nil) (const nil)
function)) function))
@ -130,20 +132,35 @@ details on ERC's auth-source integration."
(:nick (erc-downcase (erc-current-nick))) (:nick (erc-downcase (erc-current-nick)))
(v v))) (v v)))
(defun erc-sasl-auth-source-password-as-host (&rest plist)
"Call `erc-auth-source-search' with `erc-sasl-password' as `:host'.
But only do so when it's a string or a non-nil symbol, unless
that symbol is `:password', in which case, use a non-nil
`erc-session-password' instead. Otherwise, just defer to
`erc-auth-source-search' to pick a suitable `:host'. Expect
PLIST to contain keyword params known to `auth-source-search'."
(when erc-sasl-password
(when-let ((host (if (eq :password erc-sasl-password)
(and (not (functionp erc-session-password))
erc-session-password)
erc-sasl-password)))
(setq plist `(,@plist :host ,(format "%s" host)))))
(apply #'erc-auth-source-search plist))
(defun erc-sasl--read-password (prompt) (defun erc-sasl--read-password (prompt)
"Return configured option or server password. "Return configured option or server password.
PROMPT is passed to `read-passwd' if necessary." If necessary, pass PROMPT to `read-passwd'."
(if-let (if-let ((found (pcase (alist-get 'password erc-sasl--options)
((found (pcase (alist-get 'password erc-sasl--options) ((guard (alist-get 'authfn erc-sasl--options))
(let-alist erc-sasl--options
(let ((erc-sasl-user .user)
(erc-sasl-password .password)
(erc-sasl-mechanism .mechanism)
(erc-sasl-authzid .authzid)
(erc-sasl-auth-source-function .authfn))
(funcall .authfn :user (erc-sasl--get-user)))))
(:password erc-session-password) (:password erc-session-password)
((and (pred stringp) v) (unless (string-empty-p v) v)) ((and (pred stringp) v) (unless (string-empty-p v) v)))))
((and (let fn (alist-get 'authfn erc-sasl--options))
(guard fn) v
(let host
(or v (erc-networks--id-given erc-networks--id))))
(apply fn
:user (erc-sasl--get-user)
(and host (list :host (symbol-name host))))))))
(copy-sequence (erc--unfun found)) (copy-sequence (erc--unfun found))
(read-passwd prompt))) (read-passwd prompt)))

View file

@ -57,6 +57,8 @@
(erc-sasl--read-password "pwd:")) (erc-sasl--read-password "pwd:"))
"baz"))))) "baz")))))
;; This mainly tests `erc-sasl-auth-source-password-as-host'.
(ert-deftest erc-sasl--read-password--auth-source () (ert-deftest erc-sasl--read-password--auth-source ()
(ert-with-temp-file netrc-file (ert-with-temp-file netrc-file
:text (string-join :text (string-join
@ -70,33 +72,42 @@
(erc-session-server "irc.gnu.org") (erc-session-server "irc.gnu.org")
(erc-session-port 6697) (erc-session-port 6697)
(erc-networks--id (erc-networks--id-create nil)) (erc-networks--id (erc-networks--id-create nil))
calls
(fn (lambda (&rest r)
(push r calls)
(apply #'erc-auth-source-search r)))
erc-server-announced-name ; too early erc-server-announced-name ; too early
auth-source-do-cache) auth-source-do-cache
;;
(fn #'erc-sasl-auth-source-password-as-host)
calls)
(advice-add 'erc-auth-source-search :before
(lambda (&rest r) (push r calls))
'((name . erc-sasl--read-password--auth-source)))
(ert-info ("Symbol as password specifies machine") (ert-info ("Symbol as password specifies machine")
(let ((erc-sasl--options (let ((erc-sasl--options
`((user . "bob") (password . FSF.chat) (authfn . ,fn))) `((user . "bob") (password . FSF.chat) (authfn . ,fn))))
(erc-networks--id (make-erc-networks--id)))
(should (string= (erc-sasl--read-password nil) "sesame")) (should (string= (erc-sasl--read-password nil) "sesame"))
(should (equal (pop calls) '(:user "bob" :host "FSF.chat"))))) (should (equal (pop calls) '(:user "bob" :host "FSF.chat")))))
(ert-info ("ID for :host and `erc-session-username' for :user") ; *1 (ert-info (":password as password resolved to machine")
(let ((erc-session-password "FSF.chat")
(erc-sasl--options
`((user . "bob") (password . :password) (authfn . ,fn))))
(should (string= (erc-sasl--read-password nil) "sesame"))
(should (equal (pop calls) '(:user "bob" :host "FSF.chat")))))
(ert-info (":user resolved to `erc-session-username'") ; *1
(let ((erc-session-username "bob") (let ((erc-session-username "bob")
(erc-sasl--options `((user . :user) (password) (authfn . ,fn))) (erc-sasl--options `((user . :user) (password) (authfn . ,fn)))
(erc-networks--id (erc-networks--id-create 'GNU/chat))) (erc-networks--id (erc-networks--id-create 'GNU/chat)))
(should (string= (erc-sasl--read-password nil) "spam")) (should (string= (erc-sasl--read-password nil) "spam"))
(should (equal (pop calls) '(:user "bob" :host "GNU/chat"))))) (should (equal (pop calls) '(:user "bob")))))
(ert-info ("ID for :host and current nick for :user") ; *1 (ert-info (":user resolved to current nick") ; *1
(let ((erc-server-current-nick "bob") (let ((erc-server-current-nick "bob")
(erc-sasl--options `((user . :nick) (password) (authfn . ,fn))) (erc-sasl--options `((user . :nick) (password) (authfn . ,fn)))
(erc-networks--id (erc-networks--id-create 'GNU/chat))) (erc-networks--id (erc-networks--id-create 'GNU/chat)))
(should (string= (erc-sasl--read-password nil) "spam")) (should (string= (erc-sasl--read-password nil) "spam"))
(should (equal (pop calls) '(:user "bob" :host "GNU/chat"))))) (should (equal (pop calls) '(:user "bob")))))
(ert-info ("Symbol as password, entry lacks user field") (ert-info ("Symbol as password, entry lacks user field")
(let ((erc-server-current-nick "fake") (let ((erc-server-current-nick "fake")
@ -104,7 +115,10 @@
`((user . :nick) (password . MyHost) (authfn . ,fn))) `((user . :nick) (password . MyHost) (authfn . ,fn)))
(erc-networks--id (erc-networks--id-create 'GNU/chat))) (erc-networks--id (erc-networks--id-create 'GNU/chat)))
(should (string= (erc-sasl--read-password nil) "123")) (should (string= (erc-sasl--read-password nil) "123"))
(should (equal (pop calls) '(:user "fake" :host "MyHost")))))))) (should (equal (pop calls) '(:user "fake" :host "MyHost")))))
(advice-remove 'erc-auth-source-search
'erc-sasl--read-password--auth-source))))
(ert-deftest erc-sasl-create-client--plain () (ert-deftest erc-sasl-create-client--plain ()
(let* ((erc-session-password "password123") (let* ((erc-session-password "password123")