From 3a108277637fad89869d904eea963aeb8f45b20d Mon Sep 17 00:00:00 2001 From: Tino Calancha Date: Thu, 29 Aug 2019 05:18:06 +0200 Subject: Support extractions of secret fields other than password Allow users to retrieve any secret field stored in the files. Use auth-source-pass to retrieve the secret fields. Bump version to v2.1.0. * contrib/emacs/Cask: Replace dependency on `s' library with auth-source-pass dependency. * contrib/emacs/password-store.el (password-store-url-field): New option. (password-store-dir): Use `auth-source-pass-filename'. (password-store-read-field, password-store-get-field) (password-store-copy-field, password-store-parse-entry): New functions. (password-store-read-field): Use password-store-parse-entry. (password-store--save-field-in-kill-ring): New function extracted from `password-store-get'. (password-store-url): Use `password-store-get-field' and `password-store-url-field'. * contrib/emacs/README.md: Update documentation. * contrib/emacs/CHANGELOG.md: Announce changes. --- contrib/emacs/CHANGELOG.md | 12 ++++- contrib/emacs/Cask | 4 +- contrib/emacs/README.md | 20 ++++--- contrib/emacs/password-store.el | 117 ++++++++++++++++++++++++++++------------ 4 files changed, 109 insertions(+), 44 deletions(-) diff --git a/contrib/emacs/CHANGELOG.md b/contrib/emacs/CHANGELOG.md index 7149ef6..d4c865f 100644 --- a/contrib/emacs/CHANGELOG.md +++ b/contrib/emacs/CHANGELOG.md @@ -1,7 +1,15 @@ +# 2.1.0 + +* (feature) Support extraction of any secret fields stored in the files. + +* (feature) The library is now integrated with auth-source-pass; thus, the + filename of the password-store folder is set with the option + auth-source-pass-filename. + # 2.0.5 - + Improve password-store-insert message on success/failure - + # 2.0.4 * Re add password-store-timeout function to preserve backward diff --git a/contrib/emacs/Cask b/contrib/emacs/Cask index 474611f..f46b166 100644 --- a/contrib/emacs/Cask +++ b/contrib/emacs/Cask @@ -5,8 +5,8 @@ (development (depends-on "f") - (depends-on "s") (depends-on "with-editor") (depends-on "ecukes") (depends-on "ert-runner") - (depends-on "el-mock")) + (depends-on "el-mock") + (depends-on "auth-source-pass")) diff --git a/contrib/emacs/README.md b/contrib/emacs/README.md index f94b95a..d3679e9 100644 --- a/contrib/emacs/README.md +++ b/contrib/emacs/README.md @@ -15,16 +15,24 @@ website for instructions Interactive: M-x password-store-insert - Password entry: example + Password entry: foo-account Password: ........ Confirm password: ........ M-x password-store-copy - Password entry: example - Copied example to the kill ring. Will clear in 45 seconds. - Password cleared. + Password entry: foo-account + Copied password for foo-account to the kill ring. Will clear in 45 seconds. + Field password cleared. + + M-x password-store-copy-field + Password entry: foo-account + Field: username + Copied username for foo-account to the kill ring. Will clear in 45 seconds. + Field url cleared. + Lisp: - (password-store-insert "example" "password") - (password-store-get "example") ; Returns "password" + (password-store-insert "foo-account" "password") + (password-store-get "foo-account") ; Returns "password" + (password-store-get-field "foo-account" "url") ; Returns "url" diff --git a/contrib/emacs/password-store.el b/contrib/emacs/password-store.el index c4484d8..22c5444 100644 --- a/contrib/emacs/password-store.el +++ b/contrib/emacs/password-store.el @@ -4,9 +4,9 @@ ;; Author: Svend Sorensen ;; Maintainer: Tino Calancha -;; Version: 2.0.5 +;; Version: 2.1.0 ;; URL: https://www.passwordstore.org/ -;; Package-Requires: ((emacs "25") (f "0.11.0") (s "1.9.0") (with-editor "2.5.11")) +;; Package-Requires: ((emacs "25") (f "0.11.0") (s "1.9.0") (with-editor "2.5.11") (auth-source-pass "5.0.0")) ;; Keywords: tools pass password password-store ;; This file is not part of GNU Emacs. @@ -34,8 +34,8 @@ ;;; Code: (require 'f) -(require 's) (require 'with-editor) +(require 'auth-source-pass) (defgroup password-store '() "Emacs mode for password-store." @@ -55,6 +55,11 @@ :group 'password-store :type 'number) +(defcustom password-store-url-field "url" + "Field name used in the files to indicate an url." + :group 'password-store + :type 'string) + (defvar password-store-executable (executable-find "pass") "Pass executable.") @@ -176,7 +181,8 @@ Nil arguments are ignored. Output is discarded." (defun password-store-dir () "Return password store directory." - (or (getenv "PASSWORD_STORE_DIR") + (or auth-source-pass-filename + (getenv "PASSWORD_STORE_DIR") "~/.password-store")) (defun password-store--entry-to-file (entry) @@ -193,6 +199,18 @@ Nil arguments are ignored. Output is discarded." Require a matching password if `REQUIRE-MATCH' is 't'." (completing-read "Password entry: " (password-store-list) nil require-match)) +(defun password-store-parse-entry (entry) + "Return an alist of the data associated with ENTRY. + +ENTRY is the name of a password-store entry." + (auth-source-pass-parse-entry entry)) + +(defun password-store-read-field (entry) + "Read a field in the minibuffer, with completion for ENTRY." + (let* ((inhibit-message t) + (valid-fields (mapcar #'car (password-store-parse-entry entry)))) + (completing-read "Field: " valid-fields nil 'match))) + (defun password-store-list (&optional subdir) "List password entries under SUBDIR." (unless subdir (setq subdir "")) @@ -214,44 +232,79 @@ Require a matching password if `REQUIRE-MATCH' is 't'." Returns the first line of the password data. When CALLBACK is non-`NIL', call CALLBACK with the first line instead." - (if callback + (let* ((inhibit-message t) + (secret (auth-source-pass-get 'secret entry))) + (if (not callback) secret (password-store--run-show entry - (lambda (password) - (funcall callback (car (s-lines password))))) - (car (s-lines (password-store--run-show entry))))) + (lambda (_) (funcall callback secret)))))) ;;;###autoload -(defun password-store-clear () - "Clear password in kill ring." - (interactive) +(defun password-store-get-field (entry field &optional callback) + "Return FIELD for ENTRY. +FIELD is a string, for instance \"url\". +When CALLBACK is non-`NIL', call it with the line associated to FIELD instead. +If FIELD equals to symbol secret, then this function reduces to `password-store-get'." + (let* ((inhibit-message t) + (secret (auth-source-pass-get field entry))) + (if (not callback) secret + (password-store--run-show + entry + (lambda (_) (and secret (funcall callback secret))))))) + + +;;;###autoload +(defun password-store-clear (field) + "Clear FIELD in kill ring." + (interactive "i") + (unless field (setq field 'secret)) (when password-store-timeout-timer (cancel-timer password-store-timeout-timer) (setq password-store-timeout-timer nil)) (when password-store-kill-ring-pointer (setcar password-store-kill-ring-pointer "") (setq password-store-kill-ring-pointer nil) - (message "Password cleared."))) + (message "Field %s cleared." field))) + +(defun password-store--save-field-in-kill-ring (entry secret field) + (password-store-clear field) + (kill-new secret) + (setq password-store-kill-ring-pointer kill-ring-yank-pointer) + (message "Copied %s for %s to the kill ring. Will clear in %s seconds." + field entry password-store-time-before-clipboard-restore) + (setq password-store-timeout-timer + (run-at-time password-store-time-before-clipboard-restore nil + (lambda () (funcall #'password-store-clear field))))) ;;;###autoload (defun password-store-copy (entry) - "Add password for ENTRY to kill ring. + "Add password for ENTRY into the kill ring. -Clear previous password from kill ring. Pointer to kill ring is -stored in `password-store-kill-ring-pointer'. Password is cleared +Clear previous password from the kill ring. Pointer to the kill ring +is stored in `password-store-kill-ring-pointer'. Password is cleared after `password-store-time-before-clipboard-restore' seconds." (interactive (list (password-store--completing-read t))) (password-store-get entry (lambda (password) - (password-store-clear) - (kill-new password) - (setq password-store-kill-ring-pointer kill-ring-yank-pointer) - (message "Copied %s to the kill ring. Will clear in %s seconds." - entry password-store-time-before-clipboard-restore) - (setq password-store-timeout-timer - (run-at-time password-store-time-before-clipboard-restore - nil 'password-store-clear))))) + (password-store--save-field-in-kill-ring entry password 'secret)))) + +;;;###autoload +(defun password-store-copy-field (entry field) + "Add FIELD for ENTRY into the kill ring. + +Clear previous secret from the kill ring. Pointer to the kill ring is +stored in `password-store-kill-ring-pointer'. Secret field is cleared +after `password-store-timeout' seconds. +If FIELD equals to symbol secret, then this function reduces to `password-store-copy'." + (interactive + (let ((entry (password-store--completing-read))) + (list entry (password-store-read-field entry)))) + (password-store-get-field + entry + field + (lambda (secret-value) + (password-store--save-field-in-kill-ring entry secret-value field)))) ;;;###autoload (defun password-store-init (gpg-id) @@ -264,7 +317,7 @@ Separate multiple IDs with spaces." ;;;###autoload (defun password-store-insert (entry password) "Insert a new ENTRY containing PASSWORD." - (interactive (list (read-string "Password entry: ") + (interactive (list (password-store--completing-read) (read-passwd "Password: " t))) (let* ((command (format "echo %s | %s insert -m -f %s" (shell-quote-argument password) @@ -281,7 +334,7 @@ Separate multiple IDs with spaces." "Generate a new password for ENTRY with PASSWORD-LENGTH. Default PASSWORD-LENGTH is `password-store-password-length'." - (interactive (list (read-string "Password entry: ") + (interactive (list (password-store--completing-read) (when current-prefix-arg (abs (prefix-numeric-value current-prefix-arg))))) (unless password-length (setq password-length password-store-password-length)) @@ -311,16 +364,12 @@ Default PASSWORD-LENGTH is `password-store-password-length'." ;;;###autoload (defun password-store-url (entry) - "Browse URL stored in ENTRY. - -This will only browse URLs that start with http:// or https:// to -avoid sending a password to the browser." + "Browse URL stored in ENTRY." (interactive (list (password-store--completing-read t))) - (let ((url (password-store-get entry))) - (if (or (string-prefix-p "http://" url) - (string-prefix-p "https://" url)) - (browse-url url) - (error "%s" "String does not look like a URL")))) + (let ((url (password-store-get-field entry password-store-url-field))) + (if url (browse-url url) + (error "Field `%s' not found" password-store-url-field)))) + (provide 'password-store) -- cgit v1.2.3