packages feed

keystore 0.5.1.1 → 0.6.0.0

raw patch · 9 files changed

+556/−132 lines, 9 filesbinary-added

Files

changelog view
@@ -73,4 +73,15 @@ 	* add password manager  0.5.1.1 Chris Dornan <chrisd@irisconnect.co.uk> 2014-09-08-	* fixsetEnv usage to work with GHCs 7.6.3 & 7.8.3+	* fix setEnv usage to work with GHCs 7.6.3 & 7.8.3++0.6.0.0 Chris Dornan <chrisd@irisconnect.co.uk> 2014-09-21+	* add dynamic passwords to the passowrd manager+	* add password manager importing+	* fix bugs in the PasswordManager `amLoggedIn` and `passwordValid`+	  (they were throwing exceptions rather than returning False)+	* add support for shell prompts to the password manager+	* expose the optparse-applicative PasswordManager CLI parser and abstract+	  syntax for cleaner embedding in optparse-applicative parsers+    * hope dependent pacakges have stopped breaking the Hackage build+
examples/deploy/deploy.hs view
@@ -15,8 +15,10 @@ import qualified Data.ByteString.Lazy.Char8     as LBS import qualified Data.Text                      as T import qualified Data.Text.IO                   as T+import           Data.List import           System.IO import           System.Environment+import           System.SetEnv import           System.Process import           Control.Applicative import           Control.Exception@@ -38,6 +40,7 @@     , _pmc_allow_dumps    = True     , _pmc_dump_prefix    = dump_pfx     , _pmc_sample_script  = Just $ defaultSampleScript (PW_ :: PW_ SectionID) dump_pfx+    , _pmc_plus_env_var   = \(PasswordName nm) -> Just $ EnvVar $ T.concat ["DEPLOY_PW_",nm]     }   where     dump_pfx = "deploy pm"@@ -117,7 +120,11 @@ interactive_shell = do   sh <- maybe "/bin/bash" id <$> lookupEnv "SHELL"   putStrLn $ "launching password-manager shell => " ++ sh-  callProcess sh ["-i"]+  case "zsh" `isInfixOf` sh of+    True  -> do+      setEnv "ZDOTDIR" "examples/deploy/zshenv"+      callProcess sh ["-i"]+    False -> callProcess sh ["-i"]   putStrLn "password-manager shell done"  write :: Maybe FilePath -> LBS.ByteString -> IO ()
+ examples/deploy/example-pwstore.dat view

binary file changed (absent → 2527 bytes)

+ examples/deploy/zshenv/.zshrc view
@@ -0,0 +1,6 @@+source "${HOME}/.zshrc"++if (( $+commands[deploy] )) ; then+  setopt PROMPT_SUBST+  PS1='%{$fg_bold[green]%}$(deploy pm prompt)$fg_bold[blue]%} ➜ %{$reset_color%}'+fi
keystore.cabal view
@@ -1,5 +1,5 @@ Name:                   keystore-Version:                0.5.1.1+Version:                0.6.0.0 Synopsis:               Managing stores of secret things Homepage:               http://github.com/cdornan/keystore Author:                 Chris Dornan@@ -120,7 +120,13 @@   .   See the bottom <https://github.com/cdornan/keystore#launch-instructions README>   on GitHub home page for launch instructions for the deploy example.+Data-files:+    keystore.manifest+    keystore783.manifest+    examples/deploy/zshenv/.zshrc+    examples/deploy/example-pwstore.dat + Extra-source-files:     changelog Cabal-version:          >= 1.14 @@ -235,9 +241,9 @@         optparse-applicative   >= 0.9.0             ,         process                >= 1.2.0.0           ,         raw-strings-qq         >= 1.0.2             ,+        setenv                 >= 0.1               ,         text                   >= 0.11              ,         unordered-containers   >= 0.2.3.0-      GHC-Options:         -Wall -fno-warn-warnings-deprecations
+ keystore.manifest view
@@ -0,0 +1,73 @@+^=7.6.3+Cabal-1.20.0.2+MonadCatchIO-transformers-0.3.0.0+QuickCheck-2.7.3+aeson-0.6.2.1+aeson-pretty-0.7+ansi-terminal-0.6.1.1+ansi-wl-pprint-0.6.7.1+api-tools-0.4+api-tools-0.4.0.1+asn1-encoding-0.8.1.3+asn1-types-0.2.3+attoparsec-0.11.2.1+base64-bytestring-1.0.0.1+bifunctors-4.1.1.1+blaze-builder-0.3.3.2+byteable-0.1.1+bytedump-1.0+case-insensitive-1.1.0.3+cereal-0.4.0.1+cipher-aes-0.2.6+cmdargs-0.10.7+comonad-4.2+contravariant-0.6+crypto-cipher-types-0.0.9+crypto-numbers-0.2.3+crypto-pubkey-0.2.4+crypto-pubkey-types-0.4.1+crypto-random-0.0.7+cryptohash-0.11.2+distributive-0.4.4+dlist-0.6.0.1+extensible-exceptions-0.1.1.4+generic-deriving-1.6.2+hashable-1.2.1.0+keystore-0.5.0.2+keystore-0.5.0.3+keystore-0.5.1.0+lens-3.10.2+mtl-2.1.3.1+nats-0.1.2+optparse-applicative-0.9.0+optparse-applicative-0.9.1.1+parallel-3.2.0.4+parsec-3.1.5+pbkdf-1.1.1.1+primitive-0.5.2.1+process-1.2.0.0+profunctors-4.0.4+random-1.0.1.1+raw-strings-qq-1.0.2+reflection-1.3.2+regex-base-0.93.2+regex-compat-tdfa-0.95.1.4+regex-tdfa-1.2.0+safe-0.3.3+safecopy-0.8.2+scientific-0.2.0.2+securemem-0.1.3+semigroupoids-4.0.2.1+semigroups-0.12.2+setenv-0.1.1.1+split-0.2.2+syb-0.4.1+tagged-0.7+text-1.1.0.1+tf-random-0.4+transformers-0.3.0.0+transformers-compat-0.3.3.4+unordered-containers-0.2.3.3+utf8-string-0.3.7+vector-0.10.9.1+void-0.6.1
+ keystore783.manifest view
@@ -0,0 +1,67 @@+^=7.8.3+QuickCheck-2.7.6+aeson-0.8.0.0+aeson-pretty-0.7.1+ansi-terminal-0.6.1.1+ansi-wl-pprint-0.6.7.1+api-tools-0.4.0.1+asn1-encoding-0.8.1.3+asn1-types-0.2.3+attoparsec-0.12.1.1+base64-bytestring-1.0.0.1+bifunctors-4.1.1.1+byteable-0.1.1+bytedump-1.0+case-insensitive-1.2.0.0+cereal-0.4.0.1+cipher-aes-0.2.8+cmdargs-0.10.9+comonad-4.2.2+contravariant-1.2+crypto-cipher-types-0.0.9+crypto-numbers-0.2.3+crypto-pubkey-0.2.4+crypto-pubkey-types-0.4.2.2+crypto-random-0.0.8+cryptohash-0.11.6+distributive-0.4.4+dlist-0.7.1+exceptions-0.6.1+free-4.9+hashable-1.2.2.0+keystore-0.5.1.0+lens-4.3.3+mtl-2.2.1+nats-0.2+optparse-applicative-0.9.1.1+parallel-3.2.0.4+parsec-3.1.5+pbkdf-1.1.1.1+prelude-extras-0.4+primitive-0.5.3.0+profunctors-4.2.0.1+random-1.0.1.3+raw-strings-qq-1.0.2+reflection-1.5.1+regex-base-0.93.2+regex-compat-tdfa-0.95.1.4+regex-tdfa-1.2.0+safe-0.3.8+safecopy-0.8.3+scientific-0.3.3.0+securemem-0.1.3+semigroupoids-4.2+semigroups-0.15.2+setenv-0.1.1.1+split-0.2.2+syb-0.4.2+tagged-0.7.2+text-1.1.1.3+tf-random-0.5+transformers-0.4.1.0+transformers-compat-0.3.3.4+unordered-containers-0.2.5.0+utf8-string-0.3.8+vector-0.10.11.0+void-0.6.1+zlib-0.5.4.1
src/Data/KeyStore/PasswordManager.hs view
@@ -21,23 +21,35 @@     , bindMasterPassword     , setup     , passwordValid+    , passwordValid'     , isStorePresent     , amLoggedIn     , isBound+    , import_     , load+    , loadPlus     , psComment     , collect     , prime     , select     , deletePassword+    , deletePasswordPlus     , deleteSession     , status+    , prompt     , passwords+    , passwordsPlus     , sessions     , infoPassword     , infoPassword_+    , infoPasswordPlus+    , infoPasswordPlus_     , dump     , collectShell+    -- password manager CLI internals+    , passwordManager'+    , PMCommand+    , parseCommand     ) where  import           Data.KeyStore.Types.PasswordStoreModel@@ -92,6 +104,8 @@     , _pmc_allow_dumps    :: Bool         -- ^ must be true to enable 'dump' commands     , _pmc_dump_prefix    :: String       -- ^ the prefix string to be used in making up the commands from dump scripts     , _pmc_sample_script  :: Maybe String -- ^ the sample script+    , _pmc_plus_env_var   :: PasswordName -> Maybe EnvVar+                                          -- ^ map the dynamic (plus) passwords to their environment variables     }  -- | The PW class provides all of the information on the bounded enumeration type used to identify the passwords@@ -153,26 +167,7 @@  -- | the password manager CLI: it just needs the config and command line passwordManager :: PW p => PMConfig p -> [String] -> IO ()-passwordManager pmc args = do-  pmcd <- parse_command pmc args-  case pmcd of-    PMCD_version                -> putStrLn version-    PMCD_setup          nl mb_t -> setup          pmc nl mb_t-    PMCD_login        y    mb_t -> login          pmc y  mb_t-    PMCD_load            p mb_t -> load           pmc p  mb_t-    PMCD_comment            cmt -> psComment      pmc cmt-    PMCD_prime        u  p      -> prime          pmc u $ Just p-    PMCD_prime_all    u         -> prime          pmc u Nothing-    PMCD_select          mb snm -> select         pmc mb snm-    PMCD_delete_password p      -> deletePassword pmc p-    PMCD_delete_session  mb snm -> deleteSession  pmc mb snm-    PMCD_status      q          -> status         pmc q-    PMCD_passwords   b          -> passwords      pmc b-    PMCD_sessions    a b mb     -> sessions       pmc a b mb-    PMCD_info        s   p      -> infoPassword   pmc s p-    PMCD_dump        s          -> dump           pmc s-    PMCD_collect                -> collectShell   pmc-    PMCD_sample_script          -> putStr $ maybe "" id $ _pmc_sample_script pmc+passwordManager pmc args = parseCommand pmc args >>= passwordManager' pmc  -- | a sample 'HashDescription' generator to help with setting up 'PMConfig' defaultHashDescription :: Salt -> HashDescription@@ -218,9 +213,11 @@     ex <- doesFileExist _pmc_location     when ex $ error $ "password store already exists in: " ++ _pmc_location     -- get a password from stdin if we have not been passed one-    pwt <- maybe (get_pw pmc) return mb_pwt+    pwt  <- maybe (get_pw True pmc) return mb_pwt+    pwt' <- maybe (get_pw True pmc) return mb_pwt+    when (pwt/=pwt') $ error "passwords do not match"     -- need creation time and comment-    now <- getCurrentTime+    now  <- getCurrentTime     let ps =           PasswordStore             { _ps_comment = PasswordStoreComment $ T.pack $ "Created at " ++ show now@@ -239,7 +236,7 @@ -- if no 'PasswordText' is specified then one will be read from stdin login :: PW p => PMConfig p -> Bool -> Maybe PasswordText -> IO () login pmc y mb = do-  pwt <- maybe (get_pw pmc) return mb+  pwt <- maybe (get_pw True pmc) return mb   ok  <- passwordValid pmc pwt   case ok of     True  -> bindMasterPassword pmc pwt >> good >> _pmc_shell pmc@@ -251,15 +248,23 @@  -- | is this the correct master password? passwordValid :: PW p => PMConfig p -> PasswordText -> IO Bool-passwordValid pmc = password_valid pmc . mk_aek+passwordValid pmc pwt = isJust <$> passwordValid' pmc (_pmc_location pmc) pwt +-- | is this the correct master password for this keystore? Return the decrypted+-- keystore if so.+passwordValid' :: PW p => PMConfig p -> FilePath -> PasswordText -> IO (Maybe PasswordStore)+passwordValid' pmc fp = password_valid pmc fp . mk_aek+ -- | is the password store there? isStorePresent :: PW p => PMConfig p -> IO Bool isStorePresent PMConfig{..} = doesFileExist _pmc_location  -- | are we currently logged in? amLoggedIn :: PW p => PMConfig p -> IO Bool-amLoggedIn pmc = get_key pmc >>= password_valid pmc+amLoggedIn pmc = flip catch hdl $+    isJust <$> (get_key pmc >>= password_valid pmc (_pmc_location pmc))+  where+    hdl (_::SomeException) = return False  -- | is the password/session bound to a value in the store? isBound :: PW p => PMConfig p -> p -> Maybe SessionName -> IO Bool@@ -268,12 +273,21 @@     Nothing           -> False     Just Password{..} -> maybe True (\snm->Map.member snm $ _pw_sessions) mb +-- | import the contents of another keystore into the current keystore+import_ :: PW p => PMConfig p -> FilePath -> Maybe PasswordText -> IO ()+import_ pmc fp mb = wrap pmc $ \ps -> do+  pwt   <- maybe (get_pw True pmc) return mb+  mb_ps <- passwordValid' pmc fp pwt+  case mb_ps of+    Nothing  -> error "*** Password Invalid ***\n"+    Just ps' -> return $ Just $ merge_ps ps ps'+ -- | loads a password into the store; if this is a session password and the -- boolean ss is True then the session will be reset to this password also; -- if no 'PasswordText' is specified then one will be read from stdin load :: PW p => PMConfig p -> p -> Maybe PasswordText -> IO () load pmc p mb = wrap pmc $ \ps -> do-  pwt <- maybe (get_pw pmc) return mb+  pwt <- maybe (get_pw False pmc) return mb   now <- getCurrentTime   case isSession p of     Nothing  -> load_pwd ps@@ -322,6 +336,24 @@      pnm             = pwName p +-- | load a dynamic password into the Password store+loadPlus :: PW p => PMConfig p -> PasswordName -> Maybe PasswordText -> IO ()+loadPlus pmc pnm_ mb = wrap pmc $ \ps -> do+  pwt <- maybe (get_pw False pmc) return mb+  now <- getCurrentTime+  load_pwd ps+      Password+        { _pw_name      = pnm+        , _pw_text      = pwt+        , _pw_sessions  = Map.empty+        , _pw_isOneShot = False+        , _pw_primed    = False+        , _pw_setup     = now+        }+  where+    pnm             = PasswordName $ (T.cons '+') $ _PasswordName pnm_+    load_pwd ps pw  = return $ Just $ L.over ps_map (Map.insert pnm pw) ps+ -- | set the comment for the password store psComment :: PW p => PMConfig p -> PasswordStoreComment -> IO () psComment pmc cmt = wrap pmc $ \ps -> return $ Just $ L.set ps_comment cmt ps@@ -330,8 +362,15 @@ -- and bind them in their designated environmants variables collect :: PW p => PMConfig p -> CollectConfig p -> IO () collect pmc CollectConfig{..} = wrap_ pmc $ \ps -> do-    -- set up the environment+    -- set up the environment -- first the static passwords...     mapM_ (clct pmc ps) _cc_active+    -- ... then the dynamic (+) passwords+    sequence_+      [ set_env ev $ _pw_text pw+        | (pnm_,pw) <- Map.toList $ _ps_map ps+        , Just pnm  <- [is_plus pnm_]+        , Just ev   <- [_pmc_plus_env_var pmc pnm]+        ]     -- now clear down all of the primed passwords     return $ Just $ L.over ps_map (Map.map (L.set pw_primed False)) ps   where@@ -364,6 +403,10 @@ deletePassword :: PW p => PMConfig p -> p -> IO () deletePassword pmc p = wrap pmc $ \ps -> return $ Just $ L.over ps_map (Map.delete (pwName p)) ps +-- | delete a password from the store+deletePasswordPlus :: PW p => PMConfig p -> PasswordName -> IO ()+deletePasswordPlus pmc pnm = wrap pmc $ \ps -> return $ Just $ L.over ps_map (Map.delete (plussify pnm)) ps+ -- | delete a session from the store deleteSession :: PW p => PMConfig p -> Maybe p -> SessionName -> IO () deleteSession pmc mb snm = wrap pmc $ \ps -> do@@ -380,7 +423,6 @@      f   ps (p,pw,_) = Just $ L.over ps_map (Map.insert (pwName p) (L.over pw_sessions (Map.delete snm) pw)) ps - -- | print a status line; if @q@ is @True@ then don't output anything and exit -- with fail code 1 if not logged in status :: PW p => PMConfig p -> Bool -> IO ()@@ -415,6 +457,28 @@      hdl (_::SomeException) = exitWith $ ExitFailure 1 +-- | print a status apropriate for a prompt+prompt :: PW p => PMConfig p -> IO ()+prompt pmc = flip catch hdl $ do+  li <- amLoggedIn pmc+  case li of+    True  -> enquire pmc line+    False -> putStrLn "*"+  where+    line ps = putStrLn $ "[" ++ unwords sns ++ "]"+      where+        sns =+          [ T.unpack $ _SessionName $ _sd_name sd+            | pw <- Map.elems $ _ps_map ps+            , let Password{..} = pw+            , Just  p   <- [parsePwName _pw_name]+            , Just  prs <- [isSession $ cast_pmc pmc p]+            , Right sd  <- [prs _pw_text]+            , is_primed pw+            ]++    hdl (_::SomeException) = putStrLn "???"+ -- | list the passwords, one per line; if @a@ is set then all passwords will be listed, -- otherwise just the primed passwords will be listed passwords :: PW p => PMConfig p -> Bool -> IO ()@@ -445,6 +509,27 @@         , Just pwd <- [Map.lookup (pwName p) $ _ps_map ps]         ] +-- | list all of the dynamic (+) passwords+passwordsPlus :: PW p => PMConfig p -> Bool -> IO ()+passwordsPlus pmc br = do+  tz <- getCurrentTimeZone+  enquire pmc $ \ps ->+    putStr $ unlines $ map (fmt tz) $ pws ps+  where+    fmt tz (pnm,Password{..})+        | br        = nm_s+        | otherwise = printf "+%-12s $%-18s %s" nm_s ev_s su_s+      where+        nm_s = T.unpack $ _PasswordName pnm+        ev_s = T.unpack $ _EnvVar $ fromMaybe "?" $ _pmc_plus_env_var pmc pnm+        su_s = pretty_setup tz _pw_setup++    pws ps =+      [ (pnm,pw)+        | (pnm_,pw) <- Map.toList $ _ps_map ps+        , Just pnm  <- [is_plus pnm_]+        ]+ -- | list the sessions, one per line; if @p@ is specified then all of the -- sessions are listed for that password sessions :: PW p@@ -492,17 +577,17 @@ -- | get the info on a password infoPassword_ :: PW p => PMConfig p -> Bool -> p -> IO P.Doc infoPassword_ pmc sh_s p = do-  tz <- getCurrentTimeZone-  enquire pmc $ \ps ->-    return $ maybe P.empty (mk tz) $ Map.lookup pnm $ _ps_map ps+    tz <- getCurrentTimeZone+    enquire pmc $ \ps ->+      return $ maybe P.empty (mk tz) $ Map.lookup pnm $ _ps_map ps   where     mk tz pw@Password{..} =         heading           P.<$$> P.indent 4 (             sssions       P.<>             primed        P.<$$>             evar          P.<$$>-            loaded        P.<$$>             secret        P.<>+            loaded        P.<$$>             P.empty       P.<$$>             descr           )               P.<$$>@@ -536,6 +621,42 @@      ljust n s = s ++ replicate (max 0 (n-length s)) ' ' +-- | print the info for a dynamic (+) password+infoPasswordPlus :: PW p => PMConfig p -> Bool -> PasswordName -> IO ()+infoPasswordPlus pmc sh_s pnm = do+  doc <- infoPasswordPlus_ pmc sh_s pnm+  putStr $ P.displayS (P.renderPretty 0.75 120 doc) ""++-- | get the info on a dynamic (+) password+infoPasswordPlus_ :: PW p => PMConfig p -> Bool -> PasswordName -> IO P.Doc+infoPasswordPlus_ pmc sh_s pnm = do+    tz <- getCurrentTimeZone+    enquire pmc $ \ps ->+      return $ maybe P.empty (mk tz) $ Map.lookup (plussify pnm) $ _ps_map ps+  where+    mk tz Password{..} =+        heading           P.<$$> P.indent 4 (+          evar          P.<>+          secret        P.<>+          loaded+          )             P.<$$>+          P.empty+      where+        heading  = P.bold $ P.string $ "+" ++ T.unpack (_PasswordName pnm)+        evar     = case _pmc_plus_env_var pmc pnm of+          Nothing -> P.empty+          Just ev -> (line "env var" $ T.unpack $ _EnvVar $ ev          ) P.<$$> P.empty+        loaded     =  line "loaded"  $ pretty_setup tz $ _pw_setup+        secret   = case sh_s of+          True  ->   (line "secret"  $ T.unpack $ _PasswordText _pw_text) P.<$$> P.empty+          False -> P.empty++        line :: String -> String -> P.Doc+        line nm vl = P.bold(P.string $ ljust 8 nm) P.<> P.string " : " P.<>+                                                        P.hang 8 (P.string vl)++    ljust n s = s ++ replicate (max 0 (n-length s)) ' '+ -- | dump the store in a s script that can be used to reload it dump :: PW p => PMConfig p -> Bool -> IO () dump pmc inc_ssns = enquire pmc dmp >> prime pmc True Nothing@@ -603,16 +724,20 @@     ]  -- | read a passord from stdin and hash it-get_pw :: PW p => PMConfig p -> IO PasswordText-get_pw pmc = do+get_pw :: PW p => Bool -> PMConfig p -> IO PasswordText+get_pw hp pmc = do   hSetEcho stdin False   putStr "Password: "   hFlush stdout   pw <- getLine   putChar '\n'   hSetEcho stdin True-  return $ hashMasterPassword pmc pw+  return $ cond_hash hp pmc pw +cond_hash :: PW p => Bool -> PMConfig p -> String -> PasswordText+cond_hash False _   = PasswordText . T.pack+cond_hash True  pmc = hashMasterPassword pmc+ -- | use a '+' to represent a primed one-shot password,'-' otherwise prime_char :: Bool -> Char prime_char is_p = if is_p then '+' else '-'@@ -664,15 +789,18 @@   aek <- get_key pmc   load_ps pmc aek >>= f -password_valid :: PW p => PMConfig p -> AESKey -> IO Bool-password_valid pmc aek = catch ld hd+password_valid :: PW p => PMConfig p -> FilePath -> AESKey -> IO (Maybe PasswordStore)+password_valid pmc fp aek = catch ld hd   where-    ld                    = const True <$> load_ps pmc aek-    hd (_::SomeException) = return False+    ld                    = Just <$> load_ps_ pmc fp aek+    hd (_::SomeException) = return Nothing  load_ps :: PW p => PMConfig p -> AESKey -> IO PasswordStore-load_ps pmc aek = do-  aed <- load_ps' pmc+load_ps pmc = load_ps_ pmc (_pmc_location pmc)++load_ps_ :: PW p => PMConfig p -> FilePath -> AESKey -> IO PasswordStore+load_ps_ pmc fp aek = do+  aed <- load_ps' pmc fp   case decodeWithErrs $ BL.fromChunks [_Binary $ _ClearText $ decryptAES aek aed] of     Right pws -> return pws     Left  ers -> error $ prettyJSONErrorPositions ers@@ -682,9 +810,9 @@   iv <- random_bytes sizeAesIV IV   save_ps' pmc $ encryptAES aek iv $ ClearText $ Binary $ BL.toStrict $ A.encode pws -load_ps' :: PW p => PMConfig p -> IO AESSecretData-load_ps' PMConfig{..} = flip catch hdl $ do-  (iv,ct) <- B.splitAt (_Octets sizeAesIV) <$> B.readFile _pmc_location+load_ps' :: PW p => PMConfig p -> FilePath -> IO AESSecretData+load_ps' PMConfig{..} fp = flip catch hdl $ do+  (iv,ct) <- B.splitAt (_Octets sizeAesIV) <$> B.readFile fp   return     AESSecretData       { _asd_iv          = IV         $ Binary iv@@ -693,6 +821,20 @@   where     hdl (_::SomeException) = error _pmc_keystore_msg +-- | marge in the second password store into the first, all definitions in+-- the second passwords store, except the store's creation time, which is+-- taken from the first store; any sessions are also merged with the+-- sessions in the second store taking precedence+merge_ps :: PasswordStore -> PasswordStore -> PasswordStore+merge_ps ps ps' =+  PasswordStore+    { _ps_comment = _ps_comment ps'+    , _ps_map     =  Map.unionWith f (_ps_map ps) (_ps_map ps')+    , _ps_setup   = _ps_setup ps+    }+  where+    f pw pw' = L.over pw_sessions (flip Map.union $ _pw_sessions pw) pw'+ random_bytes :: Octets -> (Binary->a) -> IO a random_bytes sz f = f . Binary . fst . generateCPRNG (_Octets sz) <$> newCPRNG @@ -737,29 +879,66 @@ -- The Command Line Parser -- +-- | run a password manager command abstracy syntax command+passwordManager' :: PW p => PMConfig p -> PMCommand p -> IO ()+passwordManager' pmc pmcd =+  case pmcd of+    PMCD_version                  -> putStrLn version+    PMCD_setup          nl mb_t   -> setup              pmc nl  mb_t+    PMCD_login        y    mb_t   -> login              pmc y   mb_t+    PMCD_import         fp mb_t   -> import_            pmc fp  mb_t+    PMCD_load            p mb_t   -> load               pmc p   mb_t+    PMCD_load_plus     pnm mb_t   -> loadPlus           pmc pnm mb_t+    PMCD_comment            cmt   -> psComment          pmc cmt+    PMCD_prime        u  p        -> prime              pmc u $ Just p+    PMCD_prime_all    u           -> prime              pmc u Nothing+    PMCD_select          mb snm   -> select             pmc mb snm+    PMCD_delete_password      p   -> deletePassword     pmc p+    PMCD_delete_password_plus pnm -> deletePasswordPlus pmc pnm+    PMCD_delete_session    mb snm -> deleteSession      pmc mb snm+    PMCD_status         q         -> status             pmc q+    PMCD_prompt                   -> prompt             pmc+    PMCD_passwords      b         -> passwords          pmc b+    PMCD_passwords_plus b         -> passwordsPlus      pmc b+    PMCD_session     b            -> sessions           pmc True  b Nothing+    PMCD_sessions    b mb         -> sessions           pmc False b mb+    PMCD_info        s   p        -> infoPassword       pmc s p+    PMCD_info_plus   s   pnm      -> infoPasswordPlus   pmc s pnm+    PMCD_dump        s            -> dump               pmc s+    PMCD_collect                  -> collectShell       pmc+    PMCD_sample_script            -> putStr $ maybe "" id $ _pmc_sample_script pmc +-- | the abstract syntax for the passowd manager commands data PMCommand p     = PMCD_version-    | PMCD_setup  Bool   (Maybe PasswordText)-    | PMCD_login  Bool   (Maybe PasswordText)-    | PMCD_load        p (Maybe PasswordText)+    | PMCD_setup  Bool              (Maybe PasswordText)+    | PMCD_login  Bool              (Maybe PasswordText)+    | PMCD_import          FilePath (Maybe PasswordText)+    | PMCD_load        p            (Maybe PasswordText)+    | PMCD_load_plus   PasswordName (Maybe PasswordText)     | PMCD_comment       PasswordStoreComment     | PMCD_prime     Bool p     | PMCD_prime_all Bool     | PMCD_select          (Maybe p) SessionName     | PMCD_delete_password        p+    | PMCD_delete_password_plus PasswordName     | PMCD_delete_session  (Maybe p) SessionName-    | PMCD_status    Bool-    | PMCD_passwords Bool-    | PMCD_sessions  Bool Bool (Maybe p)-    | PMCD_info      Bool         p-    | PMCD_dump      Bool+    | PMCD_status         Bool+    | PMCD_prompt+    | PMCD_passwords      Bool+    | PMCD_passwords_plus Bool+    | PMCD_session        Bool+    | PMCD_sessions       Bool (Maybe p)+    | PMCD_info           Bool         p+    | PMCD_info_plus      Bool         PasswordName+    | PMCD_dump           Bool     | PMCD_collect     | PMCD_sample_script     deriving (Show) -parse_command :: PW p => PMConfig p -> [String] -> IO (PMCommand p)-parse_command pmc = run_parse $ command_info pmc+-- | parse a passwword manager command+parseCommand :: PW p => PMConfig p -> [String] -> IO (PMCommand p)+parseCommand pmc = run_parse $ command_info pmc  command_info :: PW p => PMConfig p -> ParserInfo (PMCommand p) command_info pmc =@@ -773,19 +952,23 @@ command_parser pmc =     subparser $ f $ g      $  command "version"             pi_version-     <> command "setup"               pi_setup-     <> command "login"               pi_login-     <> command "load"                pi_load+     <> command "setup"              (pi_setup            pmc)+     <> command "login"              (pi_login            pmc)+     <> command "import"             (pi_import           pmc)+     <> command "load"               (pi_load             pmc)      <> command "comment"             pi_comment      <> command "prime"               pi_prime      <> command "prime-all"           pi_prime_all      <> command "select"              pi_select-     <> command "delete-password"     pi_delete_password+     <> command "delete-password"    (pi_delete_password  pmc)      <> command "delete-session"      pi_delete_session      <> command "status"              pi_status+     <> command "prompt"              pi_prompt      <> command "passwords"           pi_passwords+     <> command "passwords-plus"      pi_passwords_plus+     <> command "session"             pi_session      <> command "sessions"            pi_sessions-     <> command "info"                pi_info+     <> command "info"               (pi_info             pmc)      <> command "collect"             pi_collect   where     s = command "sample-load-script"  pi_sample_script@@ -805,22 +988,28 @@         (helper <*> pure PMCD_version)         (progDesc "report the version of this package") -pi_setup :: ParserInfo (PMCommand p)-pi_setup =+pi_setup :: PW p => PMConfig p -> ParserInfo (PMCommand p)+pi_setup pmc =     h_info-        (helper <*> (PMCD_setup <$> p_no_login_sw <*> optional p_password_text))+        (helper <*> (PMCD_setup <$> p_no_login_sw <*> optional (p_password_text True pmc)))         (progDesc "setup the password store") -pi_login :: ParserInfo (PMCommand p)-pi_login =+pi_login :: PW p => PMConfig p -> ParserInfo (PMCommand p)+pi_login pmc =     h_info-        (helper <*> (PMCD_login <$> p_loop_sw <*> optional p_password_text))+        (helper <*> (PMCD_login <$> p_loop_sw <*> optional (p_password_text True pmc)))         (progDesc "login to the password manager") -pi_load :: PW p => ParserInfo (PMCommand p)-pi_load =+pi_import :: PW p => PMConfig p -> ParserInfo (PMCommand p)+pi_import pmc =     h_info-        (helper <*> (PMCD_load <$> p_pw_id <*> optional p_password_text) <* optional p_load_comment)+        (helper <*> (PMCD_import <$> p_store_fp <*> optional (p_password_text True pmc)))+        (progDesc "import the contents of another store")++pi_load :: PW p => PMConfig p -> ParserInfo (PMCommand p)+pi_load pmc =+    h_info+        (helper <*> p_load_command pmc)         (progDesc "load a password into the store")  pi_comment :: PW p => ParserInfo (PMCommand p)@@ -847,11 +1036,11 @@         (helper <*> (PMCD_select <$> optional p_pw_id_opt <*> p_session_name))         (progDesc "select a client session") -pi_delete_password :: PW p => ParserInfo (PMCommand p)-pi_delete_password =+pi_delete_password :: PW p => PMConfig p -> ParserInfo (PMCommand p)+pi_delete_password pmc =      h_info-        (helper <*> (PMCD_delete_password <$> p_pw_id))-        (progDesc "select a client session")+        (helper <*> p_delete_password pmc)+        (progDesc "delete a password from the store")  pi_delete_session :: PW p => ParserInfo (PMCommand p) pi_delete_session =@@ -865,22 +1054,41 @@         (helper <*> (PMCD_status <$> p_quiet_sw))         (progDesc "report the status of the password manager") +pi_prompt :: ParserInfo (PMCommand p)+pi_prompt =+    h_info+        (helper <*> (pure PMCD_prompt))+        (progDesc $ "report the condensed status of the password manager"+++                                  " (suitable for embedding in a shell prompt")+ pi_passwords :: ParserInfo (PMCommand p) pi_passwords =     h_info         (helper <*> (PMCD_passwords <$> p_brief_sw))         (progDesc "list the passwords") +pi_passwords_plus :: ParserInfo (PMCommand p)+pi_passwords_plus =+    h_info+        (helper <*> (PMCD_passwords_plus <$> p_brief_sw))+        (progDesc "list the dynamic ('+'') passwords")++pi_session :: PW p => ParserInfo (PMCommand p)+pi_session =+    h_info+        (helper <*> (PMCD_session <$> p_brief_sw))+        (progDesc "list the sessions")+ pi_sessions :: PW p => ParserInfo (PMCommand p) pi_sessions =     h_info-        (helper <*> (PMCD_sessions <$> p_active_sw <*> p_brief_sw <*> optional p_pw_id))+        (helper <*> (PMCD_sessions <$> p_brief_sw <*> optional p_pw_id))         (progDesc "list the sessions") -pi_info :: PW p => ParserInfo (PMCommand p)-pi_info =+pi_info :: PW p => PMConfig p -> ParserInfo (PMCommand p)+pi_info pmc =     h_info-        (helper <*> (PMCD_info <$> p_secret_sw <*> p_pw_id))+        (helper <*> p_info pmc)         (progDesc "print out the info on a password, including desriptive text")  pi_dump :: PW p => ParserInfo (PMCommand p)@@ -902,12 +1110,29 @@         (helper <*> (pure PMCD_sample_script))         (progDesc "print a sample script to define keystore passwords in the environment (PM edition)") -p_no_login_sw :: Parser Bool-p_no_login_sw =+p_load_command, p_delete_password, p_info :: PW p => PMConfig p -> Parser (PMCommand p)++p_load_command pmc =+  (PMCD_load_plus <$> p_pl_pw pmc <*> optional (p_password_text False pmc) <|>+   PMCD_load      <$> p_pw_id     <*> optional (p_password_text False pmc)      ) <* optional p_load_comment++p_delete_password pmc =+  PMCD_delete_password_plus <$> p_pl_pw pmc                     <|>+  PMCD_delete_password      <$> p_pw_id++p_info pmc = f <$> p_secret_sw <*> ((Left <$> p_pl_pw pmc) <|> (Right <$> p_pw_id))+  where+    f s_sw (Left  pnm) = PMCD_info_plus s_sw pnm+    f s_sw (Right p  ) = PMCD_info      s_sw p++-- switches++p_brief_sw :: Parser Bool+p_brief_sw =     switch-        (short  'n'            <>-         long    "no-login"    <>-         help    "do not launch an interactive shell")+        (short  'b'            <>+         long    "brief"        <>+         help    "list the identifiers only")  p_loop_sw :: Parser Bool p_loop_sw =@@ -916,6 +1141,13 @@          long    "loop"        <>          help    "on failure prompt for a new password and try again") +p_no_login_sw :: Parser Bool+p_no_login_sw =+    switch+        (short  'n'            <>+         long    "no-login"    <>+         help    "do not launch an interactive shell")+ p_quiet_sw :: Parser Bool p_quiet_sw =     switch@@ -923,19 +1155,11 @@          long    "quiet"        <>          help    "don't print anything but report with error codes (0=>logged in)") -p_brief_sw :: Parser Bool-p_brief_sw =-    switch-        (short  'b'            <>-         long    "brief"        <>-         help    "list the identifiers only")--p_active_sw :: Parser Bool-p_active_sw =+p_sessions_sw :: Parser Bool+p_sessions_sw =     switch-        (short  'a'            <>-         long    "active"      <>-         help    "list the active sessions only")+        (long    "sessions"      <>+         help    "include the sessions")  p_unprime_sw :: Parser Bool p_unprime_sw =@@ -944,24 +1168,7 @@          long    "unprime"     <>          help    "clear the prime status") -p_secret_sw :: Parser Bool-p_secret_sw =-    switch-        (short  's'            <>-         long    "secret"      <>-         help    "show the secret password")--p_sessions_sw :: Parser Bool-p_sessions_sw =-    switch-        (long    "sessions"      <>-         help    "include the sessions")--p_pw_id :: PW p => Parser p-p_pw_id =-    argument (parsePwName . PasswordName . T.pack)-        $  metavar "PASSWORD-ID"-        <> help    "a password ID"+-- options  p_pw_id_opt :: PW p => Parser p p_pw_id_opt =@@ -972,35 +1179,64 @@         <> reader  (maybe (fail "password-id not recognised") return . parsePwName . PasswordName . T.pack)         <> help    "a password ID" -p_password_text :: Parser PasswordText-p_password_text =-    argument (Just . PasswordText . T.pack)-        $  metavar "PASSWORD-TEXT"-        <> help    "the text of the password"+-- arguments -p_session_name :: Parser SessionName-p_session_name =-    argument (Just . SessionName . T.pack)-        $  metavar "SESSION"-        <> help    "a session name"+p_comment :: Parser String+p_comment = unwords <$> many p_word +p_hash :: Parser ()+p_hash = argument (\s->if s=="#" then Just () else Nothing) $ metavar "#"++h_info :: Parser a -> InfoMod a -> ParserInfo a+h_info pr = O.info (helper <*> pr)+ p_load_comment :: Parser () p_load_comment = const () <$> optional (p_hash <* p_comment) +p_password_text :: PW p => Bool -> PMConfig p -> Parser PasswordText+p_password_text hp pmc =+    argument (Just . cond_hash hp pmc)+        $  metavar "PASSWORD-TEXT"+        <> help    "the text of the password"++p_pw_id :: PW p => Parser p+p_pw_id =+    argument (parsePwName . PasswordName . T.pack)+        $  metavar "PASSWORD-ID"+        <> help    "a password ID"++p_pl_pw :: PW p => PMConfig p -> Parser PasswordName+p_pl_pw pmc =+    argument (parse_plus_pw pmc)+        $  metavar "+PASSWORD"+        <> help    "a dynamic (plus) password name"+ p_ps_comment :: Parser PasswordStoreComment p_ps_comment = PasswordStoreComment . T.pack <$> p_comment -p_hash :: Parser ()-p_hash = argument (\s->if s=="#" then Just () else Nothing) $ metavar "#"+p_secret_sw :: Parser Bool+p_secret_sw =+    switch+        (short  's'            <>+         long    "secret"      <>+         help    "show the secret password") -p_comment :: Parser String-p_comment = unwords <$> many p_word+p_session_name :: Parser SessionName+p_session_name =+    argument (Just . SessionName . T.pack)+        $  metavar "SESSION"+        <> help    "a session name" +p_store_fp :: Parser FilePath+p_store_fp =+    argument Just+        $  metavar "STORE"+        <> help    "file containing the password store to import"+ p_word :: Parser String p_word = argument Just $ metavar "WORD" -h_info :: Parser a -> InfoMod a -> ParserInfo a-h_info pr = O.info (helper <*> pr)+-- run_parse  run_parse :: ParserInfo a -> [String] -> IO a run_parse pinfo args =@@ -1018,3 +1254,21 @@       msg   <- execCompletion compl progn       putStr msg       exitWith ExitSuccess++-- plus helpers++parse_plus_pw :: PW p => PMConfig p -> String -> Maybe PasswordName+parse_plus_pw pmc s_ = case s_ of+  '+':s | isJust $ _pmc_plus_env_var pmc pnm+    -> Just pnm+    where+      pnm = PasswordName $ T.pack s+  _ -> Nothing++plussify :: PasswordName -> PasswordName+plussify = PasswordName . (T.cons '+') . _PasswordName++is_plus :: PasswordName -> Maybe PasswordName+is_plus pnm = case T.unpack $ _PasswordName pnm of+  '+':s -> Just $ PasswordName $ T.pack s+  _     -> Nothing
src/Data/KeyStore/Version.hs view
@@ -6,4 +6,4 @@     (a,b,c,d) = versionTuple  versionTuple :: (Int,Int,Int,Int)-versionTuple = (0,5,1,1)+versionTuple = (0,6,0,0)