* 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.
(item (plist-get artificial :label))
(secret (plist-get artificial :secret))
(secret (if (functionp secret) (funcall secret) secret)))
- (lambda () (apply 'secrets-create-item collection item secret args))))
+ (lambda ()
+ (apply 'auth-source-secrets-saver collection item secret args))))
(list artificial)))
+(defun auth-source-secrets-saver (collection item secret args)
+ "Wrapper around `secrets-create-item', prompting along the way.
+Respects `auth-source-save-behavior'."
+ (let ((prompt (format "Save auth info to secrets collection %s? " collection))
+ (done (not (eq auth-source-save-behavior 'ask)))
+ (bufname "*auth-source Help*")
+ doit k)
+ (while (not done)
+ (setq k (auth-source-read-char-choice prompt '(?y ?n ?N ??)))
+ (cl-case k
+ (?y (setq done t doit t))
+ (?? (save-excursion
+ (with-output-to-temp-buffer bufname
+ (princ
+ (concat "(y)es, save\n"
+ "(n)o but use the info\n"
+ "(N)o and don't ask to save again\n"
+ "(?) for help as you can see.\n"))
+ ;; Why? Doesn't with-output-to-temp-buffer already do
+ ;; the exact same thing anyway? --Stef
+ (set-buffer standard-output)
+ (help-mode))))
+ (?n (setq done t doit nil))
+ (?N (setq done t doit nil)
+ (customize-save-variable 'auth-source-save-behavior nil))
+ (t nil)))
+
+ (when doit
+ (progn
+ (auth-source-do-debug
+ "secrets-create-item: wrote 1 new item to %s" collection)
+ (message "Saved new authentication information to %s" collection)
+ (apply 'secrets-create-item collection item secret args)))))
+
;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend
(cl-defun auth-source-macos-keychain-search (&rest spec
;; Properties.
`(:array
(:dict-entry ,(concat secrets-interface-item ".Label")
- (:variant "dummy"))
- (:dict-entry ,(concat secrets-interface-item ".Type")
- (:variant ,secrets-interface-item-type-generic)))
+ (:variant " ")))
;; Secret.
`(:struct :object-path ,path
(:array :signature "y")
(secrets-create-item \"Tramp collection\" \"item\" \"geheim\"
:method \"sudo\" :user \"joe\" :host \"remote-host\")
+The key `:xdg:schema' determines the scope of the item to be
+generated, i.e. for which applications the item is intended for.
+This is just a string like \"org.freedesktop.NetworkManager.Mobile\"
+or \"org.gnome.OnlineAccounts\", the other required keys are
+determined by this. If no `:xdg:schema' is given,
+\"org.freedesktop.Secret.Generic\" is used by default.
+
The object path of the created item is returned."
(unless (member item (secrets-list-items collection))
(let ((collection-path (secrets-unlock-collection collection))
result props)
(unless (secrets-empty-path collection-path)
+ ;; Set default type if needed.
+ (unless (member :xdg:schema attributes)
+ (setq attributes
+ (append
+ attributes
+ `(:xdg:schema ,secrets-interface-item-type-generic))))
;; Create attributes list.
(while (consp (cdr attributes))
(unless (keywordp (car attributes))
(append
`(:array
(:dict-entry ,(concat secrets-interface-item ".Label")
- (:variant ,item))
- (:dict-entry ,(concat secrets-interface-item ".Type")
- (:variant ,secrets-interface-item-type-generic)))
+ (:variant ,item)))
(when props
`((:dict-entry ,(concat secrets-interface-item ".Attributes")
(:variant ,(append '(:array) props))))))
"Submit a bug report to the Tramp developers."
(interactive)
(catch 'dont-send
- (let ((reporter-prompt-for-summary-p t))
+ (let ((reporter-prompt-for-summary-p t)
+ ;; In rare cases, it could contain the password. So we make it nil.
+ tramp-password-save-function)
(reporter-submit-bug-report
tramp-bug-report-address ; to-address
(format "tramp (%s)" tramp-version) ; package name and version
(tramp-get-file-property vec "/" "fuse-mountpoint" "") "/")
(tramp-error vec 'file-error "FUSE mount denied"))
+ ;; Save the password.
+ (ignore-errors (funcall tramp-password-save-function))
+
;; Set connection-local variables.
(tramp-set-connection-local-variables vec)
(defvar tramp-current-connection nil
"Last connection timestamp.")
+(defvar tramp-password-save-function nil
+ "Password save function.
+Will be called once the password has been verified by successful
+authentication.")
+
(defconst tramp-completion-file-name-handler-alist
'((file-name-all-completions
. tramp-completion-handle-file-name-all-completions)
(with-current-buffer (tramp-get-connection-buffer vec)
(widen)
(tramp-message vec 6 "\n%s" (buffer-string)))
- (unless (eq exit 'ok)
+ (if (eq exit 'ok)
+ (ignore-errors (funcall tramp-password-save-function))
+ ;; Not successful.
(tramp-clear-passwd vec)
(delete-process proc)
(tramp-error-with-buffer
(with-current-buffer (process-buffer proc)
(tramp-check-for-regexp proc tramp-password-prompt-regexp)
(format "%s for %s " (capitalize (match-string 1)) key))))
+ (auth-source-creation-prompts `((secret . ,pw-prompt)))
;; We suspend the timers while reading the password.
(stimers (with-timeout-suspend))
auth-info auth-passwd)
(unwind-protect
(with-parsed-tramp-file-name key nil
+ (setq tramp-password-save-function nil)
(setq user
(or user (tramp-get-connection-property key "login-as" nil)))
(prog1
v "first-password-request" nil)
;; Try with Tramp's current method.
(setq auth-info
- (auth-source-search
- :max 1
- (and user :user)
- (if domain
- (concat user tramp-prefix-domain-format domain)
- user)
- :host
- (if port
- (concat host tramp-prefix-port-format port)
- host)
- :port method
- :require (cons :secret (and user '(:user))))
- auth-passwd (plist-get
- (nth 0 auth-info) :secret)
+ (car
+ (auth-source-search
+ :max 1
+ (and user :user)
+ (if domain
+ (concat
+ user tramp-prefix-domain-format domain)
+ user)
+ :host
+ (if port
+ (concat
+ host tramp-prefix-port-format port)
+ host)
+ :port method
+ :require (cons :secret (and user '(:user)))
+ :create t))
+ tramp-password-save-function
+ (plist-get auth-info :save-function)
+ auth-passwd (plist-get auth-info :secret)
auth-passwd (if (functionp auth-passwd)
(funcall auth-passwd)
auth-passwd))))
+
;; Try the password cache.
(let ((password (password-read pw-prompt key)))
- ;; FIXME test password works before caching it.
- (password-cache-add key password)
+ (setq tramp-password-save-function
+ (lambda () (password-cache-add key password)))
password)
;; Else, get the password interactively.
(read-passwd pw-prompt))
(tramp-set-connection-property v "first-password-request" nil)))
+
;; Reenable the timers.
(with-timeout-unsuspend stimers))))
(should
(equal
(secrets-get-attributes "session" "bar")
- '((:host . "remote-host") (:user . "joe")
- (:method . "sudo")
- (:xdg:schema . "org.freedesktop.Secret.Generic"))))
+ '((:xdg:schema . "org.freedesktop.Secret.Generic")
+ (:host . "remote-host") (:user . "joe") (:method . "sudo"))))
+
+ ;; Create an item with another schema.
+ (secrets-create-item
+ "session" "baz" "secret" :xdg:schema "org.gnu.Emacs.foo")
+ (should
+ (equal
+ (secrets-get-attributes "session" "baz")
+ '((:xdg:schema . "org.gnu.Emacs.foo"))))
;; Delete them.
(dolist (item (secrets-list-items "session"))
;; Search the items.
(should-not (secrets-search-items "session" :user "john"))
+ (should-not
+ (secrets-search-items "session" :xdg:schema "org.gnu.Emacs.foo"))
(should
(equal
(sort (secrets-search-items "session" :user "joe") 'string-lessp)