aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKatsumi Yamaoka2010-03-28 23:52:01 +0000
committerKatsumi Yamaoka2010-03-28 23:52:01 +0000
commitfb178e4c7213ea7a59efaf9f70eaaed75daa3206 (patch)
treeafad515a055fe533d0d7fc80121d0a407107f571
parent40f0529d4edc393ef09e93909126ef1af33e00ca (diff)
downloademacs-fb178e4c7213ea7a59efaf9f70eaaed75daa3206.tar.gz
emacs-fb178e4c7213ea7a59efaf9f70eaaed75daa3206.zip
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.
-rw-r--r--lisp/gnus/ChangeLog10
-rw-r--r--lisp/gnus/auth-source.el246
2 files changed, 200 insertions, 56 deletions
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog
index 20a8cb63997..ecbce02ad85 100644
--- a/lisp/gnus/ChangeLog
+++ b/lisp/gnus/ChangeLog
@@ -1,3 +1,13 @@
12010-03-27 Teodor Zlatanov <tzz@lifelogs.com>
2
3 * auth-source.el (auth-sources): Change default to be simpler. Explain
4 about Secret Service API sources. Improve Customize options.
5 (auth-source-pick): Change to accept any number of search parameters.
6 Implement fallbacks iteratively, not recursively. Add scoring on the
7 second pass and sort by score. Call Secret Service API when needed.
8 (auth-source-user-or-password): Use it. Call Secret Service API
9 directly when needed to get the user name and the password.
10
12010-03-24 Juanma Barranquero <lekktu@gmail.com> 112010-03-24 Juanma Barranquero <lekktu@gmail.com>
2 12
3 * message.el (message-interactive): Doc fix. 13 * message.el (message-interactive): Doc fix.
diff --git a/lisp/gnus/auth-source.el b/lisp/gnus/auth-source.el
index 3b0d700a86f..8c59aee9714 100644
--- a/lisp/gnus/auth-source.el
+++ b/lisp/gnus/auth-source.el
@@ -38,6 +38,7 @@
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."
@@ -60,6 +61,7 @@
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)))
@@ -102,9 +104,13 @@ Only relevant if `auth-source-debug' is not nil."
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.
107 109
110The default will get login and password information from a .gpg
111file, which you should set up with the EPA/EPG packages to be
112encrypted. See the auth.info manual for details.
113
108Each entry is the authentication type with optional properties. 114Each entry is the authentication type with optional properties.
109 115
110It's best to customize this with `M-x customize-variable' because the choices 116It's best to customize this with `M-x customize-variable' because the choices
@@ -121,27 +127,24 @@ can get pretty complex."
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
@@ -153,7 +156,7 @@ can get pretty complex."
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")
@@ -173,27 +176,121 @@ can get pretty complex."
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")
181;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
182;; (:source (:secrets "session") :host t :protocol t :user "joe")
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
196Common keys are :host, :protocol, and :user. A value of t in
197SPEC means to always succeed in the match. A string value is
198matched as a regex.
178 199
179Returns fallback choices (where PROTOCOL or HOST are nil) with FALLBACK t." 200The first pass skips fallback choices. If no choices are found
180 (interactive "sHost: \nsProtocol: \n") ;for testing 201on the first pass, a second pass is made including the fallback
202choices.
203
204For string (filename) sources, fallback choices are those where
205PROTOCOL or HOST are nil.
206
207For secrets.el collections, the :host and :protocol keys are not
208checked 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
@@ -204,44 +301,81 @@ Returns fallback choices (where PROTOCOL or HOST are nil) with FALLBACK t."
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
314USERNAME is optional and will be used as \"login\" in a search
315across the Secret Service API (see secrets.el) if the resulting
316items 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
318a :user attribute, the username \"joe\" will be returned.
319
209MODE can be \"login\" or \"password\" for example." 320MODE 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"
221 mode 335 mode
222 ;; don't show the password 336 ;; don't show the password
223 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found) 337 (if (and (member "password" mode) auth-source-hide-passwords)
224 host protocol) 338 "SECRET"
225 found) 339 found)
226 (dolist (choice (auth-source-pick host protocol)) 340 host protocol extras)
227 (setq found (netrc-machine-user-or-password 341 found) ; return the found data
228 mode 342 ;; else, if not found
229 (plist-get choice :source) 343 (dolist (choice (apply 'auth-source-pick search))
230 (list host) 344 (setq found (cond
231 (list (format "%s" protocol)) 345 ;; the secrets.el spec
232 (auth-source-protocol-defaults protocol))) 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)))))
233 (when found 367 (when found
234 (auth-source-do-debug 368 (auth-source-do-debug
235 "auth-source-user-or-password: found %s=%s for %s (%s)" 369 "auth-source-user-or-password: found %s=%s for %s (%s) + %s"
236 mode 370 mode
237 ;; don't show the password 371 ;; don't show the password
238 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found) 372 (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found)
239 host protocol) 373 host protocol extras)
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)))