serviceAccountModule/Tests/Test.ServiceAccount.ps1

#Define global constants
$script:Service_Account_Name = "newServiceAcct"

function New-ServiceAccount
{
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$username,
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]$password
    )
    Process
    {
        ipmo ActiveDirectory
        $oldLocation = Get-Location
        Set-Location AD:
        
        $encryptedPassword = $password | ConvertTo-SecureString -asPlainText -Force

        try
        {
            $userObject = Get-ADUser $userName -ErrorAction SilentlyContinue
        }
        catch
        {
        }
        
        if ($userObject -eq $null)
        {
            New-ADUser -Name  $userName 
        }

        Get-ADUser $userName | Set-ADAccountPassword -Reset -NewPassword $encryptedPassword -PassThru 
        Get-ADUser $userName | Set-ADUser -PasswordNeverExpires $true -PassThru -Description "Password: $password" -Enabled $true
        
        Set-Location $oldLocation
    }
}

#####################################################################
####Helper functions related to rule parsing logic###################
#####################################################################

<#
.SYNOPSIS
    Class to encapsulate parsing of the ADFS Issuances/Auth rules.
#>


class AdfsRules
{
    [System.Collections.ArrayList] hidden $rules

    <#
    .SYNOPSIS
        Constructor
    #>

    AdfsRules([string]$rawRules) 
    {
        $rulesArray = $this.ParseRules($rawRules)
        $this.rules = New-Object "System.Collections.ArrayList"
        $this.rules.AddRange($rulesArray)
    }

    <#
    .SYNOPSIS
        Utility function to parse the rules and return them as a string[].
    #>

    [string[]] hidden ParseRules([string]$rawRules)
    {
        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : BEGIN"

        $allRules = @()
        $singleRule = [string]::Empty

        $rawRules.Split("`n") | %{
            
            $line = $_.ToString().Trim()

            if (-not ([string]::IsNullOrWhiteSpace($line)) ) 
            {
                $singleRule += $_ + "`n"

                if ($line.StartsWith("=>"))
                {
                    Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Parsed rule:`n$singleRule"
                    $allRules += $singleRule
                    $singleRule = [string]::Empty
                }
            }
        }

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : END"

        return $allRules
    }

    [int] NumberOfRules()
    {
        return $this.rules.Count
    }

    <#
    .SYNOPSIS
        Finds the rule by name in the format: @RuleName = "$ruleName". Returns $null if not found.
    #>

    [string] FindByRuleName([string]$ruleName)
    {
        $ruleNameSearchString = '@RuleName = "' + $ruleName + '"'
        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Search string: $ruleNameSearchString"

        foreach ($rule in $this.rules)
        {
            if ($rule.Contains($ruleNameSearchString))
            {
                Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Found.`n$rule"
                return $rule
            }
        }

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : NOT FOUND. Returning $null"
        return $null;
    }

    <#
    .SYNOPSIS
        Replaces the specified old rule with the new one. Returns $true if the old one was found and replaced; $false otherwise.
    #>

    [bool] ReplaceRule([string]$oldRule, [string]$newRule)
    {
        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Trying to replace old rule with new.`n Old Rule:`n$oldRule`n New Rule:`n$newRule"
        $idx = $this.FindIndexForRule($oldRule)

        if ($idx -ge 0)
        {
            Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Replacing old rule with new."
            $this.rules[$idx] = $newRule
            return $true
        }

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Old rule is not found so NOT replacing it."
        return $false
    }

    <#
    .SYNOPSIS
        Removes the specified if found. Returns $true if found; $false otherwise.
    #>

    [bool] RemoveRule([string]$ruleToRemove)
    {
        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Trying to remove rule.`n Rule:`n$ruleToRemove"

        $idx = $this.FindIndexForRule($ruleToRemove)

        if ($idx -ge 0)
        {
            Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Removing rule at index: $idx."
            $this.rules.RemoveAt($idx)
            return $true
        }

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Rule is not found so NOT removing it."
        return $false
    }

    <#
    .SYNOPSIS
        Helper function to find the index of the rule. Returns index if found; -1 otherwise.
    #>

    [int] FindIndexForRule([string]$ruleToFind)
    {
        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Trying to find rule.`n Rule:`n$ruleToFind"

        for ($i = 0; $i -lt $this.rules.Count; $i++)
        {
            $rule = $this.rules[$i]

            if ($rule.trim().Equals($ruleToFind.trim()))
            {
                Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Found at index: $i."
                return $i
            }
        }

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : NOT FOUND. Returning -1"
        return -1
    }
    
    <#
    .SYNOPSIS
        Returns all the rules as string.
    #>

    [string] ToString()
    {
        return [string]::Join("`n", $this.rules.ToArray())
    }
}

# Gets internal ADFS settings by extracting them Get-AdfsProperties
function Get-AdfsInternalSettings()
{
    $settings = Get-AdfsProperties
    $settingsType = $settings.GetType()
    $propInfo = $settingsType.GetProperty("ServiceSettingsData", [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)
    $internalSettings = $propInfo.GetValue($settings, $null)
    
    return $internalSettings
}

function ValidateRules
{
    param
    (
        [parameter()]
        [switch]$CheckNotPresent
    )

    $Properties = Get-AdfsInternalSettings
    $AuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy) 
    $AuthorizationPolicyReadOnlyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicyReadOnly)

    $SID = (New-Object system.security.principal.NtAccount($Service_Account_Name )).translate([system.security.principal.securityidentifier])
    $ServiceAccountRule = "@RuleName = `"Permit Service Account`"`nexists([Type == `"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid`", Value == `"$SID`"])`n=> issue(Type = `"http://schemas.microsoft.com/authorization/claims/permit`", value = `"true`");`n`n"

    $AuthPolicyIndex = $AuthorizationPolicyRules.FindIndexForRule($ServiceAccountRule)
    $ReadOnlyIndex = $AuthorizationPolicyReadOnlyRules.FindIndexForRule($ServiceAccountRule)

    if($CheckNotPresent)
    {
        return ($AuthPolicyIndex -eq -1 -and $ReadOnlyIndex -eq -1)
    }
    return ($AuthPolicyIndex -ne -1 -and $ReadOnlyIndex -ne -1)

}


function Initialize()
{
    ipmo .\ServiceAccount.psm1
    #[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Simply a test password not used anywhere")]
    New-ServiceAccount -username $script:Service_Account_Name -password "Password"
}

Describe 'Basic functionality of adding and removing service account rule'{
    BeforeAll {
        Initialize
    }

    AfterAll {
        Remove-ADUser -Identity $script:Service_Account_Name
    }

    It "[00000]: Add-AdfsServiceAccountRule adds permit rule to ruleset"{
        Add-AdfsServiceAccountRule -ServiceAccount $script:Service_Account_Name
        ValidateRules | Should Be $true
    }

    It "[00000]: Add-AdfsServiceAccountRule fails if rule already exists"{
        $BeforeProperties = Get-AdfsInternalSettings
        $BeforeAuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy) 
        $BeforeAuthorizationPolicyReadOnlyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicyReadOnly)

        Add-AdfsServiceAccountRule -ServiceAccount $script:Service_Account_Name

        $AfterProperties = Get-AdfsInternalSettings
        $AfterAuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy) 
        $AfterAuthorizationPolicyReadOnlyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicyReadOnly)

        $AuthPolicyMatches = $BeforeAuthorizationPolicyRules.NumberOfRules() -eq $AfterAuthorizationPolicyRules.NumberOfRules()
        $ReadOnlyMatches = $BeforeAuthorizationPolicyReadOnlyRules.NumberOfRules() -eq $AfterAuthorizationPolicyReadOnlyRules.NumberOfRules()



        ($AuthPolicyMatches -eq $ReadOnlyMatches) | Should Be $true
    }

    It "[00000]: Remove-AdfsServiceAccountRule removes permit rule to ruleset"{
        Remove-AdfsServiceAccountRule -ServiceAccount $script:Service_Account_Name 
        ValidateRules -CheckNotPresent | Should Be $true
    }

    It "[00000]: Remove-AdfsServiceAccountRule does nothing if rule isn't present"{
        $BeforeProperties = Get-AdfsInternalSettings
        $BeforeAuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy) 
        $BeforeAuthorizationPolicyReadOnlyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicyReadOnly)

        Remove-AdfsServiceAccountRule -ServiceAccount $script:Service_Account_Name

        $AfterProperties = Get-AdfsInternalSettings
        $AfterAuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy) 
        $AfterAuthorizationPolicyReadOnlyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicyReadOnly)

        $AuthPolicyMatches = $BeforeAuthorizationPolicyRules.NumberOfRules() -eq $AfterAuthorizationPolicyRules.NumberOfRules()
        $ReadOnlyMatches = $BeforeAuthorizationPolicyReadOnlyRules.NumberOfRules() -eq $AfterAuthorizationPolicyReadOnlyRules.NumberOfRules()



        ($AuthPolicyMatches -eq $ReadOnlyMatches) | Should Be $true
    }

    It "[00000]: Add-AdfsServiceAccountRule adds permit rule to ruleset"{
        $ErrorThrown = $false
        try
        {
            Add-AdfsServiceAccountRule -ServiceAccount "fakeAccount"
        }
        catch
        {
            $ErrorThrown = $true
        }
        $ErrorThrown | Should Be $true
    }

}

# SIG # Begin signature block
# MIIjhAYJKoZIhvcNAQcCoIIjdTCCI3ECAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC3n1Y9DcunGw7V
# 0CKrbUhKRsU1tx9P63a8jrrRT657pKCCDYIwggYAMIID6KADAgECAhMzAAABXFSi
# Z7ZIC9ybAAAAAAFcMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTkwNjA1MTczNDU2WhcNMjAwNjAzMTczNDU2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC1Bfsypco2wGSeVhv1kfGPga1DJXLSLKz6oDb870Zcez2WOFhcKlcIozpXNwjY
# tKAnHUlDjbkJ+Ejwe/sfKf8B0gEltYzCNHgoRG1JLCCnPm+3jzTItIVDewLi0zGZ
# 4WmeR6k05qBNg9eBfgdc+6PwHNkEy+hmu7ewXTsrUxwjMX2xSC56wawzIYyqr78w
# YhZRL2MfFmH0rBViobUMU3/5MwPCbVGJY05mZMGM1x6QL9WlhA+d0JIT1q3u4jbh
# iK8wScxiDyeIykDxzluGHaa76hgIdFDRidNhTmYBEXn2r1MaLRviiLZyjEt7avi7
# qqkhXefzIZ5c2iV1tb9Kc31zAgMBAAGjggF/MIIBezArBgNVHSUEJDAiBgorBgEE
# AYI3TBMBBgorBgEEAYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUtJuPF6DKaOYX
# HqR+uQJbTOBzmX8wRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEWMBQGA1UEBRMNMjMzMTEwKzQ1NTUwMjAfBgNVHSMEGDAWgBRI
# bmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEt
# MDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAEoN
# 39sJSBkqbUsRcN8UdOReYS/hQ0qCkZged24kxOvI1oeHznc8uNljCCyaeeA5bra3
# 55bZbzYBjTxHC+aQI0OUqkYhSXjf1irM08RCjt/NehtYfcnJq7QUDX0ge+qEfOK2
# bW9XMrcomzA69ZMMNRRNh0G81xV1UlTCbBlOsjFG1+mADQwZGWtOWtlScj3OU4HR
# oiXTaF3i064ANwZdebIIdMhzaGfCdWotnSmRiBIOqYhzE+vA4FGNuS20WyUsvCKK
# sXHCtOLI6+eWRX6VHqxQ4lrmCt7e6AjMeQ7dalQMtK7ttJ05lQhjV+eo1ibnqwyh
# UbMOFJBYy5DlSXng+iBLh4VEMiVVOf+hzHJyRDyZ0oladgmYtO2hrRc237HfojsB
# tFZuKF3D2udA9JlkoK9CE1rXjw0ShCoJnvLVaht4XEnQMhvu6BVx8nAtN1o6/BO5
# N3dKqX90ZnJxBKGDVjXMQXLjP8PeWU0zhS8eKiFkCyv/zVuydqH+1wfdBFjMX0EY
# asudfJXcRNlfTASIxeuCCDSNRm/SZiD4wDsRv8bkhTYJ3eFwKHhf+4XTjVJbStOH
# sVIyRaqX7m1EU2W/AdeY9/5wjaCgW4LU72JMsdXtQRpyyJYzB07ofeYcJtDdSa08
# LGIdTlOX8pIXQtsO7WIQvlWuK+RbyKJnEaROThFGMIIHejCCBWKgAwIBAgIKYQ6Q
# 0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5
# WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQD
# Ex9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4
# BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe
# 0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato
# 88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v
# ++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDst
# rjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN
# 91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4ji
# JV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmh
# D+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbi
# wZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8Hh
# hUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaI
# jAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTl
# UAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV
# HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQF
# TuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29m
# dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNf
# MjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNf
# MjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcC
# ARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnlj
# cHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5
# AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oal
# mOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0ep
# o/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1
# HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtY
# SWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInW
# H8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZ
# iWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMd
# YzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7f
# QccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKf
# enoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOpp
# O6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZO
# SEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFVgwghVUAgEBMIGVMH4xCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAFcVKJntkgL3JsAAAAAAVww
# DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIIycKdrb
# 6RI0N3368DSUjSKcsXVOIFdR7emuUrdDDQStMEIGCisGAQQBgjcCAQwxNDAyoBSA
# EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w
# DQYJKoZIhvcNAQEBBQAEggEAFT7F2L09UcBoYpoZKPikefsmA06sDR6gb7/ZHxiU
# Y/67nFM1BsPbzbdAxG3+pLLt2dKoBwf0kpkM7PqIBFhCtGOCK2KHhQ+Rv6qUTTmY
# 04VAYQiYym/LONAqoABqSSHP4PnaSMZl+tgrkBRY97EJ1jNHORlj8/AKLabTaK2c
# XnVH7A2zP7C1J/W1qZXABIGbySh6WYsOcF0YIzcEHvrVCSDSzAV7TmNhw4/NqBYt
# G0IWyu6wRIwoeQnUVkbL1H7VQiOeQZi3+R3CkD9UU/u5fgiE36/yUF4S0A3Oi9tJ
# 2cZrYI9YZGCIznPYGyamKmRkvOtA4eBjdvQvfVqyfjJ9vaGCEuIwghLeBgorBgEE
# AYI3AwMBMYISzjCCEsoGCSqGSIb3DQEHAqCCErswghK3AgEDMQ8wDQYJYIZIAWUD
# BAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoD
# ATAxMA0GCWCGSAFlAwQCAQUABCC5mKvXWvX47LJKSGV1oDHKhv8R2S6i+val91jL
# EaZTNgIGXMtItN5dGBMyMDE5MDczMTE3MjMzOC41MDJaMASAAgH0oIHQpIHNMIHK
# MQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0
# IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNT
# IEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgc2VydmljZaCCDjkwggTxMIID2aADAgECAhMzAAAA4LIYqdTRwrT3AAAAAADg
# MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4X
# DTE4MDgyMzIwMjcwMVoXDTE5MTEyMzIwMjcwMVowgcoxCzAJBgNVBAYTAlVTMQsw
# CQYDVQQIEwJXQTEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRp
# b25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkEyNDAtNEI4Mi0x
# MzBFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBzZXJ2aWNlMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpf4Zw7HpmycexTEuUbmibIkCQz9
# LYqsngnrbYjZRnDaGuNvKFbW5R+smWrQl2coMoc25wyaH0xyBrXYhM0HOXz4XXX0
# 3eIREIHeXIfwZRiE1xRMCeHfxoR2UNYWy3YgU/4+u0MdeVXrl8uZ/4zPT7yGwZLE
# lsF/L65IUU/66mtcVq5hfkn3GCsPqQvnd7VB64AAqNGGlR7kt45aV4N9wPqbpfMI
# m2QXBsTBdQqsJT9AHzFutA6eKpvyS21sXcf6ToojqzP7cpBQ7RJzdOx10Y1w4Q4X
# yHgQs+Bj4ghBZPeAGhccrBXhZ/b8s+08iicVJLFyYbVhqtouFpj3KYcg8wIDAQAB
# o4IBGzCCARcwHQYDVR0OBBYEFGtB0wq2Oc6s7/6eOK1rkm12gIfoMB8GA1UdIwQY
# MBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6
# Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBD
# QV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIw
# MTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgw
# DQYJKoZIhvcNAQELBQADggEBAAsf3p3ZkuQ1usYG/HyHRKiPet31AeyKGJWDUFP2
# GcKteebitZcIXB+UdQmlTK/pcjSHw/JfpasvJnaLvmcHK586N5tlBBjtLjRXeHPC
# HsOWePVfugKI0+s+SBiYd8uergwAkM0Wa0fturgsdZy7GIyv1rcUA6tSBx1ngMX6
# xsbAGTtQXUKNuTMd+GbHlYlY/rrH5stJ1Jn72dIRXDHjXeIuCnbNN5GPwsFlWQcO
# trQIzhyv3PNcDu4YrrbvSV+DDY2hLhXYXojcJh8gJm6amJs+ivvSDzO+YlxC284w
# 3OsiyaVTqte4H1QwmsHq8s4FZwtgiMau4AxskzPWn8DLREkwggZxMIIEWaADAgEC
# AgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0
# aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0yNTA3MDEy
# MTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWlCgCC
# hfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/FgiIRU
# QwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeRX4FU
# sc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/XcfPfBX
# day9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogINeh4
# HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQABo4IB5jCC
# AeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8RhvFM2ha
# hW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYG
# A1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3Js
# L3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcB
# AQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kv
# Y2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSABAf8EgZUw
# gZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5taWNy
# b3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcCAjA0
# HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUAbgB0
# AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Ehb7Prpsz1
# Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7uVOMzPRg
# Eop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqRUgCvOA8X
# 9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9Va8v/rbl
# jjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8+n99lmqQ
# eKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+Y1klD3ou
# OVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh2rBQHm+9
# 8eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRyzR30uIUB
# HoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoouLGp25ay
# p0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx16HSxVXj
# ad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341Hgi62jbb
# 01+P3nSISRKhggLLMIICNAIBATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxCzAJ
# BgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlv
# bnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046QTI0MC00QjgyLTEz
# MEUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIHNlcnZpY2WiIwoBATAH
# BgUrDgMCGgMVAMZ5pIzl3naash0KpCRp+3sIUgvRoIGDMIGApH4wfDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDg68Q3MCIYDzIw
# MTkwNzMxMTUzOTM1WhgPMjAxOTA4MDExNTM5MzVaMHQwOgYKKwYBBAGEWQoEATEs
# MCowCgIFAODrxDcCAQAwBwIBAAICHwMwBwIBAAICEbcwCgIFAODtFbcCAQAwNgYK
# KwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQAC
# AwGGoDANBgkqhkiG9w0BAQUFAAOBgQCoehYNQE8/uhZTvQfCDeob+smJ8MNxcGqc
# 08WouZvsVAPU7FAxgxQw5A0Nl613OMgQxTROqjSPumnDlEu1Lyzb3zt75CZpJKOb
# oi/BPI6wJ6zpzpIVaxAfP9R8U9Lh4Fd80em4xOH1DcVmGSApLgS4dKj6DehB/fsx
# L4peQu4/HzGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy
# MDEwAhMzAAAA4LIYqdTRwrT3AAAAAADgMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkq
# hkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIJqSZot2x1+1
# VYbBWSYOwSFt/PcjTlCbxop8l7QN4JcKMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB
# 5DCBvQQgpYEvVaQzKLJXcoNRkD1VZOcMDTg/j3JdmqC70axCX5AwgZgwgYCkfjB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAOCyGKnU0cK09wAAAAAA
# 4DAiBCDwareqXLQmje1ipTFWfWzgifZ10Bl0TEUqKYfhaR39ITANBgkqhkiG9w0B
# AQsFAASCAQCPOrVGkcwSa+SU8wTQAhB6De6Fzn9Ms71574PbB/ZcxL8OrAnMP9po
# lnV167srzlzR5m/DfmnJdy9L8HLtft6GJjY+JGXjAM5488klbrdvq5aCceesac8X
# hhM52MDRwP1Jm+KI+M3uNTqSBTT3JOeSY1CMYZCl0Fag8tFKNmWtdH1sIJuWOIxG
# IYoswVAIIAPWykz1sHz6GJjb/vmqnVPEGF+EueNL0jvVIhZ53LHgSQxrkjDoaxhx
# 96G6xLPmdrcFKNStDtx4P+Zd7HMf/B3FMEPrrqSFv5umPr+6KE3OeZCG7mjHddJT
# Dx0glW9HnrlRLRdZlt8/jmYZ3RVjdAA5
# SIG # End signature block