Unverified Commit 2a235777 authored by Damien Cassou's avatar Damien Cassou Committed by Ted Zlatanov
Browse files

auth-source-pass: Add documentation; fix tests and indentation.

* doc/misc/auth.texi: Document new integration with Pass. Use @itemize
  instead of @enumerate.
* lisp/auth-source-pass.el: Fix indentation.
  (auth-source-pass--remove-directory-name): Remove.
* test/lisp/auth-source-pass-tests.el: Adjust test macros.
parent b206b95c
......@@ -85,8 +85,9 @@ password (known as the secret).
Similarly, the auth-source library supports multiple storage backend,
currently either the classic ``netrc'' backend, examples of which you
can see later in this document, or the Secret Service API@. This is
done with EIEIO-based backends and you can write your own if you want.
can see later in this document, the Secret Service API, and pass, the
standard unix password manager. This is done with EIEIO-based
backends and you can write your own if you want.
@node Help for users
@chapter Help for users
......@@ -150,9 +151,9 @@ auth-source library is not loaded for some other reason.
@defvar auth-sources
The @code{auth-sources} variable tells the auth-source library where
your netrc files or Secret Service API collection items live for a
particular host and protocol. While you can get fancy, the default
and simplest configuration is:
your netrc files, Secret Service API collection items, or your
password store live for a particular host and protocol. While you can
get fancy, the default and simplest configuration is:
@lisp
;;; old default: required :host and :port, not needed anymore
......@@ -164,6 +165,9 @@ and simplest configuration is:
;;; use the Secrets API @var{Login} collection
;;; (@pxref{Secret Service API})
(setq auth-sources '("secrets:Login"))
;;; use pass (@file{~/.password-store})
;;; (@pxref{Pass, the Unix password store})
(setq auth-sources '(password-store))
@end lisp
By adding multiple entries to @code{auth-sources} with a particular
......@@ -402,6 +406,34 @@ then fall back to @file{~/.authinfo.gpg}.
"~/.authinfo.gpg"))
@end example
@node Pass, the Unix password store
@chapter Pass, the Unix password store
@uref{http://www.passwordstore.org,,The standard unix password
manager} (or just @code{pass}) stores your passwords in
@code{gpg}-protected files following the Unix philosophy.
Emacs integration of @code{pass} follows the first approach suggested
by the pass project itself for data organization to find data. This
means that the filename of the file containing the password for a user
on a particular host must contain the host name. The file itself must
contain the password on the first line, as well as a @code{username}
field containing the username on a subsequent line. A @code{port}
field can be used to differentiate the authentication data for several
services with the same username on the same host.
Users of @code{pass} may also be interested in functionality provided
by other Emacs packages dealing with pass:
@itemize
@item
@uref{https://git.zx2c4.com/password-store/tree/contrib/emacs/password-store.el,,password-store}: library wrapping @code{pass};
@item
@uref{https://github.com/NicolasPetton/pass,,pass}: major mode to manipulate the store and edit entries;
@item
@uref{https://github.com/jabranham/helm-pass,,helm-pass}: helm interface for pass.
@end itemize
@node Help for developers
@chapter Help for developers
......@@ -517,14 +549,14 @@ or EasyPG Assistant
To quick start, here are some questions:
@enumerate
@itemize
@item
Do you use GnuPG version 2 instead of GnuPG version 1?
@item
Do you use symmetric encryption rather than public key encryption?
@item
Do you want to use gpg-agent?
@end enumerate
@end itemize
Here are configurations depending on your answers:
......
......@@ -39,8 +39,8 @@
(require 'url-parse)
(cl-defun auth-source-pass-search (&rest spec
&key backend type host user port
&allow-other-keys)
&key backend type host user port
&allow-other-keys)
"Given a property list SPEC, return search matches from the :backend.
See `auth-source-search' for details on SPEC."
(cl-assert (or (null type) (eq type (oref backend type)))
......@@ -60,7 +60,7 @@ See `auth-source-search' for details on SPEC."
:user (or (auth-source-pass-get "user" entry) user)
:secret (lambda () (auth-source-pass-get 'secret entry)))))
(auth-source-pass--do-debug "return %s as final result (plus hidden password)"
(seq-subseq retval 0 -2)) ;; remove password
(seq-subseq retval 0 -2)) ;; remove password
retval))))
;;;###autoload
......@@ -159,11 +159,6 @@ CONTENTS is the contents of a password-store formatted file."
(hostname hostname)
(t host))))
(defun auth-source-pass--remove-directory-name (name)
"Remove directories from NAME.
E.g., if NAME is \"foo/bar\", return \"bar\"."
(replace-regexp-in-string ".*/" "" name))
(defun auth-source-pass--do-debug (&rest msg)
"Call `auth-source-do-debug` with MSG and a prefix."
(apply #'auth-source-do-debug
......@@ -216,7 +211,7 @@ Only return valid entries as of `auth-source-pass--entry-valid-p'."
(member entryname (split-string entry "/"))))
(and (= (length components-host-user) 2)
(string-equal user (cadr components-host-user))))
(string-equal entryname (auth-source-pass--remove-directory-name entry)))
(string-equal entryname (file-name-nondirectory entry)))
(auth-source-pass--entry-valid-p entry)))
(auth-source-pass-entries)))
......@@ -225,8 +220,8 @@ Only return valid entries as of `auth-source-pass--entry-valid-p'."
If USER is non nil, give precedence to entries containing a user field
matching USER."
(auth-source-pass--do-debug "searching for '%s' in entry names (user: %s)"
entryname
user)
entryname
user)
(let ((matching-entries (auth-source-pass--find-all-by-entry-name entryname user)))
(pcase (length matching-entries)
(0 (auth-source-pass--do-debug "no match found")
......
......@@ -63,108 +63,102 @@
This function is intended to be set to `auth-source-debug`."
(add-to-list 'auth-source-pass--debug-log (apply #'format msg) t))
(defmacro auth-source-pass--deftest (name arglist store &rest body)
"Define a new ert-test NAME with ARGLIST using STORE as password-store.
BODY is a sequence of instructions that will be evaluated.
This macro overrides `auth-source-pass-parse-entry' and `auth-source-pass-entries' to
test code without touching the file system."
(declare (indent 3))
`(ert-deftest ,name ,arglist
(cl-letf (((symbol-function 'auth-source-pass-parse-entry) (lambda (entry) (cdr (cl-find entry ,store :key #'car :test #'string=))) )
((symbol-function 'auth-source-pass-entries) (lambda () (mapcar #'car ,store)))
((symbol-function 'auth-source-pass--entry-valid-p) (lambda (_entry) t)))
(let ((auth-source-debug #'auth-source-pass--debug)
(auth-source-pass--debug-log nil))
,@body))))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name ()
'(("foo"))
(should (equal (auth-source-pass--find-match "foo" nil)
"foo")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-part ()
'(("foo"))
(should (equal (auth-source-pass--find-match "https://foo" nil)
"foo")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-ignoring-user ()
'(("foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"foo")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-with-user ()
'(("SomeUser@foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"SomeUser@foo")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full ()
'(("SomeUser@foo") ("foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"SomeUser@foo")))
;; same as previous one except the store is in another order
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full-reversed ()
'(("foo") ("SomeUser@foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"SomeUser@foo")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain ()
'(("bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" nil)
"bar.com")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-with-user ()
'(("someone@bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" "someone")
"someone@bar.com")))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-with-bad-user ()
'(("someoneelse@bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" "someone")
nil)))
(auth-source-pass--deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-prefer-full ()
'(("bar.com") ("foo.bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" nil)
"foo.bar.com")))
(auth-source-pass--deftest auth-source-pass-dont-match-at-folder-name ()
'(("foo.bar.com/foo"))
(should (equal (auth-source-pass--find-match "foo.bar.com" nil)
nil)))
(auth-source-pass--deftest auth-source-pass-search-with-user-first ()
'(("foo") ("user@foo"))
(should (equal (auth-source-pass--find-match "foo" "user")
"user@foo"))
(auth-source-pass--should-have-message-containing "Found 1 match"))
(auth-source-pass--deftest auth-source-pass-give-priority-to-desired-user ()
'(("foo") ("subdir/foo" ("user" . "someone")))
(should (equal (auth-source-pass--find-match "foo" "someone")
"subdir/foo"))
(auth-source-pass--should-have-message-containing "Found 2 matches")
(auth-source-pass--should-have-message-containing "matching user field"))
(auth-source-pass--deftest auth-source-pass-give-priority-to-desired-user-reversed ()
'(("foo" ("user" . "someone")) ("subdir/foo"))
(should (equal (auth-source-pass--find-match "foo" "someone")
"foo"))
(auth-source-pass--should-have-message-containing "Found 2 matches")
(auth-source-pass--should-have-message-containing "matching user field"))
(auth-source-pass--deftest auth-source-pass-return-first-when-several-matches ()
'(("foo") ("subdir/foo"))
(should (equal (auth-source-pass--find-match "foo" nil)
"foo"))
(auth-source-pass--should-have-message-containing "Found 2 matches")
(auth-source-pass--should-have-message-containing "the first one"))
(auth-source-pass--deftest auth-source-pass-make-divansantana-happy ()
'(("host.com"))
(should (equal (auth-source-pass--find-match "smtp.host.com" "myusername@host.co.za")
"host.com")))
(defmacro auth-source-pass--with-store (store &rest body)
"Use STORE as password-store while executing BODY."
(declare (indent 1))
`(cl-letf (((symbol-function 'auth-source-pass-parse-entry) (lambda (entry) (cdr (cl-find entry ,store :key #'car :test #'string=))) )
((symbol-function 'auth-source-pass-entries) (lambda () (mapcar #'car ,store)))
((symbol-function 'auth-source-pass--entry-valid-p) (lambda (_entry) t)))
(let ((auth-source-debug #'auth-source-pass--debug)
(auth-source-pass--debug-log nil))
,@body)))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name ()
(auth-source-pass--with-store '(("foo"))
(should (equal (auth-source-pass--find-match "foo" nil)
"foo"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-part ()
(auth-source-pass--with-store '(("foo"))
(should (equal (auth-source-pass--find-match "https://foo" nil)
"foo"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-ignoring-user ()
(auth-source-pass--with-store '(("foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"foo"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-with-user ()
(auth-source-pass--with-store '(("SomeUser@foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"SomeUser@foo"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full ()
(auth-source-pass--with-store '(("SomeUser@foo") ("foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"SomeUser@foo"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full-reversed ()
(auth-source-pass--with-store '(("foo") ("SomeUser@foo"))
(should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil)
"SomeUser@foo"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain ()
(auth-source-pass--with-store '(("bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" nil)
"bar.com"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-with-user ()
(auth-source-pass--with-store '(("someone@bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" "someone")
"someone@bar.com"))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-with-bad-user ()
(auth-source-pass--with-store '(("someoneelse@bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" "someone")
nil))))
(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-prefer-full ()
(auth-source-pass--with-store '(("bar.com") ("foo.bar.com"))
(should (equal (auth-source-pass--find-match "foo.bar.com" nil)
"foo.bar.com"))))
(ert-deftest auth-source-pass-dont-match-at-folder-name ()
(auth-source-pass--with-store '(("foo.bar.com/foo"))
(should (equal (auth-source-pass--find-match "foo.bar.com" nil)
nil))))
(ert-deftest auth-source-pass-search-with-user-first ()
(auth-source-pass--with-store '(("foo") ("user@foo"))
(should (equal (auth-source-pass--find-match "foo" "user")
"user@foo"))
(auth-source-pass--should-have-message-containing "Found 1 match")))
(ert-deftest auth-source-pass-give-priority-to-desired-user ()
(auth-source-pass--with-store '(("foo") ("subdir/foo" ("user" . "someone")))
(should (equal (auth-source-pass--find-match "foo" "someone")
"subdir/foo"))
(auth-source-pass--should-have-message-containing "Found 2 matches")
(auth-source-pass--should-have-message-containing "matching user field")))
(ert-deftest auth-source-pass-give-priority-to-desired-user-reversed ()
(auth-source-pass--with-store '(("foo" ("user" . "someone")) ("subdir/foo"))
(should (equal (auth-source-pass--find-match "foo" "someone")
"foo"))
(auth-source-pass--should-have-message-containing "Found 2 matches")
(auth-source-pass--should-have-message-containing "matching user field")))
(ert-deftest auth-source-pass-return-first-when-several-matches ()
(auth-source-pass--with-store '(("foo") ("subdir/foo"))
(should (equal (auth-source-pass--find-match "foo" nil)
"foo"))
(auth-source-pass--should-have-message-containing "Found 2 matches")
(auth-source-pass--should-have-message-containing "the first one")))
(ert-deftest auth-source-pass-make-divansantana-happy ()
(auth-source-pass--with-store '(("host.com"))
(should (equal (auth-source-pass--find-match "smtp.host.com" "myusername@host.co.za")
"host.com"))))
(ert-deftest auth-source-pass-hostname ()
(should (equal (auth-source-pass--hostname "https://foo.bar") "foo.bar"))
......@@ -176,37 +170,32 @@ test code without touching the file system."
(should (equal (auth-source-pass--hostname-with-user "http://foo.bar") "foo.bar"))
(should (equal (auth-source-pass--hostname-with-user "https://SomeUser@foo.bar") "SomeUser@foo.bar")))
(defmacro auth-source-pass--deftest-build-result (name arglist store &rest body)
"Define a new ert-test NAME with ARGLIST using STORE as password-store.
BODY is a sequence of instructions that will be evaluated.
This macro overrides `auth-source-pass-parse-entry',
`auth-source-pass-entries', and `auth-source-pass--find-match' to
ease testing."
(declare (indent 3))
`(auth-source-pass--deftest ,name ,arglist ,store
(defmacro auth-source-pass--with-store-find-foo (store &rest body)
"Use STORE while executing BODY. \"foo\" is the matched entry."
(declare (indent 1))
`(auth-source-pass--with-store ,store
(cl-letf (((symbol-function 'auth-source-pass-find-match)
(lambda (_host _user)
"foo")))
,@body)))
(auth-source-pass--deftest-build-result auth-source-pass-build-result-return-parameters ()
'(("foo"))
(let ((result (auth-source-pass--build-result "foo" 512 "user")))
(should (equal (plist-get result :port) 512))
(should (equal (plist-get result :user) "user"))))
(ert-deftest auth-source-pass-build-result-return-parameters ()
(auth-source-pass--with-store-find-foo '(("foo"))
(let ((result (auth-source-pass--build-result "foo" 512 "user")))
(should (equal (plist-get result :port) 512))
(should (equal (plist-get result :user) "user")))))
(auth-source-pass--deftest-build-result auth-source-pass-build-result-return-entry-values ()
'(("foo" ("port" . 512) ("user" . "anuser")))
(let ((result (auth-source-pass--build-result "foo" nil nil)))
(should (equal (plist-get result :port) 512))
(should (equal (plist-get result :user) "anuser"))))
(ert-deftest auth-source-pass-build-result-return-entry-values ()
(auth-source-pass--with-store-find-foo '(("foo" ("port" . 512) ("user" . "anuser")))
(let ((result (auth-source-pass--build-result "foo" nil nil)))
(should (equal (plist-get result :port) 512))
(should (equal (plist-get result :user) "anuser")))))
(auth-source-pass--deftest-build-result auth-source-pass-build-result-entry-takes-precedence ()
'(("foo" ("port" . 512) ("user" . "anuser")))
(let ((result (auth-source-pass--build-result "foo" 1024 "anotheruser")))
(should (equal (plist-get result :port) 512))
(should (equal (plist-get result :user) "anuser"))))
(ert-deftest auth-source-pass-build-result-entry-takes-precedence ()
(auth-source-pass--with-store-find-foo '(("foo" ("port" . 512) ("user" . "anuser")))
(let ((result (auth-source-pass--build-result "foo" 1024 "anotheruser")))
(should (equal (plist-get result :port) 512))
(should (equal (plist-get result :user) "anuser")))))
(ert-deftest auth-source-pass-only-return-entries-that-can-be-open ()
(cl-letf (((symbol-function 'auth-source-pass-entries)
......@@ -220,7 +209,7 @@ ease testing."
'("foo.site.com")))
(should (equal (auth-source-pass--find-all-by-entry-name "bar.site.com" "someuser")
'()))
(should (equal (auth-pass--find-all-by-entry-name "baz.site.com" "scott")
(should (equal (auth-source-pass--find-all-by-entry-name "baz.site.com" "scott")
'("mail/baz.site.com/scott")))))
(ert-deftest auth-source-pass-entry-is-not-valid-when-unreadable ()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment