Framework/Core/SubscriptionSecurity/RBAC.ps1

using namespace Microsoft.Azure.Commands.Resources.Models.Authorization
using namespace System.Management.Automation
Set-StrictMode -Version Latest 

# Class to implement Subscription RBAC controls
class RBAC: CommandBase
{    
    hidden [SubscriptionRBAC] $Policy = $null;

    hidden [PSRoleAssignment[]] $RoleAssignments = $null;
    hidden [ActiveRBACAccount[]] $ApplicableActiveAccounts = $null;

    hidden [ActiveRBACAccount[]] $MissingActiveAccounts = $null;

    hidden [RBACAccountRoleMapping[]] $MatchedActiveAccounts = $null;
    hidden [RBACAccountRoleMapping[]] $MatchedDeprecatedAccounts = $null;

    RBAC([string] $subscriptionId, [InvocationInfo] $invocationContext, [string] $tags): 
        Base($subscriptionId, $invocationContext)
    { 
        $this.Policy = [SubscriptionRBAC] $this.LoadServerConfigFile("Subscription.RBAC.json"); 
        $this.FilterTags = $this.ConvertToStringArray($tags);
    }

    hidden [PSRoleAssignment[]] GetRoleAssignments()
    {
        if(-not $this.RoleAssignments)
        {
            $this.RoleAssignments = [RoleAssignmentHelper]::GetAzSDKRoleAssignment($true, $true);
        }
        return $this.RoleAssignments;
    }
    
    hidden [ActiveRBACAccount[]] GetApplicableActiveAccounts()
    {
        if($null -eq $this.ApplicableActiveAccounts)
        {
            $this.ApplicableActiveAccounts = @();

            $subscriptionId = $this.SubscriptionContext.SubscriptionId;
            if(($this.FilterTags | Measure-Object).Count -ne 0)
            {
                $this.Policy.ValidActiveAccounts | 
                    ForEach-Object {
                        $currentItem = $_;
                        if(($currentItem.Tags | Where-Object { $this.FilterTags -Contains $_ } | Measure-Object).Count -ne 0)
                        {
                            # Resolve the value of SubscriptionId
                            $currentItem.Scope = $global:ExecutionContext.InvokeCommand.ExpandString($currentItem.Scope);
                            if([string]::IsNullOrWhiteSpace($currentItem.Scope))
                            {
                                $currentItem.Scope = "/subscriptions/$subscriptionId"
                            }

                            $this.ApplicableActiveAccounts  += $currentItem;
                        }
                    }
            }
        }
            
        return $this.ApplicableActiveAccounts;
    }
    
    hidden [void] LoadActiveAccountStatus()
    {
        $this.MissingActiveAccounts = @();
        $this.MatchedActiveAccounts = @();

        $this.GetApplicableActiveAccounts() | Where-Object { $_.Enabled } | 
            ForEach-Object { 
                $currentItem = $_;
                $matchedAccount = $this.GetRoleAssignments() | 
                                    Where-Object { 
                                        ($_.ObjectId -eq $currentItem.ObjectId) -and 
                                        ($_.Scope -eq $currentItem.Scope) -and 
                                        ($_.RoleDefinitionName -eq $currentItem.RoleDefinitionName) 
                                    } | Select-Object -First 1
                if($matchedAccount)
                {
                    $this.MatchedActiveAccounts += [RBACAccountRoleMapping]@{
                            RoleAssignment = $matchedAccount;
                            RBACAccount = $currentItem;
                        };
                }
                else
                {
                    $this.MissingActiveAccounts += $currentItem;
                }                        
            };
    }

    hidden [void] LoadDeprecatedAccountStatus()
    {
        $this.MatchedDeprecatedAccounts =  @();
        
        $this.Policy.DeprecatedAccounts | Where-Object { $_.Enabled } | 
            ForEach-Object { 
                $currentItem = $_;
                $matchedAccounts = $this.GetRoleAssignments() | 
                                    Where-Object {  ($_.ObjectId -eq $currentItem.ObjectId) };

                if(($matchedAccounts | Measure-Object).Count -ne 0)
                {
                    $matchedAccounts | ForEach-Object {
                        $this.MatchedDeprecatedAccounts += [RBACAccountRoleMapping]@{
                                RoleAssignment = $_;
                                RBACAccount = $currentItem;
                            };
                    };
                }
            };
    }
    
    [MessageData[]] SetRBACAccounts()
    {    
        [MessageData[]] $messages = @();
        
        # Set Active accounts
        $nonConfiguredActiveAccounts = $this.GetNonConfiguredActiveAccounts(([ref]$messages));
        if($nonConfiguredActiveAccounts.Count -ne 0)
        {
            $provisionAccounts = @();
            $provisionAccounts += $nonConfiguredActiveAccounts | Where-Object { $_.Type -eq [RBACAccountType]::Provision };

            if($provisionAccounts.Count -ne 0)
            {
                # Adding Central Accounts
                $messages += [MessageData]::new([Constants]::SingleDashLine + "`r`nAdding following active accounts to the subscription. Total accounts: $($provisionAccounts.Count)", $provisionAccounts);                                            
                $errorCount = 0;
                $currentCount = 0;
                $provisionAccounts | ForEach-Object {
                    $accountName = $_.Name;
                    $currentCount += 1;
                    # Add account
                    try
                    {
                        $this.PublishCustomMessage("Adding account [$accountName] to the subscription");
                        New-AzureRmRoleAssignment -ObjectId $_.ObjectId -Scope $_.Scope -RoleDefinitionName $_.RoleDefinitionName -ErrorAction Stop | Out-Null                                         
                    }
                    catch
                    {
                        $messages += [MessageData]::new("Error while adding account [$accountName] to the subscription", $_, [MessageType]::Error);
                        $errorCount += 1;
                    }

                    $this.CommandProgress($provisionAccounts.Count, $currentCount, 5);
                };

                [MessageData[]] $resultMessages = @();
                if($errorCount -eq 0)
                {
                    $resultMessages += [MessageData]::new("All required active accounts have been added to the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                }
                elseif($errorCount -eq $provisionAccounts.Count)
                {
                    $resultMessages += [MessageData]::new("No accounts have been added to the subscription due to error occurred. Please add the accounts manually.`r`n" + [Constants]::SingleDashLine, [MessageType]::Error);
                }
                else
                {
                    $resultMessages += [MessageData]::new("$errorCount/$($provisionAccounts.Count) required active account(s) have not been added to the subscription. Please add the accounts manually.", [MessageType]::Error);
                    $resultMessages += [MessageData]::new("$($provisionAccounts.Count - $errorCount)/$($provisionAccounts.Count) required active account(s) have been added to the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                }
                
                $messages += $resultMessages;
                $this.PublishCustomMessage($resultMessages);
            }

            $nonProvisionAccounts = @();
            $nonProvisionAccounts += $nonConfiguredActiveAccounts | Where-Object { $_.Type -ne [RBACAccountType]::Provision };

            if($nonProvisionAccounts.Count -ne 0)
            {
                $messageText = "Some accounts are not added to the subscription, since the account type is not set to 'Provision' in RBAC policy file. Total accounts: $($nonProvisionAccounts.Count)";
                $messages += [MessageData]::new($messageText, $nonProvisionAccounts);
                $this.PublishCustomMessage($messageText);
            }
        }
        else
        {
        
        }

        return $messages;
    }

    [ActiveRBACAccount[]] GetNonConfiguredActiveAccounts([ref]$messages)
    {
        if($this.Policy.ValidActiveAccounts.Count -ne 0)
        {
            if($this.GetApplicableActiveAccounts().Count -ne 0)
            {
                $startMessage = [MessageData]::new("Processing RBAC rules for adding central accounts. Tags:[$([string]::Join(",", $this.FilterTags))]. Total accounts: $($this.GetApplicableActiveAccounts().Count)");
                $messages.Value += $startMessage;
                $this.PublishCustomMessage($startMessage);
                
                $disabledAccounts = $this.GetApplicableActiveAccounts() | Where-Object { -not $_.Enabled };
                if(($disabledAccounts | Measure-Object).Count -ne 0)
                {
                    $disabledMessage = "Found accounts which are disabled. Total disabled accounts: $($disabledAccounts.Count)";
                    $messages.Value += [MessageData]::new($disabledMessage, $disabledAccounts);
                    $this.PublishCustomMessage($disabledMessage, [MessageType]::Warning);
                }

                # Load all properties
                $this.LoadActiveAccountStatus();

                if($this.MatchedActiveAccounts.Count -ne 0)
                {
                    $messages.Value += [MessageData]::new("Following accounts have correct RBAC configured. Total accounts: $($this.MatchedActiveAccounts.Count)", ($this.MatchedActiveAccounts | Select-Object -Property RoleAssignment));
                }
                
                if($this.MissingActiveAccounts.Count -ne 0)
                {
                    $missingAccountsMessage = "Following accounts does not have correct RBAC configured. Total accounts: $($this.MissingActiveAccounts.Count)";

                    $messages.Value += [MessageData]::new($missingAccountsMessage, $this.MissingActiveAccounts);
                    $this.PublishCustomMessage($missingAccountsMessage);
                }
                else
                {
                    $successMessage = [MessageData]::new("All required accounts are correctly configured");
                    $messages.Value += $successMessage;
                    $this.PublishCustomMessage($successMessage);
                }
                return $this.MissingActiveAccounts;                    
            }
            else
            {
                $this.PublishCustomMessage("No active accounts have been found that matches the specified tags. Tags:[$([string]::Join(",", $this.FilterTags))].", [MessageType]::Warning);
            }
        }
        else
        {
            $this.PublishCustomMessage("No active accounts found in the RBAC policy file", [MessageType]::Warning);
        }
        return @();
    }

    [RBACAccountRoleMapping[]] GetMatchedDeprecatedAccounts([ref]$messages)
    {
        if($this.Policy.DeprecatedAccounts -and $this.Policy.DeprecatedAccounts.Count -ne 0)
        {
            #$this.PublishCustomMessage("Found deprecated accounts in the RBAC policy file");
            
            $startMessage = [MessageData]::new("Processing RBAC rules for removing deprecated central accounts. Total accounts: $($this.Policy.DeprecatedAccounts.Count)");
            $messages.Value += $startMessage;
            #$this.PublishCustomMessage($startMessage);
                
            $disabledAccounts = $this.Policy.DeprecatedAccounts | Where-Object { -not $_.Enabled };
            if(($disabledAccounts | Measure-Object).Count -ne 0)
            {
                $disabledMessage = "Found accounts which are disabled. Total disabled accounts: $($disabledAccounts.Count)";
                $messages.Value += [MessageData]::new($disabledMessage, $disabledAccounts);
                $this.PublishCustomMessage($disabledMessage, [MessageType]::Warning);
            }

            # Load all properties
            $this.LoadDeprecatedAccountStatus();
                            
            if($this.MatchedDeprecatedAccounts.Count -ne 0)
            {
                $messages.Value += [MessageData]::new("Following deprecated accounts must be removed from the subscription. Total accounts: $($this.MatchedDeprecatedAccounts.Count)", ($this.MatchedDeprecatedAccounts | Select-Object -Property RoleAssignment));
            }
            else
            {
                $resultMessage = [MessageData]::new("No deprecated accounts found on the subscription");
                $messages.Value += $resultMessage;
                $this.PublishCustomMessage($resultMessage);
            }
            return $this.MatchedDeprecatedAccounts;                    
        }
        else
        {
            $this.PublishCustomMessage("No deprecated accounts found in the RBAC policy file");
        }
        return @();
    }

    [MessageData[]] RemoveRBACAccounts()
    {    
        [MessageData[]] $messages = @();

        if($this.Policy.ValidActiveAccounts.Count -ne 0)
        {
            if($this.GetApplicableActiveAccounts().Count -ne 0)
            {
                $startMessage += [MessageData]::new("Processing RBAC rules for removing central accounts. Tags:[$([string]::Join(",", $this.FilterTags))]. Total accounts: $($this.GetApplicableActiveAccounts().Count)");
                $messages += $startMessage;
                $this.PublishCustomMessage($startMessage);

                $disabledAccounts = $this.GetApplicableActiveAccounts() | Where-Object { -not $_.Enabled };
                if(($disabledAccounts | Measure-Object).Count -ne 0)
                {
                    $disabledMessage = "Found accounts which are disabled. Total disabled accounts: $($disabledAccounts.Count)";
                    $messages += [MessageData]::new($disabledMessage, $disabledAccounts);
                    $this.PublishCustomMessage($disabledMessage, [MessageType]::Warning);
                }

                # Load all properties
                $this.LoadActiveAccountStatus();

                $messages += $this.RemoveRoleAssignments($this.MatchedActiveAccounts);
            }
            else
            {
                $this.PublishCustomMessage("No active accounts have been found that matches the specified tags. Tags:[$([string]::Join(",", $this.FilterTags))].", [MessageType]::Warning);
            }
        }
        else
        {
            $this.PublishCustomMessage("No active accounts found in the RBAC policy file", [MessageType]::Warning);
        }
                
        # Remove deprecated accounts
        $depAccounts = $this.GetMatchedDeprecatedAccounts(([ref]$messages));
        $messages += $this.RemoveRBACAccounts($depAccounts);

        return $messages;
    }

    hidden [MessageData[]] RemoveRoleAssignments([RBACAccountRoleMapping[]] $rbacRoleMapping)
    {
        [MessageData[]] $messages = @();
        if($rbacRoleMapping.Count -ne 0)
        {
            $messages += [MessageData]::new("Total matching accounts found in the subscription: $($rbacRoleMapping.Count)");
            $provisionAccounts = @();
            $provisionAccounts += $rbacRoleMapping | Where-Object { $_.RBACAccount.Type -eq [RBACAccountType]::Provision };

            $messages += $this.RemoveRBACAccounts($provisionAccounts);

            $nonProvisionAccounts = @();
            $nonProvisionAccounts += $rbacRoleMapping | Where-Object { $_.RBACAccount.Type -ne [RBACAccountType]::Provision };

            if($nonProvisionAccounts.Count -ne 0)
            {
                $messageText = "Some accounts were not removed from the subscription, since the account type is not set to 'Provision' in RBAC policy file. Total accounts: $($nonProvisionAccounts.Count)";
                $messages += [MessageData]::new($messageText, ($nonProvisionAccounts | Select-Object -Property RoleAssignment));                                            
                #$this.PublishCustomMessage($messageText);
            }
        }
        return $messages;
    }

    hidden [MessageData[]] RemoveRBACAccounts([RBACAccountRoleMapping[]] $rbacRoleMapping)
    {
        [MessageData[]] $messages = @();
        if($rbacRoleMapping.Count -ne 0)
        {
            # Removing Central Accounts
            $messages += [MessageData]::new([Constants]::SingleDashLine + "`r`nRemoving following accounts from the subscription. Total accounts: $($rbacRoleMapping.Count)", ($rbacRoleMapping | Select-Object -Property RoleAssignment));
            $errorCount = 0;
            $currentCount = 0;
            $rbacRoleMapping | ForEach-Object {
                $accountName = $_.RoleAssignment.DisplayName;
                $currentCount += 1;
                if(-not [string]::IsNullOrWhiteSpace($_.RoleAssignment.SignInName))
                {
                    $accountName += " ($($_.RoleAssignment.SignInName))";
                }
                # Remove account
                try
                {
                    $this.PublishCustomMessage("Removing account [$accountName] from the subscription");
                    Remove-AzureRmRoleAssignment -ObjectId $_.RoleAssignment.ObjectId -Scope $_.RoleAssignment.Scope -RoleDefinitionId $_.RoleAssignment.RoleDefinitionId -ErrorAction Stop | Out-Null                                         
                }
                catch
                {
                    $messages += [MessageData]::new("Error while removing account [$accountName] from the subscription", $_, [MessageType]::Error);
                    $errorCount += 1;
                }

                $this.CommandProgress($rbacRoleMapping.Count, $currentCount, 5);
            };

            [MessageData[]] $resultMessages = @();
            if($errorCount -eq 0)
            {
                $resultMessages += [MessageData]::new("All matched accounts have been removed from the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
            }
            elseif($errorCount -eq $rbacRoleMapping.Count)
            {
                $resultMessages += [MessageData]::new("No accounts have been removed from the subscription due to error occurred. Please remove the accounts manually.`r`n" + [Constants]::SingleDashLine, [MessageType]::Error);
            }
            else
            {
                $resultMessages += [MessageData]::new("$errorCount/$($rbacRoleMapping.Count) matched account(s) have not been removed from the subscription. Please remove the accounts manually.", [MessageType]::Error);
                $resultMessages += [MessageData]::new("$($rbacRoleMapping.Count - $errorCount)/$($rbacRoleMapping.Count) matched account(s) have been removed from the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
            }

            $messages += $resultMessages;
            $this.PublishCustomMessage($resultMessages);
        }
        return $messages;
    }
}

class RBACAccountRoleMapping
{
    hidden [PSRoleAssignment] $RoleAssignment = $null;
    
    hidden [RBACAccount] $RBACAccount = $null;
}
# SIG # Begin signature block
# MIIkAgYJKoZIhvcNAQcCoIIj8zCCI+8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB5x3yJdY/1YPC5
# 4dUleoGS/FxMyJZoSk+8rkkw41jLxqCCDZMwggYRMIID+aADAgECAhMzAAAAjoeR
# pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q
# UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv
# 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc
# wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg
# 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm
# +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH
# uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww
# HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y
# FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw
# MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1
# ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw
# SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv
# ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG
# AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p
# Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq
# hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR
# avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO
# qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM
# tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d
# 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX
# jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW
# UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI
# BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd
# 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald
# G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF
# he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww
# ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5
# MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw
# MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ
# TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT
# iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS
# L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H
# DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ
# PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5
# bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS
# rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh
# vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON
# 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc
# v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw
# iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC
# AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM
# HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud
# IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0
# dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF
# BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC
# Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA
# YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI
# hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s
# PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL
# UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7
# pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft
# 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4
# MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv
# FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG
# 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf
# 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY
# S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv
# QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV
# xTCCFcECAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA
# AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx
# DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq
# hkiG9w0BCQQxIgQgoluADS8GQf6C88zY80hYfM+bLtMWZJqRtBS6BaCeD/gwRAYK
# KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr
# YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAJeMvJfIX5npBpbY
# 3RWU/6jV/hqyu9TTfBsynnDSzOys6D1s++BspBsfE2WA+LEFvrF3+xC3Gfh5Tg+y
# dnHfJ70yXj3JXMz69p6KyhzKSy4T2SB6uvEb0qVhAHPLMzwyatcoDBSvaYkmCktd
# NQbjtBQn6jxC5Bb+dJXqv4wauMa/qAlfqji0BoWNVNLJA3xY3JiIVHyZLoEEkJdQ
# pk+GzxWbbUx62BHGTbqmMMThWO27549EDjTNE9IsWLGEWIuI8bBxFSMt475amQLY
# 7qkD1CQOPriOILdR0CizX3FSqxxC/hubshtwjLkM450bIdAEsKJq4/QdeSIa30b1
# Fnhgn2ShghNNMIITSQYKKwYBBAGCNwMDATGCEzkwghM1BgkqhkiG9w0BBwKgghMm
# MIITIgIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSgggEsBIIB
# KDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgaRcRc9uSaIhR
# 3WXC0x+Q0bIl+avV5wSwxEl5cEljUswCBlmSOrJ/xBgTMjAxNzA5MDUwOTM3MTAu
# Njg5WjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0Ug
# RVNOOkJCRUMtMzBDQS0yREJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIO0DCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcN
# AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw
# BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw
# MB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp
# HQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVT
# JwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q
# 6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h
# /EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+
# 79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4
# zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAd
# BgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBT
# AHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw
# FoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
# XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAx
# MC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0G
# CCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BT
# L2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBs
# AGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4IC
# AQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efw
# eL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt0
# 70IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQi
# PM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93F
# SguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4a
# rgRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qA
# xdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995y
# fmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaY
# LeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL
# 32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4
# L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPCoAMCAQIC
# EzMAAAChpf257qf8np0AAAAAAKEwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjQ4WhcNMTgwOTA3MTc1NjQ4
# WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE
# CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOkJCRUMtMzBDQS0yREJF
# MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm9ABeeYir3p8G3Ue87dn1h3ep94ANNgS
# +QfqCHsfTU3KhZR6q3ZrKgdFjVEn07ZdRqUlxmIUeYtPzOYs9eyfTXodNCI2KrjD
# 4uzFUO3T/UPBLb/F8PrPzISQ66Kmsm1XoI+5YXDUSc6IL4MuO4FKk7VJSsRlyZaF
# 5C/6rOLYVx0z9r4Q58JSGxPg+RQ2qLOb9NsV8PTSa30tuFXOEelW/5TpIQ67kVfM
# nBV5cM2OrNPjgZmYww4H39tzxc8pY/U+7DcYenP2JHW1/Mk3lDBXB9WgQBVNCxaw
# 5tU3XTzY06u8h5eHelVzS2FDwfMJiJK+zrjlhEo8FjecQc4gl4HICQIDAQABo4IB
# GzCCARcwHQYDVR0OBBYEFKtcuYK+cSrVj+DosinP+hvTt/pIMB8GA1UdIwQYMBaA
# FNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8y
# MDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAt
# MDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ
# KoZIhvcNAQELBQADggEBACNgTLgFVOlnyb45PBUywxPIswCQfxczm/12L11MErPo
# sCSi/rL0H6iyji5OEAdc6Pc0iu40HejhRIb4HtvePRKUh8GaD0Pgm/oUYau26hLj
# qohq12V35Qdb0FBT0cVa1CgvKkpReR95OSp3x2HlI38qBdomntVAtuJf3DoTdOU6
# /ar7PwL8K/n4IFJbKMpdsiAo7h0e9IqEvBdS6rMScZosHRtODXjR25MNJF4XiElU
# IfzYXCbQ6RPhbMpOvwe4O/nhnC9GDGU6nEWwCadzTCxrttcWY+D8cjiZpgXNMpFB
# ol76u9etDnuFy/MPdzt4MtNPlpEUSCPGipeXWB39pUGhggN5MIICYQIBATCB46GB
# uaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsG
# A1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOkJCRUMtMzBDQS0y
# REJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEw
# CQYFKw4DAhoFAAMVAIKuifW05j8WXCC8F+TBw0DNOetooIHCMIG/pIG8MIG5MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BS
# MScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NTdGNi1DMUUwLTU1NEMxKzApBgNV
# BAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcN
# AQEFBQACBQDdWK1iMCIYDzIwMTcwOTA1MDQ1ODEwWhgPMjAxNzA5MDYwNDU4MTBa
# MHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAN1YrWICAQAwCgIBAAICC9cCAf8wBwIB
# AAICGHYwCgIFAN1Z/uICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoD
# AaAKMAgCAQACAxbjYKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEASpxc
# bnQwkaEQw3guLPcoKD7VPE+CLc+YZ+QvqoPBFBxqYH9L7McbMsAjkkpnkLtivNT0
# Ga5eHWnZuP7LUzsrLz9DlQ/Y9SajTkNhcWFKXfe25Kky8pX3RKsHwNUk139FQTIs
# PldOqxTHPZof785BWPvVm2za4jcoSJKgloBr2b9nfzIdfMJrTI+Pf2TGKd3a8cwx
# mddqY34PblgwYq58X3Xw+/p1dLD7KrQWkmn/Ea2RyRdhaS1DghJNNmj7AKfQkZYn
# oMNjicyDnIZAwk905FXMtf0Ovy9GA2rkKgPoZKmRsD0tt6h3bhmeMC89eYW6juUi
# kZmuGfAWGY2KSoTW8zGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAAAoaX9ue6n/J6dAAAAAAChMA0GCWCGSAFlAwQCAQUAoIIB
# MjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIAnT
# JgGyOpdq1Pr1mAQKHSJo19k30iFdmiqmCgSYXZ4oMIHiBgsqhkiG9w0BCRACDDGB
# 0jCBzzCBzDCBsQQUgq6J9bTmPxZcILwX5MHDQM0562gwgZgwgYCkfjB8MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAKGl/bnup/yenQAAAAAAoTAWBBSm
# Dc2qsbSvLSMHZgss4F9KRUM0TDANBgkqhkiG9w0BAQsFAASCAQCbtiYREISKTofi
# v0CWdLiF1Wt6jhYh33vyxyH24iM4/lVGpCRy831DhVXhY3/6AXU/C8T00g0bbbc1
# 9W+Wlba8AeCJBbUvgQs4B8dfuMfD5sJwmV0rzgAtBF/23ZM2aKnJpdT8g3ru2mO2
# B8AlGvQJlTqX1NqfeeHJsa4vVVq48OCwOfiKEjGXyEg6oBfEQbH/eNe+xTv6STK+
# 6kkIDKqhbtqA/xSfrOCb98H5+DAwZ0AfGt9y3l64cGsz7PoMqBRubnYXl+YsGjgc
# VTrZeUsCxBCqUcQhez+ZhoC3YT8jNhD/+5OGFOY4q4svnOPett71MWddCY1GKJnC
# R2vWF7d2
# SIG # End signature block