aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKeith Amidon2019-05-05 20:21:43 -0700
committerDamien Cassou2019-06-24 09:15:40 +0200
commit0a580c187da9840298921a507bfe3dae3d1a00a7 (patch)
treeba3eea941f1b603e7b97c102a058c347e5dfadae
parent57e763a0a057621daac2761084556df38f7f2373 (diff)
downloademacs-0a580c187da9840298921a507bfe3dae3d1a00a7.tar.gz
emacs-0a580c187da9840298921a507bfe3dae3d1a00a7.zip
Minimize entry parsing in auth-source-pass
Prior to this commit, while searching for the most applicable entry password-store entries were decrypted and parsed to ensure they were valid. The entries were parsed in the order they were found on the filesystem and all applicable entries would be decrypted and parsed, which varied based on the contents of the password-store and the entry to be found. This is fine when the GPG key is cached and each entry can be decrypted without user interaction. However, for security some people have their GPG on a hardware token like a Yubikey setup so that they have to touch a sensor on the toke for every cryptographic operation, in which case it becomes inconvenient as each attempt to find an entry requires a variable number of touches of the hardware token. The implementation already assumes that names which contain more of the information in the search key should be preferred so there is an ordering of preference of applicable entries. If the decrypt and parsing is removed from the initial identification of applicable entries in the store then in most cases a single decrypt and parse of the most preferred entry will suffice, improving the experience for hardware token users that require interaction with the token. This commit implements that strategy. It is in spirit a refactor of the existing code. * lisp/auth-source-pass.el (auth-source-pass--matching-entries): New function, generate an ordered list of regular expression matchers for all possible names that could be in the password-store for the entry to be found and then makes a pass over the password-store entry names accumulating the matching entries in a list after the regexp that matched. This implementation ensures the password-store entry list still only has to be scanned once. (auth-source-pass--find-match-unambiguous): Use it to obtain candidate entries and then parse them one by one until an entry containing the desired information is located. When complete, return the parsed data of the entry instead of the entry name so that the information can be used directly to construct the auth-source response. (auth-source-pass--build-result): Update accordingly. (auth-source-pass--find-match): Update docstring accordingly. (auth-source-pass--select-one-entry) (auth-source-pass--entry-valid-p) (auth-source-pass--find-all-by-entry-name) (auth-source-pass--find-one-by-entry-name): Remove. (auth-source-pass--select-from-entries) (auth-source-pass--accumulate-matches) (auth-source-pass--entry-reducer) (auth-source-pass--generate-entry-suffixes) (auth-source-pass--domains) (auth-source-pass--name-port-user-suffixes): New functions. * test/lisp/auth-source-pass-tests.el: One test case was added to the test suite to verify that only the minimal number of entries are parsed in common cases. The auth-source-pass-only-return-entries-that-can-be-open test case had to be re-implemented because the function it was used eliminated as the functionality is provided elsewhere. All the other fairly substantial changes to the test suite are the result of mechanical changes that were required to adapt to auth-source-pass--find-match returning the data from a parsed password-store entry instead of the entry name.
-rw-r--r--lisp/auth-source-pass.el209
-rw-r--r--test/lisp/auth-source-pass-tests.el264
2 files changed, 281 insertions, 192 deletions
diff --git a/lisp/auth-source-pass.el b/lisp/auth-source-pass.el
index a0b0841e1f6..bcb215a6ace 100644
--- a/lisp/auth-source-pass.el
+++ b/lisp/auth-source-pass.el
@@ -77,14 +77,13 @@ See `auth-source-search' for details on SPEC."
77 77
78(defun auth-source-pass--build-result (host port user) 78(defun auth-source-pass--build-result (host port user)
79 "Build auth-source-pass entry matching HOST, PORT and USER." 79 "Build auth-source-pass entry matching HOST, PORT and USER."
80 (let ((entry (auth-source-pass--find-match host user port))) 80 (let ((entry-data (auth-source-pass--find-match host user port)))
81 (when entry 81 (when entry-data
82 (let* ((entry-data (auth-source-pass-parse-entry entry)) 82 (let ((retval (list
83 (retval (list 83 :host host
84 :host host 84 :port (or (auth-source-pass--get-attr "port" entry-data) port)
85 :port (or (auth-source-pass--get-attr "port" entry-data) port) 85 :user (or (auth-source-pass--get-attr "user" entry-data) user)
86 :user (or (auth-source-pass--get-attr "user" entry-data) user) 86 :secret (lambda () (auth-source-pass--get-attr 'secret entry-data)))))
87 :secret (lambda () (auth-source-pass--get-attr 'secret entry-data)))))
88 (auth-source-pass--do-debug "return %s as final result (plus hidden password)" 87 (auth-source-pass--do-debug "return %s as final result (plus hidden password)"
89 (seq-subseq retval 0 -2)) ;; remove password 88 (seq-subseq retval 0 -2)) ;; remove password
90 retval)))) 89 retval))))
@@ -183,33 +182,6 @@ CONTENTS is the contents of a password-store formatted file."
183 (cons (concat "auth-source-pass: " (car msg)) 182 (cons (concat "auth-source-pass: " (car msg))
184 (cdr msg)))) 183 (cdr msg))))
185 184
186(defun auth-source-pass--select-one-entry (entries user)
187 "Select one entry from ENTRIES by searching for a field matching USER."
188 (let ((number (length entries))
189 (entry-with-user
190 (and user
191 (seq-find (lambda (entry)
192 (string-equal (auth-source-pass-get "user" entry) user))
193 entries))))
194 (auth-source-pass--do-debug "found %s matches: %s" number
195 (mapconcat #'identity entries ", "))
196 (if entry-with-user
197 (progn
198 (auth-source-pass--do-debug "return %s as it contains matching user field"
199 entry-with-user)
200 entry-with-user)
201 (auth-source-pass--do-debug "return %s as it is the first one" (car entries))
202 (car entries))))
203
204(defun auth-source-pass--entry-valid-p (entry)
205 "Return t iff ENTRY can be opened.
206Also displays a warning if not. This function is slow, don't call it too
207often."
208 (if (auth-source-pass-parse-entry entry)
209 t
210 (auth-source-pass--do-debug "entry '%s' is not valid" entry)
211 nil))
212
213;; TODO: add tests for that when `assess-with-filesystem' is included 185;; TODO: add tests for that when `assess-with-filesystem' is included
214;; in Emacs 186;; in Emacs
215(defun auth-source-pass-entries () 187(defun auth-source-pass-entries ()
@@ -219,37 +191,8 @@ often."
219 (lambda (file) (file-name-sans-extension (file-relative-name file store-dir))) 191 (lambda (file) (file-name-sans-extension (file-relative-name file store-dir)))
220 (directory-files-recursively store-dir "\\.gpg$")))) 192 (directory-files-recursively store-dir "\\.gpg$"))))
221 193
222(defun auth-source-pass--find-all-by-entry-name (entryname user)
223 "Search the store for all entries either matching ENTRYNAME/USER or ENTRYNAME.
224Only return valid entries as of `auth-source-pass--entry-valid-p'."
225 (seq-filter (lambda (entry)
226 (and
227 (or
228 (let ((components-host-user
229 (member entryname (split-string entry "/"))))
230 (and (= (length components-host-user) 2)
231 (string-equal user (cadr components-host-user))))
232 (string-equal entryname (file-name-nondirectory entry)))
233 (auth-source-pass--entry-valid-p entry)))
234 (auth-source-pass-entries)))
235
236(defun auth-source-pass--find-one-by-entry-name (entryname user)
237 "Search the store for an entry matching ENTRYNAME.
238If USER is non nil, give precedence to entries containing a user field
239matching USER."
240 (auth-source-pass--do-debug "searching for '%s' in entry names (user: %s)"
241 entryname
242 user)
243 (let ((matching-entries (auth-source-pass--find-all-by-entry-name entryname user)))
244 (pcase (length matching-entries)
245 (0 (auth-source-pass--do-debug "no match found")
246 nil)
247 (1 (auth-source-pass--do-debug "found 1 match: %s" (car matching-entries))
248 (car matching-entries))
249 (_ (auth-source-pass--select-one-entry matching-entries user)))))
250
251(defun auth-source-pass--find-match (host user port) 194(defun auth-source-pass--find-match (host user port)
252 "Return a password-store entry name matching HOST, USER and PORT. 195 "Return password-store entry data matching HOST, USER and PORT.
253 196
254Disambiguate between user provided inside HOST (e.g., user@server.com) and 197Disambiguate between user provided inside HOST (e.g., user@server.com) and
255inside USER by giving priority to USER. Same for PORT." 198inside USER by giving priority to USER. Same for PORT."
@@ -263,33 +206,123 @@ inside USER by giving priority to USER. Same for PORT."
263 (or port (number-to-string (url-port url)))))) 206 (or port (number-to-string (url-port url))))))
264 207
265(defun auth-source-pass--find-match-unambiguous (hostname user port) 208(defun auth-source-pass--find-match-unambiguous (hostname user port)
266 "Return a password-store entry name matching HOSTNAME, USER and PORT. 209 "Return password-store entry data matching HOSTNAME, USER and PORT.
267If many matches are found, return the first one. If no match is found, 210If many matches are found, return the first one. If no match is found,
268return nil. 211return nil.
269 212
270HOSTNAME should not contain any username or port number." 213HOSTNAME should not contain any username or port number."
271 (or 214 (cl-reduce
272 (and user port (auth-source-pass--find-one-by-entry-name 215 (lambda (result entries)
273 (format "%s@%s%s%s" user hostname auth-source-pass-port-separator port) 216 (or result
274 user)) 217 (pcase (length entries)
275 (and user port (auth-source-pass--find-one-by-entry-name 218 (0 nil)
276 (format "%s%s%s" hostname auth-source-pass-port-separator port) 219 (1 (auth-source-pass-parse-entry (car entries)))
277 user)) 220 (_ (auth-source-pass--select-from-entries entries user)))))
278 (and user (auth-source-pass--find-one-by-entry-name 221 (auth-source-pass--matching-entries hostname user port)
279 (format "%s@%s" user hostname) 222 :initial-value nil))
280 user)) 223
281 (and port (auth-source-pass--find-one-by-entry-name 224(defun auth-source-pass--select-from-entries (entries user)
282 (format "%s%s%s" hostname auth-source-pass-port-separator port) 225 "Return best matching password-store entry data from ENTRIES.
283 nil)) 226
284 (auth-source-pass--find-one-by-entry-name hostname user) 227If USER is non nil, give precedence to entries containing a user field
285 ;; if that didn't work, remove subdomain: foo.bar.com -> bar.com 228matching USER."
286 (let ((components (split-string hostname "\\."))) 229 (cl-reduce
287 (when (= (length components) 3) 230 (lambda (result entry)
288 ;; start from scratch 231 (let ((entry-data (auth-source-pass-parse-entry entry)))
289 (auth-source-pass--find-match-unambiguous 232 (cond ((equal (auth-source-pass--get-attr "user" result) user)
290 (mapconcat 'identity (cdr components) ".") 233 result)
291 user 234 ((equal (auth-source-pass--get-attr "user" entry-data) user)
292 port))))) 235 entry-data)
236 (t
237 result))))
238 entries
239 :initial-value (auth-source-pass-parse-entry (car entries))))
240
241(defun auth-source-pass--matching-entries (hostname user port)
242 "Return all matching password-store entries for HOSTNAME, USER, & PORT.
243
244The result is a list of lists of password-store entries, where
245each sublist contains entries that actually exist in the
246password-store matching one of the entry name formats that
247auth-source-pass expects, most specific to least specific."
248 (let* ((entries-lists (mapcar
249 #'cdr
250 (auth-source-pass--accumulate-matches hostname user port)))
251 (entries (apply #'cl-concatenate (cons 'list entries-lists))))
252 (if entries
253 (auth-source-pass--do-debug (format "found: %S" entries))
254 (auth-source-pass--do-debug "no matches found"))
255 entries-lists))
256
257(defun auth-source-pass--accumulate-matches (hostname user port)
258 "Accumulate matching password-store entries into sublists.
259
260Entries matching supported formats that combine HOSTNAME, USER, &
261PORT are accumulated into sublists where the car of each sublist
262is a regular expression for matching paths in the password-store
263and the remainder is the list of matching entries."
264 (let ((suffix-match-lists
265 (mapcar (lambda (suffix) (list (format "\\(^\\|/\\)%s$" suffix)))
266 (auth-source-pass--generate-entry-suffixes hostname user port))))
267 (cl-reduce #'auth-source-pass--entry-reducer
268 (auth-source-pass-entries)
269 :initial-value suffix-match-lists)))
270
271(defun auth-source-pass--entry-reducer (match-lists entry)
272 "Match MATCH-LISTS sublists against ENTRY.
273
274The result is a copy of match-lists with the entry added to the
275end of any sublists for which the regular expression at the head
276of the list matches the entry name."
277 (mapcar (lambda (match-list)
278 (if (string-match (car match-list) entry)
279 (append match-list (list entry))
280 match-list))
281 match-lists))
282
283(defun auth-source-pass--generate-entry-suffixes (hostname user port)
284 "Return a list of possible entry path suffixes in the password-store.
285
286Based on the supported pathname patterns for HOSTNAME, USER, &
287PORT, return a list of possible suffixes for matching entries in
288the password-store."
289 (let ((domains (auth-source-pass--domains (split-string hostname "\\."))))
290 (seq-mapcat (lambda (n)
291 (auth-source-pass--name-port-user-suffixes n user port))
292 domains)))
293
294(defun auth-source-pass--domains (name-components)
295 "Return a list of possible domain names matching the hostname.
296
297This function takes a list of NAME-COMPONENTS, the strings
298separated by periods in the hostname, and returns a list of full
299domain names containing the trailing sequences of those
300components, from longest to shortest."
301 (cl-maplist (lambda (components) (mapconcat #'identity components "."))
302 name-components))
303
304(defun auth-source-pass--name-port-user-suffixes (name user port)
305 "Return a list of possible path suffixes for NAME, USER, & PORT.
306
307The resulting list is ordered from most specifc to least
308specific, with paths matching all of NAME, USER, & PORT first,
309then NAME & USER, then NAME & PORT, then just NAME."
310 (seq-mapcat
311 #'identity
312 (list
313 (when (and user port)
314 (list
315 (format "%s@%s%s%s" user name auth-source-pass-port-separator port)
316 (format "%s%s%s/%s" name auth-source-pass-port-separator port user)))
317 (when user
318 (list
319 (format "%s@%s" user name)
320 (format "%s/%s" name user)))
321 (when port
322 (list
323 (format "%s%s%s" name auth-source-pass-port-separator port)))
324 (list
325 (format "%s" name)))))
293 326
294(provide 'auth-source-pass) 327(provide 'auth-source-pass)
295;;; auth-source-pass.el ends here 328;;; auth-source-pass.el ends here
diff --git a/test/lisp/auth-source-pass-tests.el b/test/lisp/auth-source-pass-tests.el
index 1539d9611f6..2c28f799453 100644
--- a/test/lisp/auth-source-pass-tests.el
+++ b/test/lisp/auth-source-pass-tests.el
@@ -63,14 +63,19 @@
63This function is intended to be set to `auth-source-debug`." 63This function is intended to be set to `auth-source-debug`."
64 (add-to-list 'auth-source-pass--debug-log (apply #'format msg) t)) 64 (add-to-list 'auth-source-pass--debug-log (apply #'format msg) t))
65 65
66(defvar auth-source-pass--parse-log nil)
67
66(defmacro auth-source-pass--with-store (store &rest body) 68(defmacro auth-source-pass--with-store (store &rest body)
67 "Use STORE as password-store while executing BODY." 69 "Use STORE as password-store while executing BODY."
68 (declare (indent 1)) 70 (declare (indent 1))
69 `(cl-letf (((symbol-function 'auth-source-pass-parse-entry) (lambda (entry) (cdr (cl-find entry ,store :key #'car :test #'string=))) ) 71 `(cl-letf (((symbol-function 'auth-source-pass-parse-entry)
70 ((symbol-function 'auth-source-pass-entries) (lambda () (mapcar #'car ,store))) 72 (lambda (entry)
71 ((symbol-function 'auth-source-pass--entry-valid-p) (lambda (_entry) t))) 73 (add-to-list 'auth-source-pass--parse-log entry)
74 (cdr (cl-find entry ,store :key #'car :test #'string=))))
75 ((symbol-function 'auth-source-pass-entries) (lambda () (mapcar #'car ,store))))
72 (let ((auth-source-debug #'auth-source-pass--debug) 76 (let ((auth-source-debug #'auth-source-pass--debug)
73 (auth-source-pass--debug-log nil)) 77 (auth-source-pass--debug-log nil)
78 (auth-source-pass--parse-log nil))
74 ,@body))) 79 ,@body)))
75 80
76(ert-deftest auth-source-pass-any-host () 81(ert-deftest auth-source-pass-any-host ()
@@ -88,125 +93,184 @@ This function is intended to be set to `auth-source-debug`."
88 ("bar")) 93 ("bar"))
89 (should-not (auth-source-pass-search :host "baz")))) 94 (should-not (auth-source-pass-search :host "baz"))))
90 95
96(ert-deftest auth-source-pass-find-match-minimal-parsing ()
97 (let ((store-contents
98 '(("baz" ("secret" . "baz password"))
99 ("baz:123" ("secret" . "baz:123 password"))
100 ("baz/foo" ("secret" . "baz/foo password"))
101 ("foo@baz" ("secret" . "foo@baz password"))
102 ("baz:123/foo" ("secret" . "baz:123/foo password"))
103 ("foo@baz:123" ("secret" . "foo@baz:123 password"))
104 ("bar.baz" ("secret" . "bar.baz password"))
105 ("bar.baz:123" ("secret" . "bar.baz:123 password"))
106 ("bar.baz/foo" ("secret" . "bar.baz/foo password"))
107 ("foo@bar.baz" ("secret" . "foo@bar.baz password"))
108 ("bar.baz:123/foo" ("secret" . "bar.baz:123/foo password"))
109 ("foo@bar.baz:123" ("secret" . "foo@bar.baz:123 password")))))
110 (auth-source-pass--with-store store-contents
111 (auth-source-pass--find-match "bar.baz" "foo" "123")
112 (should (equal auth-source-pass--parse-log '("foo@bar.baz:123"))))
113 (auth-source-pass--with-store store-contents
114 (auth-source-pass--find-match "bar.baz" "foo" nil)
115 (should (equal auth-source-pass--parse-log '("foo@bar.baz"))))
116 (auth-source-pass--with-store store-contents
117 (auth-source-pass--find-match "bar.baz" nil "123")
118 (should (equal auth-source-pass--parse-log '("bar.baz:123"))))
119 (auth-source-pass--with-store store-contents
120 (auth-source-pass--find-match "bar.baz" nil nil)
121 (should (equal auth-source-pass--parse-log '("bar.baz"))))
122 (auth-source-pass--with-store store-contents
123 (auth-source-pass--find-match "baz" nil nil)
124 (should (equal auth-source-pass--parse-log '("baz"))))))
91 125
92(ert-deftest auth-source-pass-find-match-matching-at-entry-name () 126(ert-deftest auth-source-pass-find-match-matching-at-entry-name ()
93 (auth-source-pass--with-store '(("foo")) 127 (auth-source-pass--with-store
94 (should (equal (auth-source-pass--find-match "foo" nil nil) 128 '(("foo" ("secret" . "foo password")))
95 "foo")))) 129 (let ((result (auth-source-pass--find-match "foo" nil nil)))
130 (should (equal (auth-source-pass--get-attr "secret" result)
131 "foo password")))))
96 132
97(ert-deftest auth-source-pass-find-match-matching-at-entry-name-part () 133(ert-deftest auth-source-pass-find-match-matching-at-entry-name-part ()
98 (auth-source-pass--with-store '(("foo")) 134 (auth-source-pass--with-store
99 (should (equal (auth-source-pass--find-match "https://foo" nil nil) 135 '(("foo" ("secret" . "foo password")))
100 "foo")))) 136 (let ((result (auth-source-pass--find-match "https://foo" nil nil)))
137 (should (equal (auth-source-pass--get-attr "secret" result)
138 "foo password")))))
101 139
102(ert-deftest auth-source-pass-find-match-matching-at-entry-name-ignoring-user () 140(ert-deftest auth-source-pass-find-match-matching-at-entry-name-ignoring-user ()
103 (auth-source-pass--with-store '(("foo")) 141 (auth-source-pass--with-store
104 (should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil nil) 142 '(("foo" ("secret" . "foo password")))
105 "foo")))) 143 (let ((result (auth-source-pass--find-match "https://SomeUser@foo" nil nil)))
144 (should (equal (auth-source-pass--get-attr "secret" result)
145 "foo password")))))
106 146
107(ert-deftest auth-source-pass-find-match-matching-at-entry-name-with-user () 147(ert-deftest auth-source-pass-find-match-matching-at-entry-name-with-user ()
108 (auth-source-pass--with-store '(("SomeUser@foo")) 148 (auth-source-pass--with-store
109 (should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil nil) 149 '(("SomeUser@foo" ("secret" . "SomeUser@foo password")))
110 "SomeUser@foo")))) 150 (let ((result (auth-source-pass--find-match "https://SomeUser@foo" nil nil)))
151 (should (equal (auth-source-pass--get-attr "secret" result)
152 "SomeUser@foo password")))))
111 153
112(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full () 154(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full ()
113 (auth-source-pass--with-store '(("SomeUser@foo") ("foo")) 155 (auth-source-pass--with-store
114 (should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil nil) 156 '(("SomeUser@foo" ("secret" . "SomeUser@foo password"))
115 "SomeUser@foo")))) 157 ("foo" ("secret" . "foo password")))
158 (let ((result (auth-source-pass--find-match "https://SomeUser@foo" nil nil)))
159 (should (equal (auth-source-pass--get-attr "secret" result)
160 "SomeUser@foo password")))))
116 161
117(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full-reversed () 162(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full-reversed ()
118 (auth-source-pass--with-store '(("foo") ("SomeUser@foo")) 163 (auth-source-pass--with-store
119 (should (equal (auth-source-pass--find-match "https://SomeUser@foo" nil nil) 164 '(("foo" ("secret" . "foo password"))
120 "SomeUser@foo")))) 165 ("SomeUser@foo" ("secret" . "SomeUser@foo password")))
121 166 (let ((result (auth-source-pass--find-match "https://SomeUser@foo" nil nil)))
122(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain () 167 (should (equal (auth-source-pass--get-attr "secret" result)
168 "SomeUser@foo password")))))
169
170(ert-deftest auth-source-pass-matching-entries-name-without-subdomain ()
123 (auth-source-pass--with-store '(("bar.com")) 171 (auth-source-pass--with-store '(("bar.com"))
124 (should (equal (auth-source-pass--find-match "foo.bar.com" nil nil) 172 (should (equal (auth-source-pass--matching-entries "foo.bar.com" nil nil)
125 "bar.com")))) 173 '(nil ("bar.com") nil)))))
126 174
127(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-with-user () 175(ert-deftest auth-source-pass-matching-entries-name-without-subdomain-with-user ()
128 (auth-source-pass--with-store '(("someone@bar.com")) 176 (auth-source-pass--with-store '(("someone@bar.com"))
129 (should (equal (auth-source-pass--find-match "foo.bar.com" "someone" nil) 177 (should (equal (auth-source-pass--matching-entries "foo.bar.com" "someone" nil)
130 "someone@bar.com")))) 178 '(nil nil nil ("someone@bar.com") nil nil nil nil nil)))))
131 179
132(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-with-bad-user () 180(ert-deftest auth-source-pass-matching-entries-name-without-subdomain-with-bad-user ()
133 (auth-source-pass--with-store '(("someoneelse@bar.com")) 181 (auth-source-pass--with-store '(("someoneelse@bar.com"))
134 (should (equal (auth-source-pass--find-match "foo.bar.com" "someone" nil) 182 (should (equal (auth-source-pass--matching-entries "foo.bar.com" "someone" nil)
135 nil)))) 183 '(nil nil nil nil nil nil nil nil nil)))))
136 184
137(ert-deftest auth-source-pass-find-match-matching-at-entry-name-without-subdomain-prefer-full () 185(ert-deftest auth-source-pass-matching-entries-name-without-subdomain-prefer-full ()
138 (auth-source-pass--with-store '(("bar.com") ("foo.bar.com")) 186 (auth-source-pass--with-store '(("bar.com") ("foo.bar.com"))
139 (should (equal (auth-source-pass--find-match "foo.bar.com" nil nil) 187 (should (equal (auth-source-pass--matching-entries "foo.bar.com" nil nil)
140 "foo.bar.com")))) 188 '(("foo.bar.com") ("bar.com") nil)))))
141 189
142(ert-deftest auth-source-pass-dont-match-at-folder-name () 190(ert-deftest auth-source-pass-dont-match-at-folder-name ()
143 (auth-source-pass--with-store '(("foo.bar.com/foo")) 191 (auth-source-pass--with-store '(("foo.bar.com/foo"))
144 (should (equal (auth-source-pass--find-match "foo.bar.com" nil nil) 192 (should (equal (auth-source-pass--matching-entries "foo.bar.com" nil nil)
145 nil)))) 193 '(nil nil nil)))))
146 194
147(ert-deftest auth-source-pass-find-match-matching-host-port-and-subdir-user () 195(ert-deftest auth-source-pass-matching-entries-host-port-and-subdir-user ()
148 (auth-source-pass--with-store '(("bar.com:443/someone")) 196 (auth-source-pass--with-store '(("bar.com:443/someone"))
149 (should (equal (auth-source-pass--find-match "bar.com" "someone" "443") 197 (should (equal (auth-source-pass--matching-entries "bar.com" "someone" "443")
150 "bar.com:443/someone")))) 198 '(nil ("bar.com:443/someone") nil nil nil nil
199 nil nil nil nil nil nil)))))
151 200
152(ert-deftest auth-source-pass-find-match-matching-host-port-and-subdir-user-with-custom-separator () 201(ert-deftest auth-source-pass-matching-entries-host-port-and-subdir-user-with-custom-separator ()
153 (let ((auth-source-pass-port-separator "#")) 202 (let ((auth-source-pass-port-separator "#"))
154 (auth-source-pass--with-store '(("bar.com#443/someone")) 203 (auth-source-pass--with-store '(("bar.com#443/someone"))
155 (should (equal (auth-source-pass--find-match "bar.com" "someone" "443") 204 (should (equal (auth-source-pass--matching-entries "bar.com" "someone" "443")
156 "bar.com#443/someone"))))) 205 '(nil ("bar.com#443/someone") nil nil nil nil
157 206 nil nil nil nil nil nil))))))
158(ert-deftest auth-source-pass-find-match-matching-extracting-user-from-host () 207
159 (auth-source-pass--with-store '(("foo.com/bar")) 208(ert-deftest auth-source-pass-matching-entries-extracting-user-from-host ()
160 (should (equal (auth-source-pass--find-match "https://bar@foo.com" nil nil) 209 (auth-source-pass--with-store
161 "foo.com/bar")))) 210 '(("foo.com/bar" ("secret" . "foo.com/bar password")))
162 211 (let ((result (auth-source-pass--find-match "https://bar@foo.com" nil nil)))
163(ert-deftest auth-source-pass-search-with-user-first () 212 (should (equal (auth-source-pass--get-attr "secret" result)
213 "foo.com/bar password")))))
214
215(ert-deftest auth-source-pass-matching-entries-with-user-first ()
164 (auth-source-pass--with-store '(("foo") ("user@foo")) 216 (auth-source-pass--with-store '(("foo") ("user@foo"))
165 (should (equal (auth-source-pass--find-match "foo" "user" nil) 217 (should (equal (auth-source-pass--matching-entries "foo" "user" nil)
166 "user@foo")) 218 '(("user@foo") nil ("foo"))))
167 (auth-source-pass--should-have-message-containing "Found 1 match"))) 219 (auth-source-pass--should-have-message-containing "found: (\"user@foo\" \"foo\"")))
168 220
169(ert-deftest auth-source-pass-give-priority-to-desired-user () 221(ert-deftest auth-source-pass-give-priority-to-desired-user ()
170 (auth-source-pass--with-store '(("foo") ("subdir/foo" ("user" . "someone"))) 222 (auth-source-pass--with-store
171 (should (equal (auth-source-pass--find-match "foo" "someone" nil) 223 '(("foo" ("secret" . "foo password"))
172 "subdir/foo")) 224 ("subdir/foo" ("secret" . "subdir/foo password") ("user" . "someone")))
173 (auth-source-pass--should-have-message-containing "Found 2 matches") 225 (let ((result (auth-source-pass--find-match "foo" "someone" nil)))
174 (auth-source-pass--should-have-message-containing "matching user field"))) 226 (should (equal (auth-source-pass--get-attr "secret" result)
227 "subdir/foo password"))
228 (should (equal (auth-source-pass--get-attr "user" result)
229 "someone")))
230 (auth-source-pass--should-have-message-containing "found: (\"foo\" \"subdir/foo\"")))
175 231
176(ert-deftest auth-source-pass-give-priority-to-desired-user-reversed () 232(ert-deftest auth-source-pass-give-priority-to-desired-user-reversed ()
177 (auth-source-pass--with-store '(("foo" ("user" . "someone")) ("subdir/foo")) 233 (auth-source-pass--with-store
178 (should (equal (auth-source-pass--find-match "foo" "someone" nil) 234 '(("foo" ("secret" . "foo password") ("user" . "someone"))
179 "foo")) 235 ("subdir/foo" ("secret" . "subdir/foo password")))
180 (auth-source-pass--should-have-message-containing "Found 2 matches") 236 (let ((result (auth-source-pass--find-match "foo" "someone" nil)))
181 (auth-source-pass--should-have-message-containing "matching user field"))) 237 (should (equal (auth-source-pass--get-attr "secret" result)
238 "foo password")))
239 (auth-source-pass--should-have-message-containing "found: (\"foo\" \"subdir/foo\"")))
182 240
183(ert-deftest auth-source-pass-return-first-when-several-matches () 241(ert-deftest auth-source-pass-return-first-when-several-matches ()
184 (auth-source-pass--with-store '(("foo") ("subdir/foo")) 242 (auth-source-pass--with-store
185 (should (equal (auth-source-pass--find-match "foo" nil nil) 243 '(("foo" ("secret" . "foo password"))
186 "foo")) 244 ("subdir/foo" ("secret" . "subdir/foo password")))
187 (auth-source-pass--should-have-message-containing "Found 2 matches") 245 (let ((result (auth-source-pass--find-match "foo" nil nil)))
188 (auth-source-pass--should-have-message-containing "the first one"))) 246 (should (equal (auth-source-pass--get-attr "secret" result)
189 247 "foo password")))
190(ert-deftest auth-source-pass-make-divansantana-happy () 248 (auth-source-pass--should-have-message-containing "found: (\"foo\" \"subdir/foo\"")))
249
250(ert-deftest auth-source-pass-matching-entries-make-divansantana-happy ()
191 (auth-source-pass--with-store '(("host.com")) 251 (auth-source-pass--with-store '(("host.com"))
192 (should (equal (auth-source-pass--find-match "smtp.host.com" "myusername@host.co.za" nil) 252 (should (equal (auth-source-pass--matching-entries "smtp.host.com" "myusername@host.co.za" nil)
193 "host.com")))) 253 '(nil nil nil nil nil ("host.com") nil nil nil)))))
194 254
195(ert-deftest auth-source-pass-find-host-without-port () 255(ert-deftest auth-source-pass-find-host-without-port ()
196 (auth-source-pass--with-store '(("host.com")) 256 (auth-source-pass--with-store
197 (should (equal (auth-source-pass--find-match "host.com:8888" "someuser" nil) 257 '(("host.com" ("secret" . "host.com password")))
198 "host.com")))) 258 (let ((result (auth-source-pass--find-match "host.com:8888" "someuser" nil)))
259 (should (equal (auth-source-pass--get-attr "secret" result)
260 "host.com password")))))
199 261
200(ert-deftest auth-source-pass-find-host-with-port () 262(ert-deftest auth-source-pass-matching-entries-host-with-port ()
201 (auth-source-pass--with-store '(("host.com:443")) 263 (auth-source-pass--with-store '(("host.com:443"))
202 (should (equal (auth-source-pass--find-match "host.com" "someuser" "443") 264 (should (equal (auth-source-pass--matching-entries "host.com" "someuser" "443")
203 "host.com:443")))) 265 '(nil nil nil nil ("host.com:443") nil
266 nil nil nil nil nil nil)))))
204 267
205(ert-deftest auth-source-pass-find-host-with-custom-port-separator () 268(ert-deftest auth-source-pass-matching-entries-with-custom-port-separator ()
206 (let ((auth-source-pass-port-separator "#")) 269 (let ((auth-source-pass-port-separator "#"))
207 (auth-source-pass--with-store '(("host.com#443")) 270 (auth-source-pass--with-store '(("host.com#443"))
208 (should (equal (auth-source-pass--find-match "host.com" "someuser" "443") 271 (should (equal (auth-source-pass--matching-entries "host.com" "someuser" "443")
209 "host.com#443"))))) 272 '(nil nil nil nil ("host.com#443") nil
273 nil nil nil nil nil nil))))))
210 274
211(defmacro auth-source-pass--with-store-find-foo (store &rest body) 275(defmacro auth-source-pass--with-store-find-foo (store &rest body)
212 "Use STORE while executing BODY. \"foo\" is the matched entry." 276 "Use STORE while executing BODY. \"foo\" is the matched entry."
@@ -218,7 +282,8 @@ This function is intended to be set to `auth-source-debug`."
218 ,@body))) 282 ,@body)))
219 283
220(ert-deftest auth-source-pass-build-result-return-parameters () 284(ert-deftest auth-source-pass-build-result-return-parameters ()
221 (auth-source-pass--with-store-find-foo '(("foo")) 285 (auth-source-pass--with-store-find-foo
286 '(("foo" ("secret" . "foo password")))
222 (let ((result (auth-source-pass--build-result "foo" 512 "user"))) 287 (let ((result (auth-source-pass--build-result "foo" 512 "user")))
223 (should (equal (plist-get result :port) 512)) 288 (should (equal (plist-get result :port) 512))
224 (should (equal (plist-get result :user) "user"))))) 289 (should (equal (plist-get result :user) "user")))))
@@ -238,7 +303,9 @@ This function is intended to be set to `auth-source-debug`."
238(ert-deftest auth-source-pass-build-result-passes-full-host-to-find-match () 303(ert-deftest auth-source-pass-build-result-passes-full-host-to-find-match ()
239 (let (passed-host) 304 (let (passed-host)
240 (cl-letf (((symbol-function 'auth-source-pass--find-match) 305 (cl-letf (((symbol-function 'auth-source-pass--find-match)
241 (lambda (host _user _port) (setq passed-host host)))) 306 (lambda (host _user _port)
307 (setq passed-host host)
308 nil)))
242 (auth-source-pass--build-result "https://user@host.com:123" nil nil) 309 (auth-source-pass--build-result "https://user@host.com:123" nil nil)
243 (should (equal passed-host "https://user@host.com:123")) 310 (should (equal passed-host "https://user@host.com:123"))
244 (auth-source-pass--build-result "https://user@host.com" nil nil) 311 (auth-source-pass--build-result "https://user@host.com" nil nil)
@@ -249,27 +316,16 @@ This function is intended to be set to `auth-source-debug`."
249 (should (equal passed-host "user@host.com:443"))))) 316 (should (equal passed-host "user@host.com:443")))))
250 317
251(ert-deftest auth-source-pass-only-return-entries-that-can-be-open () 318(ert-deftest auth-source-pass-only-return-entries-that-can-be-open ()
252 (cl-letf (((symbol-function 'auth-source-pass-entries) 319 (auth-source-pass--with-store
253 (lambda () '("foo.site.com" "bar.site.com" "mail/baz.site.com/scott"))) 320 '(("foo.site.com" ("secret" . "foo.site.com password"))
254 ((symbol-function 'auth-source-pass--entry-valid-p) 321 ("bar.site.com") ; An entry name with no data is invalid
255 ;; only foo.site.com and "mail/baz.site.com/scott" are valid 322 ("mail/baz.site.com/scott" ("secret" . "mail/baz.site.com/scott password")))
256 (lambda (entry) (member entry '("foo.site.com" "mail/baz.site.com/scott"))))) 323 (should (equal (auth-source-pass--find-match "foo.site.com" "someuser" nil)
257 (should (equal (auth-source-pass--find-all-by-entry-name "foo.site.com" "someuser") 324 '(("secret" . "foo.site.com password"))))
258 '("foo.site.com"))) 325 (should (equal (auth-source-pass--find-match "bar.site.com" "someuser" nil)
259 (should (equal (auth-source-pass--find-all-by-entry-name "bar.site.com" "someuser") 326 nil))
260 '())) 327 (should (equal (auth-source-pass--find-match "baz.site.com" "scott" nil)
261 (should (equal (auth-source-pass--find-all-by-entry-name "baz.site.com" "scott") 328 '(("secret" . "mail/baz.site.com/scott password"))))))
262 '("mail/baz.site.com/scott")))))
263
264(ert-deftest auth-source-pass-entry-is-not-valid-when-unreadable ()
265 (cl-letf (((symbol-function 'auth-source-pass--read-entry)
266 (lambda (entry)
267 ;; only foo is a valid entry
268 (if (string-equal entry "foo")
269 "password"
270 nil))))
271 (should (auth-source-pass--entry-valid-p "foo"))
272 (should-not (auth-source-pass--entry-valid-p "bar"))))
273 329
274(ert-deftest auth-source-pass-can-start-from-auth-source-search () 330(ert-deftest auth-source-pass-can-start-from-auth-source-search ()
275 (auth-source-pass--with-store '(("gitlab.com" ("user" . "someone"))) 331 (auth-source-pass--with-store '(("gitlab.com" ("user" . "someone")))