Sharing.ps1

#requires -Version 5.1


function Show-KeeperRecordShare {
    <#
        .Synopsis
        Shows a record sharing information
 
        .Parameter Record
        Record UID or any object containing property Uid
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]$Records
    )
    Begin {
        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
        [string[]]$recordUids = @()

    }
    Process {
        foreach ($r in $Records) {
            $uid = $null
            if ($r -is [String]) {
                $uid = $r
            }
            elseif ($null -ne $r.Uid) {
                $uid = $r.Uid
            }
            if ($uid) {
                [KeeperSecurity.Vault.KeeperRecord] $rec = $null
                if (-not $vault.TryGetKeeperRecord($uid, [ref]$rec)) {
                    $entries = Get-KeeperChildItem -Filter $uid -ObjectType Record
                    if ($entries.Uid) {
                        $vault.TryGetRecord($entries[0].Uid, [ref]$rec) | Out-Null
                    }
                }
                if ($rec) {
                    $recordUids += $rec.Uid
                } else {
                    Write-Error -Message "Cannot find a Keeper record: $r" -ErrorAction SilentlyContinue
                }
            }
        }
    }

    End {
        $vault.GetSharesForRecords($recordUids).GetAwaiter().GetResult()
    }
}
New-Alias -Name kshrsh -Value Show-KeeperRecordShare

function Move-KeeperRecordOwnership {
    <#
        .Synopsis
        Transfers record ownership to a user
 
        .Parameter Record
        Record UID or any object containing property Uid
 
        .Parameter User
        User email
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]$Record,
        [Parameter(Mandatory = $true)]$User
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    if ($Record -is [Array]) {
        if ($Record.Count -ne 1) {
            Write-Error -Message 'Only one record is expected' -ErrorAction Stop
        }
        $Record = $Record[0]
    }
    $uid = $null
    if ($Record -is [String]) {
        $uid = $Record
    }
    elseif ($null -ne $Record.Uid) {
        $uid = $Record.Uid
    }

    if ($uid) {
        [KeeperSecurity.Vault.KeeperRecord] $rec = $null
        if (-not $vault.TryGetKeeperRecord($uid, [ref]$rec)) {
            $entries = Get-KeeperChildItem -Filter $uid -ObjectType Record
            if ($entries.Uid) {
                $vault.TryGetRecord($entries[0].Uid, [ref]$rec) | Out-Null
            }
        }
        if ($rec) {
            try {
                $vault.TransferRecordToUser($rec.Uid, $User).GetAwaiter().GetResult() | Out-Null
                Write-Output "Record `"$($rec.Title)`" was transfered to $($User)`nThe new record owner can edit or remove your access to this record."
            }
            catch [KeeperSecurity.Vault.NoActiveShareWithUserException] {
                Write-Output $_
                $prompt =  "Do you want to send share invitation request to `"$($User)`"? (Yes/No)"
                $answer = Read-Host -Prompt $prompt
                if ($answer -in 'yes', 'y') {
                    $vault.SendShareInvitationRequest($User).GetAwaiter().GetResult() | Out-Null
                    Write-Output("Invitation has been sent to $($User)`nPlease repeat this command when your invitation is accepted.");
                }
            }
        } else {
            Write-Error -Message "Cannot find a Keeper record: $Record"
        }
    }
}
New-Alias -Name ktr -Value Move-KeeperRecordOwnership

function Grant-KeeperRecordAccess {
    <#
        .Synopsis
        Shares a record with user
 
        .Parameter Record
        Record UID or any object containing property Uid
 
        .Parameter User
        User email
 
        .Parameter CanEdit
        Grant edit permission
 
        .Parameter CanShare
        Grant re-share permission
 
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]$Record,
        [Parameter(Mandatory = $true)]$User,
        [Parameter()][switch]$CanEdit,
        [Parameter()][switch]$CanShare
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    if ($Record -is [Array]) {
        if ($Record.Count -ne 1) {
            Write-Error -Message 'Only one record is expected' -ErrorAction Stop
        }
        $Record = $Record[0]
    }
    $uid = $null
    if ($Record -is [String]) {
        $uid = $Record
    }
    elseif ($null -ne $Record.Uid) {
        $uid = $Record.Uid
    }

    if ($uid) {
        [KeeperSecurity.Vault.KeeperRecord] $rec = $null
        if (-not $vault.TryGetKeeperRecord($uid, [ref]$rec)) {
            $entries = Get-KeeperChildItem -Filter $uid -ObjectType Record
            if ($entries.Uid) {
                $vault.TryGetRecord($entries[0].Uid, [ref]$rec) | Out-Null
            }
        }
        if ($rec) {
            try {
                $vault.ShareRecordWithUser($rec.Uid, $User, $CanShare.IsPresent, $CanEdit.IsPresent).GetAwaiter().GetResult() | Out-Null
                Write-Output "Record `"$($rec.Title)`" was shared with $($User)"
            }
            catch [KeeperSecurity.Vault.NoActiveShareWithUserException] {
                Write-Output $_
                $prompt =  "Do you want to send share invitation request to `"$($User)`"? (Yes/No)"
                $answer = Read-Host -Prompt $prompt
                if ($answer -in 'yes', 'y') {
                    $vault.SendShareInvitationRequest($User).GetAwaiter().GetResult() | Out-Null
                    Write-Output("Invitation has been sent to $($User)`nPlease repeat this command when your invitation is accepted.");
                }
            }
        } else {
            Write-Error -Message "Cannot find a Keeper record: $Record"
        }
    }
}
New-Alias -Name kshr -Value Grant-KeeperRecordAccess

function Revoke-KeeperRecordAccess {
    <#
        .Synopsis
        Shares a record with user
 
        .Parameter Record
        Record UID or any object containg record UID
 
        .Parameter User
        User email
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]$Record,
        [Parameter(Mandatory = $true)]$User
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    if ($Record -is [Array]) {
        if ($Record.Count -ne 1) {
            Write-Error -Message 'Only one record is expected'
            return
        }
        $Record = $Record[0]
    }
    $uid = $null
    if ($Record -is [String]) {
        $uid = $Record
    }
    elseif ($null -ne $Record.Uid) {
        $uid = $Record.Uid
    }

    $found = $false
    if ($uid) {
        [KeeperSecurity.Vault.KeeperRecord] $rec = $null
        if (-not $vault.TryGetKeeperRecord($uid, [ref]$rec)) {
            $entries = Get-KeeperChildItem -Filter $uid -ObjectType Record
            if ($entries.Uid) {
                $vault.TryGetRecord($entries[0].Uid, [ref]$rec) | Out-Null
            }
        }
        if ($rec) {
            $found = $true
            $vault.RevokeShareFromUser($rec.Uid, $User).GetAwaiter().GetResult() | Out-Null
            Write-Output "Record `"$($rec.Title)`" share has been removed from $($username)"
        }
    }
    if (-not $found) {
        Write-Error -Message "Cannot find a Keeper record: $Record"
    }

}
New-Alias -Name kushr -Value Revoke-KeeperRecordAccess

function Grant-KeeperSharedFolderAccess {
    <#
        .Synopsis
        Adds a user or team to a shared foler
 
        .Parameter SharedFolder
        Shared Folder UID, name or any object containing property Uid
 
        .Parameter User
        User email
 
        .Parameter Team
        Team Name or UID
 
        .Parameter ManageRecords
        Grant Manage Records permission
 
        .Parameter ManageUsers
        Grant Manage Users permission
 
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, Position = 0)]$SharedFolder,
        [Parameter(Mandatory = $true, ParameterSetName='user')]$User,
        [Parameter(Mandatory = $true, ParameterSetName='team')]$Team,
        [Parameter()][switch]$ManageRecords,
        [Parameter()][switch]$ManageUsers
    )

    [KeeperSecurity.Vault.VaultOnline]$private:vault = getVault

    if ($SharedFolder -is [Array]) {
        if ($SharedFolder.Count -ne 1) {
            Write-Error -Message 'Only one shared folder is expected'
            return
        }
        $SharedFolder = $SharedFolder[0]
    }
    $uid = $null
    if ($SharedFolder -is [String]) {
        $uid = $SharedFolder
    }
    elseif ($null -ne $Record.Uid) {
        $uid = $SharedFolder.Uid
    }

    if (-not $uid) {
        Write-Error -Message "Cannot find Shared Folder: $SharedFolder" -ErrorAction Stop
    }

    [KeeperSecurity.Vault.SharedFolder] $sf = $null
    if (-not $vault.TryGetSharedFolder($uid, [ref]$sf)) {
        $sf = $vault.SharedFolders | Where-Object { $_.Name -eq $uid } | Select-Object -First 1
    }
    if (-not $sf) {
        Write-Error -Message "Cannot find Shared Folder: $SharedFolder" -ErrorAction Stop
    }

    if ($User) {
        $userType = [KeeperSecurity.Vault.UserType]::User
        $userId = ([MailAddress]$User).Address
        $userName = $userId
        if (-not $userId) {
            return
        }
    }
    elseif ($Team) {
        $userType = [KeeperSecurity.Vault.UserType]::Team
        [KeeperSecurity.Vault.TeamInfo]$teamInfo = $null
        if ($vault.TryGetTeam($Team, [ref]$teamInfo)) {
            $userId = $teamInfo.TeamUid
            $userName = $teamInfo.Name
        } else {
            $teamInfo = $vault.Teams | Where-Object {  $_.Name -eq $Team } | Select-Object -First 1
            if ($teamInfo) {
                $userId = $teamInfo.TeamUid
                $userName = $teamInfo.Name
            }
        }
        if (-not $userId) {
            ensureAvalableLoaded
            $teamInfo = $Script:Context.AvailableTeams | Where-Object { $_.TeamUid -ceq $Team -or $_.Name -eq $Team  } | Select-Object -First 1
            if ($teamInfo) {
                $userId = $teamInfo.TeamUid
                $userName = $teamInfo.Name
            }
        }

        if (-not $userId) {
            Write-Error  -Message "Cannot find team: $Team" -ErrorAction Stop
        }
    }

    try {
        $options = New-Object KeeperSecurity.Vault.SharedFolderUserOptions
        $options.ManageRecords = $ManageRecords.IsPresent
        $options.ManageUsers = $ManageUsers.IsPresent
        $vault.PutUserToSharedFolder($sf.Uid, $userId, $userType, $options).GetAwaiter().GetResult() | Out-Null
        Write-Output "${userType} `"$($userName)`" has been added to shared folder `"$($sf.Name)`""
    }
    catch [KeeperSecurity.Vault.NoActiveShareWithUserException] {
        Write-Output $_
        $prompt =  "Do you want to send share invitation request to `"$($User)`"? (Yes/No)"
        $answer = Read-Host -Prompt $prompt
        if ($answer -in 'yes', 'y') {
            $vault.SendShareInvitationRequest($User).GetAwaiter().GetResult() | Out-Null
            Write-Output("Invitation has been sent to `"$($User)`"`nPlease repeat this command when your invitation is accepted.");
        }
    }

}

function Revoke-KeeperSharedFolderAccess {
    <#
        .Synopsis
        Removes record share from user
 
        .Parameter SharedFolder
        Shared Folder UID, name or any object containing property Uid
 
        .Parameter User
        User email
 
        .Parameter Team
        Team Name or UID
 
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, Position = 0)]$SharedFolder,
        [Parameter(Mandatory = $true, ParameterSetName='user')]$User,
        [Parameter(Mandatory = $true, ParameterSetName='team')]$Team
    )

    [KeeperSecurity.Vault.VaultOnline]$private:vault = getVault

    if ($SharedFolder -is [Array]) {
        if ($SharedFolder.Count -ne 1) {
            Write-Error -Message 'Only one shared folder is expected'
            return
        }
        $SharedFolder = $SharedFolder[0]
    }
    $uid = $null
    if ($SharedFolder -is [String]) {
        $uid = $SharedFolder
    }
    elseif ($null -ne $Record.Uid) {
        $uid = $SharedFolder.Uid
    }

    if (-not $uid) {
        Write-Error -Message "Cannot find Shared Folder: $SharedFolder" -ErrorAction Stop
    }

    [KeeperSecurity.Vault.SharedFolder] $sf = $null
    if (-not $vault.TryGetSharedFolder($uid, [ref]$sf)) {
        $sf = $vault.SharedFolders | Where-Object { $_.Name -eq $uid } | Select-Object -First 1
    }
    if (-not $sf) {
        Write-Error -Message "Cannot find Shared Folder: $SharedFolder" -ErrorAction Stop
    }

    if ($User) {
        $userType = [KeeperSecurity.Vault.UserType]::User
        $userId = ([MailAddress]$User).Address
        $userName = $userId
        if (-not $userId) {
            return
        }
    }
    elseif ($Team) {
        $userType = [KeeperSecurity.Vault.UserType]::Team
        [KeeperSecurity.Vault.TeamInfo]$teamInfo = $null
        if ($vault.TryGetTeam($Team, [ref]$teamInfo)) {
            $userId = $teamInfo.TeamUid
            $userName = $teamInfo.Name
        } else {
            $teamInfo = $vault.Teams | Where-Object {  $_.Name -eq $Team } | Select-Object -First 1
            if ($teamInfo) {
                $userId = $teamInfo.TeamUid
                $userName = $teamInfo.Name
            }
        }
        if (-not $userId) {
            ensureAvalableLoaded
            $teamInfo = $Script:Context.AvailableTeams | Where-Object { $_.TeamUid -ceq $Team -or $_.Name -eq $Team  } | Select-Object -First 1
            if ($teamInfo) {
                $userId = $teamInfo.TeamUid
                $userName = $teamInfo.Name
            }
        }

        if (-not $userId) {
            Write-Error  -Message "Cannot find team: $Team" -ErrorAction Stop
        }
    }

    $vault.RemoveUserFromSharedFolder($sf.Uid, $userId, $userType).GetAwaiter().GetResult() | Out-Null
    Write-Output "${userType} `"$($userName)`" has been removed from shared folder `"$($sf.Name)`""
}

function ensureAvalableLoaded {
    $vault = $Script:Context.Vault
    if (-not $vault) {
        return
    }

    if ($null -ne $Script:Context.AvailableTeams) {
        return
    }

    $Script:Context.AvailableTeams = @()
    $Script:Context.AvailableUsers = @()

    $teamTask = $vault.GetTeamsForShare()
    $userTask = $vault.GetUsersForShare()
    [System.Threading.Tasks.Task[]]$tasks = $teamTask, $userTask
    [System.Threading.Tasks.Task]::WaitAll($tasks) | Out-Null
    $Script:Context.AvailableTeams += $teamTask.GetAwaiter().GetResult()
    $userInfo = $userTask.GetAwaiter().GetResult()
    $users = @()
    $users += $userInfo.SharesWith
    $users += $userInfo.SharesFrom
    $users += $userInfo.GroupUsers

    $Script:Context.AvailableUsers += ($users | Sort-Object | Get-Unique)
}

$Keeper_TeamCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    ensureAvalableLoaded
    if (-not $Script:Context.AvailableTeams) {
        return $null
    }

    $result = @()
    $toComplete = $wordToComplete
    if ($toComplete.Length -ge 1) {
        if ($toComplete[0] -eq '''') {
            $toComplete = $toComplete.Substring(1, $toComplete.Length - 1)
            $toComplete = $toComplete -replace '''''', ''''
        }
        if ($toComplete[0] -eq '"') {
            $toComplete = $toComplete.Substring(1, $toComplete.Length - 1)
            $toComplete = $toComplete -replace '""', '"'
            $toComplete = $toComplete -replace '`"', '"'
        }
    }

    $toComplete += '*'
    foreach ($team in  $Script:Context.AvailableTeams) {
        if ($team.Name -like $toComplete) {
            $name = $team.Name
            if ($name -match ' ') {
                $name = $name -replace '''', ''''''
                $name = '''' + $name + ''''
            }
            $result += $name
        }
    }
    if ($result.Count -gt 0) {
        return $result
    } else {
        return $null
    }
}

$Keeper_UserCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    ensureAvalableLoaded
    if (-not $Script:Context.AvailableUsers) {
        return $null
    }

    $result = @()
    $toComplete = $wordToComplete
    if ($toComplete.Length -ge 1) {
        if ($toComplete[0] -eq '''') {
            $toComplete = $toComplete.Substring(1, $toComplete.Length - 1)
            $toComplete = $toComplete -replace '''''', ''''
        }
        if ($toComplete[0] -eq '"') {
            $toComplete = $toComplete.Substring(1, $toComplete.Length - 1)
            $toComplete = $toComplete -replace '""', '"'
            $toComplete = $toComplete -replace '`"', '"'
        }
    }

    $toComplete += '*'
    foreach ($user in  $Script:Context.AvailableUsers) {
        if ($user -like $toComplete) {
            $result += $user
        }
    }
    if ($result.Count -gt 0) {
        return $result
    } else {
        return $null
    }
}

Register-ArgumentCompleter -CommandName Grant-KeeperSharedFolderAccess -ParameterName Team -ScriptBlock $Keeper_TeamCompleter
Register-ArgumentCompleter -CommandName Grant-KeeperSharedFolderAccess -ParameterName User -ScriptBlock $Keeper_UserCompleter
Register-ArgumentCompleter -CommandName Grant-KeeperSharedFolderAccess -ParameterName SharedFolder -ScriptBlock $Keeper_SharedFolderCompleter

New-Alias -Name kshf -Value Grant-KeeperSharedFolderAccess

Register-ArgumentCompleter -CommandName Revoke-KeeperSharedFolderAccess -ParameterName Team -ScriptBlock $Keeper_TeamCompleter
Register-ArgumentCompleter -CommandName Revoke-KeeperSharedFolderAccess -ParameterName User -ScriptBlock $Keeper_UserCompleter
Register-ArgumentCompleter -CommandName Revoke-KeeperSharedFolderAccess -ParameterName SharedFolder -ScriptBlock $Keeper_SharedFolderCompleter

New-Alias -Name kushf -Value Revoke-KeeperSharedFolderAccess

function Get-KeeperAvailableTeam {
    <#
        .Synopsis
        Get Keeper Available Teams
 
        .Parameter Uid
        Team UID
 
        .Parameter Filter
        Return matching teams only
    #>

        [CmdletBinding()]
        [OutputType([KeeperSecurity.Vault.TeamInfo[]])]
        Param (
            [string] $Uid,
            [string] $Filter
        )

        ensureAvalableLoaded
        $teams = $Script:Context.AvailableTeams
        if ($Uid) {
            $teams | Where-Object { $_.TeamUid -ceq $Uid } | Select-Object -First 1
        } else {
            foreach ($team in $teams) {
                if ($Filter) {
                    $match = $($team.Uid, $team.Name) | Select-String $Filter | Select-Object -First 1
                    if (-not $match) {
                        continue
                    }
                }
                $team
            }
        }
    }
    New-Alias -Name kat -Value Get-KeeperAvailableTeam

    function New-KeeperOneTimeShare {
        <#
        .Synopsis
        New Keeper One-Time Share
 
        .Parameter Uid
        Shared Record UID
 
        .Parameter Expiration
        Expiration TimeSpan
 
        .Parameter ShareName
        One-Time Share Name
    #>

        [CmdletBinding()]
        [OutputType([string])]
        Param (
            [Parameter(Mandatory = $true)][string] $Uid,
            [Parameter(Mandatory=$true)][TimeSpan] $ExpireIn,
            [Parameter(Mandatory=$false)][string] $ShareName
        )

        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
        $oneTimeShare = [KeeperSecurity.Vault.ExternalRecordShareExtensions]::CreateExternalRecordShare($vault, $Uid, $ExpireIn, $ShareName).GetAwaiter().GetResult()
        return $oneTimeShare
    }

    New-Alias -Name kotsn -Value New-KeeperOneTimeShare

    function Get-KeeperOneTimeShare {
        <#
        .Synopsis
        Get Keeper One-Time Shares
 
        .Parameter Uid
        Shared Record UID
 
    #>

        [CmdletBinding()]
        [OutputType([string])]
        Param (
            [Parameter(Mandatory = $true, Position=0)][string] $Uid
        )

        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
        [KeeperSecurity.Vault.ExternalRecordShareExtensions]::GetExernalRecordShares($vault, $Uid).GetAwaiter().GetResult()
    }
    New-Alias -Name kotsg -Value Get-KeeperOneTimeShare

    function Remove-KeeperOneTimeShare {
        <#
        .Synopsis
        Deletes Keeper One-Time Share(s)
 
        .Parameter Uid
        Shared Record UID
 
        .Parameter ShareName
        One-Time Share Name
    #>

        [CmdletBinding()]
        [OutputType([string])]
        Param (
            [Parameter(Mandatory = $true)][string] $Uid,
            [string[]] $ShareName
        )

        [KeeperSecurity.Vault.VaultOnline]$vault = getVault

        $shares = Get-KeeperOneTimeShare $Uid
        [String[]]$clientUids = @()
        foreach ($n in $ShareName) {
            $share = $shares | Where-Object { $_.Name -eq $n } | Select-Object -First 1
            if ($share) {
                $clientUids += $share.ClientId
            } else {
                Write-Information -MessageData "One-Time Share not found: $n"
            }
        }
        [KeeperSecurity.Vault.ExternalRecordShareExtensions]::DeleteExernalRecordShares($vault, $Uid, $clientUids).GetAwaiter().GetResult() | Out-Null
    }
    New-Alias -Name kotsr -Value Remove-KeeperOneTimeShare

# SIG # Begin signature block
# MIIR1wYJKoZIhvcNAQcCoIIRyDCCEcQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUnqSZtOKlaBZ4x21bEj22kXIx
# B/mggg4jMIIGsDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0B
# AQwFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk
# IFJvb3QgRzQwHhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg
# Q0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5
# WRuxiEL1M4zrPYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJP
# DqFX/IiZwZHMgQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXz
# ENOLsvsI8IrgnQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bq
# HPNlaJGiTUyCEUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTC
# fMjqGzLmysL0p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaD
# G7dqZy3SvUQakhCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urO
# kfW+0/tvk2E0XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7AD
# K5GyNnm+960IHnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4
# R+Z1MI3sMJN2FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlN
# Wdt4z4FKPkBHX8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0I
# U0F8WD1Hs/q27IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwHQYDVR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaA
# FOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK
# BggrBgEFBQcDAzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4
# oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJv
# b3RHNC5jcmwwHAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcN
# AQEMBQADggIBADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcT
# Ep6QRJ9L/Z6jfCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WT
# auPrINHVUHmImoqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9
# ntSZz0rdKOtfJqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np37
# 5SFTWsPK6Wrxoj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0
# HKKlS43Nb3Y3LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL
# 6TEa/y4ZXDlx4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+1
# 6oh7cGvmoLr9Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8
# M4+uKIw8y4+ICw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrF
# hsP2JjMMB0ug0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy
# 1lKQ/a+FSCH5Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIHazCC
# BVOgAwIBAgIQAnNTGQOIer82vZ1cJyDJDjANBgkqhkiG9w0BAQsFADBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg
# Q0ExMB4XDTIyMDIwMjAwMDAwMFoXDTI1MDIwMTIzNTk1OVowcDELMAkGA1UEBhMC
# VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK
# ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5
# IEluYy4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDNgTqmksdjUyKF
# 5zWkDyghf0PLWJWdzG0TX2j8B4J55xwt+B17zd4Xc3n0dvmSVAyPQANeN+mP1chf
# 4LTRn9h4jWb8Jsfn+JzyRhj/gYINYvBnpRpqoM0z7QC9Ebwj5T61Cogm9EKGcrG+
# Ujh+Z7pTqfSUrHD8NMXhDL/UpVn+w0Pb4qg7o7AH2o94n7u/qTlMGZCs+VCAvhNr
# wPABxvFY07YGb9t5/IZlPE8vG3p1vw2SbgREgFWSEQFj6X2CIhSrbiFCW/766/Mq
# EX6qm+RyF71fD4d3yShg39guaE9o+TBl1MqVCje4bK/wGoNxCho0I6Z1fBBKloyp
# vlx3gPpU7tJJ+KpuIiel9R9dGQuscqKzehPtbRc9Abr9ThN/HrLg1sFFVMdn2oMR
# 63QCUdz+B1NuS7Ap8Ti7XvAPJHzEuQDcdMcRbkIfllJVqrb9UXEFwOPzvRU2KrcQ
# 42Jlnn4T+WenPx5Nr3o/o08WLhLTicEK1OacEowyRLBmih4Gxpdk3fUAVCEkdvmq
# TSydQpl1Bk8V88dxCkB1wMZyFYLNcddBL4kUbwjso/z6f2TtfAVYs/iIRWqs7Xqt
# 4F2BBqobOGMymwg6VgVjjzDIgJCZSbjpq2IoVTci5vli6vxgSoZ01fccSaKa4Izm
# B7DbobIkIjLgPqpnCkqlHuJj5hQ9twIDAQABo4ICBjCCAgIwHwYDVR0jBBgwFoAU
# aDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFCZd3/KEdT2t5WTIFb3TUaM4
# sTikMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0f
# BIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGg
# T4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29k
# ZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZn
# gQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BT
# MIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln
# aWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIx
# Q0ExLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQAGyDM3Cbxq
# Auhr8O2xwOoCSVKmFkXqicwlrugwLW44Y4WX+imvTrGfjj2S99k/4D5H8DgtW/u8
# tOxcCoehTOCIEwP5TLrieHppsqAR4jaJRcdAHOWiJ1bmwQBv/cBU9vaelL0oXxxf
# TwD9oDaQNuyq6p+nIJMqbKv33b8AWGe3zq4JwblaFjRDL5lUDNhPx3g/pm7JhnbX
# 7QTKydAJvpbuP5cqUH1GEeVMjc5vEELtGNy/fy7Ekm4dndX4IZcFXW5L0Lx8cReB
# hIZwA+pzdzTWQYvfxgRMb/j2uY+Tkb6Wz2x9BBS1UXiP2qrs3rhQv8DZRkUSqnko
# YD4uJP8gk8BXcIXIThgEF2YCq2hBiwna5Ijbwkmjn1lWwGv15SznTOTnrVApJqB1
# tB2s2ovUNV4CyKDPVr+9/CS6IQJfEZeHYcYLsIga2q5NZCrqZAasBfCwALVkALos
# DIWhs33vYLfETMSuk5Hd5JC+hLjVM3ZJwslvnc/wec2r0GNAiZ3a1aweC7NYuzRz
# 29Mi/eR/4ylmCltyZqYJ1JcC/g6eY2Q0xkdWc8P0yHfQ/3fe7+AKXXKNjfv858GW
# lg1Ck2lvwPdLqJWqj1FwJPiGRCB+WulPe0csTyWnf+ed45TXx69tZ6BZr0Xr2jXu
# ybBdJtg0NN0a62xxWrmX42CgsrzHzRm7OzGCAx4wggMaAgEBMH0waTELMAkGA1UE
# BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy
# dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB
# MQIQAnNTGQOIer82vZ1cJyDJDjAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEK
# MAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3
# AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUrtDfmkx899b/yCUe
# Zstr1S7trukwDQYJKoZIhvcNAQEBBQAEggIAfrePbNsM8KyHac3fnoYf5ek0a/4E
# Vmfih3ajVcG2wagK0Kwl39eyWp4/EaVh1ZDLImOp/GBuD7jdwV2aOhoAAeqUypKK
# e/9k+92IQqH9EVIoZFKQDHEI6NtafFmLvRjzPhoymgQkW2qgLivHs8fcWBEUDP6d
# I0L//xr7NBjGOEKRJozoUNyoHSYb5fuBfQh637xIp6NJX1OaG2RyKM14qdcNQwON
# q7sfVUVweEu6WaIycWA7zffEppPqInKzhmH+ZeR1CRelKqEFyRBZXdBWED5YULI3
# NO/1D85Cq/w6PnZtUe6HJCUjL0YkbJwVd1CKPEKj5bIzFffEaHU5a19y1OEzfr2B
# diY78DFvIMRqBW02GNdhyvgDkyXnt7HSORCjjIqu/ZcaQMqm8ryiv1HB3CBLY4HO
# QHzKbgD3rslqXvt55FKrf5ND1b+pD3mZRDOPA8gLah+ogyRWEr+/pumJWA5MRQCa
# 2zsna3Tf7iKsURXNkbaWF8Do286r/QDq69vPct0DJMttigwmoQJxmianvC1E/tIk
# N+FB1DjgmJg8AwJwjjtpe95Tm/YOAwRvUQo6Dapfpg5We/HbwHUkaaWuM/64adBF
# GVGEsnsxvG+vhr0TayUVr1h6Cl0fxAiUtZxOYk1X7hGWznmc2sKY126O24mBZM+K
# XMXebBzvVG/tClI=
# SIG # End signature block