Fix Bug#30246

* lisp/auth-source.el (auth-source-secrets-search): Do not
suppress creation.
(auth-source-secrets-create): Implement it.  (Bug#30246)

* lisp/net/secrets.el (secrets-debug): Set default to nil.

* test/lisp/auth-source-tests.el (secrets): Require it.
(auth-source-test-secrets-create-secret): New test.
This commit is contained in:
Michael Albinus 2018-04-13 15:21:24 +02:00
parent 9822a6a570
commit 1f31c1348c
3 changed files with 192 additions and 11 deletions

View file

@ -1514,9 +1514,6 @@ authentication tokens:
"
;; TODO
(cl-assert (not create) nil
"The Secrets API auth-source backend doesn't support creation yet")
;; TODO
;; (secrets-delete-item coll elt)
(cl-assert (not delete) nil
"The Secrets API auth-source backend doesn't support deletion yet")
@ -1576,12 +1573,168 @@ authentication tokens:
returned-keys))
plist))
items)))
(cond
;; if we need to create an entry AND none were found to match
((and create
(not items))
;; create based on the spec and record the value
(setq items (or
;; if the user did not want to create the entry
;; in the file, it will be returned
(apply (slot-value backend 'create-function) spec)
;; if not, we do the search again without :create
;; to get the updated data.
;; the result will be returned, even if the search fails
(apply #'auth-source-secrets-search
(plist-put spec :create nil))))))
items))
(defun auth-source-secrets-create (&rest spec)
;; TODO
;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec)
(debug spec))
(cl-defun auth-source-secrets-create (&rest spec
&key backend host port create
&allow-other-keys)
(let* ((base-required '(host user port secret label))
;; we know (because of an assertion in auth-source-search) that the
;; :create parameter is either t or a list (which includes nil)
(create-extra (if (eq t create) nil create))
(current-data (car (auth-source-search :max 1
:host host
:port port)))
(required (append base-required create-extra))
(collection (oref backend source))
;; `args' are the arguments for `secrets-create-item'.
args
;; `valist' is an alist
valist
;; `artificial' will be returned if no creation is needed
artificial)
;; only for base required elements (defined as function parameters):
;; fill in the valist with whatever data we may have from the search
;; we complete the first value if it's a list and use the value otherwise
(dolist (br base-required)
(let ((val (plist-get spec (auth-source--symbol-keyword br))))
(when val
(let ((br-choice (cond
;; all-accepting choice (predicate is t)
((eq t val) nil)
;; just the value otherwise
(t val))))
(when br-choice
(auth-source--aput valist br br-choice))))))
;; for extra required elements, see if the spec includes a value for them
(dolist (er create-extra)
(let ((k (auth-source--symbol-keyword er))
(keys (cl-loop for i below (length spec) by 2
collect (nth i spec))))
(when (memq k keys)
(auth-source--aput valist er (plist-get spec k)))))
;; for each required element
(dolist (r required)
(let* ((data (auth-source--aget valist r))
;; take the first element if the data is a list
(data (or (auth-source-netrc-element-or-first data)
(plist-get current-data
(auth-source--symbol-keyword r))))
;; this is the default to be offered
(given-default (auth-source--aget
auth-source-creation-defaults r))
;; the default supplementals are simple:
;; for the user, try `given-default' and then (user-login-name);
;; for the label, try `given-default' and then user@host;
;; otherwise take `given-default'
(default (cond
((and (not given-default) (eq r 'user))
(user-login-name))
((and (not given-default) (eq r 'label))
(format "%s@%s"
(or (auth-source-netrc-element-or-first
(auth-source--aget valist 'user))
(plist-get artificial :user))
(or (auth-source-netrc-element-or-first
(auth-source--aget valist 'host))
(plist-get artificial :host))))
(t given-default)))
(printable-defaults (list
(cons 'user
(or
(auth-source-netrc-element-or-first
(auth-source--aget valist 'user))
(plist-get artificial :user)
"[any user]"))
(cons 'host
(or
(auth-source-netrc-element-or-first
(auth-source--aget valist 'host))
(plist-get artificial :host)
"[any host]"))
(cons 'port
(or
(auth-source-netrc-element-or-first
(auth-source--aget valist 'port))
(plist-get artificial :port)
"[any port]"))
(cons 'label
(or
(auth-source-netrc-element-or-first
(auth-source--aget valist 'label))
(plist-get artificial :label)
"[any label]"))))
(prompt (or (auth-source--aget auth-source-creation-prompts r)
(cl-case r
(secret "%p password for %u@%h: ")
(user "%p user name for %h: ")
(host "%p host name for user %u: ")
(port "%p port for %u@%h: ")
(label "Enter label for %u@%h: "))
(format "Enter %s (%%u@%%h:%%p): " r)))
(prompt (auth-source-format-prompt
prompt
`((?u ,(auth-source--aget printable-defaults 'user))
(?h ,(auth-source--aget printable-defaults 'host))
(?p ,(auth-source--aget printable-defaults 'port))))))
;; Store the data, prompting for the password if needed.
(setq data (or data
(if (eq r 'secret)
(or (eval default) (read-passwd prompt))
(if (stringp default)
(read-string (if (string-match ": *\\'" prompt)
(concat (substring prompt 0 (match-beginning 0))
" (default " default "): ")
(concat prompt "(default " default ") "))
nil nil default)
(eval default)))))
(when data
(setq artificial (plist-put artificial
(auth-source--symbol-keyword r)
(if (eq r 'secret)
(let ((data data))
(lambda () data))
data))))
;; When r is not an empty string...
(when (and (stringp data)
(< 0 (length data))
(not (member r '(secret label))))
;; append the key (the symbol name of r)
;; and the value in r
(setq args (append args (list (auth-source--symbol-keyword r) data))))))
(plist-put
artificial
:save-function
(let* ((collection collection)
(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))))
(list artificial)))
;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend

View file

@ -158,7 +158,7 @@
(defvar secrets-enabled nil
"Whether there is a daemon offering the Secret Service API.")
(defvar secrets-debug t
(defvar secrets-debug nil
"Write debug messages")
(defconst secrets-service "org.freedesktop.secrets"

View file

@ -29,9 +29,7 @@
(require 'ert)
(require 'cl-lib)
(require 'auth-source)
(defvar secrets-enabled t
"Enable the secrets backend to test its features.")
(require 'secrets)
(defun auth-source-ensure-ignored-backend (source)
(auth-source-validate-backend source '((:source . "")
@ -289,5 +287,35 @@
(should (equal found-as-string (concat testname ": " needed)))))
(delete-file netrc-file)))
(ert-deftest auth-source-test-secrets-create-secret ()
(skip-unless secrets-enabled)
;; The "session" collection is temporary for the lifetime of the
;; Emacs process. Therefore, we don't care to delete it.
(let ((auth-sources '((:source (:secrets "session"))))
(host (md5 (concat (prin1-to-string process-environment)
(current-time-string))))
(passwd (md5 (concat (prin1-to-string process-environment)
(current-time-string) (current-time-string))))
auth-info auth-passwd)
;; Redefine `read-*' in order to avoid interactive input.
(cl-letf (((symbol-function 'read-passwd) (lambda (_) passwd))
((symbol-function 'read-string)
(lambda (_prompt _initial _history default) default)))
(setq auth-info
(car (auth-source-search
:max 1 :host host :require '(:user :secret) :create t))))
(should (functionp (plist-get auth-info :save-function)))
(funcall (plist-get auth-info :save-function))
;; Check, that the item has been created indeed.
(auth-source-forget+ :host t)
(setq auth-info (car (auth-source-search :host host))
auth-passwd (plist-get auth-info :secret)
auth-passwd (if (functionp auth-passwd)
(funcall auth-passwd)
auth-passwd))
(should (string-equal (plist-get auth-info :user) (user-login-name)))
(should (string-equal auth-passwd passwd))))
(provide 'auth-source-tests)
;;; auth-source-tests.el ends here