2008 Winter Scripting Games Event 5
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
#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
- Introduction: TOTAL DOMINATION!
- Event 1: Could I Get Your Phone Number?
- Event 2: Skating on Thin Ice
- Event 3: Instant (Runoff) Winner
- Event 4: Image is Everything
- Event 5: You Call That a Password?
- Event 6: Prime Time
- Event 7: Play Ball!
- Event 8: Making Beautiful Music
- Event 9: You're Twisting My Words
- Event 10: Blackjack!
- Recap