diff options
| -rw-r--r-- | lisp/auth-source-pass.el | 108 | ||||
| -rw-r--r-- | test/lisp/auth-source-pass-tests.el | 482 |
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 | ||
| 198 | Disambiguate between user provided inside HOST (e.g., user@server.com) and | 198 | Disambiguate between user provided inside HOST (e.g., user@server.com) and |
| 199 | inside USER by giving priority to USER. Same for PORT." | 199 | inside 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. | ||
| 204 | Disambiguate between having user provided inside HOST (e.g., | ||
| 205 | user@server.com) and inside USER by giving priority to USER. | ||
| 206 | Same 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, | |||
| 212 | return nil. | 219 | return nil. |
| 213 | 220 | ||
| 214 | HOSTNAME should not contain any username or port number." | 221 | HOSTNAME 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 | ||
| 228 | If USER is non nil, give precedence to entries containing a user field | 246 | If USER is non nil, give precedence to entries containing a user field |
| 229 | matching USER." | 247 | matching 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 | 259 | If 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)) | |
| 245 | The result is a list of lists of password-store entries, where | 263 | entry)) |
| 246 | each sublist contains entries that actually exist in the | 264 | (or entries (auth-source-pass-entries)))) |
| 247 | password-store matching one of the entry name formats that | ||
| 248 | auth-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 | |||
| 261 | Entries matching supported formats that combine HOSTNAME, USER, & | ||
| 262 | PORT are accumulated into sublists where the car of each sublist | ||
| 263 | is a regular expression for matching paths in the password-store | ||
| 264 | and 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 | |||
| 275 | The result is a copy of match-lists with the entry added to the | ||
| 276 | end of any sublists for which the regular expression at the head | ||
| 277 | of 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'. | ||
| 63 | REGEXP 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 | |||
| 94 | ENTRY, 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. | ||
| 106 | ENTRIES should be sorted from most specific to least specific. | ||
| 107 | |||
| 108 | HOSTNAME, 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 | |||
| 119 | ENTRIES, 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 | |||
| 132 | HOSTNAME, 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 | |||
| 143 | The result is a list of lists of password-store entries. Each | ||
| 144 | sublist contains the password-store entries whose names match a | ||
| 145 | suffix in `auth-source-pass--generate-entry-suffixes'. The | ||
| 146 | result 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 | |||
| 154 | HOSTNAME, 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 | |||
| 163 | HOSTNAME, 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 |