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 +3/−0
- LICENSE +165/−0
- README.md +56/−0
- Setup.hs +2/−0
- app/Main.hs +70/−0
- app/Types.hs +52/−0
- app/UI.hs +251/−0
- app/Util.hs +122/−0
- passman.cabal +102/−0
- src/Password.hs +380/−0
- test/Spec.hs +66/−0
- test/Spec/JSON.hs +55/−0
- test/Spec/NewPWData.hs +45/−0
- test/Spec/NewPWDatabase.hs +32/−0
- test/Spec/NewPWPolicy.hs +40/−0
- test/Spec/NewPWSalt.hs +44/−0
- test/Spec/PWGenerate.hs +99/−0
- test/Spec/PWGetService.hs +57/−0
- test/Spec/PWHasService.hs +54/−0
- test/Spec/PWRemoveService.hs +67/−0
- test/Spec/PWSearch.hs +57/−0
- test/Spec/PWSetService.hs +73/−0
- test/Spec/ValidatePWData.hs +49/−0
- test/Spec/ValidatePWDatabase.hs +54/−0
- test/Spec/ValidatePWPolicy.hs +65/−0
+ 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