Browse Source

bitwarden: add widget to display all passwords

Sean Farley 7 years ago
parent
commit
c95d9cfb5f
1 changed files with 192 additions and 0 deletions
  1. 192 0
      bitwarden.el

+ 192 - 0
bitwarden.el

@@ -331,6 +331,198 @@ Returns a vector of hashtables of the results."
              (json (json-read-from-string result)))
              (json (json-read-from-string result)))
            json))))
            json))))
 
 
+;================================= widget utils ================================
+
+;; bitwarden-list-dialog-mode
+(defvar bitwarden-list-dialog-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map widget-keymap)
+    (define-key map "n" 'widget-forward)
+    (define-key map "p" 'widget-backward)
+    (define-key map "r" 'bitwarden-list-all-reload)
+    ;; (define-key map "a" 'bitwarden-addpass)
+    (define-key map "s" 'bitwarden-list-all-getpass)
+    (define-key map "w" 'bitwarden-list-all-kill-ring-save)
+    ;; (define-key map "m" 'bitwarden-list-all-movepass)
+    ;; (define-key map "c" 'bitwarden-list-all-create-auth-source-account)
+    ;; (define-key map "d" 'bitwarden-list-all-deletepass)
+    (define-key map "q" 'bitwarden-list-cancel-dialog)
+    map)
+  "Keymap used in recentf dialogs.")
+
+(define-derived-mode bitwarden-list-dialog-mode nil "bitwarden-list-dialog"
+  "Major mode of recentf dialogs.
+
+\\{bitwarden-list-dialog-mode-map}"
+  :syntax-table nil
+  :abbrev-table nil
+  (setq truncate-lines t))
+
+(defsubst bitwarden-list-all-get-item-at-pos ()
+  "Get hashtable from widget at current pos in dialog widget."
+  (let ((widget (get-char-property (point) 'button)))
+    (widget-value widget)))
+
+(defsubst bitwarden-list-all-make-spaces (spaces)
+  "Create a string with SPACES number of whitespaces."
+  (mapconcat 'identity (make-list spaces " ") ""))
+
+(defsubst bitwarden-pad-to-width (item width)
+  "Create a string with ITEM padded to WIDTH."
+  (if (= (length item) width)
+      item
+    (if (>= (length item) width)
+        (concat (substring item 0 (- width 1)) "…")
+      (concat item (bitwarden-list-all-make-spaces (- width (length item)))))))
+
+;================================ widget actions ===============================
+
+;; Dialog settings and actions
+(defun bitwarden-list-cancel-dialog (&rest _ignore)
+  "Cancel the current dialog.
+IGNORE arguments."
+  (interactive)
+  (kill-buffer (current-buffer))
+  (bitwarden--message "dialog canceled" nil t))
+
+(defun bitwarden-list-all-kill-ring-save (&optional widget-item)
+  "Bitwarden `kill-ring-save', insert password to kill ring.
+
+If WIDGET-ITEM is not supplied then look for the widget at the
+current point."
+  (interactive)
+  (let* ((item (or widget-item
+                   (bitwarden-list-all-get-item-at-pos)))
+         (type (gethash "type" item))
+         (login (gethash "login" item)))
+    (if (not (eq type 1))
+        (bitwarden--message "error: not a login item" nil t)
+      (kill-new (gethash "password" login))
+      (message "Password added to kill ring"))))
+
+(defun bitwarden-list-all-item-action (widget &rest _ignore)
+  "Do action to element associated with WIDGET's value.
+IGNORE other arguments."
+  (bitwarden-list-all-kill-ring-save (widget-value widget))
+  (kill-buffer (current-buffer)))
+
+;=================================== widgets ===================================
+
+(defmacro bitwarden-list-dialog (name &rest forms)
+  "Show a dialog buffer with NAME, setup with FORMS."
+  (declare (indent 1) (debug t))
+  `(with-current-buffer (get-buffer-create ,name)
+     ;; Cleanup buffer
+     (let ((inhibit-read-only t)
+           (ol (overlay-lists)))
+       (mapc 'delete-overlay (car ol))
+       (mapc 'delete-overlay (cdr ol))
+       (erase-buffer))
+     (bitwarden-list-dialog-mode)
+     ,@forms
+     (widget-setup)
+     (switch-to-buffer (current-buffer))))
+
+(defsubst bitwarden-list-all-make-element (item)
+  "Create a new cons list from ITEM."
+  (let* ((folder-id (gethash "folderId" item))
+         (login-item (gethash "login" item)))
+    (cons folder-id
+          (list (cons (concat
+                       (bitwarden-pad-to-width (gethash "name" item) 40)
+                       (and login-item
+                            (bitwarden-pad-to-width (gethash "username"
+                                                             login-item) 32))
+                       (format-time-string
+                        "%Y-%m-%d %T"
+                        (date-to-time (bitwarden-pad-to-width
+                                       (gethash "revisionDate" item) 24))))
+                      item)))))
+
+(defun bitwarden-list-all-tree (key val)
+  "Return a `tree-widget' of folders.
+
+Creates a widget with text KEY and items VAL."
+  ;; Represent a sub-menu with a tree widget
+  `(tree-widget
+    :open t
+    :match ignore
+    :node (item :tag ,key
+                :sample-face bold
+                :format "%{%t%}\n")
+    ,@(mapcar 'bitwarden-list-all-item val)))
+
+(defun bitwarden-list-all-item (pass-element)
+  "Return a widget to display PASS-ELEMENT in a dialog buffer."
+
+  ;; Represent a single file with a link widget
+  `(link :tag ,(car pass-element)
+         :button-prefix ""
+         :button-suffix ""
+         :button-face default
+         :format "%[%t\n%]"
+         :help-echo ,(concat "Viewing item " (gethash "id" (cdr pass-element)))
+         :action bitwarden-list-all-item-action
+         ,(cdr pass-element)))
+
+(defun bitwarden-list-all-items (items)
+  "Return a list of widgets to display ITEMS in a dialog buffer."
+  (let* ((folders (mapcar (lambda (e)
+                            (cons
+                             (gethash "id" e)
+                             (gethash "name" e)))
+                          (bitwarden-folders)))
+         (hash (make-hash-table :test 'equal)))
+
+  ;; create hash table where the keys are the folders and each value is a list
+  ;; of the password items
+  (dolist (x (mapcar 'bitwarden-list-all-make-element items))
+    (let* ((folder-id (car x))
+           (key (cdr (assoc folder-id folders)))
+           (val (cdr x))
+           (klist (gethash key hash)))
+      (puthash key (append klist val) hash)))
+
+  ;; TODO: Add headers over list. Think append and concat should be used for
+  ;;this.
+  (mapcar (lambda (key)
+            (bitwarden-list-all-tree key (gethash key hash)))
+          (sort (hash-table-keys hash) #'string<))))
+
+;;;###autoload
+(defun bitwarden-list-all ()
+  "Show a dialog, listing all entries associated with `bitwarden-user'.
+If optional argument GROUP is given, only entries in GROUP will be listed."
+  (interactive)
+  (bitwarden-list-dialog "*bitwarden-list*"
+    (widget-insert (concat "Bitwarden list mode.\n"
+                           "Usage:\n"
+                           "\t<enter> open URL\n"
+                           "\tn next line\n"
+                           "\tp previous line\n"
+                           "\tr reload accounts\n"
+                           ;; "\ta add password\n"
+                           "\ts show password\n"
+                           "\tw add password to kill ring\n"
+                           ;; "\tm move account to group\n"
+                           ;; "\tc create auth-source from account\n"
+                           ;; "\td delete account\n"
+                           "\tq quit\n"))
+    ;; Use a L&F that looks like the recentf menu.
+    (tree-widget-set-theme "folder")
+
+    (apply 'widget-create
+           `(group
+             :indent 0
+             :format "\n%v\n"
+             ,@(bitwarden-list-all-items
+                (bitwarden-search))))
+
+    (widget-create
+     'push-button
+     :notify 'bitwarden-list-cancel-dialog
+     "Cancel")
+    (goto-char (point-min))))
 
 
 (provide 'bitwarden)
 (provide 'bitwarden)