packages feed

passman (empty) → 0.1

raw patch · 25 files changed

+2060/−0 lines, 25 filesdep +HCLdep +HUnitdep +SHAsetup-changed

Dependencies added: HCL, HUnit, SHA, aeson, base, base64-bytestring, bytestring, containers, lens, passman, random, text, transformers

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for passman++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,165 @@+                   GNU LESSER GENERAL PUBLIC LICENSE+                       Version 3, 29 June 2007++ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.+++  This version of the GNU Lesser General Public License incorporates+the terms and conditions of version 3 of the GNU General Public+License, supplemented by the additional permissions listed below.++  0. Additional Definitions.++  As used herein, "this License" refers to version 3 of the GNU Lesser+General Public License, and the "GNU GPL" refers to version 3 of the GNU+General Public License.++  "The Library" refers to a covered work governed by this License,+other than an Application or a Combined Work as defined below.++  An "Application" is any work that makes use of an interface provided+by the Library, but which is not otherwise based on the Library.+Defining a subclass of a class defined by the Library is deemed a mode+of using an interface provided by the Library.++  A "Combined Work" is a work produced by combining or linking an+Application with the Library.  The particular version of the Library+with which the Combined Work was made is also called the "Linked+Version".++  The "Minimal Corresponding Source" for a Combined Work means the+Corresponding Source for the Combined Work, excluding any source code+for portions of the Combined Work that, considered in isolation, are+based on the Application, and not on the Linked Version.++  The "Corresponding Application Code" for a Combined Work means the+object code and/or source code for the Application, including any data+and utility programs needed for reproducing the Combined Work from the+Application, but excluding the System Libraries of the Combined Work.++  1. Exception to Section 3 of the GNU GPL.++  You may convey a covered work under sections 3 and 4 of this License+without being bound by section 3 of the GNU GPL.++  2. Conveying Modified Versions.++  If you modify a copy of the Library, and, in your modifications, a+facility refers to a function or data to be supplied by an Application+that uses the facility (other than as an argument passed when the+facility is invoked), then you may convey a copy of the modified+version:++   a) under this License, provided that you make a good faith effort to+   ensure that, in the event an Application does not supply the+   function or data, the facility still operates, and performs+   whatever part of its purpose remains meaningful, or++   b) under the GNU GPL, with none of the additional permissions of+   this License applicable to that copy.++  3. Object Code Incorporating Material from Library Header Files.++  The object code form of an Application may incorporate material from+a header file that is part of the Library.  You may convey such object+code under terms of your choice, provided that, if the incorporated+material is not limited to numerical parameters, data structure+layouts and accessors, or small macros, inline functions and templates+(ten or fewer lines in length), you do both of the following:++   a) Give prominent notice with each copy of the object code that the+   Library is used in it and that the Library and its use are+   covered by this License.++   b) Accompany the object code with a copy of the GNU GPL and this license+   document.++  4. Combined Works.++  You may convey a Combined Work under terms of your choice that,+taken together, effectively do not restrict modification of the+portions of the Library contained in the Combined Work and reverse+engineering for debugging such modifications, if you also do each of+the following:++   a) Give prominent notice with each copy of the Combined Work that+   the Library is used in it and that the Library and its use are+   covered by this License.++   b) Accompany the Combined Work with a copy of the GNU GPL and this license+   document.++   c) For a Combined Work that displays copyright notices during+   execution, include the copyright notice for the Library among+   these notices, as well as a reference directing the user to the+   copies of the GNU GPL and this license document.++   d) Do one of the following:++       0) Convey the Minimal Corresponding Source under the terms of this+       License, and the Corresponding Application Code in a form+       suitable for, and under terms that permit, the user to+       recombine or relink the Application with a modified version of+       the Linked Version to produce a modified Combined Work, in the+       manner specified by section 6 of the GNU GPL for conveying+       Corresponding Source.++       1) Use a suitable shared library mechanism for linking with the+       Library.  A suitable mechanism is one that (a) uses at run time+       a copy of the Library already present on the user's computer+       system, and (b) will operate properly with a modified version+       of the Library that is interface-compatible with the Linked+       Version.++   e) Provide Installation Information, but only if you would otherwise+   be required to provide such information under section 6 of the+   GNU GPL, and only to the extent that such information is+   necessary to install and execute a modified version of the+   Combined Work produced by recombining or relinking the+   Application with a modified version of the Linked Version. (If+   you use option 4d0, the Installation Information must accompany+   the Minimal Corresponding Source and Corresponding Application+   Code. If you use option 4d1, you must provide the Installation+   Information in the manner specified by section 6 of the GNU GPL+   for conveying Corresponding Source.)++  5. Combined Libraries.++  You may place library facilities that are a work based on the+Library side by side in a single library together with other library+facilities that are not Applications and are not covered by this+License, and convey such a combined library under terms of your+choice, if you do both of the following:++   a) Accompany the combined library with a copy of the same work based+   on the Library, uncombined with any other library facilities,+   conveyed under the terms of this License.++   b) Give prominent notice with the combined library that part of it+   is a work based on the Library, and explaining where to find the+   accompanying uncombined form of the same work.++  6. Revised Versions of the GNU Lesser General Public License.++  The Free Software Foundation may publish revised and/or new versions+of the GNU Lesser General Public License from time to time. Such new+versions will be similar in spirit to the present version, but may+differ in detail to address new problems or concerns.++  Each version is given a distinguishing version number. If the+Library as you received it specifies that a certain numbered version+of the GNU Lesser General Public License "or any later version"+applies to it, you have the option of following the terms and+conditions either of that published version or of any later version+published by the Free Software Foundation. If the Library as you+received it does not specify a version number of the GNU Lesser+General Public License, you may choose any version of the GNU Lesser+General Public License ever published by the Free Software Foundation.++  If the Library as you received it specifies that a proxy can decide+whether future versions of the GNU Lesser General Public License shall+apply, that proxy's public statement of acceptance of any version is+permanent authorization for you to choose that version for the+Library.
+ README.md view
@@ -0,0 +1,56 @@+# passman++Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++## Executive Summary++This package is a very paranoid password manager.  It consists of two+parts: a front-end command-line interface, and a back-end library+(which can be used to create prettier interfaces).++It is important to note that there is no password recovery service.+If you enter an incorrect master password, you will simply get+incorrect passwords from the database.  This is by design.  It makes+the password database much more resistant to a brute-force attack.+DON'T FORGET YOUR MASTER PASSWORD.++Once installed, the program can be run by typing `passman` in your+terminal.++## Installing++This package uses [Haskell Stack](https://haskellstack.org).  Please+refer to [their+website](https://docs.haskellstack.org/en/stable/README/#how-to-install)+for instructions on installing Haskell Stack.  Once you have done+this, you can simply enterg the command `stack install passman` in the+terminal to install passman.++## GitHub++The most recent version of passman can be found on GitHub at+<https://github.com/jlamothe/passman>.++## Pull Requests++Pull requests are welcome, but should be made to the `dev` branch.++## Donations++Bitcoin donations are accepted (but not required) at:+18hqEsXCinyauDp6smPUEVuscjDdasTKvr
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,70 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Main where++import Control.Monad (mapM_)+import Control.Monad.Trans.State as S+import Data.Maybe (maybe)+import System.Console.HCL (Request, reqFail, reqIO, runRequest)+import System.Environment (lookupEnv)+import System.Random (getStdGen)++import Password++import Types+import UI+import Util++main :: IO ()+main = runRequest setup >>= mapM_ (S.evalStateT mainMenu)++setup :: Request Status+setup = do+  g  <- reqIO getStdGen+  p  <- getDBPath+  db <- loadFrom p+  pw <- getMasterPass+  return $ Status g pw p db++getDBPath :: Request FilePath+getDBPath = reqIO (lookupEnv "HOME") >>= maybe+  (do+    reqIO $ putStrLn "ERROR: can't find home directory"+    reqFail)+  (\home -> case pathDelim home of+    Nothing -> do+      reqIO $ putStrLn "ERROR: unsupported home path"+      reqFail+    Just delim -> return $ home +++      (if last home == delim then "" else [delim]) +++      ".passman.json")++pathDelim :: FilePath -> Maybe Char+pathDelim = foldr+  (\x a -> case x of+    '/'  -> Just '/'+    '\\' -> Just '\\'+    _    -> a)+  Nothing++--jl
+ app/Types.hs view
@@ -0,0 +1,52 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++{-# LANGUAGE TemplateHaskell #-}++module Types (Status (Status), gen, dbPath, masterPass, database) where++import Control.Lens (makeLenses, set, (^.))+import System.Random (RandomGen (next, split), StdGen)++import Password++data Status = Status+  { _gen        :: StdGen+  , _masterPass :: String+  , _dbPath     :: FilePath+  , _database   :: PWDatabase+  }++makeLenses ''Status++instance RandomGen Status where+  next s = (x, s') where+    (x, g') = next g+    s' = set gen g' s+    g = s^.gen+  split s = (s1, s2) where+    s1 = set gen g1 s+    s2 = set gen g2 s+    (g1, g2) = split g+    g = s^.gen++--jl
+ app/UI.hs view
@@ -0,0 +1,251 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module UI (getMasterPass, mainMenu) where++import Control.Applicative ((<|>))+import Control.Lens (over, set, view, (^.))+import Control.Monad (when)+import Control.Monad.Trans.Class (lift)+import qualified Control.Monad.Trans.State as S+import Data.Maybe (maybe)+import System.Console.HCL+  ( Request+  , prompt+  , reqDefault+  , reqFail+  , reqIf+  , reqInt+  , reqIO+  , reqPassword+  , reqResp+  , required+  , runRequest+  )++import Password++import Types+import Util++getMasterPass :: Request String+getMasterPass = do+  p1 <- required $ prompt "master password: " reqPassword+  p2 <- required $ prompt "confirm master password: " reqPassword+  if p1 /= p2+    then do+      reqIO $ putStrLn "The passwords you entered do not match."+      reqFail+    else return p1++mainMenu :: S.StateT Status IO ()+mainMenu =+  menu "Main Menu"+    [ ( "add a password",         addPassword      )+    , ( "view/edit a password",   viewEditMenu     )+    , ( "change master password", changeMasterPass )+    , ( "lock session",           lockSession      )+    , ( "quit",                   quit             )+    ]++addPassword :: S.StateT Status IO ()+addPassword = do+  svc <- req $ prompt "service name: " reqResp+  ifServExists svc+    (do+      edit <- req (confirm $+        "The service already exists in the database.\n" +++        "Would you like to edit it?")+      if edit+        then servMenu svc+        else mainMenu)+    (do+      d <- buildData+      setService svc d+      showPass svc+      servMenu svc)++viewEditMenu :: S.StateT Status IO ()+viewEditMenu = menu "View/Edit Password"+  [ ( "search services", searchServ )+  , ( "list services",   listServ   )+  , ( "cancel",          mainMenu   )+  ]++changeMasterPass :: S.StateT Status IO ()+changeMasterPass = do+  oldP <- S.gets $ view masterPass+  newP <- req $ reqDefault getMasterPass oldP+  S.modify $ set masterPass newP+  mainMenu++lockSession :: S.StateT Status IO ()+lockSession = do+  lift $ putStrLn "\nThe session is locked."+  pass <- S.gets $ view masterPass+  x <- req $ prompt "master password: " reqPassword+  if x == pass+    then mainMenu+    else lockSession++quit :: S.StateT Status IO ()+quit = save++buildData :: S.StateT Status IO PWData+buildData = do+  d <- run newPWData+  req $ reqIf (confirm "Would you like to change the password policy?")+    (do+      let p = d^.pwPolicy+      p' <- reqDefault (editPolicy p) p+      return $ set pwPolicy p' d)+    (return d)++searchServ :: S.StateT Status IO ()+searchServ = do+  svc <- req $ prompt "service name: " reqResp+  db  <- S.gets $ view database+  case pwSearch svc db of+    []  -> do+      lift $ putStrLn "\nThe service could not be found in the database."+      mainMenu+    [x] -> servMenu x+    xs  -> selectServ xs++listServ :: S.StateT Status IO ()+listServ = S.gets (view database) >>= selectServ . pwSearch ""++selectServ :: [String] -> S.StateT Status IO ()+selectServ xs = menu "Select Service" $+  map (\x -> (x, servMenu x)) xs +++  [("(cancel)", mainMenu)]++servMenu :: String -> S.StateT Status IO ()+servMenu x = menu x+  [ ( "show password",  showPass x >> servMenu x )+  , ( "edit password",  editPassMenu x           )+  , ( "remove service", removeServ x             )+  , ( "rename service", renameServ x             )+  , ( "back",           mainMenu                 )+  ]++editPassMenu :: String -> S.StateT Status IO ()+editPassMenu x = menu (x ++ " : Edit Password")+  [ ( "generate new password", changeSalt x   )+  , ( "edit password policy",  doEditPolicy x )+  , ( "back",                  servMenu x     )+  ]++removeServ :: String -> S.StateT Status IO ()+removeServ x = do+  go <- req $ confirm $+    "Are you sure you want to delete the password for " ++ x ++ "?"+  if go+    then do+      removeServ' x+      mainMenu+    else servMenu x++removeServ' :: String -> S.StateT Status IO ()+removeServ' = S.modify . over database . pwRemoveService++renameServ :: String -> S.StateT Status IO ()+renameServ x = do+  y <- req $ prompt "new service name: " reqResp+  if x == y+    then servMenu x+    else ifServExists y+      (do+        overwrite <- req $ confirm $+          y ++ " already exists.\n" +++          "Would you like to overwrite it?"+        if overwrite+          then renameServ' x y+          else servMenu x)+      (renameServ' x y)++renameServ' :: String -> String -> S.StateT Status IO ()+renameServ' x y = withService x mainMenu $ \d -> do+  removeServ' x+  setService y d+  servMenu y++changeSalt :: String -> S.StateT Status IO ()+changeSalt x = withService x mainMenu $ \d -> do+  salt <- run newPWSalt+  setService x $ set pwSalt salt d+  showPass x+  editPassMenu x++doEditPolicy :: String -> S.StateT Status IO ()+doEditPolicy x = withService x mainMenu $ \d -> do+  let p = d^.pwPolicy+  p' <- req $ reqDefault (editPolicy p) p+  setService x $ set pwPolicy p' d+  showPass x+  editPassMenu x++showPass :: String -> S.StateT Status IO ()+showPass x = do+  lift $ putStrLn ""+  withService x+    (lift $ putStrLn "The service could not be found in the database.") $+    \d -> do+      pw <- S.gets $ view masterPass+      lift $ putStrLn $ case pwGenerate pw d of+        Nothing -> "The password data were not valid."+        Just pw -> "password for " ++ x ++ ": " ++ pw++-- TODO: refactor this monstrosity+editPolicy :: PWPolicy -> Request PWPolicy+editPolicy p = do+  p <- edit "length" (p^.pwLength) pwLength p+  p <- edit "min upper case"  (p^.pwUpper) pwUpper p+  p <- edit "min lower case" (p^.pwLower) pwLower p+  p <- edit "min digits" (p^.pwDigits) pwDigits p+  p <- special p+  if validatePWPolicy p+    then return p+    else do+      reqIO $ putStrLn $+        "\nThe password policy you entered is invalid\n." +++        "It will not be changed."+      reqFail+  where+    edit l v t p = do+      v <- reqDefault+        (prompt ("new " ++ l ++ " (default " ++ show v ++ "): ") reqInt) v+      return $ set t v p+    special p = do+      reqIO $ putStrLn $ "Special characters are currently " +++        (case p^.pwSpecial of+          Nothing -> "not "+          Just _  -> "") ++ "allowed."+      reqIf (confirm "Would you like to allow special characters?")+        (case p^.pwSpecial of+          Nothing -> do+            x <- required $ prompt "min special chars: " reqInt+            return $ set pwSpecial (Just x) p+          Just x -> edit "min special chars" x (pwSpecial.traverse) p)+        (return $ set pwSpecial Nothing p)++--jl
+ app/Util.hs view
@@ -0,0 +1,122 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Util+  ( menu+  , run+  , withService+  , ifServExists+  , setService+  , req+  , tryReq+  , confirm+  , loadFrom+  , save+  ) where++import Control.Lens (over, view)+import Control.Monad (join)+import Control.Monad.Trans.Class (lift)+import qualified Control.Monad.Trans.State as S+import Data.Aeson (decodeFileStrict, encodeFile)+import Data.Maybe (fromJust)+import System.Console.HCL+  ( Request+  , prompt+  , reqAgree+  , reqChar+  , reqDefault+  , reqIf+  , reqIO+  , reqMenu+  , required+  , runRequest+  )++import Password++import Types++menu+  :: String+  -> [(String, S.StateT Status IO a)]+  -> S.StateT Status IO a+menu title = reqState . prompt ("\n*** " ++ title ++ " ***") .+  reqMenu . map menuItem++menuItem :: (String, a) -> (String, Request a)+menuItem (str, x) = (str, return x)++reqState :: Request (S.StateT s IO a) -> S.StateT s IO a+reqState = join . req++run :: Monad m => (s -> (a, s)) -> S.StateT s m a+run f = S.StateT $ return . f++withService+  :: String+  -> S.StateT Status IO a+  -> (PWData -> S.StateT Status IO a)+  -> S.StateT Status IO a+withService srv fb act = do+  db <- S.gets $ view database+  case pwGetService srv db of+    Nothing -> fb+    Just x  -> act x++ifServExists+  :: String+  -> S.StateT Status IO a+  -> S.StateT Status IO a+  -> S.StateT Status IO a+ifServExists s x y = do+  db <- S.gets $ view database+  if pwHasService s db+    then x+    else y++setService :: String -> PWData -> S.StateT Status IO ()+setService k = S.modify . over database . pwSetService k++req :: Request a -> S.StateT s IO a+req = lift . fmap fromJust . runRequest . required++tryReq :: Request a -> S.StateT s IO (Maybe a)+tryReq = lift . runRequest++confirm :: String -> Request Bool+confirm x = prompt (x ++ " (y/n): ") $ reqAgree Nothing $ fmap return reqChar++loadFrom :: FilePath -> Request PWDatabase+loadFrom path = reqDefault+  (reqIO (decodeFileStrict path))+  (Just newPWDatabase) >>= maybe+  (return newPWDatabase)+  return++save :: S.StateT Status IO ()+save = do+  path <- S.gets $ view dbPath+  db   <- S.gets $ view database+  lift $ encodeFile path db++--jl
+ passman.cabal view
@@ -0,0 +1,102 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.1.+--+-- see: https://github.com/sol/hpack+--+-- hash: a473235a2ea1c6b4f7ae03fe7d61721790a46db852b52e49ea852a4b47abdd03++name:           passman+version:        0.1+synopsis:       a simple password manager+description:    Please see the README on GitHub at <https://github.com/jlamothe/passman#readme>+category:       Security+homepage:       https://github.com/jlamothe/passman#readme+bug-reports:    https://github.com/jlamothe/passman/issues+author:         Jonathan Lamothe+maintainer:     jlamothe1980@gmail.com+copyright:      (C) 2018 Jonathan Lamothe+license:        LGPL-3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/jlamothe/passman++library+  exposed-modules:+      Password+  other-modules:+      Paths_passman+  hs-source-dirs:+      src+  build-depends:+      SHA+    , aeson+    , base >=4.7 && <5+    , base64-bytestring+    , bytestring+    , containers+    , lens+    , random+    , text+  default-language: Haskell2010++executable passman+  main-is: Main.hs+  other-modules:+      Types+      UI+      Util+      Paths_passman+  hs-source-dirs:+      app+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      HCL >=1.7.1 && <2+    , aeson+    , base >=4.7 && <5+    , bytestring+    , containers+    , lens+    , passman+    , random+    , transformers+  default-language: Haskell2010++test-suite passman-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Spec.JSON+      Spec.NewPWData+      Spec.NewPWDatabase+      Spec.NewPWPolicy+      Spec.NewPWSalt+      Spec.PWGenerate+      Spec.PWGetService+      Spec.PWHasService+      Spec.PWRemoveService+      Spec.PWSearch+      Spec.PWSetService+      Spec.ValidatePWData+      Spec.ValidatePWDatabase+      Spec.ValidatePWPolicy+      Paths_passman+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      HUnit+    , aeson+    , base >=4.7 && <5+    , bytestring+    , containers+    , lens+    , passman+    , random+  default-language: Haskell2010
+ src/Password.hs view
@@ -0,0 +1,380 @@+{-|++Module:      Password+Description: a simple password manager+Copyright:   (C) 2018 Jonathan Lamothe+License:     LGPLv3 (or later)+Maintainer:  jlamothe1980@gmail.com++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE  OverloadedStrings #-}++module Password (+  -- * Data Types+  PWDatabase, PWData(..), PWPolicy (..), PWSalt,+  -- ** Lenses+  -- $lenses+  -- *** PWData+  pwPolicy, pwSalt,+  -- *** PWPolicy+  pwLength, pwUpper, pwLower, pwDigits, pwSpecial,+  -- ** Default Instances+  newPWDatabase, newPWData, newPWPolicy, newPWSalt,+  -- ** Validations+  validatePWDatabase, validatePWData, validatePWPolicy,+  -- * Functions+  -- ** Password Generator+  pwGenerate,+  -- ** Password Checkers+  pwCountUpper, pwCountLower, pwCountDigits, pwCountSpecial, pwCount,+  -- ** Database Functions+  pwHasService, pwSetService, pwGetService, pwRemoveService, pwSearch+  ) where++import Control.Lens (makeLenses, over, set, (^.))+import Data.Aeson+  ( FromJSON (parseJSON)+  , ToJSON (toJSON)+  , Value (String)+  , object+  , withObject+  , withText+  , (.:)+  , (.=)+  )+import qualified Data.ByteString.Lazy as B+import qualified Data.ByteString.Base64.Lazy as B64+import Data.Char (isUpper, isLower, isDigit, isAlphaNum, toLower)+import Data.Digest.Pure.SHA+import qualified Data.Map as M+import Data.Maybe (fromMaybe)+import qualified Data.Text as T'+import qualified Data.Text.Lazy as T+import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)+import System.Random (RandomGen, randoms, split)++-- | a mapping of service names to password data+type PWDatabase = M.Map String PWData++-- | data necessary to construct a password+data PWData = PWData+  { _pwPolicy :: PWPolicy+  -- ^ the password policy+  , _pwSalt :: PWSalt+  -- ^ random data used to generate the password+  } deriving (Eq, Show)++-- | defines a password policy+data PWPolicy = PWPolicy+  { _pwLength :: Int+  -- ^ password length+  , _pwUpper :: Int+  -- ^ the minimum number of upper case characters+  , _pwLower :: Int+  -- ^ the minimum number of lower case characters+  , _pwDigits :: Int+  -- ^ the minimum number of digits+  , _pwSpecial :: Maybe Int+  -- ^ the minimum number of non-alphanumeric characters (not allowed+  -- if @"Nothing"@)+  } deriving (Eq, Show)++-- | the "salt" used to generate a password+type PWSalt = B.ByteString++-- $lenses The following functions are automatically generated by+-- @makeLenses@. See the+-- [lens](http://hackage.haskell.org/package/lens) package for further+-- details.++makeLenses ''PWPolicy+makeLenses ''PWData++instance FromJSON PWData where+  parseJSON = withObject "PWData" $ \v -> PWData+    <$> v .: "policy"+    <*> v .: "salt"++instance FromJSON PWPolicy where+  parseJSON = withObject "PWPolicy" $ \v -> PWPolicy+    <$> v .: "length"+    <*> v .: "min_upper"+    <*> v .: "min_lower"+    <*> v .: "min_digits"+    <*> v .: "min_special"++instance FromJSON B.ByteString where+  parseJSON = withText "ByteString" $ \v ->+    case B64.decode $ encodeUtf8 $ T.pack $ T'.unpack v of+      Left x  -> fail x+      Right x -> return x++instance ToJSON PWData where+  toJSON d = object+    [ "policy" .= (d^.pwPolicy)+    , "salt" .= (d^.pwSalt)+    ]++instance ToJSON PWPolicy where+  toJSON p = object+    [ "length" .= (p^.pwLength)+    , "min_upper" .= (p^.pwUpper)+    , "min_lower" .= (p^.pwLower)+    , "min_digits" .= (p^.pwDigits)+    , "min_special" .= (p^.pwSpecial)+    ]++instance ToJSON B.ByteString where+  toJSON = toJSON . toB64++-- | default (empty) password database+newPWDatabase :: PWDatabase+newPWDatabase = M.empty++-- | builds a new @'PWData'@+newPWData+  :: RandomGen g+  => g+  -- ^ the random generator to use+  -> (PWData, g)+  -- ^ the result and new random generator+newPWData g = (result, g') where+  result = PWData newPWPolicy salt+  (salt, g') = newPWSalt g++-- | default password policy+newPWPolicy :: PWPolicy+newPWPolicy = PWPolicy 16 0 0 0 (Just 0)++-- | builds a new salt+newPWSalt+  :: RandomGen g+  => g+  -- ^ the random generator to use+  -> (PWSalt, g)+  -- ^ the result and new random generator+newPWSalt g = (result, g2) where+  result = B.pack $ take 32 $ randoms g1+  (g1, g2) = split g++-- | validates a password database+validatePWDatabase+  :: PWDatabase+  -- ^ the database to be validated+  -> Bool+  -- ^ @"True"@ if valid; @"False"@ otherwise+validatePWDatabase = all validatePWData++-- | validates password data+validatePWData+  :: PWData+  -- ^ the data to be validated+  -> Bool+  -- ^ @"True"@ if valid; @"False"@ otherwise+validatePWData x =+  validatePWPolicy (x^.pwPolicy) &&+  B.length (x^.pwSalt) > 0++-- | validates a password policy+validatePWPolicy+  :: PWPolicy+  -- ^ the policy being validated+  -> Bool+  -- ^ indicates whether or not the policy is valid+validatePWPolicy x = and+  [ needed <= x^.pwLength+  , x^.pwLength >= 0+  , x^.pwUpper >= 0+  , x^.pwLower >= 0+  , x^.pwDigits >= 0+  , fromMaybe 0 (x^.pwSpecial) >= 0+  ] where+    needed = x^.pwUpper + x^.pwLower + x^.pwDigits + special+    special = fromMaybe 0 $ x^.pwSpecial++-- | generates a password+pwGenerate+  :: String+  -- ^ the master password+  -> PWData+  -- ^ the password parameters+  -> Maybe String+  -- ^ the resulting password, if possible; @"Nothing"@ if the data is+  -- invalid+pwGenerate pw d = if validatePWData d+  then Just $ mkPass (mkPool seed) (d^.pwPolicy)+  else Nothing+  where seed = mkSeed pw d++-- | counts upper case characters in a password+pwCountUpper+  :: String+  -- ^ the password+  -> Int+  -- ^ the count+pwCountUpper = pwCount isUpper++-- | counts lower case characters in a password+pwCountLower+  :: String+  -- ^ the password+  -> Int+  -- ^ the count+pwCountLower = pwCount isLower++-- | counts digits in a password+pwCountDigits+  :: String+  -- ^ the password+  -> Int+  -- ^ the count+pwCountDigits = pwCount isDigit++-- | counts special characters in a password+pwCountSpecial+  :: String+  -- ^ the password+  -> Int+  -- ^ the count+pwCountSpecial = pwCount isSpecial++-- | counts characters matching a specific constraint+pwCount+  :: (Char -> Bool)+  -- ^ the constraint+  -> String+  -- ^ the string being checked+  -> Int+  -- ^ the count+pwCount f = length . filter f++-- | checks to see if a service is in the database+pwHasService+  :: String+  -- ^ the service name+  -> PWDatabase+  -- ^ the database to check+  -> Bool+  -- ^ returns @"True"@ if found; @"False"@ otherwise+pwHasService x db = elem x $ M.keys db++-- | adds a service to the database, or overwrites an existing one+pwSetService+  :: String+  -- ^ the service name+  -> PWData+  -- ^ the password data for the service+  -> PWDatabase+  -- ^ the database to add to+  -> PWDatabase+  -- ^ the resulting database+pwSetService = M.insert++-- | attempts to get a service from the database+pwGetService+  :: String+  -- ^ the service name+  -> PWDatabase+  -- ^ the database to check+  -> Maybe PWData+  -- ^ the service's password data, or @"Nothing"@ if the service is+  -- not found+pwGetService = M.lookup++-- | removes a service from the database+pwRemoveService+  :: String+  -- ^ the service being removed+  -> PWDatabase+  -- ^ the database the service is being removed from+  -> PWDatabase+  -- ^ the resulting database+pwRemoveService = M.delete++-- | searches for a service+pwSearch+  :: String+  -- ^ the search string+  -> PWDatabase+  -- ^ the database to search+  -> [String]+  -- ^ the matching service names+pwSearch x db = filter (\y -> l y `contains` l x) $ M.keys db where+  l = map toLower++isSpecial :: Char -> Bool+isSpecial = not . isAlphaNum++mkPass :: String -> PWPolicy -> String+mkPass (x:xs) p = let p' = nextPolicy x p in+  if p^.pwLength <= 0+  then ""+  else if validatePWPolicy p'+  then x : mkPass xs p'+  else mkPass xs p++mkPool :: B.ByteString -> String+mkPool = toB64 . raw where+  raw x = let x' = mkHash x in+    x' `B.append` raw x++mkSeed :: String -> PWData -> B.ByteString+mkSeed pw d = toUTF8 pw `B.append` (d^.pwSalt)++mkHash :: B.ByteString -> B.ByteString+mkHash = raw . show . sha256 where+  raw (x:y:xs) = read ("0x" ++ [x] ++ [y]) `B.cons` raw xs+  raw [_] = error "odd number of hex digits in hash"+  raw "" = B.empty++nextPolicy :: Char -> PWPolicy -> PWPolicy+nextPolicy x p = over pwLength pred $+  if isUpper x+  then dec pwUpper+  else if isLower x+  then dec pwLower+  else if isDigit x+  then dec pwDigits+  else case p^.pwSpecial of+    Nothing -> set pwSpecial (Just (-1)) p+    Just _  -> dec $ pwSpecial . traverse+  where+    dec l = over l (max 0 . pred) p++toUTF8 :: String -> B.ByteString+toUTF8 = encodeUtf8 . T.pack++toB64 :: B.ByteString -> String+toB64 = T.unpack . decodeUtf8 . B64.encode++contains :: String -> String -> Bool+_ `contains` "" = True+"" `contains` _ = False+xs@(x:xs') `contains` ys+  | xs `startsWith` ys = True+  | otherwise = xs' `contains` ys++startsWith :: String -> String -> Bool+_ `startsWith` "" = True+"" `startsWith` _ = False+(x:xs) `startsWith` (y:ys)+  | x == y = xs `startsWith` ys+  | otherwise = False++--jl
+ test/Spec.hs view
@@ -0,0 +1,66 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Main where++import Control.Monad (when)+import System.Exit (exitFailure)+import Test.HUnit (errors, failures, runTestTT, Test(TestList))++import qualified Spec.JSON as JSON+import qualified Spec.NewPWData as NewPWData+import qualified Spec.NewPWDatabase as NewPWDatabase+import qualified Spec.NewPWPolicy as NewPWPolicy+import qualified Spec.NewPWSalt as NewPWSalt+import qualified Spec.PWGenerate as PWGenerate+import qualified Spec.PWGetService as PWGetService+import qualified Spec.PWHasService as PWHasService+import qualified Spec.PWRemoveService as PWRemoveService+import qualified Spec.PWSearch as PWSearch+import qualified Spec.PWSetService as PWSetService+import qualified Spec.ValidatePWData as ValidatePWData+import qualified Spec.ValidatePWDatabase as ValidatePWDatabase+import qualified Spec.ValidatePWPolicy as ValidatePWPolicy++main = do+  counts <- runTestTT tests+  when (failures counts > 0 || errors counts > 0)+    exitFailure++tests = TestList+  [ NewPWDatabase.tests+  , NewPWData.tests+  , NewPWPolicy.tests+  , NewPWSalt.tests+  , ValidatePWDatabase.tests+  , ValidatePWData.tests+  , ValidatePWPolicy.tests+  , PWGenerate.tests+  , PWHasService.tests+  , PWSetService.tests+  , PWGetService.tests+  , PWSearch.tests+  , PWRemoveService.tests+  , JSON.tests+  ]++--jl
+ test/Spec/JSON.hs view
@@ -0,0 +1,55 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.JSON (tests) where++import Data.Aeson (eitherDecode, encode, decode)+import qualified Data.ByteString.Lazy as B+import qualified Data.Map as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "JSON" $ TestList [success, failure]++success = TestLabel "succeasful encoding/decoding" $+  eitherDecode (encode db) ~?= Right db++failure = TestLabel "decoding failure" $+  (decode B.empty :: Maybe PWDatabase) ~?= Nothing++db = M.fromList+  [ ( "foo", foo )+  , ( "bar", bar )+  , ( "baz", baz )+  ]++(foo, g') = newPWData g++(bar, g'') = newPWData g'++(baz, _) = newPWData g''++g = mkStdGen 1++--jl
+ test/Spec/NewPWData.hs view
@@ -0,0 +1,45 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.NewPWData (tests) where++import Control.Lens ((^.))+import System.Random (mkStdGen)+import Test.HUnit (Test  (..), (~?=))++import Password++tests = TestLabel "newPData" $ TestList+  [ testSalt x+  , testPolicy x+  ] where (x, _) = newPWData g++testSalt x = TestLabel "pwSalt" $+  x^.pwSalt ~?= salt where+    (salt, _) = newPWSalt g++testPolicy x = TestLabel "pwPolicy" $+  x^.pwPolicy ~?= newPWPolicy++g = mkStdGen 1++--jl
+ test/Spec/NewPWDatabase.hs view
@@ -0,0 +1,32 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.NewPWDatabase (tests) where++import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "newPWDatabase" $+  length newPWDatabase ~?= 0++--jl
+ test/Spec/NewPWPolicy.hs view
@@ -0,0 +1,40 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.NewPWPolicy (tests) where++import Control.Lens ((^.))+import Test.HUnit (Test(..), (~?=))++import Password++tests = TestLabel "PWPolicy" $ TestList $ map test'+  [ ( "pwLength",  newPWPolicy^.pwLength  ~?= 16     )+  , ( "pwUpper",   newPWPolicy^.pwUpper   ~?= 0      )+  , ( "pwLower",   newPWPolicy^.pwLower   ~?= 0      )+  , ( "pwDigits",  newPWPolicy^.pwDigits  ~?= 0      )+  , ( "pwSpecial", newPWPolicy^.pwSpecial ~?= Just 0 )+  ]++test' (label, x) = TestLabel label x++--jl
+ test/Spec/NewPWSalt.hs view
@@ -0,0 +1,44 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.NewPWSalt (tests) where++import qualified Data.ByteString.Lazy as B+import System.Random (mkStdGen)+import Test.HUnit (Test(..), assertBool, (~?=))++import Password++tests = TestLabel "newPWSalt" $ TestList+  [ testLength salt+  , testDiff salt salt'+  ] where+    (salt, g') = newPWSalt g+    (salt', _) = newPWSalt g'+    g = mkStdGen 1++testLength x = TestLabel "salt length" $ B.length x ~?= 32++testDiff x y = TestLabel "different generators" $ TestCase $+  assertBool "salts match" $ x /= y++--jl
+ test/Spec/PWGenerate.hs view
@@ -0,0 +1,99 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.PWGenerate (tests) where++import Control.Lens (set, (^.))+import Data.Maybe (fromJust)+import System.Random (mkStdGen)+import Test.HUnit+  (Test (..)+  , assertBool+  , assertEqual+  , assertFailure+  , (~?=)+  )++import Password++tests = TestLabel "pwGenerate" $ TestList+  [ defaultData+  , invalidPolicy+  , constraints+  , noSpecial+  , differentMaster+  ]++defaultData = TestLabel "default data" $ TestCase $+  case pwGenerate "foo" validData of+    Nothing -> assertFailure "no password generated"+    Just x  -> assertEqual "incorrect password length"+      (validData^.pwPolicy.pwLength) (length x)++invalidPolicy = TestLabel "invalid policy" $+  pwGenerate "foo" invalidPolicy' ~?= Nothing++constraints = TestLabel "strict constraints" $ TestCase $+  case pwGenerate "foo" constraints' of+    Nothing -> assertFailure "no password generated"+    Just x  -> do+      assertEqual "incorrect password length"+        (constraints'^.pwPolicy.pwLength) (length x)+      assertEqual "incorrect number of upper case"+        (constraints'^.pwPolicy.pwUpper) (pwCountUpper x)+      assertEqual "incorrect number of lower case"+        (constraints'^.pwPolicy.pwLower) (pwCountLower x)+      assertEqual "incorrect number of digita"+        (constraints'^.pwPolicy.pwDigits) (pwCountDigits x)+      assertEqual "incorrect number of special characters"+        (fromJust $ constraints'^.pwPolicy.pwSpecial) (pwCountSpecial x)++noSpecial = TestLabel "no special chars" $ TestCase $+  case pwGenerate "foo" noSpecial' of+    Nothing -> assertFailure "no password generated"+    Just x  -> do+      assertEqual "incorrect password length"+        (noSpecial'^.pwPolicy.pwLength) (length x)+      assertEqual "special characters found" 0 $ pwCountSpecial x++differentMaster = TestLabel "different master passwords" $ TestCase $+  assertBool "passwords match" $+  fromJust (pwGenerate "foo" validData) /=+  fromJust (pwGenerate "bar" validData)++(validData, _) = newPWData g++invalidPolicy' = set (pwPolicy.pwLength) (-1) validData++constraints' = set (pwPolicy.pwUpper) 4 $+  set (pwPolicy.pwLower) 4 $+  set (pwPolicy.pwDigits) 4 $+  set (pwPolicy.pwSpecial) (Just 4)+  validData++noSpecial' = set (pwPolicy.pwLength) 256 $+  set (pwPolicy.pwSpecial) Nothing+  validData++g = mkStdGen 1++--jl
+ test/Spec/PWGetService.hs view
@@ -0,0 +1,57 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.PWGetService (tests) where++import qualified Data.Map as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "pwGetService" $ TestList+  [ empty, found, notFound ]++empty = TestLabel "empty database" $+  pwGetService "foo" newPWDatabase ~?= Nothing++found = TestLabel "service found" $+  pwGetService "foo" db ~?= Just foo++notFound = TestLabel "service not found" $+  pwGetService "quux" db ~?= Nothing++db = M.fromList+  [ ( "foo", foo )+  , ( "bar", bar )+  , ( "baz", baz )+  ]++(foo, g') = newPWData g++(bar, g'') = newPWData g'++(baz, _) = newPWData g''++g = mkStdGen 1++--jl
+ test/Spec/PWHasService.hs view
@@ -0,0 +1,54 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.PWHasService (tests) where++import qualified Data.Map as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "pwHasService" $ TestList $ map test'+  [ ( "empty database", "foo",  newPWDatabase, False )+  , ( "in database",    "foo",  db,            True  )+  , ( "not found",      "quux", db,            False )+  ]++test' (label, x, db, expect) = TestLabel label $+  pwHasService x db ~?= expect++db = M.fromList+  [ ( "foo", foo )+  , ( "bar", bar )+  , ( "baz", baz )+  ]++(foo, g') = newPWData g++(bar, g'') = newPWData g'++(baz, _) = newPWData g''++g = mkStdGen 1++--jl
+ test/Spec/PWRemoveService.hs view
@@ -0,0 +1,67 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.PWRemoveService (tests) where++import qualified Data.Map.Lazy as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), assertBool, (~?=))++import Password++tests = TestLabel "pwRemoveService" $ TestList+  [ emptyDB+  , existingService+  , missingService+  ]++emptyDB = TestLabel "empty database" $+  pwRemoveService "foo" newPWDatabase ~?= newPWDatabase++existingService = TestLabel "existing service" $+  test' "foo" ["bar", "baz"]++missingService = TestLabel "missing service" $+  test' "quux" ["foo", "bar", "baz"]++test' serv keys = let db' = pwRemoveService serv db in+  TestList $+  TestLabel "key count" (length keys ~?= length (M.keys db')) :+  map+  (\x -> TestLabel x $ TestCase $ assertBool "service missing" $ pwHasService x db')+  keys++db = M.fromList+  [ ( "foo", foo )+  , ( "bar", bar )+  , ( "baz", baz )+  ]++(foo, g') = newPWData g++(bar, g'') = newPWData g'++(baz, _) = newPWData g''++g = mkStdGen 1++--jl
+ test/Spec/PWSearch.hs view
@@ -0,0 +1,57 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.PWSearch (tests) where++import qualified Data.Map as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), assertBool, (~?=))++import Password++tests = TestLabel "pwSearch" $ TestList $ map test'+  [ ( "no results",   "quux", []                    )+  , ( "some results", "A",    ["bar", "baz"]        )+  , ( "all results",  "",     ["foo", "bar", "baz"] )+  ]++test' (label, str, expect) = TestLabel label $ TestList $+  TestLabel "length" (length result ~?= length expect) :+  map (\x -> TestLabel ("has " ++ x) $ TestCase $+    assertBool "not found" $ elem x expect) result+  where result = pwSearch str db++db = M.fromList+  [ ( "foo", foo )+  , ( "bar", bar )+  , ( "baz", baz )+  ]++(foo, g') = newPWData g++(bar, g'') = newPWData g'++(baz, _) = newPWData g''++g = mkStdGen 1++--jl
+ test/Spec/PWSetService.hs view
@@ -0,0 +1,73 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.PWSetService (tests) where++import qualified Data.Map as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "pwSetService" $ TestList+  [ addToEmpty, addToNonEmpty, addToExisting ]++addToEmpty = tests' "empty database" newPWDatabase 1++addToNonEmpty = tests' "non-empty database" nonEmpty 3++addToExisting = tests' "existing database" existing 3++tests' label db size = TestLabel label $ TestList+  [ dbSize result size+  , find result+  ] where+    result = pwSetService "foo" foo db++dbSize db expect = TestLabel "database size" $+  length db ~?= expect++find db = TestLabel "record" $+  M.lookup "foo" db ~?= Just foo++nonEmpty = M.fromList+  [ ( "bar", bar )+  , ( "baz", baz )+  ]++existing = M.fromList+  [ ( "foo", foo' )+  , ( "bar", bar  )+  , ( "baz", baz  )+  ]++(foo, g1) = newPWData g++(foo', g2) = newPWData g1++(bar, g3) = newPWData g2++(baz, _) = newPWData g3++g = mkStdGen 1++--jl
+ test/Spec/ValidatePWData.hs view
@@ -0,0 +1,49 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.ValidatePWData (tests) where++import Control.Lens (set)+import qualified Data.ByteString.Lazy as B+import System.Random (mkStdGen)+import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "validatePWData" $ TestList $ map test'+  [ ( "valid",          new,           True  )+  , ( "invalid policy", invalidPolicy, False )+  , ( "invalid salt",   invalidSalt,   False )+  ]++test' (label, x, expect) = TestLabel label $+  validatePWData x ~?= expect++(new, _) = newPWData g++invalidPolicy = set (pwPolicy.pwLength) (-1) new++invalidSalt = set pwSalt B.empty new++g = mkStdGen 1++--jl
+ test/Spec/ValidatePWDatabase.hs view
@@ -0,0 +1,54 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.ValidatePWDatabase (tests) where++import Control.Lens (set)+import qualified Data.Map as M+import System.Random (mkStdGen)+import Test.HUnit (Test (..), (~?=))++import Password++tests = TestLabel "validatePWDatabase" $ TestList $ map test'+  [ ( "empty",       newPWDatabase, True  )+  , ( "valid",       validDB,       True  )+  , ( "foo invalid", fooInvalid,    False )+  , ( "bar invalid", barInvalid,    False )+  ]++test' (label, x, expect) = TestLabel label $+  validatePWDatabase x ~?= expect++validDB = M.fromList [("foo", validData), ("bar", validData)]++fooInvalid = M.insert "foo" invalidData validDB++barInvalid = M.insert "bar" invalidData validDB++(validData, _) = newPWData g++invalidData = set (pwPolicy.pwLength) (-1) validData++g = mkStdGen 1++--jl
+ test/Spec/ValidatePWPolicy.hs view
@@ -0,0 +1,65 @@+{-++passman+Copyright (C) 2018 Jonathan Lamothe+<jlamothe1980@gmail.com>++This program is free software: you can redistribute it and/or modify+it under the terms of the GNU Lesser General Public License as+published by the Free Software Foundation, either version 3 of the+License, or (at your option) any later version.++This program is distributed in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this program.  If not, see+<https://www.gnu.org/licenses/>.++-}++module Spec.ValidatePWPolicy (tests) where++import Control.Lens (set)+import Test.HUnit (Test(..), (~?=))++import Password++tests = TestLabel "validatePWPolicy" $ TestList $ map test'+  [ ( "default",          id,                        True  )+  , ( "no special chars", set pwSpecial Nothing,     True  )+  , ( "valid minimums",   validMins,                 True  )+  , ( "excessive upper",  set pwUpper 99,            False )+  , ( "excessive lower",  set pwLower 99,            False )+  , ( "excessive digits", set pwDigits 99,           False )+  , ( "excessive total",  excessive,                 False )+  , ( "short",            set pwLength 8,            True  )+  , ( "short valid",      shortValid,                True  )+  , ( "short invalid",    shortInvalid,              False )+  , ( "negative length",  set pwLength (-1),         False )+  , ( "negative upper",   set pwUpper (-1),          False )+  , ( "negative lower",   set pwLower (-1),          False )+  , ( "negative digits",  set pwDigits (-1),         False )+  , ( "negative special", set pwSpecial (Just (-1)), False )+  ]++test' (label, f, expect) = TestLabel label $+  validatePWPolicy x ~?= expect where+  x = f newPWPolicy++validMins = setAll 1++excessive = setAll 5++shortValid = set pwLength 8 . setAll 2++shortInvalid = set pwLength 8 . set pwUpper 9++setAll x = set pwUpper x .+  set pwLower x .+  set pwDigits x .+  set pwSpecial (Just x)++--jl