packages feed

elocrypt 2.0.1 → 2.1.0

raw patch · 11 files changed

+295/−281 lines, 11 filesdep +containersPVP ok

version bump matches the API change (PVP)

Dependencies added: containers

API changes (from Hackage documentation)

- Data.Elocrypt: newtype GenOptions
+ Data.Elocrypt: [genDigits] :: GenOptions -> Bool
+ Data.Elocrypt: [genSpecials] :: GenOptions -> Bool
+ Data.Elocrypt: data GenOptions
+ Data.Elocrypt: numerize1 :: MonadRandom m => Int -> String -> m String
+ Data.Elocrypt: numerizeR :: MonadRandom m => Int -> String -> m String
+ Data.Elocrypt: specialize1 :: MonadRandom m => Int -> String -> m String
+ Data.Elocrypt: specializeR :: MonadRandom m => Int -> String -> m String
+ Data.Elocrypt.Utils: isSymbol :: Char -> Bool
+ Data.Elocrypt.Utils: numeralConversions :: Map Char [Char]
+ Data.Elocrypt.Utils: symbolConversions :: Map Char [Char]
+ Data.Elocrypt.Utils: toDigit :: Char -> String
+ Data.Elocrypt.Utils: toSymbol :: Char -> String
+ Data.Elocrypt.Utils: update1 :: Monad m => (Char -> m Char) -> String -> Int -> m String
+ Data.Elocrypt.Utils: updateR :: MonadRandom m => (Char -> m Char) -> Rational -> String -> m String
- Data.Elocrypt: GenOptions :: Bool -> GenOptions
+ Data.Elocrypt: GenOptions :: Bool -> Bool -> Bool -> GenOptions

Files

README.md view
@@ -5,8 +5,8 @@  ## Prerequisites In order to build or install you will need- * GHC (tested on 8.0)- * cabal-install (tested on 1.24)+ * GHC (tested on 8.6.5)+ * Stack (tested on 2.1.3.1)  ## Installing Elocrypt is on [Hackage](https://hackage.haskell.org/package/elocrypt).  Installation is as easy as:@@ -15,8 +15,8 @@ ```  Binaries are also available:- * [elocrypt-2.0.0-linux-bin.tar.gz](https://github.com/sgillespie/elocrypt/releases/download/v2.0.0/elocrypt-2.0.0-linux-bin.tar.gz)- * [elocrypt-2.0.0-windows-bin.exe](https://github.com/sgillespie/elocrypt/releases/download/v2.0.0/elocrypt-2.0.0-windows-bin.exe)+ * [elocrypt-2.1.0-linux-bin.tar.gz](https://github.com/sgillespie/elocrypt/releases/download/v2.1.0/elocrypt-v2.1.0-linux-bin.tar.gz)+ * [elocrypt-2.1.0-osx-bin.exe](https://github.com/sgillespie/elocrypt/releases/download/v2.1.0/elocrypt-v2.1.0-osx-bin.tar.gz)  ## Running Generate a list passwords:@@ -36,8 +36,8 @@  ## Building In order to build or install you will need- * [GHC](https://www.haskell.org/ghc) (tested on 8.0)- * [Haskell Stack](https://haskellstack.org) (tested on 1.24)+ * [GHC](https://www.haskell.org/ghc) (tested on 8.6.5)+ * [Haskell Stack](https://haskellstack.org) (tested on 2.1.3.1)  Build elocrypt: ```
elocrypt.cabal view
@@ -1,5 +1,5 @@ name:                elocrypt-version:             2.0.1+version:             2.1.0 synopsis:            Generate easy-to-remember, hard-to-guess passwords homepage:            https://www.github.com/sgillespie/elocrypt license:             OtherLicense@@ -30,11 +30,13 @@                    library   exposed-modules:     Data.Elocrypt,-                       Data.Elocrypt.Trigraph+                       Data.Elocrypt.Trigraph,+                       Data.Elocrypt.Utils   -- other-modules:          -- other-extensions:       build-depends:       base >=4.7 && <5,                        MonadRandom,+                       containers,                        random   hs-source-dirs:      src/lib   default-language:    Haskell2010@@ -53,6 +55,7 @@                        MonadRandom,                        proctest,                        QuickCheck,+                       containers,                        random,                        tasty,                        tasty-quickcheck,@@ -63,26 +66,8 @@   other-modules:       Test.Elocrypt.Instances,                        Test.Elocrypt.QuickCheck,                        Test.Elocrypt.TrigraphTest,+                       Test.Elocrypt.UtilsTest,                        Test.ElocryptTest-  type:                exitcode-stdio-1.0--test-suite ui-test-  build-depends:       base >= 4.7 && <5,-                       elocrypt,-                       proctest,-                       MonadRandom,-                       QuickCheck,-                       random,-                       tasty,-                       tasty-quickcheck,-                       tasty-th >= 0.1.7-  default-language:    Haskell2010-  hs-source-dirs:      test-  main-is:             IntegTests.hs-  other-modules:       IntegTest.Elocrypt.PassphraseTest,-                       IntegTest.Elocrypt.PasswordTest,-                       Test.Elocrypt.QuickCheck,-                       Test.Elocrypt.Instances   type:                exitcode-stdio-1.0  test-suite elocrypt-lint
src/cli/Main.hs view
@@ -16,16 +16,18 @@ termLen :: Int termHeight :: Int -version = "elocrypt 2.0.1"+version = "elocrypt 2.1.0" termLen = 80 termHeight = 10  data Options = Options {       optCapitals  :: Bool,       -- Include capital letters?+      optDigits    :: Bool,       -- Inlcude digits?       optLength    :: Int,        -- Size of the password(s)       optMaxLength :: Int,       optNumber    :: Maybe Int,  -- Number of passwords to generate       optPassType  :: PassType,   -- Generate passwords or passphrases+      optSpecials  :: Bool,       -- Include special characters?       optHelp      :: Bool,       optVersion   :: Bool   } deriving (Show)@@ -38,10 +40,12 @@ defaultOptions :: Options defaultOptions = Options   { optCapitals = False+  , optDigits = False   , optLength = 8   , optMaxLength = 10   , optNumber = Nothing   , optPassType = Word+  , optSpecials = False   , optHelp = False   , optVersion = False   }@@ -54,6 +58,16 @@     (NoArg (\o -> o { optCapitals = True }))     "Include at least one capital letter"   , Option+    ['d']+    ["digits"]+    (NoArg (\o -> o { optDigits = True }))+    "Include numerals"+  , Option+    ['s']+    ["symbols"]+    (NoArg (\o -> o { optSpecials = True }))+    "Include special characters"+  , Option     ['n']     ["number"]     (ReqArg (\n o -> o { optNumber = Just (read n) }) "NUMBER")@@ -132,8 +146,12 @@     lines' = fromMaybe termHeight n  getGenOptions :: Options -> GenOptions-getGenOptions Options { optCapitals = caps } =-  genOptions { genCapitals = caps }+getGenOptions opts+  = genOptions+    { genCapitals = optCapitals opts,+      genDigits = optDigits opts,+      genSpecials = optSpecials opts+    }  usage :: String usage = usageInfo (intercalate "\n" headerLines) options
src/lib/Data/Elocrypt.hs view
@@ -11,27 +11,37 @@ module Data.Elocrypt where  import Data.Elocrypt.Trigraph+import Data.Elocrypt.Utils  import Control.Monad import Control.Monad.Random hiding (next) import Data.Bool import Data.Char+import Data.List (findIndices) import Data.Maybe+import Data.Ratio import Prelude hiding (min, max)+import qualified Data.Map as M  -- * Data Types  -- |Options for generating passwords or passphrases. Do not use -- this constructor directly. Instead use 'genOptions' to construct -- an instance.-newtype GenOptions = GenOptions {-  genCapitals :: Bool+data GenOptions = GenOptions {+  genCapitals :: Bool,+  genDigits   :: Bool,+  genSpecials :: Bool } deriving (Eq, Show)  -- |Default options for generating passwords or passphrases. This is -- the preferred way to construct 'GenOptions'. genOptions :: GenOptions-genOptions = GenOptions {genCapitals = False}+genOptions = GenOptions {+  genCapitals = False,+  genDigits   = False,+  genSpecials = False+}  -- * Random password generators @@ -120,8 +130,15 @@   pass <- if len > 2 then lastN (len - 2) f2' else return (take len f2')   let pass' = reverse pass -  if genCapitals opts then capitalizeR len pass' else return pass'+  -- Apply the transformations in order, if appropriate options specified+  let ms =+        [(genCapitals opts, capitalizeR),+         (genDigits opts,   numerizeR),+         (genSpecials opts, specializeR)+        ] +  foldM (\p f -> f len p) pass' . map snd . filter fst $ ms+ -- |Plural version of mkPassword.  Generate an infinite list of passwords using --  the MonadRandom m.  MonadRandom is exposed here for extra control. --@@ -220,17 +237,37 @@ -- |Randomly capitalize at least 1 character. Additional characters capitalize -- at a probability of 1/12 capitalizeR :: MonadRandom m => Int -> String -> m String-capitalizeR len s = mapM capitalize s >>= capitalize1 len-  where capitalize ch = fromList [(ch, 12), (toUpper ch, 1)]+capitalizeR len s = capitalize s >>= capitalize1 len+  where capitalize = updateR (return . toUpper) (1 % 12)  -- |Randomly capitalize 1 character-capitalize1-  :: MonadRandom m-  => Int -- ^ length-  -> String -- ^ the string to capitalize-  -> m String-capitalize1 len s = capitalize1' <$> getRandomR (0, len - 1)-  where-    capitalize1' pos =-      let (prefix, ch : suffix) = splitAt (pos - 1) s-      in prefix ++ (toUpper ch : suffix)+capitalize1 :: MonadRandom m => Int -> String -> m String+capitalize1 len s = getRandomR (0, len - 1) >>= capitalize1' s+  where capitalize1' = update1 (return . toUpper)+  +-- |Randomly numerize at least 1 character. Additional characters numerize+-- at a probability of 1/6+numerizeR :: MonadRandom m => Int -> String -> m String+numerizeR len s = numerize s >>= numerize1 len+  where numerize = updateR (uniform . toDigit) (1 % 6)++numerize1 :: MonadRandom m => Int -> String -> m String+numerize1 len s+  | null candidates = return s+  | otherwise = uniform candidates >>= numerize1' s+  where candidates = findIndices (`elem` M.keys numeralConversions) s+        numerize1' = update1 (uniform . toDigit)++-- |Randomly make at least 1 character a symbol. Additional characters specialize+-- at a probability of 1/4+specializeR :: MonadRandom m => Int -> String -> m String+specialize1 :: MonadRandom m => Int -> String -> m String++specializeR len s = specialize s >>= specialize1 len+  where specialize = updateR (uniform . toSymbol) (1 % 6)++specialize1 len s+  | null candidates = return s+  | otherwise = uniform candidates >>= specialize1' s+  where candidates = findIndices (`elem` M.keys symbolConversions) s+        specialize1' = update1 (uniform . toSymbol)
+ src/lib/Data/Elocrypt/Utils.hs view
@@ -0,0 +1,63 @@+module Data.Elocrypt.Utils where++import Data.Char (isAlphaNum, isSpace)+import Data.Maybe (fromMaybe)+import Data.Ratio+import qualified Data.Map as M++import Control.Monad.Random (MonadRandom(), fromList)++-- |A mapping from letters to numbers that look like them+numeralConversions = M.fromList [+  ('o', ['0']),+  ('l', ['1']),+  ('z', ['2']),+  ('e', ['3']),+  ('a', ['4']),+  ('s', ['5']),+  ('g', ['6', '9']),+  ('t', ['7']),+  ('b', ['8'])]++-- |A mapping from letters to symbols that look like them+symbolConversions = M.fromList [+  ('a', ['@']),+  ('l', ['!']),+  ('s', ['$'])]++-- |Map a letter to one or more digits, if possible+toDigit :: Char -> String+toDigit c = fromMaybe [c] (numeralConversions M.!? c)++-- |Map a letter to one or more symbols, if possible+toSymbol :: Char -> String+toSymbol c = fromMaybe [c] (symbolConversions M.!? c)++-- |Selects special characters+isSymbol :: Char -> Bool+isSymbol c = not (isAlphaNum c || isSpace c)++-- |Randomly update characters at the specified probability+updateR+  :: MonadRandom m+  => (Char -> m Char)+  -> Rational+  -> String+  -> m String+updateR f prob = mapM f'+  where f' ch = do+          ch' <- f ch+          fromList [+            (ch, toRational $ denominator prob),+            (ch', toRational $ numerator prob)]++-- |Update character at position pos+update1+  :: Monad m+  => (Char -> m Char) -- ^ Update function+  -> String           -- ^ the string to update+  -> Int              -- ^ the position to update+  -> m String+update1 _ "" _   = return ""+update1 f s  pos = (\ch' -> prefix ++ ch' : suffix) <$> f ch+  where (prefix, ch : suffix) = splitAt pos s
− test/IntegTest/Elocrypt/PassphraseTest.hs
@@ -1,121 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module IntegTest.Elocrypt.PassphraseTest where--import Control.Monad-import Data.Bool-import Data.Char-import Data.List-import Data.Maybe--import Test.QuickCheck-import Test.Tasty hiding (Timeout)-import Test.Tasty.QuickCheck (testProperty)-import Test.Tasty.TH-import qualified Test.Proctest as Proctest--import Test.Elocrypt.QuickCheck--tests :: TestTree-tests = $(testGroupGenerator)--elocrypt = "elocrypt"---- |Passphrases consist of words in specified length range-prop_printsWordsWithLengthRange :: PhraseCliOptions -> Property-prop_printsWordsWithLengthRange (PhraseCliOptions opts)-  = isJust minLen && isJust maxLen && fromJust maxLen <= 79 ==>-    ioProperty $ do-      (_, out, _, _) <- run opts-      response <- readHandle out--      let words'  = words response-          minLen' = fromJust minLen-          maxLen' = fromJust maxLen--      return (all ((\n -> n >= minLen' && n <= maxLen') . length) words')--  where CliOptions{cliLength=minLen, cliMaxLength=maxLen} = opts---- |Passphrases consist of words with specified minimum range-prop_printsWordsWithMinLength:: PhraseCliOptions -> Property-prop_printsWordsWithMinLength(PhraseCliOptions opts)-  = isJust minLen && fromJust minLen <= 79 ==>-    ioProperty $ do-      (_, out, _, _) <- run opts-      response <- readHandle out--      return . all ((>= min (fromJust minLen) 10) . length) . words $ response--  where CliOptions{cliLength=minLen} = opts---- |Prints the specificed number of phrases-prop_printsSpecifiedNumberOfPassphrases :: PhraseCliOptions -> Property-prop_printsSpecifiedNumberOfPassphrases (PhraseCliOptions opts)-  = isJust number && -    (isJust maxLen || isNothing len) && -- maxLen > len-    maybe True (< 79) maxLen ==>        -- minLen/maxLen < 79--    ioProperty $ do--      (_, out, _, _) <- run opts-      response       <- readHandle out--      let phrases = lines response--      return $ counterexample (failMsg phrases) -                              (fromJust number == length phrases)--  where CliOptions{cliNumber=number, cliLength=len, cliMaxLength=maxLen} = opts-        failMsg p = "length phrases (" ++ show (length p) ++ -                    ") /= " ++ show (fromJust number)---- |Prints multiple passwords per line-prop_printsMultipleWordsPerLine :: PhraseCliOptions -                                -> LessThan20 Int-                                -> LessThan20 Int-                                -> Property-prop_printsMultipleWordsPerLine (PhraseCliOptions opts) (LT20 min) (LT20 max)-  = ioProperty $ do-      let opts' = opts { -- Make sure we can fit 2+ words on a line-        cliLength = Just min, -        cliMaxLength = Just max}--      (_, out, _, _) <- run opts'-      response <- readHandle out--      return $-        all (>1) . tail . reverse . map (length . words) . lines $ response---- |Always prints a passphrase-prop_printsLongPassphrases :: PhraseCliOptions-                           -> Positive Int-                           -> Positive Int -                           -> Property-prop_printsLongPassphrases (PhraseCliOptions opts) (Positive min) (Positive max)-  = forAll (scale (*7) arbitrary) $ \min ->-      ioProperty $ do-        let minLen = getPositive min-            opts' = opts{-              cliLength = Just minLen,-              cliMaxLength = Just (minLen + max)}--        (_, out, _, _) <- run opts-        response       <- readHandle out-        -        return $-          cover 20 (minLen > 80) "long" $ -          all (>=1) . map (length . words) . lines $ response---- |Prints capitals when specified-prop_printsCapitals :: PhraseCliOptions -> Property-prop_printsCapitals (PhraseCliOptions opts)-  = ioProperty $ do-    let opts' = opts { cliCapitals = True }--    (_, out, _, _) <- run opts'-    response <- readHandle out--    let phrases = lines response--    return $-      cover 80 (any (any isUpper) phrases) "has caps" True
− test/IntegTest/Elocrypt/PasswordTest.hs
@@ -1,98 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module IntegTest.Elocrypt.PasswordTest where--import Data.Char-import Data.List-import Data.Maybe-import Control.Monad--import Test.Tasty hiding (Timeout)-import Test.Tasty.QuickCheck (testProperty)-import Test.Tasty.TH--import Test.Elocrypt.QuickCheck--tests :: TestTree-tests = $(testGroupGenerator)--elocrypt = "elocrypt"---- |All passwords have specified length-prop_printsPasswordsWithLength :: WordCliOptions -> Property-prop_printsPasswordsWithLength (WordCliOptions opts)-  = isJust len ==>-    ioProperty $ do-      (_, out, _, _) <- run opts-      response <- readHandle out--      let len'   = fromJust len-          words' = words response--      return (all ((==) len' . length) words')--  where CliOptions{cliLength=len} = opts---- |Prints nothing when length is 0-prop_printsNothingWhenLengthIsZero :: WordCliOptions -> Property-prop_printsNothingWhenLengthIsZero (WordCliOptions opts)-  = ioProperty $ do-      let opts' = opts { cliLength = Just 0 }--      (_, out, _, _) <- run opts'-      response <- readHandle out-      return (response == "")---- |Always prints at least 1 password-prop_printsLongPasswords :: WordCliOptions -> Property-prop_printsLongPasswords (WordCliOptions opts)-  = forAll (scale (*7) arbitrary) $ \len ->-      ioProperty $ do-        let len'  = getPositive len-            opts' = opts { cliLength = Just len' }--        (_, out, _, _) <- run opts'-        response <- readHandle out--        return $-          cover 30 (len' > 80) "long" $-            all (>=1) . map (length . words) . lines $ response---- |Prints the specified number of passwords-prop_printsNumberPasswords :: Positive Int -> WordCliOptions -> Property-prop_printsNumberPasswords (Positive num) (WordCliOptions opts)-  = ioProperty $ do-      let opts' = opts { cliNumber = Just num }--      (_, out, _, _) <- run opts'-      response <- readHandle out--      let words' = words response--      return $ num == length words'---- |Prints multiple passwords per line when length is sufficiently small-prop_printsMultiplePasswordsPerLine :: WordCliOptions -> Property-prop_printsMultiplePasswordsPerLine (WordCliOptions opts)-  = isNothing len || fromJust len <= 38 ==>-    ioProperty $ do-      (_, out, _, _) <- run opts-      response <- readHandle out--      return $-        all (>1) . tail . reverse . map (length . words) . lines $ response--  where CliOptions{cliLength=len} = opts---- |Prints capitals when specified-prop_printsCapitals :: WordCliOptions -> Property-prop_printsCapitals (WordCliOptions opts)-  = ioProperty $ do-      let opts' = opts { cliCapitals = True }--      (_, out, _, _) <- run opts'-      response <- readHandle out--      let passes = words response--      return $-        cover 80 (any (any isUpper) passes) "has caps" True
test/Test/Elocrypt/QuickCheck.hs view
@@ -32,10 +32,12 @@ -- options data CliOptions = CliOptions {   cliCapitals   :: Bool,+  cliDigits     :: Bool,   cliLength     :: Maybe Int,   cliMaxLength  :: Maybe Int,   cliNumber     :: Maybe Int,-  cliPassphrase :: Bool+  cliPassphrase :: Bool,+  cliSpecials   :: Bool } deriving Eq  instance Show CliOptions where@@ -51,16 +53,20 @@  instance Arbitrary WordCliOptions where   arbitrary = do-    caps <- arbitrary-    len  <- arbitrary-    num  <- arbitrary+    caps     <- arbitrary+    digits   <- arbitrary+    len      <- arbitrary+    num      <- arbitrary+    specials <- arbitrary      return $ WordCliOptions CliOptions{        cliCapitals   = caps,+      cliDigits     = digits,       cliLength     = fromPositive len,       cliMaxLength  = Nothing,       cliNumber     = fromPositive num,-      cliPassphrase = False+      cliPassphrase = False,+      cliSpecials   = specials     }        where fromPositive = fmap getPositive@@ -75,10 +81,12 @@  instance Arbitrary PhraseCliOptions where   arbitrary = do-    caps   <- arbitrary-    minLen <- arbitrary-    maxLen <- arbitrary-    num    <- arbitrary+    caps     <- arbitrary+    digits   <- arbitrary+    minLen   <- arbitrary+    maxLen   <- arbitrary+    num      <- arbitrary+    specials <- arbitrary      -- CLI gets very inconsistent when len >= 80     -- TODO: Fix ^^^@@ -87,12 +95,14 @@      return $ PhraseCliOptions CliOptions{        cliCapitals   = caps,+      cliDigits     = digits,       cliLength     = minLen',       cliMaxLength  = maxLen',         -- CLI gets very inconsistent when number >= 20       -- TODO: Fix ^^^       cliNumber     = fromLT20 num,-      cliPassphrase = True+      cliPassphrase = True,+      cliSpecials   = specials     }  newtype LessThan20 n = LT20 { getLT20 :: n } deriving (Eq, Show)@@ -111,13 +121,15 @@ -- |Convert CliOptions into a list of cli arguments getOptions :: CliOptions -> [String] getOptions opts-  = caps ++ num ++ phrase ++ len ++ maxLen+  = caps ++ digits ++ specials ++ num ++ phrase ++ len ++ maxLen   where caps   = ["-c" | cliCapitals opts]+        digits = ["-d" | cliDigits opts]         len    = maybe [] (singleton . show) (cliLength opts)         maxLen = maybe [] (singleton . show) (cliMaxLength opts)         maxLen' = cliLength opts >> cliMaxLength opts         num    = maybe [] (("-n":) . singleton . show) (cliNumber opts)         phrase = ["-p" | cliPassphrase opts]+        specials = ["-s" | cliSpecials opts]  -- |Run elocrypt with the given options run :: CliOptions -> IO (Handle, Handle, Handle, ProcessHandle)@@ -142,7 +154,7 @@ fromPositive = fmap getPositive  readHandle :: Handle -> IO String-readHandle = (<$>) asUtf8Str . waitOutput (seconds 2) 5000+readHandle = (<$>) asUtf8Str . waitOutput (seconds 5) 5000  assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool assertExitedSuccess t = fmap (== ExitSuccess) . assertExitedTimeout t
+ test/Test/Elocrypt/UtilsTest.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE TemplateHaskell #-}+module Test.Elocrypt.UtilsTest where++import Control.Monad.Random+import Data.Maybe (isNothing)+import Data.List (find, elem, sort)+import Data.Ratio ((%))+import qualified Data.Map as M++import Test.QuickCheck+import Test.QuickCheck.Monadic (monadicIO)+import Test.Tasty+import Test.Tasty.QuickCheck (testProperty)+import Test.Tasty.TH++import Data.Elocrypt.Utils+import Test.Elocrypt.QuickCheck+import Test.Elocrypt.Instances++tests :: TestTree+tests = $(testGroupGenerator)++-- |Maps letters to all digits 0-9+prop_numeralConversionsHasAllDigits :: Bool+prop_numeralConversionsHasAllDigits+  = sort digits == ['0'..'9']+  where digits = M.foldr (++) [] numeralConversions++-- |toDigit never returns a letter that looks like a number+prop_toDigitDoesntLookLikeNumber :: Char -> Bool+prop_toDigitDoesntLookLikeNumber c+  = isNothing $ find (`elem` candidates) c'+  where candidates = M.keys numeralConversions+        c' = toDigit c++-- |toSymbol never returns a letter that looks like a symbol+prop_toSymbolDoesntLookLikeSymbol :: Char -> Bool+prop_toSymbolDoesntLookLikeSymbol c+  = isNothing $ find (`elem` M.keys symbolConversions) c'+  where c' = toSymbol c++prop_toSymbolReturnsSymbols :: Bool+prop_toSymbolReturnsSymbols = all isSymbol ss+  where ks = M.keys symbolConversions+        ss = concatMap toSymbol ks++-- |isSymbol returns false for letters and numbers+prop_isSymbolAlphaNumReturnsFalse :: Char -> Property+prop_isSymbolAlphaNumReturnsFalse c+  = c `elem` (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']) ==> not (isSymbol c)++prop_isSymbolWhitespaceReturnsFalse :: Bool+prop_isSymbolWhitespaceReturnsFalse = all (not . isSymbol) " \n"++-- |updateR always updates when prob = 1/0+prop_updateRAlwaysUpdates :: String -> StdGen -> Bool+prop_updateRAlwaysUpdates s = evalRand $ do+  let f _ = return '*'+      prob = 99999 % 1 -- Make prob absurdly high since we can't use 0 denoms+  s' <- updateR f prob s++  -- Make sure almost all letters are '*'+  return $ length (filter (=='*') s') >= length s - 1++-- |updateR never updates when prob = 0/1+prop_updateRNeverUpdates :: String -> StdGen -> Bool+prop_updateRNeverUpdates s = evalRand $ do+  let f _ = return '*'+      prob = 0 % 1+  s' <- updateR f prob s++  return $ s == s'++-- |update1 Always updates at least one character+prop_update1AlwaysUpdates1 :: String -> Property+prop_update1AlwaysUpdates1 s = not (null s) ==> do+  let f = (const . return) '*'+  +  pos <- choose (0, length s - 1)+  s'  <- update1 f s pos++  return $ elem '*' s'++-- |update1 allows empty string+prop_update1DoesntCrashOnEmpty :: Property+prop_update1DoesntCrashOnEmpty = monadicIO $ do+  let f _ = return '*'+  s <- update1 f "" 0++  return (s == "")
test/Test/ElocryptTest.hs view
@@ -4,7 +4,7 @@ import Control.Monad import Control.Monad.Random import Data.Bool-import Data.Char+import Data.Char hiding (isSymbol) import Data.List import Data.Maybe import Test.QuickCheck@@ -14,6 +14,7 @@  import Data.Elocrypt import Data.Elocrypt.Trigraph+import Data.Elocrypt.Utils (isSymbol) import Test.Elocrypt.QuickCheck import Test.Elocrypt.Instances @@ -42,11 +43,36 @@ -- |Most passwords have more than one capital prop_newPasswordHasMultipleCaps :: Positive Int -> StdGen -> Property prop_newPasswordHasMultipleCaps (Positive len) gen =-  cover 50 (length (filter isUpper pass) > 1) "has multiple caps"-    $ any isLower pass+  checkCoverage $+    cover 50 (length (filter isUpper pass) > 1) "has multiple caps" True   where     pass = newPassword (len + 2) opts gen     opts = genOptions { genCapitals = True }++-- |Passwords are all letters when numbers is false+prop_newPasswordIsAlpha :: Positive Int -> StdGen -> Bool+prop_newPasswordIsAlpha (Positive len) gen = all isAlpha pass+  where+    pass = newPassword len opts gen+    opts = genOptions { genDigits = False }++-- |Most passwords have at least one number+prop_newPasswordHasDigits :: Positive Int -> StdGen -> Property+prop_newPasswordHasDigits (Positive len) gen =+  checkCoverage $+    cover 50 (any isDigit pass) "has digits" True+  where+    pass = newPassword (len + 2) opts gen+    opts = genOptions { genDigits = True }++-- |Most passwords have at least one special character+prop_newPasswordHasSpecials :: Positive Int -> StdGen -> Property+prop_newPasswordHasSpecials (Positive len) gen =+  checkCoverage $+    cover 50 (any isSymbol pass) "has specials" True+  where+    pass = newPassword (len + 2) opts gen+    opts = genOptions { genSpecials = True }  -- |Third and each successive character is taken from the trigraph prop_3rdCharHasPositiveFrequency
test/Tests.hs view
@@ -5,10 +5,12 @@  import qualified Test.ElocryptTest as PasswordTest import qualified Test.Elocrypt.TrigraphTest as TrigraphTest+import qualified Test.Elocrypt.UtilsTest as UtilsTest  main :: IO () main = defaultMain tests  tests :: TestTree tests = testGroup "Unit Tests" [PasswordTest.tests,-                                TrigraphTest.tests]+                                TrigraphTest.tests,+                                UtilsTest.tests]