If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 5: You Call That a Password?

 

First off, before I describe this event, I would like to protest most all of these rules as bad IT practice. Unnecessary password rules just make your users angrier. ANYWAY.

In this challenge we are told to score passwords based on a set of specific rules. What's awesome about my solution is I use the hotness that is metaprogramming. Okay, so, it's programming hotness. I don't claim that programming hotness in any way translates to actual, real-life hotness, I could be wrong.

Source

#PROBLEM #5


#POSTSCRIPT-- problem #5 should be run as its own file
#If you attempt to "run" this batch of scripts, this
#part will have errors.
param($password)


$filename = "C:\scripts\wordlist.txt"
$words = cat $filename


function Create-PasswordObject ($passwordString)
{
    $o = new-object PSObject
    Add-Member -inputObject $o -memberType NoteProperty -name "Password" -value $passwordString
   
    Add-Member -inputObject $o -memberType ScriptMethod -name "IsNotAnActualWord" -value { -not (Is-InWordsList $this.Password) }       
    Add-Member -inputObject $o -memberType ScriptMethod -name "MinusFirstLetterIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Substring(1))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "MinusLastLetterIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Substring(0,$this.Password.Length-1))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "SubstitutedZeroIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Replace("0","o"))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "SubstitutedOneIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Replace("1","l"))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "IsPasswordLengthOptimal" -value { (10 -le $this.Password.Length) -and ($this.Password.Length -le 20) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsDigit" -value { $this.Password -match "[0-9]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsUppercaseLetter" -value { $this.Password -cmatch "[A-Z]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsLowerCaseLetter" -value { $this.Password -cmatch "[a-z]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsSymbol" -value { $this.Password -cmatch "[^a-zA-Z0-9]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "NotContainsFourUppercaseInSuccession" -value { $this.Password -cnotmatch "[A-Z]{4}" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "NotContainsFourLowercaseInSuccession" -value { $this.Password -cnotmatch "[a-z]{4}" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "NotContainsDuplicates" -value { -not ( Contains-DuplicateLetters $this.Password ) }


    $o
}


function Is-InWordsList ($w)
{
    $words -contains $w
}


#POSTSCRIPT-OOPS: MOW did this way better by doing:
#    $passString.ToCharArray() | group | ? { $_.Count -ge 2 }
#So much more elegant! Done in "THE POWERSHELL WAY!"
#Meanwhile, until I saw his code, I was proud of using
#a hash table approach (below).
function Contains-DuplicateLetters ($passString)
{
    $charCounts = @{}
    foreach ($char in $passString.ToCharArray())
    {
        $charCounts[$char] += 1;
    }
   
    $charCounts.Values | ? { $_ -ge 2 }
}


function Calculate-PasswordScore ($passwordObject)
{
    #POSTSCRIPT: Metaprogramming alert! Yeah, check it out, I'm looping through
    #all the scriptmethods on the object and invoking them.
    $passwordCheckMethods = $passwordObject.PSObject.Methods | ? { $_ -is [System.Management.Automation.PSScriptMethod] }
    $results = $passwordCheckMethods | % { $_.Invoke() }
   
    ( $results | ? { $_ -eq $TRUE } ).Count
}


function Calculate-PasswordStrength ($passwordObject)
{
    $score = Calculate-PasswordScore $passwordObject
    if ($score -lt 6)
    {
        "weak"
    }
    elseif ($score -lt 11)    
    {
        "moderately-strong"
    }
    else
    {
        "strong"
    }
}


$weaknessMessages = @{
    "IsNotAnActualWord" = "Password is an actual word.";
    "MinusFirstLetterIsNotAnActualWord" = "Password, minus the first letter, is an actual word.";
    "MinusLastLetterIsNotAnActualWord" = "Password, minus the last letter, is an actual word.";
    "SubstitutedZeroIsNotAnActualWord" = "Password, substituting the letter O for 0 (zero), is an actual word.";
    "SubstitutedOneIsNotAnActualWord" = "Password, substituting the letter L for 1 (one), is an actual word.";
    "IsPasswordLengthOptimal" = "Password should be between 10 and 20 characters long.";
    "ContainsDigit" = "Password does not contain a digit.";
    "ContainsUppercaseLetter" = "Password does not contain an uppercase letter.";
    "ContainsLowerCaseLetter" = "Password does not contain a lowercase letter.";
    "ContainsSymbol" = "Password does not contain a symbol (i.e. a non-alphanumeric character).";
    "NotContainsFourUppercaseInSuccession" = "Password should not contain four uppercase letters in succession.";
    "NotContainsFourLowercaseInSuccession" = "Password should not contain four lowercase letters in succession.";
    "NotContainsDuplicates" = "Password should not contain duplicate characters.";
}


function Show-PasswordWeaknesses ($passwordObject)
{
    $passwordCheckMethods = $passwordObject.PSObject.Methods | ? { $_ -is [System.Management.Automation.PSScriptMethod] }
   
    foreach ($checkMethod in $passwordCheckMethods)
    {
        if ($checkMethod.Invoke() -eq $FALSE)
        {
            $weaknessMessages[$checkMethod.Name]
        }
    }
}


#POSTSCRIPT--I used this function to test out my password checks on
#a bunch of different passwords. Well, fine. It's dead code though; don't go looking
#for references to it.
function Test-Problem5 ($testPw)
{
    $passwordObject = create-passwordobject $testPw
    $passwordCheckMethods = $passwordObject.PSObject.Methods | ? { $_ -is [System.Management.Automation.PSScriptMethod] }


    foreach ($checkMethod in $passwordCheckMethods)
    {
        "'$testPw' - $($checkMethod.Name) - $($checkMethod.Invoke())"
    }
}


function Solve-Problem5
{
    $passwordObject = Create-PasswordObject $password
    $output = @()
   
    $output += Show-PasswordWeaknesses $passwordObject
    $output += ""
    $output += "A password score of $(Calculate-PasswordScore $passwordObject) indicates a $(Calculate-PasswordStrength $passwordObject) password."
   
    Write-Host ($output | out-string)
}


#POSTSCRIPT - again, this will cause problems if you attempt to
#run this problem #5 as part of the larger "allsolutions" script.
# number 5 must be its own file.
Solve-Problem5

2008 Winter Scripting Game Events: Index