comparison lisp/gnus/auth-source.el @ 107563:95c2fdf14356

2010-03-27 Teodor Zlatanov <tzz@lifelogs.com> * auth-source.el (auth-sources): Change default to be simpler. Explain about Secret Service API sources. Improve Customize options. (auth-source-pick): Change to accept any number of search parameters. Implement fallbacks iteratively, not recursively. Add scoring on the second pass and sort by score. Call Secret Service API when needed. (auth-source-user-or-password): Use it. Call Secret Service API directly when needed to get the user name and the password.
author Katsumi Yamaoka <yamaoka@jpl.org>
date Sun, 28 Mar 2010 23:52:01 +0000
parents 0fe940324254
children 3350692f1a89
comparison
equal deleted inserted replaced
107562:aae79c4d6845 107563:95c2fdf14356
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-search-items "secrets")
39 (autoload 'secrets-get-alias "secrets") 39 (autoload 'secrets-get-alias "secrets")
40 (autoload 'secrets-get-attribute "secrets") 40 (autoload 'secrets-get-attribute "secrets")
41 (autoload 'secrets-get-secret "secrets")
41 42
42 (defgroup auth-source nil 43 (defgroup auth-source nil
43 "Authentication sources." 44 "Authentication sources."
44 :version "23.1" ;; No Gnus 45 :version "23.1" ;; No Gnus
45 :group 'gnus) 46 :group 'gnus)
58 (symbol :tag "Protocol") 59 (symbol :tag "Protocol")
59 (repeat :tag "Names" 60 (repeat :tag "Names"
60 (string :tag "Name"))))) 61 (string :tag "Name")))))
61 62
62 ;;; generate all the protocols in a format Customize can use 63 ;;; generate all the protocols in a format Customize can use
64 ;;; TODO: generate on the fly from auth-source-protocols
63 (defconst auth-source-protocols-customize 65 (defconst auth-source-protocols-customize
64 (mapcar (lambda (a) 66 (mapcar (lambda (a)
65 (let ((p (car-safe a))) 67 (let ((p (car-safe a)))
66 (list 'const 68 (list 'const
67 :tag (upcase (symbol-name p)) 69 :tag (upcase (symbol-name p))
100 Only relevant if `auth-source-debug' is not nil." 102 Only relevant if `auth-source-debug' is not nil."
101 :group 'auth-source 103 :group 'auth-source
102 :version "23.2" ;; No Gnus 104 :version "23.2" ;; No Gnus
103 :type `boolean) 105 :type `boolean)
104 106
105 (defcustom auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t)) 107 (defcustom auth-sources '((:source "~/.authinfo.gpg"))
106 "List of authentication sources. 108 "List of authentication sources.
109
110 The default will get login and password information from a .gpg
111 file, which you should set up with the EPA/EPG packages to be
112 encrypted. See the auth.info manual for details.
107 113
108 Each entry is the authentication type with optional properties. 114 Each entry is the authentication type with optional properties.
109 115
110 It's best to customize this with `M-x customize-variable' because the choices 116 It's best to customize this with `M-x customize-variable' because the choices
111 can get pretty complex." 117 can get pretty complex."
119 (list :tag "secrets.el (Secret Service API/KWallet/GNOME KeyRing)" 125 (list :tag "secrets.el (Secret Service API/KWallet/GNOME KeyRing)"
120 (const :format "" :value :secrets) 126 (const :format "" :value :secrets)
121 (choice :tag "Collection to use" 127 (choice :tag "Collection to use"
122 (string :tag "Collection name") 128 (string :tag "Collection name")
123 (const :tag "Default" 'default) 129 (const :tag "Default" 'default)
124 (const :tag "Any" t) 130 (const :tag "Login" "login")
125 (const :tag "Temporary" "session") 131 (const :tag "Temporary" "session"))))
126 (string :tag "Specific session name")
127 (const :tag "Fallback" nil))))
128 (const :format "" :value :host)
129 (choice :tag "Host (machine) choice"
130 (const :tag "Any" t)
131 (regexp :tag "Host (machine) regular expression (TODO)")
132 (const :tag "Fallback" nil))
133 (const :format "" :value :protocol)
134 (choice :tag "Protocol"
135 (const :tag "Any" t)
136 (const :tag "Fallback" nil)
137 ,@auth-source-protocols-customize)
138 (repeat :tag "Extra Parameters" :inline t 132 (repeat :tag "Extra Parameters" :inline t
139 (choice :tag "Extra parameter" 133 (choice :tag "Extra parameter"
140 (list :tag "Preferred username" :inline t 134 (list :tag "Host (omit to match as a fallback)"
141 (const :format "" :value :preferred-username) 135 (const :format "" :value :host)
136 (choice :tag "Host (machine) choice"
137 (const :tag "Any" t)
138 (regexp :tag "Host (machine) regular expression")))
139 (list :tag "Protocol (omit to match as a fallback)"
140 (const :format "" :value :protocol)
141 (choice :tag "Protocol"
142 (const :tag "Any" t)
143 ,@auth-source-protocols-customize))
144 (list :tag "User (omit to match as a fallback)" :inline t
145 (const :format "" :value :user)
142 (choice :tag "Personality or username" 146 (choice :tag "Personality or username"
143 (const :tag "Any" t) 147 (const :tag "Any" t)
144 (const :tag "Fallback" nil)
145 (string :tag "Specific user name")))))))) 148 (string :tag "Specific user name"))))))))
146 149
147 ;; temp for debugging 150 ;; temp for debugging
148 ;; (unintern 'auth-source-protocols) 151 ;; (unintern 'auth-source-protocols)
149 ;; (unintern 'auth-sources) 152 ;; (unintern 'auth-sources)
151 ;; (setq auth-sources nil) 154 ;; (setq auth-sources nil)
152 ;; (format "%S" auth-sources) 155 ;; (format "%S" auth-sources)
153 ;; (customize-variable 'auth-source-protocols) 156 ;; (customize-variable 'auth-source-protocols)
154 ;; (setq auth-source-protocols nil) 157 ;; (setq auth-source-protocols nil)
155 ;; (format "%S" auth-source-protocols) 158 ;; (format "%S" auth-source-protocols)
156 ;; (auth-source-pick "a" 'imap) 159 ;; (auth-source-pick nil :host "a" :port 'imap)
157 ;; (auth-source-user-or-password "login" "imap.myhost.com" 'imap) 160 ;; (auth-source-user-or-password "login" "imap.myhost.com" 'imap)
158 ;; (auth-source-user-or-password "password" "imap.myhost.com" 'imap) 161 ;; (auth-source-user-or-password "password" "imap.myhost.com" 'imap)
159 ;; (auth-source-user-or-password-imap "login" "imap.myhost.com") 162 ;; (auth-source-user-or-password-imap "login" "imap.myhost.com")
160 ;; (auth-source-user-or-password-imap "password" "imap.myhost.com") 163 ;; (auth-source-user-or-password-imap "password" "imap.myhost.com")
161 ;; (auth-source-protocol-defaults 'imap) 164 ;; (auth-source-protocol-defaults 'imap)
171 (let ((logger (if (functionp auth-source-debug) 174 (let ((logger (if (functionp auth-source-debug)
172 auth-source-debug 175 auth-source-debug
173 'message))) 176 'message)))
174 (apply logger msg)))) 177 (apply logger msg))))
175 178
176 (defun auth-source-pick (host protocol &optional fallback) 179 ;; (auth-source-pick nil :host "any" :protocol 'imap :user "joe")
177 "Parse `auth-sources' for HOST, and PROTOCOL matches. 180 ;; (auth-source-pick t :host "any" :protocol 'imap :user "joe")
178 181 ;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
179 Returns fallback choices (where PROTOCOL or HOST are nil) with FALLBACK t." 182 ;; (:source (:secrets "session") :host t :protocol t :user "joe")
180 (interactive "sHost: \nsProtocol: \n") ;for testing 183 ;; (:source (:secrets "login") :host t :protocol t)
184 ;; (:source "~/.authinfo.gpg" :host t :protocol t)))
185
186 ;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
187 ;; (:source (:secrets "session") :host t :protocol t :user "joe")
188 ;; (:source (:secrets "login") :host t :protocol t)
189 ;; ))
190
191 ;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t)))
192
193 (defun auth-source-pick (&rest spec)
194 "Parse `auth-sources' for matches of the SPEC plist.
195
196 Common keys are :host, :protocol, and :user. A value of t in
197 SPEC means to always succeed in the match. A string value is
198 matched as a regex.
199
200 The first pass skips fallback choices. If no choices are found
201 on the first pass, a second pass is made including the fallback
202 choices.
203
204 For string (filename) sources, fallback choices are those where
205 PROTOCOL or HOST are nil.
206
207 For secrets.el collections, the :host and :protocol keys are not
208 checked for fallback choices."
181 (let (choices) 209 (let (choices)
182 (dolist (choice auth-sources) 210 (dolist (fallback '(nil t))
183 (let ((h (plist-get choice :host)) 211 (let ((keys (loop for i below (length spec) by 2
184 (p (plist-get choice :protocol))) 212 collect (nth i spec)))
185 (when (and 213 (default-session-fallback "login"))
186 (or (equal t h) 214 (dolist (choice auth-sources)
187 (and (stringp h) (string-match h host)) 215 (let* ((s (plist-get choice :source))
188 (and fallback (equal h nil))) 216 ;; this is only set for Secret Service API specs (see secrets.el)
189 (or (equal t p) 217 (coll (plist-get s :secrets))
190 (and (symbolp p) (equal p protocol)) 218 (score 0))
191 (and fallback (equal p nil)))) 219 (cond
192 (push choice choices)))) 220 (coll ; use secrets.el here
193 (if choices 221 (when (eq coll 'default)
194 choices 222 (setq coll (secrets-get-alias "default"))
195 (unless fallback 223 (unless coll
196 (auth-source-pick host protocol t))))) 224 (auth-source-do-debug
225 "No 'default' alias. Trying collection '%s'."
226 default-session-fallback)
227 (setq coll default-session-fallback)))
228 (let* ((coll-search (cond
229 ((stringp coll) coll)
230
231 ;; when the collection is nil:
232 ;; in fallback mode, accept it as any
233 ;; otherwise, hope to fail
234 ((null coll) (if fallback
235 nil
236 " *fallback-fail*"))))
237 ;; assemble a search query for secrets-search-items
238 ;; in fallback mode, host and protocol are not checked
239 (other-search (loop for k
240 in (if fallback
241 (remove :host
242 (remove :protocol keys))
243 keys)
244 append (list
245 k
246 ;; convert symbols to a string
247 (let ((v (plist-get spec k)))
248 (if (stringp v)
249 v
250 (prin1-to-string v))))))
251 ;; the score is based on how exact the search was,
252 ;; plus base score = 1 for any match
253 (score (1+ (length other-search)))
254 (results (apply 'secrets-search-items
255 coll-search
256 other-search)))
257 (auth-source-do-debug
258 "auth-source-pick: got items %s in collection '%s' + %s"
259 results coll-search other-search)
260 ;; put the results in the choices variable
261 (dolist (result results)
262 (setq choices (cons (list score
263 `(:source secrets
264 :item ,result
265 :collection ,coll
266 :search ,coll-search
267 ,@other-search))
268 choices)))))
269 ;; this is any non-secrets spec (currently means a string filename)
270 (t
271 (let ((match t))
272 (dolist (k keys)
273 (let* ((v (plist-get spec k))
274 (choicev (plist-get choice k)))
275 (setq match
276 (and match
277 (or (eq t choicev) ; source always matches spec key
278 ;; source key gives regex to match against spec
279 (and (stringp choicev) (string-match choicev v))
280 ;; source key gives symbol to match against spec
281 (and (symbolp choicev) (eq choicev v))
282 ;; in fallback mode, missing source key is OK
283 fallback)))
284 (when match (incf score)))) ; increment the score for each match
285
286 ;; now if the whole iteration resulted in a match:
287 (when match
288 (setq choices (cons (list score choice) choices))))))))
289 ;; when there were matches, skip the second pass
290 (when choices (return choices))))
291
292 ;; return the results sorted by score
293 (mapcar 'cadr (sort choices (lambda (x y) (> (car x) (car y)))))))
197 294
198 (defun auth-source-forget-user-or-password (mode host protocol) 295 (defun auth-source-forget-user-or-password (mode host protocol)
199 (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing 296 (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing
200 (remhash (format "%s %s:%s" mode host protocol) auth-source-cache)) 297 (remhash (format "%s %s:%s" mode host protocol) auth-source-cache))
201 298
202 (defun auth-source-forget-all-cached () 299 (defun auth-source-forget-all-cached ()
203 "Forget all cached auth-source authentication tokens." 300 "Forget all cached auth-source authentication tokens."
204 (interactive) 301 (interactive)
205 (setq auth-source-cache (make-hash-table :test 'equal))) 302 (setq auth-source-cache (make-hash-table :test 'equal)))
206 303
207 (defun auth-source-user-or-password (mode host protocol) 304 ;; (progn
305 ;; (auth-source-forget-all-cached)
306 ;; (list
307 ;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other")
308 ;; (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")))
310
311 (defun auth-source-user-or-password (mode host protocol &optional username)
208 "Find MODE (string or list of strings) matching HOST and PROTOCOL. 312 "Find MODE (string or list of strings) matching HOST and PROTOCOL.
313
314 USERNAME is optional and will be used as \"login\" in a search
315 across the Secret Service API (see secrets.el) if the resulting
316 items don't have a username. This means that if you search for
317 username \"joe\" and it matches an item but the item doesn't have
318 a :user attribute, the username \"joe\" will be returned.
319
209 MODE can be \"login\" or \"password\" for example." 320 MODE can be \"login\" or \"password\" for example."
210 (auth-source-do-debug 321 (auth-source-do-debug
211 "auth-source-user-or-password: get %s for %s (%s)" 322 "auth-source-user-or-password: get %s for %s (%s) + user=%s"
212 mode host protocol) 323 mode host protocol username)
213 (let* ((listy (listp mode)) 324 (let* ((listy (listp mode))
214 (mode (if listy mode (list mode))) 325 (mode (if listy mode (list mode)))
215 (cname (format "%s %s:%s" mode host protocol)) 326 (extras (when username `(:user ,username)))
327 (cname (format "%s %s:%s %s" mode host protocol extras))
328 (search (list :host host :protocol protocol))
329 (search (if username (append search (list :user username)) search))
216 (found (gethash cname auth-source-cache))) 330 (found (gethash cname auth-source-cache)))
217 (if found 331 (if found
218 (progn 332 (progn
219 (auth-source-do-debug 333 (auth-source-do-debug
220 "auth-source-user-or-password: cached %s=%s for %s (%s)" 334 "auth-source-user-or-password: cached %s=%s for %s (%s) + %s"
335 mode
336 ;; don't show the password
337 (if (and (member "password" mode) auth-source-hide-passwords)
338 "SECRET"
339 found)
340 host protocol extras)
341 found) ; return the found data
342 ;; else, if not found
343 (dolist (choice (apply 'auth-source-pick search))
344 (setq found (cond
345 ;; the secrets.el spec
346 ((eq (plist-get choice :source) 'secrets)
347 (let ((coll (plist-get choice :search))
348 (item (plist-get choice :item)))
349 (mapcar (lambda (m)
350 (if (equal "password" m)
351 (secrets-get-secret coll item)
352 ;; the user name is either
353 (or
354 ;; the secret's attribute :user, or
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
368 (auth-source-do-debug
369 "auth-source-user-or-password: found %s=%s for %s (%s) + %s"
221 mode 370 mode
222 ;; don't show the password 371 ;; don't show the password
223 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found) 372 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found)
224 host protocol) 373 host protocol extras)
225 found)
226 (dolist (choice (auth-source-pick host protocol))
227 (setq found (netrc-machine-user-or-password
228 mode
229 (plist-get choice :source)
230 (list host)
231 (list (format "%s" protocol))
232 (auth-source-protocol-defaults protocol)))
233 (when found
234 (auth-source-do-debug
235 "auth-source-user-or-password: found %s=%s for %s (%s)"
236 mode
237 ;; don't show the password
238 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found)
239 host protocol)
240 (setq found (if listy found (car-safe found))) 374 (setq found (if listy found (car-safe found)))
241 (when auth-source-do-cache 375 (when auth-source-do-cache
242 (puthash cname found auth-source-cache))) 376 (puthash cname found auth-source-cache)))
243 (return found))))) 377 (return found)))))
244 378
245 (defun auth-source-protocol-defaults (protocol) 379 (defun auth-source-protocol-defaults (protocol)
246 "Return a list of default ports and names for PROTOCOL." 380 "Return a list of default ports and names for PROTOCOL."
247 (cdr-safe (assoc protocol auth-source-protocols))) 381 (cdr-safe (assoc protocol auth-source-protocols)))
248 382
249 (defun auth-source-user-or-password-imap (mode host) 383 (defun auth-source-user-or-password-imap (mode host)