diff options
author | Michael Albinus <michael.albinus@gmx.de> | 2010-06-08 15:05:11 +0200 |
---|---|---|
committer | Michael Albinus <michael.albinus@gmx.de> | 2010-06-08 15:05:11 +0200 |
commit | 0e4966fb65bb4374d334d127ad1de1f55f5c86c8 (patch) | |
tree | 30e5a39e556d7c521cc3b87dad912a7f11a15fc8 /lisp/gnus | |
parent | d7c5d87df66ddde23546e919ca4078f00be4d20b (diff) | |
download | emacs-0e4966fb65bb4374d334d127ad1de1f55f5c86c8.tar.gz |
* 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/gnus')
-rw-r--r-- | lisp/gnus/ChangeLog | 12 | ||||
-rw-r--r-- | lisp/gnus/auth-source.el | 345 |
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 @@ +2010-06-08 Michael Albinus <michael.albinus@gmx.de> + + * 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. + 2010-06-07 Teemu Likonen <tlikonen@iki.fi> (tiny change) * 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 @@ (eval-when-compile (require 'cl)) (autoload 'netrc-machine-user-or-password "netrc") -(autoload 'secrets-search-items "secrets") +(autoload 'secrets-create-item "secrets") +(autoload 'secrets-delete-item "secrets") (autoload 'secrets-get-alias "secrets") (autoload 'secrets-get-attribute "secrets") (autoload 'secrets-get-secret "secrets") +(autoload 'secrets-list-collections "secrets") +(autoload 'secrets-search-items "secrets") (defgroup auth-source nil "Authentication sources." @@ -122,7 +125,7 @@ can get pretty complex." (const :format "" :value :source) (choice :tag "Authentication backend choice" (string :tag "Authentication Source (file)") - (list :tag "secrets.el (Secret Service API/KWallet/GNOME KeyRing)" + (list :tag "secrets.el (Secret Service API/KWallet/GNOME Keyring)" (const :format "" :value :secrets) (choice :tag "Collection to use" (string :tag "Collection name") @@ -178,123 +181,182 @@ can get pretty complex." ;; (auth-source-pick nil :host "any" :protocol 'imap :user "joe") ;; (auth-source-pick t :host "any" :protocol 'imap :user "joe") -;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") -;; (:source (:secrets "session") :host t :protocol t :user "joe") +;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") +;; (:source (:secrets "session") :host t :protocol t :user "joe") ;; (:source (:secrets "login") :host t :protocol t) ;; (:source "~/.authinfo.gpg" :host t :protocol t))) -;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") -;; (:source (:secrets "session") :host t :protocol t :user "joe") +;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") +;; (:source (:secrets "session") :host t :protocol t :user "joe") ;; (:source (:secrets "login") :host t :protocol t) ;; )) ;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t))) +(defun auth-get-source (entry) + "Return the source string of ENTRY, which is one entry in `auth-sources'. +If it is a Secret Service API, return the collection name, otherwise +the file name." + (let ((source (plist-get entry :source))) + (if (stringp source) + source + ;; Secret Service API. + (setq source (plist-get source :secrets)) + (when (eq source 'default) + (setq source (or (secrets-get-alias "default") "login"))) + (or source "session")))) + (defun auth-source-pick (&rest spec) "Parse `auth-sources' for matches of the SPEC plist. Common keys are :host, :protocol, and :user. A value of t in SPEC means to always succeed in the match. A string value is -matched as a regex. - -The first pass skips fallback choices. If no choices are found -on the first pass, a second pass is made including the fallback -choices. - -For string (filename) sources, fallback choices are those where -PROTOCOL or HOST are nil. - -For secrets.el collections, the :host and :protocol keys are not -checked for fallback choices." - (let (choices) - (dolist (fallback '(nil t)) - (let ((keys (loop for i below (length spec) by 2 - collect (nth i spec))) - (default-session-fallback "login")) - (dolist (choice auth-sources) - (let* ((s (plist-get choice :source)) - ;; this is only set for Secret Service API specs (see secrets.el) - (coll (and (consp s) (plist-get s :secrets))) - (score 0)) - (cond - (coll ; use secrets.el here - (when (eq coll 'default) - (setq coll (secrets-get-alias "default")) - (unless coll - (auth-source-do-debug - "No 'default' alias. Trying collection '%s'." - default-session-fallback) - (setq coll default-session-fallback))) - (let* ((coll-search (cond - ((stringp coll) coll) - - ;; when the collection is nil: - ;; in fallback mode, accept it as any - ;; otherwise, hope to fail - ((null coll) (if fallback - nil - " *fallback-fail*")))) - ;; assemble a search query for secrets-search-items - ;; in fallback mode, host and protocol are not checked - (other-search (loop for k - in (if fallback - (remove :host - (remove :protocol keys)) - keys) - append (list - k - ;; convert symbols to a string - (let ((v (plist-get spec k))) - (if (stringp v) - v - (prin1-to-string v)))))) - ;; the score is based on how exact the search was, - ;; plus base score = 1 for any match - (score (1+ (length other-search))) - (results (apply 'secrets-search-items - coll-search - other-search))) - (auth-source-do-debug - "auth-source-pick: got items %s in collection '%s' + %s" - results coll-search other-search) - ;; put the results in the choices variable - (dolist (result results) - (setq choices (cons (list score - `(:source secrets - :item ,result - :collection ,coll - :search ,coll-search - ,@other-search)) - choices))))) - ;; this is any non-secrets spec (currently means a string filename) - (t - (let ((match t)) - (dolist (k keys) - (let* ((v (plist-get spec k)) - (choicev (plist-get choice k))) - (setq match - (and match - (or (eq t choicev) ; source always matches spec key - ;; source key gives regex to match against spec - (and (stringp choicev) (string-match choicev v)) - ;; source key gives symbol to match against spec - (and (symbolp choicev) (eq choicev v)) - ;; in fallback mode, missing source key is OK - fallback))) - (when match (incf score)))) ; increment the score for each match - - ;; now if the whole iteration resulted in a match: - (when match - (setq choices (cons (list score choice) choices)))))))) - ;; when there were matches, skip the second pass - (when choices (return choices)))) - - ;; return the results sorted by score - (mapcar 'cadr (sort choices (lambda (x y) (> (car x) (car y))))))) - -(defun auth-source-forget-user-or-password (mode host protocol) +matched as a regex." + (let ((keys (loop for i below (length spec) by 2 collect (nth i spec))) + choices) + (dolist (choice (copy-tree auth-sources) choices) + (let ((source (plist-get choice :source)) + (match t)) + (when + (and + ;; Check existence of source. + (if (consp source) + ;; Secret Service API. + (member (auth-get-source choice) (secrets-list-collections)) + ;; authinfo file. + (file-exists-p source)) + + ;; Check keywords. + (dolist (k keys match) + (let* ((v (plist-get spec k)) + (choicev (plist-get choice k))) + (setq match + (and match + (or + ;; source always matches spec key + (eq t choicev) + ;; source key gives regex to match against spec + (and (stringp choicev) (string-match choicev v)) + ;; source key gives symbol to match against spec + (and (symbolp choicev) (eq choicev v)))))))) + + (add-to-list 'choices choice 'append)))))) + +(defun auth-source-retrieve (mode entry &rest spec) + "Retrieve MODE credentials according to SPEC from ENTRY." + (catch 'no-password + (let ((host (plist-get spec :host)) + (user (plist-get spec :user)) + (prot (plist-get spec :protocol)) + (source (plist-get entry :source)) + result) + (cond + ;; Secret Service API. + ((consp source) + (let ((coll (auth-get-source entry)) + item) + ;; Loop over candidates with a matching host attribute. + (dolist (elt (secrets-search-items coll :host host) item) + (when (and (or (not user) + (string-equal + user (secrets-get-attribute coll elt :user))) + (or (not prot) + (string-equal + prot (secrets-get-attribute coll elt :protocol)))) + (setq item elt) + (return elt))) + ;; Compose result. + (when item + (setq result + (mapcar (lambda (m) + (if (string-equal "password" m) + (or (secrets-get-secret coll item) + ;; When we do not find a password, + ;; we return nil anyway. + (throw 'no-password nil)) + (or (secrets-get-attribute coll item :user) + user))) + (if (consp mode) mode (list mode))))) + (if (consp mode) result (car result)))) + ;; Anything else is netrc. + (t + (let ((search (list source (list host) (list (format "%s" prot)) + (auth-source-protocol-defaults prot)))) + (setq result + (mapcar (lambda (m) + (if (string-equal "password" m) + (or (apply + 'netrc-machine-user-or-password m search) + ;; When we do not find a password, we + ;; return nil anyway. + (throw 'no-password nil)) + (or (apply + 'netrc-machine-user-or-password m search) + user))) + (if (consp mode) mode (list mode))))) + (if (consp mode) result (car result))))))) + +(defun auth-source-create (mode entry &rest spec) + "Create interactively credentials according to SPEC in ENTRY. +Return structure as specified by MODE." + (let* ((host (plist-get spec :host)) + (user (plist-get spec :user)) + (prot (plist-get spec :protocol)) + (source (plist-get entry :source)) + (name (concat (if user (format "%s@" user)) + host + (if prot (format ":%s" prot)))) + result) + (setq result + (mapcar + (lambda (m) + (if (equal "password" m) + (let ((passwd (read-passwd "Password: "))) + (cond + ;; Secret Service API. + ((consp source) + (apply + 'secrets-create-item + (auth-get-source entry) name passwd spec)) + (t)) ;; netrc not implemented yes. + passwd) + (or + ;; the originally requested :user + user + "unknown-user"))) + (if (consp mode) mode (list mode)))) + (if (consp mode) result (car result)))) + +(defun auth-source-delete (entry &rest spec) + "Delete credentials according to SPEC in ENTRY." + (let ((host (plist-get spec :host)) + (user (plist-get spec :user)) + (prot (plist-get spec :protocol)) + (source (plist-get entry :source))) + (cond + ;; Secret Service API. + ((consp source) + (let ((coll (auth-get-source entry))) + ;; Loop over candidates with a matching host attribute. + (dolist (elt (secrets-search-items coll :host host)) + (when (and (or (not user) + (string-equal + user (secrets-get-attribute coll elt :user))) + (or (not prot) + (string-equal + prot (secrets-get-attribute coll elt :protocol)))) + (secrets-delete-item coll elt))))) + (t)))) ;; netrc not implemented yes. + +(defun auth-source-forget-user-or-password + (mode host protocol &optional username) + "Remove cached authentication token." (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing - (remhash (format "%s %s:%s" mode host protocol) auth-source-cache)) + (remhash + (if username + (format "%s %s:%s %s" mode host protocol username) + (format "%s %s:%s" mode host protocol)) + auth-source-cache)) (defun auth-source-forget-all-cached () "Forget all cached auth-source authentication tokens." @@ -308,7 +370,8 @@ checked for fallback choices." ;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "tzz") ;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "joe"))) -(defun auth-source-user-or-password (mode host protocol &optional username) +(defun auth-source-user-or-password + (mode host protocol &optional username create-missing delete-existing) "Find MODE (string or list of strings) matching HOST and PROTOCOL. USERNAME 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 username \"joe\" and it matches an item but the item doesn't have a :user attribute, the username \"joe\" will be returned. -MODE can be \"login\" or \"password\" for example." +A non nil DELETE-EXISTING means deleting any matching password +entry in the respective sources. This is useful only when +CREATE-MISSING is non nil as well; the intended use case is to +remove wrong password entries. + +If no matching entry is found, and CREATE-MISSING is non nil, +the password will be retrieved interactively, and it will be +stored in the password database which matches best (see +`auth-sources'). + +MODE can be \"login\" or \"password\"." (auth-source-do-debug "auth-source-user-or-password: get %s for %s (%s) + user=%s" mode host protocol username) (let* ((listy (listp mode)) (mode (if listy mode (list mode))) - (extras (when username `(:user ,username))) - (cname (format "%s %s:%s %s" mode host protocol extras)) + (cname (if username + (format "%s %s:%s %s" mode host protocol username) + (format "%s %s:%s" mode host protocol))) (search (list :host host :protocol protocol)) (search (if username (append search (list :user username)) search)) - (found (gethash cname auth-source-cache))) + (found (if (not delete-existing) + (gethash cname auth-source-cache) + (remhash cname auth-source-cache) + nil))) (if found (progn (auth-source-do-debug @@ -337,45 +414,35 @@ MODE can be \"login\" or \"password\" for example." (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found) - host protocol extras) + host protocol username) found) ; return the found data ;; else, if not found - (dolist (choice (apply 'auth-source-pick search)) - (setq found (cond - ;; the secrets.el spec - ((eq (plist-get choice :source) 'secrets) - (let ((coll (plist-get choice :search)) - (item (plist-get choice :item))) - (mapcar (lambda (m) - (if (equal "password" m) - (secrets-get-secret coll item) - ;; the user name is either - (or - ;; the secret's attribute :user, or - (secrets-get-attribute coll item :user) - ;; the originally requested :user - username - "unknown-user"))) - mode))) - (t ; anything else is netrc - (netrc-machine-user-or-password - mode - (plist-get choice :source) - (list host) - (list (format "%s" protocol)) - (auth-source-protocol-defaults protocol))))) + (let ((choices (apply 'auth-source-pick search))) + (dolist (choice choices) + (if delete-existing + (apply 'auth-source-delete choice search) + (setq found (apply 'auth-source-retrieve mode choice search))) + (and found (return found))) + + ;; We haven't found something, so we will create it interactively. + (when (and (not found) choices create-missing) + (setq found (apply 'auth-source-create mode (car choices) search))) + + ;; Cache the result. (when found (auth-source-do-debug "auth-source-user-or-password: found %s=%s for %s (%s) + %s" mode ;; don't show the password - (if (and (member "password" mode) auth-source-hide-passwords) "SECRET" found) - host protocol extras) + (if (and (member "password" mode) auth-source-hide-passwords) + "SECRET" found) + host protocol username) (setq found (if listy found (car-safe found))) (when auth-source-do-cache (puthash cname found auth-source-cache))) - (return found))))) - + + found)))) + (defun auth-source-protocol-defaults (protocol) "Return a list of default ports and names for PROTOCOL." (cdr-safe (assoc protocol auth-source-protocols))) |