diff options
| author | Michael Albinus | 2018-04-23 10:16:06 +0200 |
|---|---|---|
| committer | Michael Albinus | 2018-04-23 10:16:06 +0200 |
| commit | 0ecc10a7771bf1f62d15b2e6c747bee9f7a557ff (patch) | |
| tree | 7547eb4f8b8282b88d811189b26fa6dc42d06d77 | |
| parent | e7044d294c1b1779b3124b27ba0f09b22b64df20 (diff) | |
| download | emacs-0ecc10a7771bf1f62d15b2e6c747bee9f7a557ff.tar.gz emacs-0ecc10a7771bf1f62d15b2e6c747bee9f7a557ff.zip | |
Let Tramp save passwords
* lisp/auth-source.el (auth-source-secrets-saver): New defun.
(auth-source-secrets-create): Use it.
* lisp/net/secrets.el (secrets-struct-secret-content-type):
(secrets-create-item): Do not hard-code :xdg:schema.
* lisp/net/tramp.el (tramp-password-save-function): New defvar.
(tramp-read-passwd): Set it properly.
(tramp-process-actions):
* lisp/net/tramp-gvfs.el (tramp-gvfs-maybe-open-connection):
Save password.
* lisp/net/tramp-cmds.el (tramp-bug): Don't report
`tramp-password-save-function'.
* test/lisp/net/secrets-tests.el (secrets-test03-items):
Extend test with another :xdg:schema.
| -rw-r--r-- | lisp/auth-source.el | 37 | ||||
| -rw-r--r-- | lisp/net/secrets.el | 21 | ||||
| -rw-r--r-- | lisp/net/tramp-cmds.el | 4 | ||||
| -rw-r--r-- | lisp/net/tramp-gvfs.el | 3 | ||||
| -rw-r--r-- | lisp/net/tramp.el | 50 | ||||
| -rw-r--r-- | test/lisp/net/secrets-tests.el | 15 |
6 files changed, 102 insertions, 28 deletions
diff --git a/lisp/auth-source.el b/lisp/auth-source.el index a2ed47a0d45..df3622a412a 100644 --- a/lisp/auth-source.el +++ b/lisp/auth-source.el | |||
| @@ -1732,10 +1732,45 @@ authentication tokens: | |||
| 1732 | (item (plist-get artificial :label)) | 1732 | (item (plist-get artificial :label)) |
| 1733 | (secret (plist-get artificial :secret)) | 1733 | (secret (plist-get artificial :secret)) |
| 1734 | (secret (if (functionp secret) (funcall secret) secret))) | 1734 | (secret (if (functionp secret) (funcall secret) secret))) |
| 1735 | (lambda () (apply 'secrets-create-item collection item secret args)))) | 1735 | (lambda () |
| 1736 | (apply 'auth-source-secrets-saver collection item secret args)))) | ||
| 1736 | 1737 | ||
| 1737 | (list artificial))) | 1738 | (list artificial))) |
| 1738 | 1739 | ||
| 1740 | (defun auth-source-secrets-saver (collection item secret args) | ||
| 1741 | "Wrapper around `secrets-create-item', prompting along the way. | ||
| 1742 | Respects `auth-source-save-behavior'." | ||
| 1743 | (let ((prompt (format "Save auth info to secrets collection %s? " collection)) | ||
| 1744 | (done (not (eq auth-source-save-behavior 'ask))) | ||
| 1745 | (bufname "*auth-source Help*") | ||
| 1746 | doit k) | ||
| 1747 | (while (not done) | ||
| 1748 | (setq k (auth-source-read-char-choice prompt '(?y ?n ?N ??))) | ||
| 1749 | (cl-case k | ||
| 1750 | (?y (setq done t doit t)) | ||
| 1751 | (?? (save-excursion | ||
| 1752 | (with-output-to-temp-buffer bufname | ||
| 1753 | (princ | ||
| 1754 | (concat "(y)es, save\n" | ||
| 1755 | "(n)o but use the info\n" | ||
| 1756 | "(N)o and don't ask to save again\n" | ||
| 1757 | "(?) for help as you can see.\n")) | ||
| 1758 | ;; Why? Doesn't with-output-to-temp-buffer already do | ||
| 1759 | ;; the exact same thing anyway? --Stef | ||
| 1760 | (set-buffer standard-output) | ||
| 1761 | (help-mode)))) | ||
| 1762 | (?n (setq done t doit nil)) | ||
| 1763 | (?N (setq done t doit nil) | ||
| 1764 | (customize-save-variable 'auth-source-save-behavior nil)) | ||
| 1765 | (t nil))) | ||
| 1766 | |||
| 1767 | (when doit | ||
| 1768 | (progn | ||
| 1769 | (auth-source-do-debug | ||
| 1770 | "secrets-create-item: wrote 1 new item to %s" collection) | ||
| 1771 | (message "Saved new authentication information to %s" collection) | ||
| 1772 | (apply 'secrets-create-item collection item secret args))))) | ||
| 1773 | |||
| 1739 | ;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend | 1774 | ;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend |
| 1740 | 1775 | ||
| 1741 | (cl-defun auth-source-macos-keychain-search (&rest spec | 1776 | (cl-defun auth-source-macos-keychain-search (&rest spec |
diff --git a/lisp/net/secrets.el b/lisp/net/secrets.el index 8070ccf96e2..f7cc011615e 100644 --- a/lisp/net/secrets.el +++ b/lisp/net/secrets.el | |||
| @@ -331,9 +331,7 @@ It returns t if not." | |||
| 331 | ;; Properties. | 331 | ;; Properties. |
| 332 | `(:array | 332 | `(:array |
| 333 | (:dict-entry ,(concat secrets-interface-item ".Label") | 333 | (:dict-entry ,(concat secrets-interface-item ".Label") |
| 334 | (:variant "dummy")) | 334 | (:variant " "))) |
| 335 | (:dict-entry ,(concat secrets-interface-item ".Type") | ||
| 336 | (:variant ,secrets-interface-item-type-generic))) | ||
| 337 | ;; Secret. | 335 | ;; Secret. |
| 338 | `(:struct :object-path ,path | 336 | `(:struct :object-path ,path |
| 339 | (:array :signature "y") | 337 | (:array :signature "y") |
| @@ -649,11 +647,24 @@ keys are keyword symbols, starting with a colon. Example: | |||
| 649 | (secrets-create-item \"Tramp collection\" \"item\" \"geheim\" | 647 | (secrets-create-item \"Tramp collection\" \"item\" \"geheim\" |
| 650 | :method \"sudo\" :user \"joe\" :host \"remote-host\") | 648 | :method \"sudo\" :user \"joe\" :host \"remote-host\") |
| 651 | 649 | ||
| 650 | The key `:xdg:schema' determines the scope of the item to be | ||
| 651 | generated, i.e. for which applications the item is intended for. | ||
| 652 | This is just a string like \"org.freedesktop.NetworkManager.Mobile\" | ||
| 653 | or \"org.gnome.OnlineAccounts\", the other required keys are | ||
| 654 | determined by this. If no `:xdg:schema' is given, | ||
| 655 | \"org.freedesktop.Secret.Generic\" is used by default. | ||
| 656 | |||
| 652 | The object path of the created item is returned." | 657 | The object path of the created item is returned." |
| 653 | (unless (member item (secrets-list-items collection)) | 658 | (unless (member item (secrets-list-items collection)) |
| 654 | (let ((collection-path (secrets-unlock-collection collection)) | 659 | (let ((collection-path (secrets-unlock-collection collection)) |
| 655 | result props) | 660 | result props) |
| 656 | (unless (secrets-empty-path collection-path) | 661 | (unless (secrets-empty-path collection-path) |
| 662 | ;; Set default type if needed. | ||
| 663 | (unless (member :xdg:schema attributes) | ||
| 664 | (setq attributes | ||
| 665 | (append | ||
| 666 | attributes | ||
| 667 | `(:xdg:schema ,secrets-interface-item-type-generic)))) | ||
| 657 | ;; Create attributes list. | 668 | ;; Create attributes list. |
| 658 | (while (consp (cdr attributes)) | 669 | (while (consp (cdr attributes)) |
| 659 | (unless (keywordp (car attributes)) | 670 | (unless (keywordp (car attributes)) |
| @@ -675,9 +686,7 @@ The object path of the created item is returned." | |||
| 675 | (append | 686 | (append |
| 676 | `(:array | 687 | `(:array |
| 677 | (:dict-entry ,(concat secrets-interface-item ".Label") | 688 | (:dict-entry ,(concat secrets-interface-item ".Label") |
| 678 | (:variant ,item)) | 689 | (:variant ,item))) |
| 679 | (:dict-entry ,(concat secrets-interface-item ".Type") | ||
| 680 | (:variant ,secrets-interface-item-type-generic))) | ||
| 681 | (when props | 690 | (when props |
| 682 | `((:dict-entry ,(concat secrets-interface-item ".Attributes") | 691 | `((:dict-entry ,(concat secrets-interface-item ".Attributes") |
| 683 | (:variant ,(append '(:array) props)))))) | 692 | (:variant ,(append '(:array) props)))))) |
diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el index cbb9cd37005..b05f475f2fd 100644 --- a/lisp/net/tramp-cmds.el +++ b/lisp/net/tramp-cmds.el | |||
| @@ -181,7 +181,9 @@ This includes password cache, file cache, connection cache, buffers." | |||
| 181 | "Submit a bug report to the Tramp developers." | 181 | "Submit a bug report to the Tramp developers." |
| 182 | (interactive) | 182 | (interactive) |
| 183 | (catch 'dont-send | 183 | (catch 'dont-send |
| 184 | (let ((reporter-prompt-for-summary-p t)) | 184 | (let ((reporter-prompt-for-summary-p t) |
| 185 | ;; In rare cases, it could contain the password. So we make it nil. | ||
| 186 | tramp-password-save-function) | ||
| 185 | (reporter-submit-bug-report | 187 | (reporter-submit-bug-report |
| 186 | tramp-bug-report-address ; to-address | 188 | tramp-bug-report-address ; to-address |
| 187 | (format "tramp (%s)" tramp-version) ; package name and version | 189 | (format "tramp (%s)" tramp-version) ; package name and version |
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el index b3d5339321b..199ac4fad24 100644 --- a/lisp/net/tramp-gvfs.el +++ b/lisp/net/tramp-gvfs.el | |||
| @@ -2041,6 +2041,9 @@ connection if a previous connection has died for some reason." | |||
| 2041 | (tramp-get-file-property vec "/" "fuse-mountpoint" "") "/") | 2041 | (tramp-get-file-property vec "/" "fuse-mountpoint" "") "/") |
| 2042 | (tramp-error vec 'file-error "FUSE mount denied")) | 2042 | (tramp-error vec 'file-error "FUSE mount denied")) |
| 2043 | 2043 | ||
| 2044 | ;; Save the password. | ||
| 2045 | (ignore-errors (funcall tramp-password-save-function)) | ||
| 2046 | |||
| 2044 | ;; Set connection-local variables. | 2047 | ;; Set connection-local variables. |
| 2045 | (tramp-set-connection-local-variables vec) | 2048 | (tramp-set-connection-local-variables vec) |
| 2046 | 2049 | ||
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 5c785b16d89..c394f28a561 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el | |||
| @@ -1192,6 +1192,11 @@ means to use always cached values for the directory contents." | |||
| 1192 | (defvar tramp-current-connection nil | 1192 | (defvar tramp-current-connection nil |
| 1193 | "Last connection timestamp.") | 1193 | "Last connection timestamp.") |
| 1194 | 1194 | ||
| 1195 | (defvar tramp-password-save-function nil | ||
| 1196 | "Password save function. | ||
| 1197 | Will be called once the password has been verified by successful | ||
| 1198 | authentication.") | ||
| 1199 | |||
| 1195 | (defconst tramp-completion-file-name-handler-alist | 1200 | (defconst tramp-completion-file-name-handler-alist |
| 1196 | '((file-name-all-completions | 1201 | '((file-name-all-completions |
| 1197 | . tramp-completion-handle-file-name-all-completions) | 1202 | . tramp-completion-handle-file-name-all-completions) |
| @@ -3852,7 +3857,9 @@ connection buffer." | |||
| 3852 | (with-current-buffer (tramp-get-connection-buffer vec) | 3857 | (with-current-buffer (tramp-get-connection-buffer vec) |
| 3853 | (widen) | 3858 | (widen) |
| 3854 | (tramp-message vec 6 "\n%s" (buffer-string))) | 3859 | (tramp-message vec 6 "\n%s" (buffer-string))) |
| 3855 | (unless (eq exit 'ok) | 3860 | (if (eq exit 'ok) |
| 3861 | (ignore-errors (funcall tramp-password-save-function)) | ||
| 3862 | ;; Not successful. | ||
| 3856 | (tramp-clear-passwd vec) | 3863 | (tramp-clear-passwd vec) |
| 3857 | (delete-process proc) | 3864 | (delete-process proc) |
| 3858 | (tramp-error-with-buffer | 3865 | (tramp-error-with-buffer |
| @@ -4458,12 +4465,14 @@ Invokes `password-read' if available, `read-passwd' else." | |||
| 4458 | (with-current-buffer (process-buffer proc) | 4465 | (with-current-buffer (process-buffer proc) |
| 4459 | (tramp-check-for-regexp proc tramp-password-prompt-regexp) | 4466 | (tramp-check-for-regexp proc tramp-password-prompt-regexp) |
| 4460 | (format "%s for %s " (capitalize (match-string 1)) key)))) | 4467 | (format "%s for %s " (capitalize (match-string 1)) key)))) |
| 4468 | (auth-source-creation-prompts `((secret . ,pw-prompt))) | ||
| 4461 | ;; We suspend the timers while reading the password. | 4469 | ;; We suspend the timers while reading the password. |
| 4462 | (stimers (with-timeout-suspend)) | 4470 | (stimers (with-timeout-suspend)) |
| 4463 | auth-info auth-passwd) | 4471 | auth-info auth-passwd) |
| 4464 | 4472 | ||
| 4465 | (unwind-protect | 4473 | (unwind-protect |
| 4466 | (with-parsed-tramp-file-name key nil | 4474 | (with-parsed-tramp-file-name key nil |
| 4475 | (setq tramp-password-save-function nil) | ||
| 4467 | (setq user | 4476 | (setq user |
| 4468 | (or user (tramp-get-connection-property key "login-as" nil))) | 4477 | (or user (tramp-get-connection-property key "login-as" nil))) |
| 4469 | (prog1 | 4478 | (prog1 |
| @@ -4474,31 +4483,38 @@ Invokes `password-read' if available, `read-passwd' else." | |||
| 4474 | v "first-password-request" nil) | 4483 | v "first-password-request" nil) |
| 4475 | ;; Try with Tramp's current method. | 4484 | ;; Try with Tramp's current method. |
| 4476 | (setq auth-info | 4485 | (setq auth-info |
| 4477 | (auth-source-search | 4486 | (car |
| 4478 | :max 1 | 4487 | (auth-source-search |
| 4479 | (and user :user) | 4488 | :max 1 |
| 4480 | (if domain | 4489 | (and user :user) |
| 4481 | (concat user tramp-prefix-domain-format domain) | 4490 | (if domain |
| 4482 | user) | 4491 | (concat |
| 4483 | :host | 4492 | user tramp-prefix-domain-format domain) |
| 4484 | (if port | 4493 | user) |
| 4485 | (concat host tramp-prefix-port-format port) | 4494 | :host |
| 4486 | host) | 4495 | (if port |
| 4487 | :port method | 4496 | (concat |
| 4488 | :require (cons :secret (and user '(:user)))) | 4497 | host tramp-prefix-port-format port) |
| 4489 | auth-passwd (plist-get | 4498 | host) |
| 4490 | (nth 0 auth-info) :secret) | 4499 | :port method |
| 4500 | :require (cons :secret (and user '(:user))) | ||
| 4501 | :create t)) | ||
| 4502 | tramp-password-save-function | ||
| 4503 | (plist-get auth-info :save-function) | ||
| 4504 | auth-passwd (plist-get auth-info :secret) | ||
| 4491 | auth-passwd (if (functionp auth-passwd) | 4505 | auth-passwd (if (functionp auth-passwd) |
| 4492 | (funcall auth-passwd) | 4506 | (funcall auth-passwd) |
| 4493 | auth-passwd)))) | 4507 | auth-passwd)))) |
| 4508 | |||
| 4494 | ;; Try the password cache. | 4509 | ;; Try the password cache. |
| 4495 | (let ((password (password-read pw-prompt key))) | 4510 | (let ((password (password-read pw-prompt key))) |
| 4496 | ;; FIXME test password works before caching it. | 4511 | (setq tramp-password-save-function |
| 4497 | (password-cache-add key password) | 4512 | (lambda () (password-cache-add key password))) |
| 4498 | password) | 4513 | password) |
| 4499 | ;; Else, get the password interactively. | 4514 | ;; Else, get the password interactively. |
| 4500 | (read-passwd pw-prompt)) | 4515 | (read-passwd pw-prompt)) |
| 4501 | (tramp-set-connection-property v "first-password-request" nil))) | 4516 | (tramp-set-connection-property v "first-password-request" nil))) |
| 4517 | |||
| 4502 | ;; Reenable the timers. | 4518 | ;; Reenable the timers. |
| 4503 | (with-timeout-unsuspend stimers)))) | 4519 | (with-timeout-unsuspend stimers)))) |
| 4504 | 4520 | ||
diff --git a/test/lisp/net/secrets-tests.el b/test/lisp/net/secrets-tests.el index dc9c7f1004a..23512d48ee5 100644 --- a/test/lisp/net/secrets-tests.el +++ b/test/lisp/net/secrets-tests.el | |||
| @@ -169,9 +169,16 @@ | |||
| 169 | (should | 169 | (should |
| 170 | (equal | 170 | (equal |
| 171 | (secrets-get-attributes "session" "bar") | 171 | (secrets-get-attributes "session" "bar") |
| 172 | '((:host . "remote-host") (:user . "joe") | 172 | '((:xdg:schema . "org.freedesktop.Secret.Generic") |
| 173 | (:method . "sudo") | 173 | (:host . "remote-host") (:user . "joe") (:method . "sudo")))) |
| 174 | (:xdg:schema . "org.freedesktop.Secret.Generic")))) | 174 | |
| 175 | ;; Create an item with another schema. | ||
| 176 | (secrets-create-item | ||
| 177 | "session" "baz" "secret" :xdg:schema "org.gnu.Emacs.foo") | ||
| 178 | (should | ||
| 179 | (equal | ||
| 180 | (secrets-get-attributes "session" "baz") | ||
| 181 | '((:xdg:schema . "org.gnu.Emacs.foo")))) | ||
| 175 | 182 | ||
| 176 | ;; Delete them. | 183 | ;; Delete them. |
| 177 | (dolist (item (secrets-list-items "session")) | 184 | (dolist (item (secrets-list-items "session")) |
| @@ -206,6 +213,8 @@ | |||
| 206 | 213 | ||
| 207 | ;; Search the items. | 214 | ;; Search the items. |
| 208 | (should-not (secrets-search-items "session" :user "john")) | 215 | (should-not (secrets-search-items "session" :user "john")) |
| 216 | (should-not | ||
| 217 | (secrets-search-items "session" :xdg:schema "org.gnu.Emacs.foo")) | ||
| 209 | (should | 218 | (should |
| 210 | (equal | 219 | (equal |
| 211 | (sort (secrets-search-items "session" :user "joe") 'string-lessp) | 220 | (sort (secrets-search-items "session" :user "joe") 'string-lessp) |