diff options
| -rw-r--r-- | lisp/auth-source-pass.el | 209 | ||||
| -rw-r--r-- | test/lisp/auth-source-pass-tests.el | 264 |
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. | ||
| 206 | Also displays a warning if not. This function is slow, don't call it too | ||
| 207 | often." | ||
| 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. | ||
| 224 | Only 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. | ||
| 238 | If USER is non nil, give precedence to entries containing a user field | ||
| 239 | matching 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 | ||
| 254 | Disambiguate between user provided inside HOST (e.g., user@server.com) and | 197 | Disambiguate between user provided inside HOST (e.g., user@server.com) and |
| 255 | inside USER by giving priority to USER. Same for PORT." | 198 | inside 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. |
| 267 | If many matches are found, return the first one. If no match is found, | 210 | If many matches are found, return the first one. If no match is found, |
| 268 | return nil. | 211 | return nil. |
| 269 | 212 | ||
| 270 | HOSTNAME should not contain any username or port number." | 213 | HOSTNAME 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) | 227 | If 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 | 228 | matching 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 | |||
| 244 | The result is a list of lists of password-store entries, where | ||
| 245 | each sublist contains entries that actually exist in the | ||
| 246 | password-store matching one of the entry name formats that | ||
| 247 | auth-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 | |||
| 260 | Entries matching supported formats that combine HOSTNAME, USER, & | ||
| 261 | PORT are accumulated into sublists where the car of each sublist | ||
| 262 | is a regular expression for matching paths in the password-store | ||
| 263 | and 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 | |||
| 274 | The result is a copy of match-lists with the entry added to the | ||
| 275 | end of any sublists for which the regular expression at the head | ||
| 276 | of 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 | |||
| 286 | Based on the supported pathname patterns for HOSTNAME, USER, & | ||
| 287 | PORT, return a list of possible suffixes for matching entries in | ||
| 288 | the 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 | |||
| 297 | This function takes a list of NAME-COMPONENTS, the strings | ||
| 298 | separated by periods in the hostname, and returns a list of full | ||
| 299 | domain names containing the trailing sequences of those | ||
| 300 | components, 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 | |||
| 307 | The resulting list is ordered from most specifc to least | ||
| 308 | specific, with paths matching all of NAME, USER, & PORT first, | ||
| 309 | then 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 @@ | |||
| 63 | This function is intended to be set to `auth-source-debug`." | 63 | This 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"))) |