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.
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}
symbol (other than @code{:password}), like @samp{Libera.Chat}, ERC
will use it for the @code{:host} field in an auth-source query.
Actually, the same goes for when this option is @code{nil} but an
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.
when needed, no questions asked. Or, if you'd rather use auth-source,
set @code{erc-sasl-auth-source-function} to a function, and ERC will
perform an auth-source query instead. As last resort in all cases,
ERC will prompt you for input.
Lastly, if your mechanism is @code{ecdsa-nist256p-challenge}, this
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
options (@pxref{ERC auth-source functions}) except that the default
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
@defopt erc-sasl-authzid
@ -1143,10 +1152,11 @@ machine Example.Net login aph-bot password sesame
(erc-tls :server "irc.libera.chat" :port 6697
:client-certificate t)))
('example
(let ((erc-sasl-auth-source-function #'erc-auth-source-search)
(erc-sasl-password 'Example.Net))
(let ((erc-sasl-auth-source-function
#'erc-sasl-auth-source-password-as-host))
(erc-tls :server "irc.example.net" :port 6697
:user "alyssa")))))
:user "alyssa"
:password "Example.Net")))))
@end lisp
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
"Optional account password to send when authenticating.
When the value is a string, ERC will use it unconditionally for
most mechanisms. Likewise with `:password', except ERC will
instead use the \"session password\" on file, which often
originates from the entry-point commands `erc' or `erc-tls'.
Otherwise, when `erc-sasl-auth-source-function' is a function,
ERC will attempt an auth-source query, possibly using a non-nil
symbol for the suggested `:host' parameter if set as this
option's value or passed as an `:id' to `erc-tls'. Failing that,
ERC will prompt for input.
When `erc-sasl-auth-source-function' is a function, ERC will
attempt an auth-source query and prompt for input if it fails.
Otherwise, when the value is a nonempty string, ERC will use it
unconditionally for most mechanisms. Likewise with `:password',
except ERC will instead use the \"session password\" on file, if
any, which often originates from the entry-point commands `erc'
or `erc-tls'. As with auth-source, ERC will prompt for input as
a fallback.
Note that, with `:password', ERC will forgo sending a traditional
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
"Function to query auth-source for an SASL password.
Called with keyword params known to `auth-source-search', which
includes `erc-sasl-user' for the `:user' field and
`erc-sasl-password' for the `:host' field, when the latter option
is a non-nil, non-keyword symbol. In return, ERC expects a
string to send as the SASL password, or nil, to move on to the
next approach, as described in the doc string for the option
`erc-sasl-password'. See info node `(erc) Connecting' for
details on ERC's auth-source integration."
:type '(choice (function-item erc-auth-source-search)
If provided, this function should expect to be called with any
number of keyword params known to `auth-source-search', even
though ERC itself only specifies `:user' paired with a
\"resolved\" `erc-sasl-user' value. When calling this function,
ERC binds all options defined in this library, such as
`erc-sasl-password', to their values from entry-point invocation.
In return, ERC expects a string to send as the SASL password, or
nil, in which case, ERC will prompt the for input. See info
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)
function))
@ -130,20 +132,35 @@ details on ERC's auth-source integration."
(:nick (erc-downcase (erc-current-nick)))
(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)
"Return configured option or server password.
PROMPT is passed to `read-passwd' if necessary."
(if-let
((found (pcase (alist-get 'password erc-sasl--options)
(:password erc-session-password)
((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))))))))
If necessary, pass PROMPT to `read-passwd'."
(if-let ((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)
((and (pred stringp) v) (unless (string-empty-p v) v)))))
(copy-sequence (erc--unfun found))
(read-passwd prompt)))

View file

@ -57,6 +57,8 @@
(erc-sasl--read-password "pwd:"))
"baz")))))
;; This mainly tests `erc-sasl-auth-source-password-as-host'.
(ert-deftest erc-sasl--read-password--auth-source ()
(ert-with-temp-file netrc-file
:text (string-join
@ -70,33 +72,42 @@
(erc-session-server "irc.gnu.org")
(erc-session-port 6697)
(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
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")
(let ((erc-sasl--options
`((user . "bob") (password . FSF.chat) (authfn . ,fn)))
(erc-networks--id (make-erc-networks--id)))
`((user . "bob") (password . FSF.chat) (authfn . ,fn))))
(should (string= (erc-sasl--read-password nil) "sesame"))
(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")
(erc-sasl--options `((user . :user) (password) (authfn . ,fn)))
(erc-networks--id (erc-networks--id-create 'GNU/chat)))
(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")
(erc-sasl--options `((user . :nick) (password) (authfn . ,fn)))
(erc-networks--id (erc-networks--id-create 'GNU/chat)))
(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")
(let ((erc-server-current-nick "fake")
@ -104,7 +115,10 @@
`((user . :nick) (password . MyHost) (authfn . ,fn)))
(erc-networks--id (erc-networks--id-create 'GNU/chat)))
(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 ()
(let* ((erc-session-password "password123")