Checking if a password is strong enough - Haskell vs procedural language

functional-programming, haskell

Solution

You are right: if the password is already known to be ok, it will still try to establish the other conditions. Hence I would do it differently:

check passwd = go criteria passwd
    where 
      criteria = [(10, const True), (2, isUpper), (2, isLower), (2, isDigit), (2, isSpecial)]
      -- password is ok if at least 3 criteria have counted down to 0
      ok = (>=3) . length . filter (==0) . map fst
      go crit pwd
         | ok crit = True
         | null pwd = False
         | otherwise = go (map (trans (head pwd)) crit) (tail pwd)
      trans ch (0, p) = (0, p)
      trans ch (n, p) = if p c then (n-1, p) else (n, p)

The idea is to check on every character, if the criteria list indicates that the passwd is ok and return True in this case. Then check if the end of the password has been reached, in that case the passwd is invalid. And otherwise, we have not yet established all needed criteria, but have a nonempty passwd. Hence, we transform the criteria list by applying all functions where the counter is not 0 yet to the current character, downcounting successes, and recurse.

Note that `(10, const True)` encodes the condition: "Passwd is at least 10 characters long."

Problem

I implemented a function in Python that checks if a password is strong enough. A password is strong enough if it passes 3 out of 5 checks. This is the Python function: ``` def is_valid(password): checks = { lambda x : True : 10, lambda x : x.isupper() : 2, lambda x : x.islower() : 2, lambda x : x.isdigit() : 2, lambda x : x in frozenset("~!@#$%^&*()-=_+[]{}<>?/\\`") : 2, } for c in password: for func in list(checks): if func(c): checks[func] -= 1 if checks[func] == 0: del checks[func] if len(checks) <= 2: return True return False ``` This function can operate on an infinite password if it is strong enough. If it's not, then the function will hang: ``` >>> is_valid(itertools.cycle("!!xxxxxxxx")) True >>> is_valid(itertools.cycle("UUxxxxxxxx")) True ``` I woundered if I could implement the same function in Haskell in a more elegant way, so I came up with this solution: ``` isValid :: String -> Bool isValid password = let checks = atLeast 10 password:map containsChars [isUpper, isLower, isSpecialChar, isDigit] in atLeast 3 $ filter (==True) checks where containsChars predicate = length (take 2 $ filter predicate password) == 2 isSpecialChar c = isPunctuation c || isSymbol c atLeast n seq = length (take n seq) == n ``` This solution seems a bit more elegant but has a drawback over the procedural solution. If the password is infinite and does not have enough uppercase characters, the function will hang even if it passes the other conditions: ``` *Main Data.Char> isValid (cycle "UUxxxxxxxx") True *Main Data.Char> isValid (cycle "!!xxxxxxxx") -- hangs ``` Is there a way to implement an elegant soultion in Haskell which doesn't have this drawback? BTW: Is there something built-in in Haskell that I can use instead of the `atLeast` function that I implemented?

Original source