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
@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
;;; 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
"~/.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
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:
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"))
(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)
'("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 ()