Adds a new function to spawn a pass prompt which will use xdotool to

type in a password, bypassing the clipboard. Also incorporate some shell
escapes to properly handle pass labels with spaces and special
characters in them.
This commit is contained in:
Nick Hu
2017-06-28 11:38:22 +01:00
parent 676d83ce83
commit cee5aa2a58
2 changed files with 45 additions and 22 deletions

View File

@@ -145,6 +145,13 @@
- Update logout key combination (modm+shift+Q) to work with modern - Update logout key combination (modm+shift+Q) to work with modern
* `XMonad.Prompt.Pass`
- New function `passTypePrompt` which uses `xdotool` to type in a password
from the store, bypassing the clipboard.
- Now handles password labels with spaces and special characters inside
them.
## 0.13 (February 10, 2017) ## 0.13 (February 10, 2017)
### Breaking Changes ### Breaking Changes

View File

@@ -8,9 +8,10 @@
-- Stability : unstable -- Stability : unstable
-- Portability : unportable -- Portability : unportable
-- --
-- This module provides 3 <XMonad.Prompt> to ease passwords manipulation (generate, read, remove): -- This module provides 4 <XMonad.Prompt> to ease password manipulation (generate, read, remove):
-- --
-- - one to lookup passwords in the password-storage. -- - two to lookup passwords in the password-store; one of which copies to the
-- clipboard, and the other uses @xdotool@ to type the password directly.
-- --
-- - one to generate a password for a given password label that the user inputs. -- - one to generate a password for a given password label that the user inputs.
-- --
@@ -18,28 +19,26 @@
-- --
-- All those prompts benefit from the completion system provided by the module <XMonad.Prompt>. -- All those prompts benefit from the completion system provided by the module <XMonad.Prompt>.
-- --
-- The password store is setuped through an environment variable PASSWORD_STORE_DIR. -- The password store is setup through an environment variable PASSWORD_STORE_DIR,
-- If this is set, use the content of the variable. -- or @$HOME\/.password-store@ if it is unset.
-- Otherwise, the password store is located on user's home @$HOME\/.password-store@.
--
-- --
-- Source: -- Source:
-- --
-- - The password storage implementation is <http://git.zx2c4.com/password-store the password-store cli>. -- - The password store implementation is <http://git.zx2c4.com/password-store the password-store cli>.
-- --
-- - Inspired from <http://babushk.in/posts/combining-xmonad-and-pass.html> -- - Inspired by <http://babushk.in/posts/combining-xmonad-and-pass.html>
-- --
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
module XMonad.Prompt.Pass ( module XMonad.Prompt.Pass (
-- * Usages -- * Usage
-- $usages -- $usage
passPrompt passPrompt
, passGeneratePrompt , passGeneratePrompt
, passRemovePrompt , passRemovePrompt
, passTypePrompt
) where ) where
import Control.Monad (liftM)
import XMonad.Core import XMonad.Core
import XMonad.Prompt ( XPrompt import XMonad.Prompt ( XPrompt
, showXPrompt , showXPrompt
@@ -54,7 +53,7 @@ import System.FilePath (takeExtension, dropExtension, combine)
import System.Posix.Env (getEnv) import System.Posix.Env (getEnv)
import XMonad.Util.Run (runProcessWithInput) import XMonad.Util.Run (runProcessWithInput)
-- $usages -- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
-- --
-- > import XMonad.Prompt.Pass -- > import XMonad.Prompt.Pass
@@ -69,17 +68,17 @@ import XMonad.Util.Run (runProcessWithInput)
-- --
-- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings". -- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings".
-- --
-- - how to setup the password storage, see <http://git.zx2c4.com/password-store/about/> -- - how to setup the password store, see <http://git.zx2c4.com/password-store/about/>
-- --
type Predicate = String -> String -> Bool type Predicate = String -> String -> Bool
getPassCompl :: [String] -> Predicate -> String -> IO [String] getPassCompl :: [String] -> Predicate -> String -> IO [String]
getPassCompl compls p s = do return $ filter (p s) compls getPassCompl compls p s = pure $ filter (p s) compls
type PromptLabel = String type PromptLabel = String
data Pass = Pass PromptLabel newtype Pass = Pass PromptLabel
instance XPrompt Pass where instance XPrompt Pass where
showXPrompt (Pass prompt) = prompt ++ ": " showXPrompt (Pass prompt) = prompt ++ ": "
@@ -98,8 +97,8 @@ passwordStoreFolderDefault home = combine home ".password-store"
passwordStoreFolder :: IO String passwordStoreFolder :: IO String
passwordStoreFolder = passwordStoreFolder =
getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir
where computePasswordStoreDir Nothing = liftM passwordStoreFolderDefault getHomeDirectory where computePasswordStoreDir Nothing = passwordStoreFolderDefault <$> getHomeDirectory
computePasswordStoreDir (Just storeDir) = return storeDir computePasswordStoreDir (Just storeDir) = pure storeDir
-- | A pass prompt factory -- | A pass prompt factory
-- --
@@ -126,23 +125,40 @@ passGeneratePrompt = mkPassPrompt "Generate password" generatePassword
passRemovePrompt :: XPConfig -> X () passRemovePrompt :: XPConfig -> X ()
passRemovePrompt = mkPassPrompt "Remove password" removePassword passRemovePrompt = mkPassPrompt "Remove password" removePassword
-- | A prompt to type in a password for a given entry.
-- This doesn't touch the clipboard.
--
passTypePrompt :: XPConfig -> X ()
passTypePrompt = mkPassPrompt "Type password" typePassword
-- | Select a password. -- | Select a password.
-- --
selectPassword :: String -> X () selectPassword :: String -> X ()
selectPassword passLabel = spawn $ "pass --clip " ++ passLabel selectPassword passLabel = spawn $ "pass --clip \"" ++ escapeQuote passLabel ++ "\""
-- | Generate a 30 characters password for a given entry. -- | Generate a 30 characters password for a given entry.
-- If the entry already exists, it is updated with a new password. -- If the entry already exists, it is updated with a new password.
-- --
generatePassword :: String -> X () generatePassword :: String -> X ()
generatePassword passLabel = spawn $ "pass generate --force " ++ passLabel ++ " 30" generatePassword passLabel = spawn $ "pass generate --force \"" ++ escapeQuote passLabel ++ "\" 30"
-- | Remove a password stored for a given entry. -- | Remove a password stored for a given entry.
-- --
removePassword :: String -> X () removePassword :: String -> X ()
removePassword passLabel = spawn $ "pass rm --force " ++ passLabel removePassword passLabel = spawn $ "pass rm --force \"" ++ escapeQuote passLabel ++ "\""
-- | Retrieve the list of passwords from the password storage 'passwordStoreDir -- | Type a password stored for a given entry using xdotool.
--
typePassword :: String -> X ()
typePassword passLabel = spawn $ "xdotool type --clearmodifiers $(pass \"" ++ escapeQuote passLabel ++ "\"|head -n1)"
escapeQuote :: String -> String
escapeQuote = concatMap escape
where escape :: Char -> String
escape '"' = ['\\', '\"']
escape x = pure x
-- | Retrieve the list of passwords from the password store 'passwordStoreDir
getPasswords :: FilePath -> IO [String] getPasswords :: FilePath -> IO [String]
getPasswords passwordStoreDir = do getPasswords passwordStoreDir = do
files <- runProcessWithInput "find" [ files <- runProcessWithInput "find" [
@@ -150,7 +166,7 @@ getPasswords passwordStoreDir = do
"-type", "f", "-type", "f",
"-name", "*.gpg", "-name", "*.gpg",
"-printf", "%P\n"] [] "-printf", "%P\n"] []
return $ map removeGpgExtension $ lines files pure . map removeGpgExtension $ lines files
removeGpgExtension :: String -> String removeGpgExtension :: String -> String
removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file