diff options
| author | Michael Albinus | 2018-04-13 15:21:24 +0200 |
|---|---|---|
| committer | Michael Albinus | 2018-04-13 15:21:24 +0200 |
| commit | 1f31c1348c4ddec31664e78f8cf4b9514d2a32c6 (patch) | |
| tree | 1b9ab34b0ff5cb0b24ebe7f0355fd2d18aac517e | |
| parent | 9822a6a5708227897432f47d3f676c646b7bd4b2 (diff) | |
| download | emacs-1f31c1348c4ddec31664e78f8cf4b9514d2a32c6.tar.gz emacs-1f31c1348c4ddec31664e78f8cf4b9514d2a32c6.zip | |
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.
| -rw-r--r-- | lisp/auth-source.el | 167 | ||||
| -rw-r--r-- | lisp/net/secrets.el | 2 | ||||
| -rw-r--r-- | test/lisp/auth-source-tests.el | 34 |
3 files changed, 192 insertions, 11 deletions
diff --git a/lisp/auth-source.el b/lisp/auth-source.el index 355c11fbf3a..a2ed47a0d45 100644 --- a/lisp/auth-source.el +++ b/lisp/auth-source.el | |||
| @@ -1514,9 +1514,6 @@ authentication tokens: | |||
| 1514 | " | 1514 | " |
| 1515 | 1515 | ||
| 1516 | ;; TODO | 1516 | ;; TODO |
| 1517 | (cl-assert (not create) nil | ||
| 1518 | "The Secrets API auth-source backend doesn't support creation yet") | ||
| 1519 | ;; TODO | ||
| 1520 | ;; (secrets-delete-item coll elt) | 1517 | ;; (secrets-delete-item coll elt) |
| 1521 | (cl-assert (not delete) nil | 1518 | (cl-assert (not delete) nil |
| 1522 | "The Secrets API auth-source backend doesn't support deletion yet") | 1519 | "The Secrets API auth-source backend doesn't support deletion yet") |
| @@ -1576,12 +1573,168 @@ authentication tokens: | |||
| 1576 | returned-keys)) | 1573 | returned-keys)) |
| 1577 | plist)) | 1574 | plist)) |
| 1578 | items))) | 1575 | items))) |
| 1576 | (cond | ||
| 1577 | ;; if we need to create an entry AND none were found to match | ||
| 1578 | ((and create | ||
| 1579 | (not items)) | ||
| 1580 | |||
| 1581 | ;; create based on the spec and record the value | ||
| 1582 | (setq items (or | ||
| 1583 | ;; if the user did not want to create the entry | ||
| 1584 | ;; in the file, it will be returned | ||
| 1585 | (apply (slot-value backend 'create-function) spec) | ||
| 1586 | ;; if not, we do the search again without :create | ||
| 1587 | ;; to get the updated data. | ||
| 1588 | |||
| 1589 | ;; the result will be returned, even if the search fails | ||
| 1590 | (apply #'auth-source-secrets-search | ||
| 1591 | (plist-put spec :create nil)))))) | ||
| 1579 | items)) | 1592 | items)) |
| 1580 | 1593 | ||
| 1581 | (defun auth-source-secrets-create (&rest spec) | 1594 | (cl-defun auth-source-secrets-create (&rest spec |
| 1582 | ;; TODO | 1595 | &key backend host port create |
| 1583 | ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec) | 1596 | &allow-other-keys) |
| 1584 | (debug spec)) | 1597 | (let* ((base-required '(host user port secret label)) |
| 1598 | ;; we know (because of an assertion in auth-source-search) that the | ||
| 1599 | ;; :create parameter is either t or a list (which includes nil) | ||
| 1600 | (create-extra (if (eq t create) nil create)) | ||
| 1601 | (current-data (car (auth-source-search :max 1 | ||
| 1602 | :host host | ||
| 1603 | :port port))) | ||
| 1604 | (required (append base-required create-extra)) | ||
| 1605 | (collection (oref backend source)) | ||
| 1606 | ;; `args' are the arguments for `secrets-create-item'. | ||
| 1607 | args | ||
| 1608 | ;; `valist' is an alist | ||
| 1609 | valist | ||
| 1610 | ;; `artificial' will be returned if no creation is needed | ||
| 1611 | artificial) | ||
| 1612 | |||
| 1613 | ;; only for base required elements (defined as function parameters): | ||
| 1614 | ;; fill in the valist with whatever data we may have from the search | ||
| 1615 | ;; we complete the first value if it's a list and use the value otherwise | ||
| 1616 | (dolist (br base-required) | ||
| 1617 | (let ((val (plist-get spec (auth-source--symbol-keyword br)))) | ||
| 1618 | (when val | ||
| 1619 | (let ((br-choice (cond | ||
| 1620 | ;; all-accepting choice (predicate is t) | ||
| 1621 | ((eq t val) nil) | ||
| 1622 | ;; just the value otherwise | ||
| 1623 | (t val)))) | ||
| 1624 | (when br-choice | ||
| 1625 | (auth-source--aput valist br br-choice)))))) | ||
| 1626 | |||
| 1627 | ;; for extra required elements, see if the spec includes a value for them | ||
| 1628 | (dolist (er create-extra) | ||
| 1629 | (let ((k (auth-source--symbol-keyword er)) | ||
| 1630 | (keys (cl-loop for i below (length spec) by 2 | ||
| 1631 | collect (nth i spec)))) | ||
| 1632 | (when (memq k keys) | ||
| 1633 | (auth-source--aput valist er (plist-get spec k))))) | ||
| 1634 | |||
| 1635 | ;; for each required element | ||
| 1636 | (dolist (r required) | ||
| 1637 | (let* ((data (auth-source--aget valist r)) | ||
| 1638 | ;; take the first element if the data is a list | ||
| 1639 | (data (or (auth-source-netrc-element-or-first data) | ||
| 1640 | (plist-get current-data | ||
| 1641 | (auth-source--symbol-keyword r)))) | ||
| 1642 | ;; this is the default to be offered | ||
| 1643 | (given-default (auth-source--aget | ||
| 1644 | auth-source-creation-defaults r)) | ||
| 1645 | ;; the default supplementals are simple: | ||
| 1646 | ;; for the user, try `given-default' and then (user-login-name); | ||
| 1647 | ;; for the label, try `given-default' and then user@host; | ||
| 1648 | ;; otherwise take `given-default' | ||
| 1649 | (default (cond | ||
| 1650 | ((and (not given-default) (eq r 'user)) | ||
| 1651 | (user-login-name)) | ||
| 1652 | ((and (not given-default) (eq r 'label)) | ||
| 1653 | (format "%s@%s" | ||
| 1654 | (or (auth-source-netrc-element-or-first | ||
| 1655 | (auth-source--aget valist 'user)) | ||
| 1656 | (plist-get artificial :user)) | ||
| 1657 | (or (auth-source-netrc-element-or-first | ||
| 1658 | (auth-source--aget valist 'host)) | ||
| 1659 | (plist-get artificial :host)))) | ||
| 1660 | (t given-default))) | ||
| 1661 | (printable-defaults (list | ||
| 1662 | (cons 'user | ||
| 1663 | (or | ||
| 1664 | (auth-source-netrc-element-or-first | ||
| 1665 | (auth-source--aget valist 'user)) | ||
| 1666 | (plist-get artificial :user) | ||
| 1667 | "[any user]")) | ||
| 1668 | (cons 'host | ||
| 1669 | (or | ||
| 1670 | (auth-source-netrc-element-or-first | ||
| 1671 | (auth-source--aget valist 'host)) | ||
| 1672 | (plist-get artificial :host) | ||
| 1673 | "[any host]")) | ||
| 1674 | (cons 'port | ||
| 1675 | (or | ||
| 1676 | (auth-source-netrc-element-or-first | ||
| 1677 | (auth-source--aget valist 'port)) | ||
| 1678 | (plist-get artificial :port) | ||
| 1679 | "[any port]")) | ||
| 1680 | (cons 'label | ||
| 1681 | (or | ||
| 1682 | (auth-source-netrc-element-or-first | ||
| 1683 | (auth-source--aget valist 'label)) | ||
| 1684 | (plist-get artificial :label) | ||
| 1685 | "[any label]")))) | ||
| 1686 | (prompt (or (auth-source--aget auth-source-creation-prompts r) | ||
| 1687 | (cl-case r | ||
| 1688 | (secret "%p password for %u@%h: ") | ||
| 1689 | (user "%p user name for %h: ") | ||
| 1690 | (host "%p host name for user %u: ") | ||
| 1691 | (port "%p port for %u@%h: ") | ||
| 1692 | (label "Enter label for %u@%h: ")) | ||
| 1693 | (format "Enter %s (%%u@%%h:%%p): " r))) | ||
| 1694 | (prompt (auth-source-format-prompt | ||
| 1695 | prompt | ||
| 1696 | `((?u ,(auth-source--aget printable-defaults 'user)) | ||
| 1697 | (?h ,(auth-source--aget printable-defaults 'host)) | ||
| 1698 | (?p ,(auth-source--aget printable-defaults 'port)))))) | ||
| 1699 | |||
| 1700 | ;; Store the data, prompting for the password if needed. | ||
| 1701 | (setq data (or data | ||
| 1702 | (if (eq r 'secret) | ||
| 1703 | (or (eval default) (read-passwd prompt)) | ||
| 1704 | (if (stringp default) | ||
| 1705 | (read-string (if (string-match ": *\\'" prompt) | ||
| 1706 | (concat (substring prompt 0 (match-beginning 0)) | ||
| 1707 | " (default " default "): ") | ||
| 1708 | (concat prompt "(default " default ") ")) | ||
| 1709 | nil nil default) | ||
| 1710 | (eval default))))) | ||
| 1711 | |||
| 1712 | (when data | ||
| 1713 | (setq artificial (plist-put artificial | ||
| 1714 | (auth-source--symbol-keyword r) | ||
| 1715 | (if (eq r 'secret) | ||
| 1716 | (let ((data data)) | ||
| 1717 | (lambda () data)) | ||
| 1718 | data)))) | ||
| 1719 | |||
| 1720 | ;; When r is not an empty string... | ||
| 1721 | (when (and (stringp data) | ||
| 1722 | (< 0 (length data)) | ||
| 1723 | (not (member r '(secret label)))) | ||
| 1724 | ;; append the key (the symbol name of r) | ||
| 1725 | ;; and the value in r | ||
| 1726 | (setq args (append args (list (auth-source--symbol-keyword r) data)))))) | ||
| 1727 | |||
| 1728 | (plist-put | ||
| 1729 | artificial | ||
| 1730 | :save-function | ||
| 1731 | (let* ((collection collection) | ||
| 1732 | (item (plist-get artificial :label)) | ||
| 1733 | (secret (plist-get artificial :secret)) | ||
| 1734 | (secret (if (functionp secret) (funcall secret) secret))) | ||
| 1735 | (lambda () (apply 'secrets-create-item collection item secret args)))) | ||
| 1736 | |||
| 1737 | (list artificial))) | ||
| 1585 | 1738 | ||
| 1586 | ;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend | 1739 | ;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend |
| 1587 | 1740 | ||
diff --git a/lisp/net/secrets.el b/lisp/net/secrets.el index e5ab5b31ab0..8070ccf96e2 100644 --- a/lisp/net/secrets.el +++ b/lisp/net/secrets.el | |||
| @@ -158,7 +158,7 @@ | |||
| 158 | (defvar secrets-enabled nil | 158 | (defvar secrets-enabled nil |
| 159 | "Whether there is a daemon offering the Secret Service API.") | 159 | "Whether there is a daemon offering the Secret Service API.") |
| 160 | 160 | ||
| 161 | (defvar secrets-debug t | 161 | (defvar secrets-debug nil |
| 162 | "Write debug messages") | 162 | "Write debug messages") |
| 163 | 163 | ||
| 164 | (defconst secrets-service "org.freedesktop.secrets" | 164 | (defconst secrets-service "org.freedesktop.secrets" |
diff --git a/test/lisp/auth-source-tests.el b/test/lisp/auth-source-tests.el index eb93f7488e4..2f5a9320b17 100644 --- a/test/lisp/auth-source-tests.el +++ b/test/lisp/auth-source-tests.el | |||
| @@ -29,9 +29,7 @@ | |||
| 29 | (require 'ert) | 29 | (require 'ert) |
| 30 | (require 'cl-lib) | 30 | (require 'cl-lib) |
| 31 | (require 'auth-source) | 31 | (require 'auth-source) |
| 32 | 32 | (require 'secrets) | |
| 33 | (defvar secrets-enabled t | ||
| 34 | "Enable the secrets backend to test its features.") | ||
| 35 | 33 | ||
| 36 | (defun auth-source-ensure-ignored-backend (source) | 34 | (defun auth-source-ensure-ignored-backend (source) |
| 37 | (auth-source-validate-backend source '((:source . "") | 35 | (auth-source-validate-backend source '((:source . "") |
| @@ -289,5 +287,35 @@ | |||
| 289 | (should (equal found-as-string (concat testname ": " needed))))) | 287 | (should (equal found-as-string (concat testname ": " needed))))) |
| 290 | (delete-file netrc-file))) | 288 | (delete-file netrc-file))) |
| 291 | 289 | ||
| 290 | (ert-deftest auth-source-test-secrets-create-secret () | ||
| 291 | (skip-unless secrets-enabled) | ||
| 292 | ;; The "session" collection is temporary for the lifetime of the | ||
| 293 | ;; Emacs process. Therefore, we don't care to delete it. | ||
| 294 | (let ((auth-sources '((:source (:secrets "session")))) | ||
| 295 | (host (md5 (concat (prin1-to-string process-environment) | ||
| 296 | (current-time-string)))) | ||
| 297 | (passwd (md5 (concat (prin1-to-string process-environment) | ||
| 298 | (current-time-string) (current-time-string)))) | ||
| 299 | auth-info auth-passwd) | ||
| 300 | ;; Redefine `read-*' in order to avoid interactive input. | ||
| 301 | (cl-letf (((symbol-function 'read-passwd) (lambda (_) passwd)) | ||
| 302 | ((symbol-function 'read-string) | ||
| 303 | (lambda (_prompt _initial _history default) default))) | ||
| 304 | (setq auth-info | ||
| 305 | (car (auth-source-search | ||
| 306 | :max 1 :host host :require '(:user :secret) :create t)))) | ||
| 307 | (should (functionp (plist-get auth-info :save-function))) | ||
| 308 | (funcall (plist-get auth-info :save-function)) | ||
| 309 | |||
| 310 | ;; Check, that the item has been created indeed. | ||
| 311 | (auth-source-forget+ :host t) | ||
| 312 | (setq auth-info (car (auth-source-search :host host)) | ||
| 313 | auth-passwd (plist-get auth-info :secret) | ||
| 314 | auth-passwd (if (functionp auth-passwd) | ||
| 315 | (funcall auth-passwd) | ||
| 316 | auth-passwd)) | ||
| 317 | (should (string-equal (plist-get auth-info :user) (user-login-name))) | ||
| 318 | (should (string-equal auth-passwd passwd)))) | ||
| 319 | |||
| 292 | (provide 'auth-source-tests) | 320 | (provide 'auth-source-tests) |
| 293 | ;;; auth-source-tests.el ends here | 321 | ;;; auth-source-tests.el ends here |