aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/auth-source-pass.el108
-rw-r--r--test/lisp/auth-source-pass-tests.el482
2 files changed, 349 insertions, 241 deletions
diff --git a/lisp/auth-source-pass.el b/lisp/auth-source-pass.el
index af421645cbc..295bda507b0 100644
--- a/lisp/auth-source-pass.el
+++ b/lisp/auth-source-pass.el
@@ -197,10 +197,17 @@ CONTENTS is the contents of a password-store formatted file."
197 197
198Disambiguate between user provided inside HOST (e.g., user@server.com) and 198Disambiguate between user provided inside HOST (e.g., user@server.com) and
199inside USER by giving priority to USER. Same for PORT." 199inside USER by giving priority to USER. Same for PORT."
200 (apply #'auth-source-pass--find-match-unambiguous (auth-source-pass--disambiguate host user port)))
201
202(defun auth-source-pass--disambiguate (host &optional user port)
203 "Return (HOST USER PORT) after disambiguation.
204Disambiguate between having user provided inside HOST (e.g.,
205user@server.com) and inside USER by giving priority to USER.
206Same for PORT."
200 (let* ((url (url-generic-parse-url (if (string-match-p ".*://" host) 207 (let* ((url (url-generic-parse-url (if (string-match-p ".*://" host)
201 host 208 host
202 (format "https://%s" host))))) 209 (format "https://%s" host)))))
203 (auth-source-pass--find-match-unambiguous 210 (list
204 (or (url-host url) host) 211 (or (url-host url) host)
205 (or user (url-user url)) 212 (or user (url-user url))
206 ;; url-port returns 443 (because of the https:// above) by default 213 ;; url-port returns 443 (because of the https:// above) by default
@@ -212,74 +219,49 @@ If many matches are found, return the first one. If no match is found,
212return nil. 219return nil.
213 220
214HOSTNAME should not contain any username or port number." 221HOSTNAME should not contain any username or port number."
215 (cl-reduce 222 (let ((all-entries (auth-source-pass-entries))
216 (lambda (result entries) 223 (suffixes (auth-source-pass--generate-entry-suffixes hostname user port)))
217 (or result 224 (auth-source-pass--do-debug "searching for entries matching hostname=%S, user=%S, port=%S"
218 (pcase (length entries) 225 hostname (or user "") (or port ""))
219 (0 nil) 226 (auth-source-pass--do-debug "corresponding suffixes to search for: %S" suffixes)
220 (1 (auth-source-pass-parse-entry (car entries))) 227 (catch 'auth-source-pass-break
221 (_ (auth-source-pass--select-from-entries entries user))))) 228 (dolist (suffix suffixes)
222 (auth-source-pass--matching-entries hostname user port) 229 (let* ((matching-entries (auth-source-pass--entries-matching-suffix suffix all-entries))
223 :initial-value nil)) 230 (best-entry-data (auth-source-pass--select-from-entries matching-entries user)))
231 (pcase (length matching-entries)
232 (0 (auth-source-pass--do-debug "found no entries matching %S" suffix))
233 (1 (auth-source-pass--do-debug "found 1 entry matching %S: %S"
234 suffix
235 (car matching-entries)))
236 (_ (auth-source-pass--do-debug "found %s entries matching %S: %S"
237 (length matching-entries)
238 suffix
239 matching-entries)))
240 (when best-entry-data
241 (throw 'auth-source-pass-break best-entry-data)))))))
224 242
225(defun auth-source-pass--select-from-entries (entries user) 243(defun auth-source-pass--select-from-entries (entries user)
226 "Return best matching password-store entry data from ENTRIES. 244 "Return best matching password-store entry data from ENTRIES.
227 245
228If USER is non nil, give precedence to entries containing a user field 246If USER is non nil, give precedence to entries containing a user field
229matching USER." 247matching USER."
230 (cl-reduce 248 (let (fallback)
231 (lambda (result entry) 249 (catch 'auth-source-pass-break
232 (let ((entry-data (auth-source-pass-parse-entry entry))) 250 (dolist (entry entries fallback)
233 (cond ((equal (auth-source-pass--get-attr "user" result) user) 251 (let ((entry-data (auth-source-pass-parse-entry entry)))
234 result) 252 (when (and entry-data (not fallback))
235 ((equal (auth-source-pass--get-attr "user" entry-data) user) 253 (setq fallback entry-data)
236 entry-data) 254 (when (or (not user) (equal (auth-source-pass--get-attr "user" entry-data) user))
237 (t 255 (throw 'auth-source-pass-break entry-data))))))))
238 result)))) 256
239 entries 257(defun auth-source-pass--entries-matching-suffix (suffix entries)
240 :initial-value (auth-source-pass-parse-entry (car entries)))) 258 "Return entries matching SUFFIX.
241 259If ENTRIES is nil, use the result of calling `auth-source-pass-entries' instead."
242(defun auth-source-pass--matching-entries (hostname user port) 260 (cl-remove-if-not
243 "Return all matching password-store entries for HOSTNAME, USER, & PORT. 261 (lambda (entry) (string-match-p
244 262 (format "\\(^\\|/\\)%s$" (regexp-quote suffix))
245The result is a list of lists of password-store entries, where 263 entry))
246each sublist contains entries that actually exist in the 264 (or entries (auth-source-pass-entries))))
247password-store matching one of the entry name formats that
248auth-source-pass expects, most specific to least specific."
249 (let* ((entries-lists (mapcar
250 #'cdr
251 (auth-source-pass--accumulate-matches hostname user port)))
252 (entries (apply #'cl-concatenate (cons 'list entries-lists))))
253 (if entries
254 (auth-source-pass--do-debug (format "found: %S" entries))
255 (auth-source-pass--do-debug "no matches found"))
256 entries-lists))
257
258(defun auth-source-pass--accumulate-matches (hostname user port)
259 "Accumulate matching password-store entries into sublists.
260
261Entries matching supported formats that combine HOSTNAME, USER, &
262PORT are accumulated into sublists where the car of each sublist
263is a regular expression for matching paths in the password-store
264and the remainder is the list of matching entries."
265 (let ((suffix-match-lists
266 (mapcar (lambda (suffix) (list (format "\\(^\\|/\\)%s$" suffix)))
267 (auth-source-pass--generate-entry-suffixes hostname user port))))
268 (cl-reduce #'auth-source-pass--entry-reducer
269 (auth-source-pass-entries)
270 :initial-value suffix-match-lists)))
271
272(defun auth-source-pass--entry-reducer (match-lists entry)
273 "Match MATCH-LISTS sublists against ENTRY.
274
275The result is a copy of match-lists with the entry added to the
276end of any sublists for which the regular expression at the head
277of the list matches the entry name."
278 (mapcar (lambda (match-list)
279 (if (string-match (car match-list) entry)
280 (append match-list (list entry))
281 match-list))
282 match-lists))
283 265
284(defun auth-source-pass--generate-entry-suffixes (hostname user port) 266(defun auth-source-pass--generate-entry-suffixes (hostname user port)
285 "Return a list of possible entry path suffixes in the password-store. 267 "Return a list of possible entry path suffixes in the password-store.
diff --git a/test/lisp/auth-source-pass-tests.el b/test/lisp/auth-source-pass-tests.el
index 2c28f799453..6f0d308cebf 100644
--- a/test/lisp/auth-source-pass-tests.el
+++ b/test/lisp/auth-source-pass-tests.el
@@ -52,11 +52,21 @@
52(defvar auth-source-pass--debug-log nil 52(defvar auth-source-pass--debug-log nil
53 "Contains a list of all messages passed to `auth-source-do-debug`.") 53 "Contains a list of all messages passed to `auth-source-do-debug`.")
54 54
55(defun auth-source-pass--should-have-message-containing (regexp) 55(defun auth-source-pass--have-message-matching (regexp)
56 "Assert that at least one `auth-source-do-debug` matched REGEXP." 56 "Return non-nil iff at least one `auth-source-do-debug` match REGEXP."
57 (should (seq-find (lambda (message) 57 (seq-find (lambda (message)
58 (string-match regexp message)) 58 (string-match regexp message))
59 auth-source-pass--debug-log))) 59 auth-source-pass--debug-log))
60
61(defun auth-source-pass--explain--have-message-matching (regexp)
62 "Explainer function for `auth-source-pass--have-message-matching'.
63REGEXP is the same as in `auth-source-pass--have-message-matching'."
64 `(regexp
65 ,regexp
66 messages
67 ,(mapconcat #'identity auth-source-pass--debug-log "\n- ")))
68
69(put #'auth-source-pass--have-message-matching 'ert-explainer #'auth-source-pass--explain--have-message-matching)
60 70
61(defun auth-source-pass--debug (&rest msg) 71(defun auth-source-pass--debug (&rest msg)
62 "Format MSG and add that to `auth-source-pass--debug-log`. 72 "Format MSG and add that to `auth-source-pass--debug-log`.
@@ -78,6 +88,82 @@ This function is intended to be set to `auth-source-debug`."
78 (auth-source-pass--parse-log nil)) 88 (auth-source-pass--parse-log nil))
79 ,@body))) 89 ,@body)))
80 90
91(defun auth-source-pass--explain-match-entry-p (entry hostname &optional user port)
92 "Explainer function for `auth-source-pass-match-entry-p'.
93
94ENTRY, HOSTNAME, USER and PORT are the same as in `auth-source-pass-match-entry-p'."
95 `(entry
96 ,entry
97 store
98 ,(auth-source-pass-entries)
99 matching-entries
100 ,(auth-source-pass--matching-entries hostname user port)))
101
102(put 'auth-source-pass-match-entry-p 'ert-explainer #'auth-source-pass--explain-match-entry-p)
103
104(defun auth-source-pass--includes-sorted-entries (entries hostname &optional user port)
105 "Return non-nil iff ENTRIES matching the parameters are found in store.
106ENTRIES should be sorted from most specific to least specific.
107
108HOSTNAME, USER and PORT are passed unchanged to
109`auth-source-pass--matching-entries'."
110 (if (seq-empty-p entries)
111 t
112 (and
113 (auth-source-pass-match-entry-p (car entries) hostname user port)
114 (auth-source-pass--includes-sorted-entries (cdr entries) hostname user port))))
115
116(defun auth-source-pass--explain-includes-sorted-entries (entries hostname &optional user port)
117 "Explainer function for `auth-source-pass--includes-sorted-entries'.
118
119ENTRIES, HOSTNAME, USER and PORT are the same as in `auth-source-pass--includes-sorted-entries'."
120 `(store
121 ,(auth-source-pass-entries)
122 matching-entries
123 ,(auth-source-pass--matching-entries hostname user port)
124 entries
125 ,entries))
126
127(put 'auth-source-pass--includes-sorted-entries 'ert-explainer #'auth-source-pass--explain-includes-sorted-entries)
128
129(defun auth-source-pass--explain-match-any-entry-p (hostname &optional user port)
130 "Explainer function for `auth-source-pass-match-any-entry-p'.
131
132HOSTNAME, USER and PORT are the same as in `auth-source-pass-match-any-entry-p'."
133 `(store
134 ,(auth-source-pass-entries)
135 matching-entries
136 ,(auth-source-pass--matching-entries hostname user port)))
137
138(put 'auth-source-pass-match-any-entry-p 'ert-explainer #'auth-source-pass--explain-match-any-entry-p)
139
140(defun auth-source-pass--matching-entries (hostname &optional user port)
141 "Return password-store entries matching HOSTNAME, USER, PORT.
142
143The result is a list of lists of password-store entries. Each
144sublist contains the password-store entries whose names match a
145suffix in `auth-source-pass--generate-entry-suffixes'. The
146result is ordered the same way as the suffixes."
147 (let ((entries (auth-source-pass-entries)))
148 (mapcar (lambda (suffix) (auth-source-pass--entries-matching-suffix suffix entries))
149 (auth-source-pass--generate-entry-suffixes hostname user port))))
150
151(defun auth-source-pass-match-entry-p (entry hostname &optional user port)
152 "Return non-nil iff an ENTRY matching the parameters is found in store.
153
154HOSTNAME, USER and PORT are passed unchanged to
155`auth-source-pass--matching-entries'."
156 (cl-find-if
157 (lambda (entries) (cl-find entry entries :test #'string=))
158 (auth-source-pass--matching-entries hostname user port)))
159
160(defun auth-source-pass-match-any-entry-p (hostname &optional user port)
161 "Return non-nil iff there is at least one entry matching the parameters.
162
163HOSTNAME, USER and PORT are passed unchanged to
164`auth-source-pass--matching-entries'."
165 (cl-find-if #'identity (auth-source-pass--matching-entries hostname user port)))
166
81(ert-deftest auth-source-pass-any-host () 167(ert-deftest auth-source-pass-any-host ()
82 (auth-source-pass--with-store '(("foo" ("port" . "foo-port") ("host" . "foo-user")) 168 (auth-source-pass--with-store '(("foo" ("port" . "foo-port") ("host" . "foo-user"))
83 ("bar")) 169 ("bar"))
@@ -93,6 +179,101 @@ This function is intended to be set to `auth-source-debug`."
93 ("bar")) 179 ("bar"))
94 (should-not (auth-source-pass-search :host "baz")))) 180 (should-not (auth-source-pass-search :host "baz"))))
95 181
182(ert-deftest auth-source-pass--disambiguate-extract-host-from-hostname ()
183 ;; no user or port
184 (should (equal (cl-first (auth-source-pass--disambiguate "foo")) "foo"))
185 ;; only user
186 (should (equal (cl-first (auth-source-pass--disambiguate "user@foo")) "foo"))
187 ;; only port
188 (should (equal (cl-first (auth-source-pass--disambiguate "https://foo")) "foo"))
189 (should (equal (cl-first (auth-source-pass--disambiguate "foo:80")) "foo"))
190 ;; both user and port
191 (should (equal (cl-first (auth-source-pass--disambiguate "https://user@foo")) "foo"))
192 (should (equal (cl-first (auth-source-pass--disambiguate "user@foo:80")) "foo"))
193 ;; all of the above with a trailing path
194 (should (equal (cl-first (auth-source-pass--disambiguate "foo/path")) "foo"))
195 (should (equal (cl-first (auth-source-pass--disambiguate "user@foo/path")) "foo"))
196 (should (equal (cl-first (auth-source-pass--disambiguate "https://foo/path")) "foo"))
197 (should (equal (cl-first (auth-source-pass--disambiguate "foo:80/path")) "foo"))
198 (should (equal (cl-first (auth-source-pass--disambiguate "https://user@foo/path")) "foo"))
199 (should (equal (cl-first (auth-source-pass--disambiguate "user@foo:80/path")) "foo")))
200
201(ert-deftest auth-source-pass--disambiguate-extract-user-from-hostname ()
202 ;; no user or port
203 (should (equal (cl-second (auth-source-pass--disambiguate "foo")) nil))
204 ;; only user
205 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo")) "user"))
206 ;; only port
207 (should (equal (cl-second (auth-source-pass--disambiguate "https://foo")) nil))
208 (should (equal (cl-second (auth-source-pass--disambiguate "foo:80")) nil))
209 ;; both user and port
210 (should (equal (cl-second (auth-source-pass--disambiguate "https://user@foo")) "user"))
211 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo:80")) "user"))
212 ;; all of the above with a trailing path
213 (should (equal (cl-second (auth-source-pass--disambiguate "foo/path")) nil))
214 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo/path")) "user"))
215 (should (equal (cl-second (auth-source-pass--disambiguate "https://foo/path")) nil))
216 (should (equal (cl-second (auth-source-pass--disambiguate "foo:80/path")) nil))
217 (should (equal (cl-second (auth-source-pass--disambiguate "https://user@foo/path")) "user"))
218 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo:80/path")) "user")))
219
220(ert-deftest auth-source-pass--disambiguate-prefer-user-parameter ()
221 ;; no user or port
222 (should (equal (cl-second (auth-source-pass--disambiguate "foo" "user2")) "user2"))
223 ;; only user
224 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo" "user2")) "user2"))
225 ;; only port
226 (should (equal (cl-second (auth-source-pass--disambiguate "https://foo" "user2")) "user2"))
227 (should (equal (cl-second (auth-source-pass--disambiguate "foo:80" "user2")) "user2"))
228 ;; both user and port
229 (should (equal (cl-second (auth-source-pass--disambiguate "https://user@foo" "user2")) "user2"))
230 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo:80" "user2")) "user2"))
231 ;; all of the above with a trailing path
232 (should (equal (cl-second (auth-source-pass--disambiguate "foo/path" "user2")) "user2"))
233 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo/path" "user2")) "user2"))
234 (should (equal (cl-second (auth-source-pass--disambiguate "https://foo/path" "user2")) "user2"))
235 (should (equal (cl-second (auth-source-pass--disambiguate "foo:80/path" "user2")) "user2"))
236 (should (equal (cl-second (auth-source-pass--disambiguate "https://user@foo/path" "user2")) "user2"))
237 (should (equal (cl-second (auth-source-pass--disambiguate "user@foo:80/path" "user2")) "user2")))
238
239(ert-deftest auth-source-pass--disambiguate-extract-port-from-hostname ()
240 ;; no user or port
241 (should (equal (cl-third (auth-source-pass--disambiguate "foo")) "443"))
242 ;; only user
243 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo")) "443"))
244 ;; only port
245 (should (equal (cl-third (auth-source-pass--disambiguate "https://foo")) "443"))
246 (should (equal (cl-third (auth-source-pass--disambiguate "foo:80")) "80"))
247 ;; both user and port
248 (should (equal (cl-third (auth-source-pass--disambiguate "https://user@foo")) "443"))
249 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo:80")) "80"))
250 ;; all of the above with a trailing path
251 (should (equal (cl-third (auth-source-pass--disambiguate "foo/path")) "443"))
252 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo/path")) "443"))
253 (should (equal (cl-third (auth-source-pass--disambiguate "https://foo/path")) "443"))
254 (should (equal (cl-third (auth-source-pass--disambiguate "foo:80/path")) "80"))
255 (should (equal (cl-third (auth-source-pass--disambiguate "https://user@foo/path")) "443"))
256 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo:80/path")) "80")))
257
258(ert-deftest auth-source-pass--disambiguate-prefer-port-parameter ()
259 ;; no user or port
260 (should (equal (cl-third (auth-source-pass--disambiguate "foo" "user2" "8080")) "8080"))
261 ;; only user
262 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo" "user2" "8080")) "8080"))
263 ;; only port
264 (should (equal (cl-third (auth-source-pass--disambiguate "https://foo" "user2" "8080")) "8080"))
265 (should (equal (cl-third (auth-source-pass--disambiguate "foo:80" "user2" "8080")) "8080"))
266 ;; both user and port
267 (should (equal (cl-third (auth-source-pass--disambiguate "https://user@foo" "user2" "8080")) "8080"))
268 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo:80" "user2" "8080")) "8080"))
269 ;; all of the above with a trailing path
270 (should (equal (cl-third (auth-source-pass--disambiguate "foo/path" "user2" "8080")) "8080"))
271 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo/path" "user2" "8080")) "8080"))
272 (should (equal (cl-third (auth-source-pass--disambiguate "https://foo/path" "user2" "8080")) "8080"))
273 (should (equal (cl-third (auth-source-pass--disambiguate "foo:80/path" "user2" "8080")) "8080"))
274 (should (equal (cl-third (auth-source-pass--disambiguate "https://user@foo/path" "user2" "8080")) "8080"))
275 (should (equal (cl-third (auth-source-pass--disambiguate "user@foo:80/path" "user2" "8080")) "8080")))
276
96(ert-deftest auth-source-pass-find-match-minimal-parsing () 277(ert-deftest auth-source-pass-find-match-minimal-parsing ()
97 (let ((store-contents 278 (let ((store-contents
98 '(("baz" ("secret" . "baz password")) 279 '(("baz" ("secret" . "baz password"))
@@ -121,156 +302,110 @@ This function is intended to be set to `auth-source-debug`."
121 (should (equal auth-source-pass--parse-log '("bar.baz")))) 302 (should (equal auth-source-pass--parse-log '("bar.baz"))))
122 (auth-source-pass--with-store store-contents 303 (auth-source-pass--with-store store-contents
123 (auth-source-pass--find-match "baz" nil nil) 304 (auth-source-pass--find-match "baz" nil nil)
124 (should (equal auth-source-pass--parse-log '("baz")))))) 305 (should (equal auth-source-pass--parse-log '("baz"))))
125 306 (auth-source-pass--with-store
126(ert-deftest auth-source-pass-find-match-matching-at-entry-name () 307 '(("dir1/bar.com" ("key" . "val"))
127 (auth-source-pass--with-store 308 ("dir2/bar.com" ("key" . "val"))
128 '(("foo" ("secret" . "foo password"))) 309 ("dir3/bar.com" ("key" . "val")))
129 (let ((result (auth-source-pass--find-match "foo" nil nil))) 310 (auth-source-pass--find-match "bar.com" nil nil)
130 (should (equal (auth-source-pass--get-attr "secret" result) 311 (should (= (length auth-source-pass--parse-log) 1)))))
131 "foo password"))))) 312
132 313(ert-deftest auth-source-pass--find-match-return-parsed-data ()
133(ert-deftest auth-source-pass-find-match-matching-at-entry-name-part () 314 (auth-source-pass--with-store '(("bar.com" ("key" . "val")))
134 (auth-source-pass--with-store 315 (should (consp (auth-source-pass--find-match "bar.com" nil nil))))
135 '(("foo" ("secret" . "foo password"))) 316 (auth-source-pass--with-store '(("dir1/bar.com" ("key1" . "val1")) ("dir2/bar.com" ("key2" . "val2")))
136 (let ((result (auth-source-pass--find-match "https://foo" nil nil))) 317 (should (consp (auth-source-pass--find-match "bar.com" nil nil)))))
137 (should (equal (auth-source-pass--get-attr "secret" result) 318
138 "foo password"))))) 319(ert-deftest auth-source-pass--matching-entries ()
139
140(ert-deftest auth-source-pass-find-match-matching-at-entry-name-ignoring-user ()
141 (auth-source-pass--with-store
142 '(("foo" ("secret" . "foo password")))
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")))))
146
147(ert-deftest auth-source-pass-find-match-matching-at-entry-name-with-user ()
148 (auth-source-pass--with-store
149 '(("SomeUser@foo" ("secret" . "SomeUser@foo password")))
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")))))
153
154(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full ()
155 (auth-source-pass--with-store
156 '(("SomeUser@foo" ("secret" . "SomeUser@foo password"))
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")))))
161
162(ert-deftest auth-source-pass-find-match-matching-at-entry-name-prefer-full-reversed ()
163 (auth-source-pass--with-store
164 '(("foo" ("secret" . "foo password"))
165 ("SomeUser@foo" ("secret" . "SomeUser@foo password")))
166 (let ((result (auth-source-pass--find-match "https://SomeUser@foo" nil nil)))
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 ()
171 (auth-source-pass--with-store '(("bar.com")) 320 (auth-source-pass--with-store '(("bar.com"))
172 (should (equal (auth-source-pass--matching-entries "foo.bar.com" nil nil) 321 (should (auth-source-pass-match-entry-p "bar.com" "bar.com"))
173 '(nil ("bar.com") nil))))) 322 ;; match even if sub-domain is asked for
174 323 (should (auth-source-pass-match-entry-p "bar.com" "foo.bar.com"))
175(ert-deftest auth-source-pass-matching-entries-name-without-subdomain-with-user () 324 ;; match even if a user is asked for
176 (auth-source-pass--with-store '(("someone@bar.com")) 325 (should (auth-source-pass-match-entry-p "bar.com" "bar.com" "user"))
177 (should (equal (auth-source-pass--matching-entries "foo.bar.com" "someone" nil) 326 ;; match even if user as an @ sign
178 '(nil nil nil ("someone@bar.com") nil nil nil nil nil))))) 327 (should (auth-source-pass-match-entry-p "bar.com" "bar.com" "user@someplace"))
179 328 ;; match even if a port is asked for
180(ert-deftest auth-source-pass-matching-entries-name-without-subdomain-with-bad-user () 329 (should (auth-source-pass-match-entry-p "bar.com" "bar.com" nil "8080"))
181 (auth-source-pass--with-store '(("someoneelse@bar.com")) 330 ;; match even if a user and a port are asked for
182 (should (equal (auth-source-pass--matching-entries "foo.bar.com" "someone" nil) 331 (should (auth-source-pass-match-entry-p "bar.com" "bar.com" "user" "8080"))
183 '(nil nil nil nil nil nil nil nil nil))))) 332 ;; don't match if a '.' is replaced with another character
184 333 (auth-source-pass--with-store '(("barXcom"))
185(ert-deftest auth-source-pass-matching-entries-name-without-subdomain-prefer-full () 334 (should-not (auth-source-pass-match-any-entry-p "bar.com" nil nil)))))
186 (auth-source-pass--with-store '(("bar.com") ("foo.bar.com")) 335
187 (should (equal (auth-source-pass--matching-entries "foo.bar.com" nil nil) 336(ert-deftest auth-source-pass--matching-entries-find-entries-with-a-username ()
188 '(("foo.bar.com") ("bar.com") nil))))) 337 (auth-source-pass--with-store '(("user@foo"))
189 338 (should (auth-source-pass-match-entry-p "user@foo" "foo" "user")))
190(ert-deftest auth-source-pass-dont-match-at-folder-name () 339 ;; match even if sub-domain is asked for
191 (auth-source-pass--with-store '(("foo.bar.com/foo")) 340 (auth-source-pass--with-store '(("user@bar.com"))
192 (should (equal (auth-source-pass--matching-entries "foo.bar.com" nil nil) 341 (should (auth-source-pass-match-entry-p "user@bar.com" "foo.bar.com" "user")))
193 '(nil nil nil))))) 342 ;; don't match if no user is asked for
194 343 (auth-source-pass--with-store '(("user@foo"))
195(ert-deftest auth-source-pass-matching-entries-host-port-and-subdir-user () 344 (should-not (auth-source-pass-match-any-entry-p "foo")))
196 (auth-source-pass--with-store '(("bar.com:443/someone")) 345 ;; don't match if user is different
197 (should (equal (auth-source-pass--matching-entries "bar.com" "someone" "443") 346 (auth-source-pass--with-store '(("user1@foo"))
198 '(nil ("bar.com:443/someone") nil nil nil nil 347 (should-not (auth-source-pass-match-any-entry-p "foo" "user2")))
199 nil nil nil nil nil nil))))) 348 ;; don't match if sub-domain is asked for but user is different
200 349 (auth-source-pass--with-store '(("user1@bar.com"))
201(ert-deftest auth-source-pass-matching-entries-host-port-and-subdir-user-with-custom-separator () 350 (should-not (auth-source-pass-match-any-entry-p "foo.bar.com" "user2"))))
351
352(ert-deftest auth-source-pass--matching-entries-find-entries-with-a-port ()
353 (auth-source-pass--with-store '(("bar.com:8080"))
354 (should (auth-source-pass-match-entry-p "bar.com:8080" "bar.com" nil "8080"))))
355
356(ert-deftest auth-source-pass--matching-entries-find-entries-with-slash ()
357 ;; match if entry filename matches user
358 (auth-source-pass--with-store '(("foo.com/user"))
359 (should (auth-source-pass-match-entry-p "foo.com/user" "foo.com" "user")))
360 ;; match with port if entry filename matches user
361 (auth-source-pass--with-store '(("foo.com:8080/user"))
362 (should (auth-source-pass-match-entry-p "foo.com:8080/user" "foo.com" "user" "8080")))
363 ;; don't match if entry filename doesn't match user
364 (auth-source-pass--with-store '(("foo.com/baz"))
365 (should-not (auth-source-pass-match-any-entry-p "foo.com" "user"))))
366
367(ert-deftest auth-source-pass-matching-entries-with-custom-separator ()
202 (let ((auth-source-pass-port-separator "#")) 368 (let ((auth-source-pass-port-separator "#"))
203 (auth-source-pass--with-store '(("bar.com#443/someone")) 369 (auth-source-pass--with-store '(("bar.com#443/someone"))
204 (should (equal (auth-source-pass--matching-entries "bar.com" "someone" "443") 370 (should (auth-source-pass-match-entry-p "bar.com#443/someone" "bar.com" "someone" "443")))))
205 '(nil ("bar.com#443/someone") nil nil nil nil 371
206 nil nil nil nil nil nil)))))) 372(ert-deftest auth-source-pass--matching-entries-sort-results ()
207 373 (auth-source-pass--with-store '(("user@foo") ("foo"))
208(ert-deftest auth-source-pass-matching-entries-extracting-user-from-host () 374 (should (auth-source-pass--includes-sorted-entries '("user@foo" "foo") "foo" "user")))
209 (auth-source-pass--with-store 375 ;; same, but store is reversed
210 '(("foo.com/bar" ("secret" . "foo.com/bar password")))
211 (let ((result (auth-source-pass--find-match "https://bar@foo.com" nil nil)))
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 ()
216 (auth-source-pass--with-store '(("foo") ("user@foo")) 376 (auth-source-pass--with-store '(("foo") ("user@foo"))
217 (should (equal (auth-source-pass--matching-entries "foo" "user" nil) 377 (should (auth-source-pass--includes-sorted-entries '("user@foo" "foo") "foo" "user")))
218 '(("user@foo") nil ("foo")))) 378 ;; with sub-domain
219 (auth-source-pass--should-have-message-containing "found: (\"user@foo\" \"foo\""))) 379 (auth-source-pass--with-store '(("bar.com") ("foo.bar.com"))
220 380 (should (auth-source-pass--includes-sorted-entries '("foo.bar.com" "bar.com") "foo.bar.com")))
221(ert-deftest auth-source-pass-give-priority-to-desired-user () 381 ;; matching user in the entry data takes priority
222 (auth-source-pass--with-store 382 (auth-source-pass--with-store '(("dir1/bar.com") ("dir2/bar.com" ("user" . "user")))
223 '(("foo" ("secret" . "foo password")) 383 (should (auth-source-pass--includes-sorted-entries
224 ("subdir/foo" ("secret" . "subdir/foo password") ("user" . "someone"))) 384 '("dir2/bar.com" "dir1/bar.com")
225 (let ((result (auth-source-pass--find-match "foo" "someone" nil))) 385 "bar.com" "user")))
226 (should (equal (auth-source-pass--get-attr "secret" result) 386 ;; same, but store is reversed
227 "subdir/foo password")) 387 (auth-source-pass--with-store '(("dir2/bar.com" ("user" . "user")) ("dir1/bar.com"))
228 (should (equal (auth-source-pass--get-attr "user" result) 388 (should (auth-source-pass--includes-sorted-entries
229 "someone"))) 389 '("dir2/bar.com" "dir1/bar.com")
230 (auth-source-pass--should-have-message-containing "found: (\"foo\" \"subdir/foo\""))) 390 "bar.com" "user"))))
231 391
232(ert-deftest auth-source-pass-give-priority-to-desired-user-reversed () 392(ert-deftest auth-source-pass-all-supported-organizations ()
233 (auth-source-pass--with-store 393 ;; test every possible entry to store this data: user=rms host=gnu.org port=22
234 '(("foo" ("secret" . "foo password") ("user" . "someone")) 394 (dolist (entry '(;; only host name
235 ("subdir/foo" ("secret" . "subdir/foo password"))) 395 "gnu.org"
236 (let ((result (auth-source-pass--find-match "foo" "someone" nil))) 396 ;; hostname + user
237 (should (equal (auth-source-pass--get-attr "secret" result) 397 "gnu.org/rms" "rms@gnu.org"
238 "foo password"))) 398 ;; hostname + port
239 (auth-source-pass--should-have-message-containing "found: (\"foo\" \"subdir/foo\""))) 399 "gnu.org:22"
240 400 ;; hostname + user + port
241(ert-deftest auth-source-pass-return-first-when-several-matches () 401 "gnu.org:22/rms" "rms@gnu.org:22"
242 (auth-source-pass--with-store 402 ;; all of the above in a random folder
243 '(("foo" ("secret" . "foo password")) 403 "a/b/gnu.org"
244 ("subdir/foo" ("secret" . "subdir/foo password"))) 404 "a/b/gnu.org/rms" "a/b/rms@gnu.org"
245 (let ((result (auth-source-pass--find-match "foo" nil nil))) 405 "a/b/gnu.org:22"
246 (should (equal (auth-source-pass--get-attr "secret" result) 406 "a/b/gnu.org:22/rms" "a/b/rms@gnu.org:22"))
247 "foo password"))) 407 (auth-source-pass--with-store `((,entry))
248 (auth-source-pass--should-have-message-containing "found: (\"foo\" \"subdir/foo\""))) 408 (should (auth-source-pass-match-entry-p entry "gnu.org" "rms" "22")))))
249
250(ert-deftest auth-source-pass-matching-entries-make-divansantana-happy ()
251 (auth-source-pass--with-store '(("host.com"))
252 (should (equal (auth-source-pass--matching-entries "smtp.host.com" "myusername@host.co.za" nil)
253 '(nil nil nil nil nil ("host.com") nil nil nil)))))
254
255(ert-deftest auth-source-pass-find-host-without-port ()
256 (auth-source-pass--with-store
257 '(("host.com" ("secret" . "host.com password")))
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")))))
261
262(ert-deftest auth-source-pass-matching-entries-host-with-port ()
263 (auth-source-pass--with-store '(("host.com:443"))
264 (should (equal (auth-source-pass--matching-entries "host.com" "someuser" "443")
265 '(nil nil nil nil ("host.com:443") nil
266 nil nil nil nil nil nil)))))
267
268(ert-deftest auth-source-pass-matching-entries-with-custom-port-separator ()
269 (let ((auth-source-pass-port-separator "#"))
270 (auth-source-pass--with-store '(("host.com#443"))
271 (should (equal (auth-source-pass--matching-entries "host.com" "someuser" "443")
272 '(nil nil nil nil ("host.com#443") nil
273 nil nil nil nil nil nil))))))
274 409
275(defmacro auth-source-pass--with-store-find-foo (store &rest body) 410(defmacro auth-source-pass--with-store-find-foo (store &rest body)
276 "Use STORE while executing BODY. \"foo\" is the matched entry." 411 "Use STORE while executing BODY. \"foo\" is the matched entry."
@@ -300,33 +435,6 @@ This function is intended to be set to `auth-source-debug`."
300 (should (equal (plist-get result :port) 512)) 435 (should (equal (plist-get result :port) 512))
301 (should (equal (plist-get result :user) "anuser"))))) 436 (should (equal (plist-get result :user) "anuser")))))
302 437
303(ert-deftest auth-source-pass-build-result-passes-full-host-to-find-match ()
304 (let (passed-host)
305 (cl-letf (((symbol-function 'auth-source-pass--find-match)
306 (lambda (host _user _port)
307 (setq passed-host host)
308 nil)))
309 (auth-source-pass--build-result "https://user@host.com:123" nil nil)
310 (should (equal passed-host "https://user@host.com:123"))
311 (auth-source-pass--build-result "https://user@host.com" nil nil)
312 (should (equal passed-host "https://user@host.com"))
313 (auth-source-pass--build-result "user@host.com" nil nil)
314 (should (equal passed-host "user@host.com"))
315 (auth-source-pass--build-result "user@host.com:443" nil nil)
316 (should (equal passed-host "user@host.com:443")))))
317
318(ert-deftest auth-source-pass-only-return-entries-that-can-be-open ()
319 (auth-source-pass--with-store
320 '(("foo.site.com" ("secret" . "foo.site.com password"))
321 ("bar.site.com") ; An entry name with no data is invalid
322 ("mail/baz.site.com/scott" ("secret" . "mail/baz.site.com/scott password")))
323 (should (equal (auth-source-pass--find-match "foo.site.com" "someuser" nil)
324 '(("secret" . "foo.site.com password"))))
325 (should (equal (auth-source-pass--find-match "bar.site.com" "someuser" nil)
326 nil))
327 (should (equal (auth-source-pass--find-match "baz.site.com" "scott" nil)
328 '(("secret" . "mail/baz.site.com/scott password"))))))
329
330(ert-deftest auth-source-pass-can-start-from-auth-source-search () 438(ert-deftest auth-source-pass-can-start-from-auth-source-search ()
331 (auth-source-pass--with-store '(("gitlab.com" ("user" . "someone"))) 439 (auth-source-pass--with-store '(("gitlab.com" ("user" . "someone")))
332 (auth-source-pass-enable) 440 (auth-source-pass-enable)
@@ -334,6 +442,24 @@ This function is intended to be set to `auth-source-debug`."
334 (should (equal (plist-get result :user) "someone")) 442 (should (equal (plist-get result :user) "someone"))
335 (should (equal (plist-get result :host) "gitlab.com"))))) 443 (should (equal (plist-get result :host) "gitlab.com")))))
336 444
445(ert-deftest auth-source-pass-prints-meaningful-debug-log ()
446 (auth-source-pass--with-store '()
447 (auth-source-pass--find-match "gitlab.com" nil nil)
448 (should (auth-source-pass--have-message-matching
449 "entries matching hostname=\"gitlab.com\""))
450 (should (auth-source-pass--have-message-matching
451 "corresponding suffixes to search for: .*\"gitlab.com\""))
452 (should (auth-source-pass--have-message-matching
453 "found no entries matching \"gitlab.com\"")))
454 (auth-source-pass--with-store '(("gitlab.com"))
455 (auth-source-pass--find-match "gitlab.com" nil nil)
456 (should (auth-source-pass--have-message-matching
457 "found 1 entry matching \"gitlab.com\": \"gitlab.com\"")))
458 (auth-source-pass--with-store '(("a/gitlab.com") ("b/gitlab.com"))
459 (auth-source-pass--find-match "gitlab.com" nil nil)
460 (should (auth-source-pass--have-message-matching
461 "found 2 entries matching \"gitlab.com\": (\"a/gitlab.com\" \"b/gitlab.com\")"))))
462
337(provide 'auth-source-pass-tests) 463(provide 'auth-source-pass-tests)
338 464
339;;; auth-source-pass-tests.el ends here 465;;; auth-source-pass-tests.el ends here