aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
authorMichael Albinus2010-06-08 15:05:11 +0200
committerMichael Albinus2010-06-08 15:05:11 +0200
commit0e4966fb65bb4374d334d127ad1de1f55f5c86c8 (patch)
tree30e5a39e556d7c521cc3b87dad912a7f11a15fc8 /lisp
parentd7c5d87df66ddde23546e919ca4078f00be4d20b (diff)
downloademacs-0e4966fb65bb4374d334d127ad1de1f55f5c86c8.tar.gz
emacs-0e4966fb65bb4374d334d127ad1de1f55f5c86c8.zip
* auth-source.el (top): Autoload `secrets-list-collections',
`secrets-create-item', `secrets-delete-item'. (auth-sources): Fix tag string. (auth-get-source, auth-source-retrieve, auth-source-create) (auth-source-delete): New defuns. (auth-source-pick): Rewrite in order to avoid 2 passes. (auth-source-forget-user-or-password): New parameter USERNAME. (auth-source-user-or-password): New parameters CREATE-MISSING and DELETE-EXISTING. Retrieve password interactively, if needed.
Diffstat (limited to 'lisp')
-rw-r--r--lisp/gnus/ChangeLog12
-rw-r--r--lisp/gnus/auth-source.el345
2 files changed, 218 insertions, 139 deletions
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog
index 3ab4ed98aca..5be038b5519 100644
--- a/lisp/gnus/ChangeLog
+++ b/lisp/gnus/ChangeLog
@@ -1,3 +1,15 @@
12010-06-08 Michael Albinus <michael.albinus@gmx.de>
2
3 * auth-source.el (top): Autoload `secrets-list-collections',
4 `secrets-create-item', `secrets-delete-item'.
5 (auth-sources): Fix tag string.
6 (auth-get-source, auth-source-retrieve, auth-source-create)
7 (auth-source-delete): New defuns.
8 (auth-source-pick): Rewrite in order to avoid 2 passes.
9 (auth-source-forget-user-or-password): New parameter USERNAME.
10 (auth-source-user-or-password): New parameters CREATE-MISSING and
11 DELETE-EXISTING. Retrieve password interactively, if needed.
12
12010-06-07 Teemu Likonen <tlikonen@iki.fi> (tiny change) 132010-06-07 Teemu Likonen <tlikonen@iki.fi> (tiny change)
2 14
3 * gnus-agent.el (gnus-agent-expire-unagentized-dirs): Don't ask about 15 * gnus-agent.el (gnus-agent-expire-unagentized-dirs): Don't ask about
diff --git a/lisp/gnus/auth-source.el b/lisp/gnus/auth-source.el
index a5e323c0395..89b2ef3d11d 100644
--- a/lisp/gnus/auth-source.el
+++ b/lisp/gnus/auth-source.el
@@ -35,10 +35,13 @@
35 35
36(eval-when-compile (require 'cl)) 36(eval-when-compile (require 'cl))
37(autoload 'netrc-machine-user-or-password "netrc") 37(autoload 'netrc-machine-user-or-password "netrc")
38(autoload 'secrets-search-items "secrets") 38(autoload 'secrets-create-item "secrets")
39(autoload 'secrets-delete-item "secrets")
39(autoload 'secrets-get-alias "secrets") 40(autoload 'secrets-get-alias "secrets")
40(autoload 'secrets-get-attribute "secrets") 41(autoload 'secrets-get-attribute "secrets")
41(autoload 'secrets-get-secret "secrets") 42(autoload 'secrets-get-secret "secrets")
43(autoload 'secrets-list-collections "secrets")
44(autoload 'secrets-search-items "secrets")
42 45
43(defgroup auth-source nil 46(defgroup auth-source nil
44 "Authentication sources." 47 "Authentication sources."
@@ -122,7 +125,7 @@ can get pretty complex."
122 (const :format "" :value :source) 125 (const :format "" :value :source)
123 (choice :tag "Authentication backend choice" 126 (choice :tag "Authentication backend choice"
124 (string :tag "Authentication Source (file)") 127 (string :tag "Authentication Source (file)")
125 (list :tag "secrets.el (Secret Service API/KWallet/GNOME KeyRing)" 128 (list :tag "secrets.el (Secret Service API/KWallet/GNOME Keyring)"
126 (const :format "" :value :secrets) 129 (const :format "" :value :secrets)
127 (choice :tag "Collection to use" 130 (choice :tag "Collection to use"
128 (string :tag "Collection name") 131 (string :tag "Collection name")
@@ -178,123 +181,182 @@ can get pretty complex."
178 181
179;; (auth-source-pick nil :host "any" :protocol 'imap :user "joe") 182;; (auth-source-pick nil :host "any" :protocol 'imap :user "joe")
180;; (auth-source-pick t :host "any" :protocol 'imap :user "joe") 183;; (auth-source-pick t :host "any" :protocol 'imap :user "joe")
181;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") 184;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
182;; (:source (:secrets "session") :host t :protocol t :user "joe") 185;; (:source (:secrets "session") :host t :protocol t :user "joe")
183;; (:source (:secrets "login") :host t :protocol t) 186;; (:source (:secrets "login") :host t :protocol t)
184;; (:source "~/.authinfo.gpg" :host t :protocol t))) 187;; (:source "~/.authinfo.gpg" :host t :protocol t)))
185 188
186;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") 189;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
187;; (:source (:secrets "session") :host t :protocol t :user "joe") 190;; (:source (:secrets "session") :host t :protocol t :user "joe")
188;; (:source (:secrets "login") :host t :protocol t) 191;; (:source (:secrets "login") :host t :protocol t)
189;; )) 192;; ))
190 193
191;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t))) 194;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t)))
192 195
196(defun auth-get-source (entry)
197 "Return the source string of ENTRY, which is one entry in `auth-sources'.
198If it is a Secret Service API, return the collection name, otherwise
199the file name."
200 (let ((source (plist-get entry :source)))
201 (if (stringp source)
202 source
203 ;; Secret Service API.
204 (setq source (plist-get source :secrets))
205 (when (eq source 'default)
206 (setq source (or (secrets-get-alias "default") "login")))
207 (or source "session"))))
208
193(defun auth-source-pick (&rest spec) 209(defun auth-source-pick (&rest spec)
194 "Parse `auth-sources' for matches of the SPEC plist. 210 "Parse `auth-sources' for matches of the SPEC plist.
195 211
196Common keys are :host, :protocol, and :user. A value of t in 212Common keys are :host, :protocol, and :user. A value of t in
197SPEC means to always succeed in the match. A string value is 213SPEC means to always succeed in the match. A string value is
198matched as a regex. 214matched as a regex."
199 215 (let ((keys (loop for i below (length spec) by 2 collect (nth i spec)))
200The first pass skips fallback choices. If no choices are found 216 choices)
201on the first pass, a second pass is made including the fallback 217 (dolist (choice (copy-tree auth-sources) choices)
202choices. 218 (let ((source (plist-get choice :source))
203 219 (match t))
204For string (filename) sources, fallback choices are those where 220 (when
205PROTOCOL or HOST are nil. 221 (and
206 222 ;; Check existence of source.
207For secrets.el collections, the :host and :protocol keys are not 223 (if (consp source)
208checked for fallback choices." 224 ;; Secret Service API.
209 (let (choices) 225 (member (auth-get-source choice) (secrets-list-collections))
210 (dolist (fallback '(nil t)) 226 ;; authinfo file.
211 (let ((keys (loop for i below (length spec) by 2 227 (file-exists-p source))
212 collect (nth i spec))) 228
213 (default-session-fallback "login")) 229 ;; Check keywords.
214 (dolist (choice auth-sources) 230 (dolist (k keys match)
215 (let* ((s (plist-get choice :source)) 231 (let* ((v (plist-get spec k))
216 ;; this is only set for Secret Service API specs (see secrets.el) 232 (choicev (plist-get choice k)))
217 (coll (and (consp s) (plist-get s :secrets))) 233 (setq match
218 (score 0)) 234 (and match
219 (cond 235 (or
220 (coll ; use secrets.el here 236 ;; source always matches spec key
221 (when (eq coll 'default) 237 (eq t choicev)
222 (setq coll (secrets-get-alias "default")) 238 ;; source key gives regex to match against spec
223 (unless coll 239 (and (stringp choicev) (string-match choicev v))
224 (auth-source-do-debug 240 ;; source key gives symbol to match against spec
225 "No 'default' alias. Trying collection '%s'." 241 (and (symbolp choicev) (eq choicev v))))))))
226 default-session-fallback) 242
227 (setq coll default-session-fallback))) 243 (add-to-list 'choices choice 'append))))))
228 (let* ((coll-search (cond 244
229 ((stringp coll) coll) 245(defun auth-source-retrieve (mode entry &rest spec)
230 246 "Retrieve MODE credentials according to SPEC from ENTRY."
231 ;; when the collection is nil: 247 (catch 'no-password
232 ;; in fallback mode, accept it as any 248 (let ((host (plist-get spec :host))
233 ;; otherwise, hope to fail 249 (user (plist-get spec :user))
234 ((null coll) (if fallback 250 (prot (plist-get spec :protocol))
235 nil 251 (source (plist-get entry :source))
236 " *fallback-fail*")))) 252 result)
237 ;; assemble a search query for secrets-search-items 253 (cond
238 ;; in fallback mode, host and protocol are not checked 254 ;; Secret Service API.
239 (other-search (loop for k 255 ((consp source)
240 in (if fallback 256 (let ((coll (auth-get-source entry))
241 (remove :host 257 item)
242 (remove :protocol keys)) 258 ;; Loop over candidates with a matching host attribute.
243 keys) 259 (dolist (elt (secrets-search-items coll :host host) item)
244 append (list 260 (when (and (or (not user)
245 k 261 (string-equal
246 ;; convert symbols to a string 262 user (secrets-get-attribute coll elt :user)))
247 (let ((v (plist-get spec k))) 263 (or (not prot)
248 (if (stringp v) 264 (string-equal
249 v 265 prot (secrets-get-attribute coll elt :protocol))))
250 (prin1-to-string v)))))) 266 (setq item elt)
251 ;; the score is based on how exact the search was, 267 (return elt)))
252 ;; plus base score = 1 for any match 268 ;; Compose result.
253 (score (1+ (length other-search))) 269 (when item
254 (results (apply 'secrets-search-items 270 (setq result
255 coll-search 271 (mapcar (lambda (m)
256 other-search))) 272 (if (string-equal "password" m)
257 (auth-source-do-debug 273 (or (secrets-get-secret coll item)
258 "auth-source-pick: got items %s in collection '%s' + %s" 274 ;; When we do not find a password,
259 results coll-search other-search) 275 ;; we return nil anyway.
260 ;; put the results in the choices variable 276 (throw 'no-password nil))
261 (dolist (result results) 277 (or (secrets-get-attribute coll item :user)
262 (setq choices (cons (list score 278 user)))
263 `(:source secrets 279 (if (consp mode) mode (list mode)))))
264 :item ,result 280 (if (consp mode) result (car result))))
265 :collection ,coll 281 ;; Anything else is netrc.
266 :search ,coll-search 282 (t
267 ,@other-search)) 283 (let ((search (list source (list host) (list (format "%s" prot))
268 choices))))) 284 (auth-source-protocol-defaults prot))))
269 ;; this is any non-secrets spec (currently means a string filename) 285 (setq result
270 (t 286 (mapcar (lambda (m)
271 (let ((match t)) 287 (if (string-equal "password" m)
272 (dolist (k keys) 288 (or (apply
273 (let* ((v (plist-get spec k)) 289 'netrc-machine-user-or-password m search)
274 (choicev (plist-get choice k))) 290 ;; When we do not find a password, we
275 (setq match 291 ;; return nil anyway.
276 (and match 292 (throw 'no-password nil))
277 (or (eq t choicev) ; source always matches spec key 293 (or (apply
278 ;; source key gives regex to match against spec 294 'netrc-machine-user-or-password m search)
279 (and (stringp choicev) (string-match choicev v)) 295 user)))
280 ;; source key gives symbol to match against spec 296 (if (consp mode) mode (list mode)))))
281 (and (symbolp choicev) (eq choicev v)) 297 (if (consp mode) result (car result)))))))
282 ;; in fallback mode, missing source key is OK 298
283 fallback))) 299(defun auth-source-create (mode entry &rest spec)
284 (when match (incf score)))) ; increment the score for each match 300 "Create interactively credentials according to SPEC in ENTRY.
285 301Return structure as specified by MODE."
286 ;; now if the whole iteration resulted in a match: 302 (let* ((host (plist-get spec :host))
287 (when match 303 (user (plist-get spec :user))
288 (setq choices (cons (list score choice) choices)))))))) 304 (prot (plist-get spec :protocol))
289 ;; when there were matches, skip the second pass 305 (source (plist-get entry :source))
290 (when choices (return choices)))) 306 (name (concat (if user (format "%s@" user))
291 307 host
292 ;; return the results sorted by score 308 (if prot (format ":%s" prot))))
293 (mapcar 'cadr (sort choices (lambda (x y) (> (car x) (car y))))))) 309 result)
294 310 (setq result
295(defun auth-source-forget-user-or-password (mode host protocol) 311 (mapcar
312 (lambda (m)
313 (if (equal "password" m)
314 (let ((passwd (read-passwd "Password: ")))
315 (cond
316 ;; Secret Service API.
317 ((consp source)
318 (apply
319 'secrets-create-item
320 (auth-get-source entry) name passwd spec))
321 (t)) ;; netrc not implemented yes.
322 passwd)
323 (or
324 ;; the originally requested :user
325 user
326 "unknown-user")))
327 (if (consp mode) mode (list mode))))
328 (if (consp mode) result (car result))))
329
330(defun auth-source-delete (entry &rest spec)
331 "Delete credentials according to SPEC in ENTRY."
332 (let ((host (plist-get spec :host))
333 (user (plist-get spec :user))
334 (prot (plist-get spec :protocol))
335 (source (plist-get entry :source)))
336 (cond
337 ;; Secret Service API.
338 ((consp source)
339 (let ((coll (auth-get-source entry)))
340 ;; Loop over candidates with a matching host attribute.
341 (dolist (elt (secrets-search-items coll :host host))
342 (when (and (or (not user)
343 (string-equal
344 user (secrets-get-attribute coll elt :user)))
345 (or (not prot)
346 (string-equal
347 prot (secrets-get-attribute coll elt :protocol))))
348 (secrets-delete-item coll elt)))))
349 (t)))) ;; netrc not implemented yes.
350
351(defun auth-source-forget-user-or-password
352 (mode host protocol &optional username)
353 "Remove cached authentication token."
296 (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing 354 (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing
297 (remhash (format "%s %s:%s" mode host protocol) auth-source-cache)) 355 (remhash
356 (if username
357 (format "%s %s:%s %s" mode host protocol username)
358 (format "%s %s:%s" mode host protocol))
359 auth-source-cache))
298 360
299(defun auth-source-forget-all-cached () 361(defun auth-source-forget-all-cached ()
300 "Forget all cached auth-source authentication tokens." 362 "Forget all cached auth-source authentication tokens."
@@ -308,7 +370,8 @@ checked for fallback choices."
308;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "tzz") 370;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "tzz")
309;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "joe"))) 371;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "joe")))
310 372
311(defun auth-source-user-or-password (mode host protocol &optional username) 373(defun auth-source-user-or-password
374 (mode host protocol &optional username create-missing delete-existing)
312 "Find MODE (string or list of strings) matching HOST and PROTOCOL. 375 "Find MODE (string or list of strings) matching HOST and PROTOCOL.
313 376
314USERNAME is optional and will be used as \"login\" in a search 377USERNAME is optional and will be used as \"login\" in a search
@@ -317,17 +380,31 @@ items don't have a username. This means that if you search for
317username \"joe\" and it matches an item but the item doesn't have 380username \"joe\" and it matches an item but the item doesn't have
318a :user attribute, the username \"joe\" will be returned. 381a :user attribute, the username \"joe\" will be returned.
319 382
320MODE can be \"login\" or \"password\" for example." 383A non nil DELETE-EXISTING means deleting any matching password
384entry in the respective sources. This is useful only when
385CREATE-MISSING is non nil as well; the intended use case is to
386remove wrong password entries.
387
388If no matching entry is found, and CREATE-MISSING is non nil,
389the password will be retrieved interactively, and it will be
390stored in the password database which matches best (see
391`auth-sources').
392
393MODE can be \"login\" or \"password\"."
321 (auth-source-do-debug 394 (auth-source-do-debug
322 "auth-source-user-or-password: get %s for %s (%s) + user=%s" 395 "auth-source-user-or-password: get %s for %s (%s) + user=%s"
323 mode host protocol username) 396 mode host protocol username)
324 (let* ((listy (listp mode)) 397 (let* ((listy (listp mode))
325 (mode (if listy mode (list mode))) 398 (mode (if listy mode (list mode)))
326 (extras (when username `(:user ,username))) 399 (cname (if username
327 (cname (format "%s %s:%s %s" mode host protocol extras)) 400 (format "%s %s:%s %s" mode host protocol username)
401 (format "%s %s:%s" mode host protocol)))
328 (search (list :host host :protocol protocol)) 402 (search (list :host host :protocol protocol))
329 (search (if username (append search (list :user username)) search)) 403 (search (if username (append search (list :user username)) search))
330 (found (gethash cname auth-source-cache))) 404 (found (if (not delete-existing)
405 (gethash cname auth-source-cache)
406 (remhash cname auth-source-cache)
407 nil)))
331 (if found 408 (if found
332 (progn 409 (progn
333 (auth-source-do-debug 410 (auth-source-do-debug
@@ -337,45 +414,35 @@ MODE can be \"login\" or \"password\" for example."
337 (if (and (member "password" mode) auth-source-hide-passwords) 414 (if (and (member "password" mode) auth-source-hide-passwords)
338 "SECRET" 415 "SECRET"
339 found) 416 found)
340 host protocol extras) 417 host protocol username)
341 found) ; return the found data 418 found) ; return the found data
342 ;; else, if not found 419 ;; else, if not found
343 (dolist (choice (apply 'auth-source-pick search)) 420 (let ((choices (apply 'auth-source-pick search)))
344 (setq found (cond 421 (dolist (choice choices)
345 ;; the secrets.el spec 422 (if delete-existing
346 ((eq (plist-get choice :source) 'secrets) 423 (apply 'auth-source-delete choice search)
347 (let ((coll (plist-get choice :search)) 424 (setq found (apply 'auth-source-retrieve mode choice search)))
348 (item (plist-get choice :item))) 425 (and found (return found)))
349 (mapcar (lambda (m) 426
350 (if (equal "password" m) 427 ;; We haven't found something, so we will create it interactively.
351 (secrets-get-secret coll item) 428 (when (and (not found) choices create-missing)
352 ;; the user name is either 429 (setq found (apply 'auth-source-create mode (car choices) search)))
353 (or 430
354 ;; the secret's attribute :user, or 431 ;; Cache the result.
355 (secrets-get-attribute coll item :user)
356 ;; the originally requested :user
357 username
358 "unknown-user")))
359 mode)))
360 (t ; anything else is netrc
361 (netrc-machine-user-or-password
362 mode
363 (plist-get choice :source)
364 (list host)
365 (list (format "%s" protocol))
366 (auth-source-protocol-defaults protocol)))))
367 (when found 432 (when found
368 (auth-source-do-debug 433 (auth-source-do-debug
369 "auth-source-user-or-password: found %s=%s for %s (%s) + %s" 434 "auth-source-user-or-password: found %s=%s for %s (%s) + %s"
370 mode 435 mode
371 ;; don't show the password 436 ;; don't show the password
372 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found) 437 (if (and (member "password" mode) auth-source-hide-passwords)
373 host protocol extras) 438 "SECRET" found)
439 host protocol username)
374 (setq found (if listy found (car-safe found))) 440 (setq found (if listy found (car-safe found)))
375 (when auth-source-do-cache 441 (when auth-source-do-cache
376 (puthash cname found auth-source-cache))) 442 (puthash cname found auth-source-cache)))
377 (return found))))) 443
378 444 found))))
445
379(defun auth-source-protocol-defaults (protocol) 446(defun auth-source-protocol-defaults (protocol)
380 "Return a list of default ports and names for PROTOCOL." 447 "Return a list of default ports and names for PROTOCOL."
381 (cdr-safe (assoc protocol auth-source-protocols))) 448 (cdr-safe (assoc protocol auth-source-protocols)))