AuthCommands.ps1

#requires -Version 5.1

$expires = @(
    [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin,
    [KeeperSecurity.Authentication.TwoFactorDuration]::Every30Days,
    [KeeperSecurity.Authentication.TwoFactorDuration]::Forever)

function Test-InteractiveSession {
    if ($psISE) {
        return $true
    }
    if ($PSIsInteractive) {
        return $true
    }
    if ($PSPrivateMetadata.JobId) {
        return $false
    }
    return $Host.UI.SupportsVirtualTerminal
}

function twoFactorChannelToText ([KeeperSecurity.Authentication.TwoFactorChannel] $channel) {
    if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::Authenticator) {
        return 'authenticator'
    }
    if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::TextMessage) {
        return 'sms'
    }
    if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::DuoSecurity) {
        return 'duo'
    }
    if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::RSASecurID) {
        return 'rsa'
    }
    if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::KeeperDNA) {
        return 'dna'
    }
    return ''
}

function deviceApprovalChannelToText ([KeeperSecurity.Authentication.DeviceApprovalChannel]$channel) {
    if ($channel -eq [KeeperSecurity.Authentication.DeviceApprovalChannel]::Email) {
        return 'email'
    }
    if ($channel -eq [KeeperSecurity.Authentication.DeviceApprovalChannel]::KeeperPush) {
        return 'keeper'
    }
    if ($channel -eq [KeeperSecurity.Authentication.DeviceApprovalChannel]::TwoFactorAuth) {
        return '2fa'
    }
    return ''
}

function twoFactorDurationToExpire ([KeeperSecurity.Authentication.TwoFactorDuration] $duration) {
    if ($duration -eq [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin) {
        return 'now'
    }
    if ($duration -eq [KeeperSecurity.Authentication.TwoFactorDuration]::Forever) {
        return 'never'
    }
    return "$([int]$duration)_days"
}


function getStepPrompt ([KeeperSecurity.Authentication.IAuthentication] $auth) {
    $prompt = "`nUnsupported ($($auth.step.State.ToString()))"
    if ($auth.step -is [KeeperSecurity.Authentication.Sync.DeviceApprovalStep]) {
        $prompt = "`nDevice Approval ($(deviceApprovalChannelToText $auth.step.DefaultChannel))"
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.TwoFactorStep]) {
        $channelText = twoFactorChannelToText $auth.step.DefaultChannel
        $prompt = "`n2FA channel($($channelText)) expire[$(twoFactorDurationToExpire $auth.step.Duration)]"
    }

    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) {
        $prompt = "`nMaster Password"
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoTokenStep]) {
        $prompt = "`nSSO Token"
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoDataKeyStep]) {
        $prompt = "`nSSO Login Approval"
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.ReadyToLoginStep]) {
        $prompt = "`nLogin"
    }

    return $prompt
}

function printStepHelp ([KeeperSecurity.Authentication.IAuthentication] $auth) {
    $commands = @()
    if ($auth.step -is [KeeperSecurity.Authentication.Sync.DeviceApprovalStep]) {
        $channels = @()
        foreach ($ch in $auth.step.Channels) {
            $channels += deviceApprovalChannelToText $ch
        }
        if ($channels) {
            $commands += "channel=<$($channels -join ' | ')> to change channel."
        }
        $commands += "`"push`" to send a push to the channel"
        $commands += '<code> to send a code to the channel'
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.TwoFactorStep]) {
        $channels = @()
        foreach ($ch in $auth.step.Channels) {
            $channelText = twoFactorChannelToText $ch
            if ($channelText) {
                $channels += $channelText
            }
        }
        if ($channels) {
            $commands += "channel=<$($channels -join ' | ')> to change channel."
        }

        $channels = @()
        foreach ($ch in $auth.step.Channels) {
            $pushes = $auth.step.GetChannelPushActions($ch)
            if ($null -ne $pushes) {
                foreach ($push in $pushes) {
                    $channels += [KeeperSecurity.Authentication.AuthUIExtensions]::GetPushActionText($push)
                }
            }
        }
        if ($channels) {
            $commands += "`"$($channels -join ' | ')`" to send a push/code"
        }

        $channels = @()
        foreach ($exp in $expires) {
            $channels += twoFactorDurationToExpire $exp
        }
        $commands += "expire=<$($channels -join ' | ')> to set 2fa expiration."
        $commands += '<code> to send a 2fa code.'
    }

    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) {
        $commands += '<password> to send a master password.'
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoTokenStep]) {
        $commands += $auth.step.SsoLoginUrl
        $commands += ''
        if (-not $auth.step.LoginAsProvider) {
            $commands += '"password" to login using master password.'
        }
        $commands += '<sso token> paste SSO login token.'
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoDataKeyStep]) {
        $channels = @()
        foreach ($ch in $auth.step.Channels) {
            $channels += [KeeperSecurity.Authentication.AuthUIExtensions]::SsoDataKeyShareChannelText($ch)
        }
        if ($channels) {
            $commands += "`"$($channels -join ' | ')`" to request login approval"
        }
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.ReadyToLoginStep]) {
        $commands += '"login <Keeper Email>" login to Keeper as user'
        $commands += '"login_sso <Enterprise Domain>" login to Enterprise Domain'
    }

    if ($commands) {
        Write-Output "`nAvailable Commands`n"
        foreach ($command in $commands) {
            Write-Output $command
        }
        Write-Output '<Enter> to resume'
    }
}

function executeStepAction ([KeeperSecurity.Authentication.IAuthentication] $auth, [string] $action) {

    function tryExpireToTwoFactorDuration ([string] $expire, [ref] [KeeperSecurity.Authentication.TwoFactorDuration] $duration) {
        $result = $true
        if ($expire -eq 'now') {
            $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin
        }
        elseif ($expire -eq 'never') {
            $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::Forever
        }
        elseif ($expire -eq '30_days') {
            $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::Every30Days
        }
        else {
            $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin
        }

        return $result
    }

    function tryTextToDeviceApprovalChannel ([string] $text, [ref] [KeeperSecurity.Authentication.DeviceApprovalChannel] $channel) {
        $result = $true
        if ($text -eq 'email') {
            $channel.Value = [KeeperSecurity.Authentication.DeviceApprovalChannel]::Email
        }
        elseif ($text -eq 'keeper') {
            $channel.Value = [KeeperSecurity.Authentication.DeviceApprovalChannel]::KeeperPush
        }
        elseif ($text -eq '2fa') {
            $channel.Value = [KeeperSecurity.Authentication.DeviceApprovalChannel]::TwoFactorAuth
        }
        else {
            Write-Output 'Unsupported device approval channel:', $text
            $result = $false
        }

        return $result
    }

    function tryTextToTwoFactorChannel ([string] $text, [ref] [KeeperSecurity.Authentication.TwoFactorChannel] $channel) {
        $result = $true
        if ($text -eq 'authenticator') {
            $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::Authenticator
        }
        elseif ($text -eq 'sms') {
            $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::TextMessage
        }
        elseif ($text -eq 'duo') {
            $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::DuoSecurity
        }
        elseif ($text -eq 'rsa') {
            $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::RSASecurID
        }
        elseif ($text -eq 'dna') {
            $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::KeeperDNA
        }
        else {
            Write-Output 'Unsupported 2FA channel:', $text
            $result = $false
        }

        return $result
    }

    if ($auth.step -is [KeeperSecurity.Authentication.Sync.DeviceApprovalStep]) {
        if ($action -eq 'push') {
            $auth.step.SendPush($auth.step.DefaultChannel).GetAwaiter().GetResult() | Out-Null
        }
        elseif ($action -match 'channel\s*=\s*(.*)') {
            $ch = $Matches.1
            [KeeperSecurity.Authentication.DeviceApprovalChannel]$cha = $auth.step.DefaultChannel
            if (tryTextToDeviceApprovalChannel ($ch) ([ref]$cha)) {
                $auth.step.DefaultChannel = $cha
            }
        }
        else {
            Try {
                $auth.step.SendCode($auth.step.DefaultChannel, $action).GetAwaiter().GetResult() | Out-Null
            }
            Catch [KeeperSecurity.Authentication.KeeperApiException] {
                Write-Warning $_
            }
            Catch {
                Write-Error $_
            }
        }
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.TwoFactorStep]) {
        if ($action -match 'channel\s*=\s*(.*)') {
            $ch = $Matches.1
            [KeeperSecurity.Authentication.TwoFactorChannel]$cha = $auth.step.DefaultChannel
            if (tryTextToTwoFactorChannel($ch) ([ref]$cha)) {
                $auth.step.DefaultChannel = $cha
            }
        }
        elseif ($action -match 'expire\s*=\s*(.*)') {
            $exp = $Matches.1
            [KeeperSecurity.Authentication.TwoFactorDuration]$dur = $auth.step.Duration
            if (tryExpireToTwoFactorDuration($exp) ([ref]$dur)) {
                $auth.step.Duration = $dur
            }
        }
        else {
            foreach ($cha in $auth.step.Channels) {
                $pushes = $auth.step.GetChannelPushActions($cha)
                if ($null -ne $pushes) {
                    foreach ($push in $pushes) {
                        if ($action -eq [KeeperSecurity.Authentication.AuthUIExtensions]::GetPushActionText($push)) {
                            $auth.step.SendPush($push).GetAwaiter().GetResult() | Out-Null
                            return
                        }
                    }
                }
                Try {
                    $auth.step.SendCode($auth.step.DefaultChannel, $action).GetAwaiter().GetResult() | Out-Null
                }
                Catch {
                    Write-Error $_
                }
            }
        }
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) {
        Try {
            $auth.step.VerifyPassword($action).GetAwaiter().GetResult() | Out-Null
        }
        Catch [KeeperSecurity.Authentication.KeeperAuthFailed] {
            Write-Warning 'Invalid password'
        }
        Catch {
            Write-Error $_
        }
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoTokenStep]) {
        if ($action -eq 'password') {
            $auth.step.LoginWithPassword().GetAwaiter().GetResult() | Out-Null
        }
        else {
            $auth.step.SetSsoToken($action).GetAwaiter().GetResult() | Out-Null
        }
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoDataKeyStep]) {
        [KeeperSecurity.Authentication.DataKeyShareChannel]$channel = [KeeperSecurity.Authentication.DataKeyShareChannel]::KeeperPush
        if ([KeeperSecurity.Authentication.AuthUIExtensions]::TryParseDataKeyShareChannel($action, [ref]$channel)) {
            $auth.step.RequestDataKey($channel).GetAwaiter().GetResult() | Out-Null
        }
    }
    elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.ReadyToLoginStep]) {
        if ($action -match '^login\s+(.*)$') {
            $username = $Matches.1
            $auth.Login($username).GetAwaiter().GetResult() | Out-Null
        }
        elseif ($action -match '^login_sso\s+(.*)$') {
            $providerName = $Matches.1
            $auth.LoginSso($providerName).GetAwaiter().GetResult() | Out-Null
        }
    }
}

function Connect-Keeper {
    <#
    .Synopsis
    Login to Keeper

   .Parameter Username
    User email

    .Parameter Password
    User password

    .Parameter NewLogin
    Do not use Last Login information

    .Parameter SsoPassword
    Use Master Password for SSO account

    .Parameter SsoProvider
    Login using SSO provider

    .Parameter Server
    Change default keeper server

    .Parameter Config
    Config file name
#>

    [CmdletBinding(DefaultParameterSetName = 'regular')]
    Param(
        [Parameter(Position = 0)][string] $Username,
        [Parameter()] [SecureString]$Password,
        [Parameter()][switch] $NewLogin,
        [Parameter(ParameterSetName = 'sso_password')][switch] $SsoPassword,
        [Parameter(ParameterSetName = 'sso_provider')][switch] $SsoProvider,
        [Parameter()][string] $Server,
        [Parameter()][string] $Config
    )

    Disconnect-Keeper -Resume | Out-Null
    if ($Config) {
        $storage = New-Object KeeperSecurity.Configuration.JsonConfigurationStorage $Config
    } else {
        $storage = New-Object KeeperSecurity.Configuration.JsonConfigurationStorage
    }

    if (-not $Server) {
        $Server = $storage.LastServer
        if ($Server) {
            Write-Information -MessageData "`nUsing Keeper Server: $Server`n"
        }
        else {
            Write-Information -MessageData "`nUsing Default Keeper Server: $([KeeperSecurity.Authentication.KeeperEndpoint]::DefaultKeeperServer)`n"
        }
    }


    $endpoint = New-Object KeeperSecurity.Authentication.KeeperEndpoint($storage, $Server)
    $endpoint.DeviceName = 'PowerShell Commander'
    $endpoint.ClientVersion = 'c16.11.0'
    $authFlow = New-Object KeeperSecurity.Authentication.Sync.AuthSync($storage, $endpoint)

    $authFlow.ResumeSession = $true
    $authFlow.AlternatePassword = $SsoPassword.IsPresent

    if (-not $NewLogin.IsPresent -and -not $SsoProvider.IsPresent) {
        if (-not $Username) {
            $Username = $storage.LastLogin
        }
    }

    $namePrompt = 'Keeper Username'
    if ($SsoProvider.IsPresent) {
        $namePrompt = 'Enterprise Domain'
    }

    if ($Username) {
        Write-Output "$(($namePrompt + ': ').PadLeft(21, ' ')) $Username"
    }
    elseif (Test-InteractiveSession) {
        while (-not $Username) {
            $Username = Read-Host -Prompt $namePrompt.PadLeft(20, ' ')
        }
    } else {
        Write-Error "Non-interactive session detected" -ErrorAction Stop 
    }

    if ($SsoProvider.IsPresent) {
        $authFlow.LoginSso($Username).GetAwaiter().GetResult() | Out-Null
    }
    else {
        $passwords = @()
        if ($Password) {
            if ($Password -is [SecureString]) {
                $passwords += [Net.NetworkCredential]::new('', $Password).Password
            }
            elseif ($Password -is [String]) {
                $passwords += $Password
            }
        }
        $authFlow.Login($Username, $passwords).GetAwaiter().GetResult() | Out-Null
    }
    Write-Output ""
    while (-not $authFlow.IsCompleted) {
        if ($lastStep -ne $authFlow.Step.State) {
            printStepHelp $authFlow
            $lastStep = $authFlow.Step.State
        }

        $prompt = getStepPrompt $authFlow

        if ($authFlow.Step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) {
            if (Test-InteractiveSession) {
                $securedPassword = Read-Host -Prompt $prompt -AsSecureString
                if ($securedPassword.Length -gt 0) {
                    $action = [Net.NetworkCredential]::new('', $securedPassword).Password
                }
                else {
                    $action = ''
                }
            } else {
                Write-Error "Non-interactive session detected" -ErrorAction Stop 
            }
        }
        else {
            if (Test-InteractiveSession) {
                $action = Read-Host -Prompt $prompt
            } else {
                Write-Error "Non-interactive session detected" -ErrorAction Stop 
            }
        }

        if ($action) {
            if ($action -eq '?') {
            }
            else {
                executeStepAction $authFlow $action
            }
        }
    }

    if ($authFlow.Step.State -ne [KeeperSecurity.Authentication.Sync.AuthState]::Connected) {
        if ($authFlow.Step -is [KeeperSecurity.Authentication.Sync.ErrorStep]) {
            Write-Warning $authFlow.Step.Message
        }
        return
    }

    $auth = $authFlow
    if ([KeeperSecurity.Authentication.AuthExtensions]::IsAuthenticated($auth)) {
        Write-Debug -Message "Connected to Keeper as $Username"

        $vault = New-Object KeeperSecurity.Vault.VaultOnline($auth)
        $task = $vault.SyncDown()
        Write-Information -MessageData 'Syncing ...'
        $task.GetAwaiter().GetResult() | Out-Null
        $vault.AutoSync = $true

        $Script:Context.Auth = $auth
        $Script:Context.Vault = $vault

        [KeeperSecurity.Vault.VaultData]$vaultData = $vault
        Write-Information -MessageData "Decrypted $($vaultData.RecordCount) record(s)"
        Set-KeeperLocation -Path '\' | Out-Null
    }
}

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

    $prefixes = @('', 'dev.', 'qa.')
    $suffixes = $('.com', '.eu')

    $prefixes | ForEach-Object { $p = $_; $suffixes | ForEach-Object { $s = $_; "${p}keepersecurity${s}" } } | Where-Object { $_.StartsWith($wordToComplete) }
}
Register-ArgumentCompleter -Command Connect-Keeper -ParameterName Server -ScriptBlock $Keeper_ConfigServerCompleter
New-Alias -Name kc -Value Connect-Keeper

function Disconnect-Keeper {
    <#
    .Synopsis
    Logout from Keeper
#>


    [CmdletBinding()]
    Param(
        [Parameter()][switch] $Resume
    )

    $Script:Context.AvailableTeams = $null
    $Script:Context.AvailableUsers = $null
    
    $Script:Context.ManagedCompanyId = 0
    $Script:Context.Enterprise = $null

    $vault = $Script:Context.Vault
    if ($vault) {
        $vault.Dispose() | Out-Null
    }
    $Script:Context.Vault = $null

    [KeeperSecurity.Authentication.IAuthentication] $auth = $Script:Context.Auth
    if ($auth) {
        if (-not $Resume.IsPresent) {
            $auth.Logout().GetAwaiter().GetResult() | Out-Null
        }
        $auth.Dispose() | Out-Null

    }
    $Script:Context.Auth = $null
}
New-Alias -Name kq -Value Disconnect-Keeper

function Sync-Keeper {
    <#
    .Synopsis
    Sync down with Keeper
#>


    [CmdletBinding()]
    [KeeperSecurity.Vault.VaultOnline]$vault = $Script:Context.Vault
    if ($vault) {
        $task = $vault.SyncDown()
        $task.GetAwaiter().GetResult() | Out-Null
    }
    else {
        Write-Error -Message "Not connected" -ErrorAction Stop
    }
}
New-Alias -Name ks -Value Sync-Keeper

function Get-KeeperInformation {
    <#
    .Synopsis
    Prints account license information
    #>


    $vault = getVault
    [KeeperSecurity.Authentication.IAuthentication]$auth = $vault.Auth

    [KeeperSecurity.Authentication.AccountLicense]$license = $auth.AuthContext.License
    switch ($license.AccountType) {
        0 { $accountType = $license.ProductTypeName }
        1 { $accountType = 'Family Plan'}
        2 { $accountType = 'Enterprise' }
        Default { $accountType = $license.ProductTypeName }
    }
    $accountType = 'Enterprise'
    [PSCustomObject]@{
        PSTypeName  = "KeeperSecurity.License.Info"
        User        = $auth.Username
        Server      = $auth.Endpoint.Server
        Admin       = $auth.AuthContext.IsEnterpriseAdmin
        AccountType = $accountType 
        RenewalDate = $license.ExpirationDate
        StorageCapacity = [int] [Math]::Truncate($license.BytesTotal / (1024 * 1024 * 1024))
        StorageUsage = [int] [Math]::Truncate($license.BytesUsed * 100 / $license.BytesTotal)
        StorageExpires = $license.StorageExpirationDate
    }

    if ($license.AccountType -eq 2) {
        $enterprise = getEnterprise
        if ($enterprise) {
            $enterpriseLicense = $enterprise.enterpriseData.EnterpriseLicense
            $productTypeId = $enterpriseLicense.ProductTypeId
            if ($productTypeId -in @(2, 5)) {
                $tier = $enterpriseLicense.Tier
                if ($tier -eq 1) {
                    $plan = 'Enterprise'
                } else {
                    $plan = 'Business'
                }
            }
            elseif ($productTypeId -in @(9, 10)) {
                $distributor = $enterpriseLicense.Distributor
                if ($distributor -eq $true) {
                    $plan = 'Distributor'
                } else {
                    $plan = 'Managed MSP'
                }
            }
            elseif ($productTypeId -in @(11, 12)) {
                $plan = 'Keeper MSP'
            }
            elseif ($productTypeId -eq 8) {
                $tier = $enterpriseLicense.Tier
                if ($tier -eq 1) {
                    $plan = 'Enterprise'
                } else {
                    $plan = 'Business'
                }
                $plan = "MC $plan"
            } else {
                $plan = 'Unknown'
            }
            if ($productTypeId -in @(5, 10, 12)) {
                $plan = "$plan Trial"
            }

            $enterpriseInfo = [PSCustomObject]@{
                PSTypeName  = "KeeperSecurity.License.EnterpriseInfo"
                LicenseType = 'Enterprise'
                EnterpriseName = $enterprise.loader.EnterpriseName
                BasePlan    = $plan
            }
            if ($enterpriseLicense.Paid) {
                $expiration = $enterpriseLicense.Expiration
                if ($expiration -gt 0) {
                    $exp = [KeeperSecurity.Utils.DateTimeOffsetExtensions]::FromUnixTimeMilliseconds($expiration)
                    $expDate = $exp.ToString('d')
                    Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'Expires' -Value $expDate
                }
                
                switch ($enterpriseLicense.filePlanTypeId) {
                    -1 { $filePlan = 'No Storage' }
                    0 { $filePlan = 'Trial' }
                    1 { $filePlan = '1GB' }
                    2 { $filePlan = '10GB' }
                    3 { $filePlan = '50GB' }
                    4 { $filePlan = '100GB' }
                    5 { $filePlan = '250GB' }
                    6 { $filePlan = '500GB' }
                    7 { $filePlan = '1TB' }
                    8 { $filePlan = '10TB' }
                    Default { $filePlan = '???' }
                }
                Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'StorageCapacity' -Value $filePlan

                $numberOfSeats = $enterpriseLicense.NumberOfSeats
                if ($numberOfSeats -gt 0) {
                    Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'TotalUsers' -Value $numberOfSeats
                }
                $seatsAllocated = $enterpriseLicense.SeatsAllocated
                if ($seatsAllocated -gt 0) {
                    Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'ActiveUsers' -Value $seatsAllocated
                }
                $seatsPending = $enterpriseLicense.SeatsPending
                if ($seatsAllocated -gt 0) {
                    Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'InvitedUsers' -Value $SeatsPending
                }

            }
            $enterpriseInfo
        }
    }
}
New-Alias -Name kwhoami -Value Get-KeeperInformation

function compareArrays {
    param ($array1, $array2)

    if ($array1.Length -eq $array2.Length) {
        foreach ($i in 0..($array1.Length-1)) {
            if ($array1[$i] -ne $array2[$i]) {
                return $false
            }
        }
        return $true
    }
    return $false
}

function formatTimeout {
    param ($timeout)

    if ($timeout -gt 0) {
        $dayMillis = [TimeSpan]::FromDays(1).TotalMilliseconds
        if ($logoutTimer -gt $dayMillis) { 
            return "$([Math]::Round($logoutTimer / $dayMillis)) day(s)"
        }

        $hourMillis = [TimeSpan]::FromHours(1).TotalMilliseconds
        if ($logoutTimer -gt $hourMillis) { 
            return "$([Math]::Round($logoutTimer / $hourMillis)) hour(s)"
        }

        $minuteMillis = [TimeSpan]::FromMinutes(1).TotalMilliseconds
        return "$([Math]::Round($logoutTimer / $minuteMillis)) minute(s)"
    }
}

function Get-KeeperDeviceSettings {
    <#
    .SYNOPSIS
    Display settings of the current device
    #>


    $vault = getVault
    $auth = $vault.Auth

    $accountSummary = [KeeperSecurity.Authentication.AuthExtensions]::LoadAccountSummary($auth).GetAwaiter().GetResult()
    $device = $accountSummary.Devices | Where-Object { compareArrays $_.EncryptedDeviceToken $auth.DeviceToken } | Select-Object -First 1
    if (-not $device) {
        Write-Error -Message "The current device could not be found" -ErrorAction Stop
    }

    $logoutTimer = $accountSummary.Settings.LogoutTimer
    if ($logoutTimer -gt 0) {
        $logoutTimerText = formatTimeout $logoutTimer
    } else {
        $logoutTimerText = '1 hour(s)'
    }

    $persistentLoginRestricted = $false
    if ($accountSummary.Enforcements.Booleans) {
        $plp = $accountSummary.Enforcements.Booleans | Where-Object { $_.Key -eq 'restrict_persistent_login' } | Select-Object -First 1
        if ($plp) {
            $persistentLoginRestricted = $plp.Value
        }
    }
    $persistentLoginEnabled = $false
    if (-not $persistentLoginRestricted) {
        $persistentLoginEnabled = $accountSummary.Settings.PersistentLogin
    }

    $settings = [PSCustomObject]@{
        PSTypeName  = "KeeperSecurity.Authentication.DeviceInfo"
        DeviceName = $device.DeviceName
        PersistentLogin = $persistentLoginEnabled
        DataKeyPresent = $device.EncryptedDataKeyPresent
        IpAutoApprove = -not $accountSummary.Settings.IpDisableAutoApprove
        IsSsoUser = $accountSummary.Settings.SsoUser
        DeviceLogoutTimeout = $logoutTimerText
    }

    if ($accountSummary.Enforcements.Longs) {
        $enf = $accountSummary.Enforcements.Longs | Where-Object { $_.Key -eq 'logout_timer_desktop' } | Select-Object -First 1
        if ($enf.Length -eq 1) {
            $entLogoutTimer = $enf.Value
            if ($entLogoutTimer -gt 0) {
                $entLogoutTimerText = formatTimeout $entLogoutTimer
                Add-Member -InputObject $settings -MemberType NoteProperty -Name 'EnterpriseLogoutTimeout' -Value $entLogoutTimerText
            }
        }
    }
    $settings
}

function Set-KeeperDeviceSettings {
    <#
    .SYNOPSIS
        Modifies the current device settings

    .PARAMETER NewName
        Modifies device name

    .PARAMETER Timeout
        Sets inactivity timeout. Format: NUMBER[h|d]
        default - minutes, h - hours, d - days
    
    .PARAMETER Register
        Register current device for Persistent Login

    .PARAMETER PersistentLogin
        Enables or disables Persistent login for account
        ON | OFF

    .PARAMETER IpAutoApprove
        Enables or disables Automatic Approval by IP address for account
        ON | OFF

    .EXAMPLE
        C:\PS> Set-KeeperDeviceSettings -NewName 'Azure' -Timeout 30d -PersistentLogin ON -Register
    #>


    [CmdletBinding()]
    Param (
        [Parameter()][String] $NewName,
        [Parameter(HelpMessage='NUMBER[h|d]')][String] $Timeout,
        [Parameter()][Switch] $Register,
        [Parameter()][ValidateSet('ON', 'OFF')][String] $PersistentLogin,
        [Parameter()][ValidateSet('ON', 'OFF')][String] $IpAutoApprove
    )

    $vault = getVault
    $auth = $vault.Auth

    $accountSummary = [KeeperSecurity.Authentication.AuthExtensions]::LoadAccountSummary($auth).GetAwaiter().GetResult()
    $device = $accountSummary.Devices | Where-Object { compareArrays $_.EncryptedDeviceToken $auth.DeviceToken } | Select-Object -First 1
    if (-not $device) {
        Write-Error -Message "The current device could not be found" -ErrorAction Stop
    }

    $changed = $false

    if ($NewName) {
        $request = New-Object Authentication.DeviceUpdateRequest
        $request.ClientVersion = $auth.Endpoint.ClientVersion
        $request.DeviceStatus = [Authentication.DeviceStatus]::DeviceOk
        $request.DeviceName = $NewName
        $request.EncryptedDeviceToken = $device.EncryptedDeviceToken

        $auth.ExecuteAuthRest("authentication/update_device", $request, $null, 0).GetAwaiter().GetResult() | Out-Null
        Write-Information "Device name was changed to `"$NewName`""
        $changed = $true
    }

    $persistentLoginRestricted = $false
    if ($accountSummary.Enforcements.Booleans) {
        $plp = $accountSummary.Enforcements.Booleans | Where-Object { $_.Key -eq 'restrict_persistent_login' } | Select-Object -First 1
        if ($plp) {
            $persistentLoginRestricted = $plp.Value
        }
    }
    if ($Register.IsPresent) {
        if ($persistentLoginRestricted -eq $true) {
            Write-Error "Persistent Login feature is restricted by Enterprise Administrator" -ErrorAction Stop
        }

        $registered = [KeeperSecurity.Authentication.AuthExtensions]::RegisterDataKeyForDevice($auth, $device).GetAwaiter().GetResult()
        if ($registered) {
            Write-Information "Device is registered for Persistent Login"
        }
        $changed = $true
    }

    if ($PersistentLogin) {
        if ($persistentLoginRestricted -eq $true) {
            Write-Error "Persistent Login feature is restricted by Enterprise Administrator" -ErrorAction Stop
        }
        $value = '0'
        if ($PersistentLogin -eq 'ON') {
            $value = '1'
        }
        [KeeperSecurity.Authentication.AuthExtensions]::SetSessionParameter($auth, 'persistent_login', $value).GetAwaiter().GetResult() | Out-Null
        $changed = $true
    }

    if ($IpAutoApprove) {
        $value = '1'
        if ($IpAutoApprove -eq 'ON') {
            $value = '0'
        }
        [KeeperSecurity.Authentication.AuthExtensions]::SetSessionParameter($auth, 'ip_disable_auto_approve', $value).GetAwaiter().GetResult() | Out-Null
        $changed = $true
    }

    if ($Timeout) {
        $lastLetter = $Timeout[-1]
        if ($lastLetter -eq 'd') {
            $timeoutInt = $Timeout.Substring(0, $Timeout.Length - 1)
        }
        elseif ($lastLetter -eq 'h') {
            $timeoutInt = $Timeout.Substring(0, $Timeout.Length - 1)
        } else {
            $lastLetter = ''
            $timeoutInt = $Timeout
        }

        $minutes = $null
        $b = [int]::TryParse($timeoutInt, [ref]$minutes)
        if (-not $b) {
            Write-Error "Invalid timeout value `"$Timeout`". Format NUMBER[h|d]. d-days, h-hours. default minutes " -ErrorAction Stop
        }
        if ($lastLetter -eq 'h') {
            $minutes = $minutes * 60
        }
        elseif ($lastLetter -eq 'd') {
            $minutes = $minutes * (60 * 24) 
        }
        [KeeperSecurity.Authentication.AuthExtensions]::SetSessionInactivityTimeout($auth, $minutes).GetAwaiter().GetResult() | Out-Null
        $changed = $true
    }

    if (-not $changed) {
        Get-KeeperDeviceSettings
    }
}
New-Alias -Name this-device -Value Set-KeeperDeviceSettings
# SIG # Begin signature block
# MIIngQYJKoZIhvcNAQcCoIIncjCCJ24CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCApov4BNnT7mspu
# WPF7Jg8Gm+4jgqCtV3iro81t8cqcv6CCIQQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXH
# JQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMf
# UBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w
# 1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRk
# tFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYb
# qMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUm
# cJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP6
# 5x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzK
# QtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo
# 80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjB
# Jgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXche
# MBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB
# /wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU
# 7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDig
# NqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI
# hvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd
# 4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiC
# qBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl
# /Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeC
# RK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYT
# gAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/
# a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37
# xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmL
# NriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0
# YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJ
# RyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIG
# sDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw
# HhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0
# ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zr
# PYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHM
# gQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8Irg
# nQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyC
# EUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0
# p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQa
# khCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0
# XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960I
# HnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2
# FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBH
# X8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q2
# 7IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD
# VR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1k
# TN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcD
# AzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj
# ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmww
# HAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIB
# ADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6j
# fCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmI
# moqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtf
# JqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrx
# oj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3
# LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx
# 4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9
# Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+I
# Cw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug
# 0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5
# Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQ
# C65mvFq6f5WHxvnpBOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0
# ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAw
# MDAwMFoXDTM1MTEyNTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp
# Z2lDZXJ0MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjE
# iDtqmeOlwf0KMCBDEr4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOc
# Re8+CEJp+3R2O8oo76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/
# GLoUb35SfWHh43rOH3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0Cha
# V76Nhnj37DEYTX9ReNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8U
# uKGn9966fR5X6kgXj3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHw
# SJ+QQRZ1fisD8UTVDSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4
# EfvFrpVNnes4c16Jidj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzI
# Xp4P0wXkgNs+CO/CacBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3Jyidx
# W48jwBqIJqImd93NRxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizch
# NULpUEoA6Vva7b1XCB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJ
# cv6dQ4aEKOX5AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/
# BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEE
# AjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8w
# HQYDVR0OBBYEFJ9XLAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuG
# SWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQw
# OTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKG
# TGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJT
# QTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIB
# AD2tHh92mVvjOIQSR9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq
# 3igpwrPvBmZdrlWBb0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcH
# zBMutB6HzeledbDCzFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTV
# OoJ4eTq7gj9UFAL1UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4H
# v5swO+aAXxWUm3WpByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgt
# d7/fvWTlCs30VAGEsshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaid
# RJXrI+UzB6vAlk/8a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhd
# mm4bhYsVA6G2WgNFYagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dH
# PoWrUhftNpFC5H7QEY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDi
# CLg4D+TPVgKx2EgEdeoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7z
# cEO1xwcdcqJsyz/JceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHSTCCBTGgAwIBAgIQ
# BaOjGrg1T58olh09AgdhuDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0
# ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTI0
# MTIzMTAwMDAwMFoXDTI1MTIzMDIzNTk1OVowgdExEzARBgsrBgEEAYI3PAIBAxMC
# VVMxGTAXBgsrBgEEAYI3PAIBAhMIRGVsYXdhcmUxHTAbBgNVBA8MFFByaXZhdGUg
# T3JnYW5pemF0aW9uMRAwDgYDVQQFEwczNDA3OTg1MQswCQYDVQQGEwJVUzERMA8G
# A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xHTAbBgNVBAoTFEtlZXBl
# ciBTZWN1cml0eSBJbmMuMR0wGwYDVQQDExRLZWVwZXIgU2VjdXJpdHkgSW5jLjCC
# AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAM7/rBevApUP+XJjlSxdyASA
# AnLFQ1r4NFXPo/S0RaTv1OCahApEeSN6oy+0OwbLNlwaQeooOanMcZhh64/+fF8S
# zCMHDc/Pv8aBsd1B2XIw/VT+Nawfj0NxAX1zpKPp/tPqavm6smRDMOAeOo7qLxzI
# u68bS2EnqvST1367tMpxhggrVl3GYKPhdCPeNDRskwheCSxI2czR8oe7mguo2nVa
# ZR5VEq4xYkMZwTuT7RN8ER4r5crOSbJFyabp79SgYP7NyKmDcYZ6XJ26AfZsEDZr
# e4VhzaqO0rl8i5HBmVmDKwU0PaIoAUdyeultIaS5oe0FjcTjGtrkBl+B7TCtvN1J
# RE9Tmy3spnqLyvlRhrVJdDKCGovQKKJk87BAjIoiNSmEXs0H0PbB1ZYOA6m4ce7/
# BOmUafliYWBqrWHmHixqi/ha5ZKxKlYxGlikD4p1WlMmDEBhg3RPodW1Z5eGq92Z
# exMGOWsfOQp3YhTDdMOA7tjWP2XzAaebGxCeOENEpQIDAQABo4ICAjCCAf4wHwYD
# VR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFOcovsKg6xAz
# zjzRmmWQRpa7p47MMD0GA1UdIAQ2MDQwMgYFZ4EMAQMwKTAnBggrBgEFBQcCARYb
# aHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
# QTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggIBALIq
# AoEjkKZluMiOffwU+V+wiKkmDblKIZymyszEZot+niB6g7tRXrWkQo6gn8OG2qG6
# IO8L+o0VvwW0+V08p6gVqb0jeR9kCm7kDZk2RmzevhZDrRbZj0Q7Kb3pIeD9KEuc
# RfEF0UGqgp0q7jerFXPzKtQk5kJpP65sSRV7bghIMWtq5sdHn/iGUMj+8Fd9AExq
# 4kR+dyTw/6p1ZFiY7pIv4YAjjDrjkyUMSogt6ej9YGwTC8yVXJsjarLq2F+svwn8
# NlU+T03U/ZjXc/ZxDc5g3iqrl5Gm9QCaLhG2aLIrGRXN59Pcokp7JFNa6nkkWSSg
# h4w01tz+xRSyiqKWAXNs2lHTD2F9ceGlz9Uw/RvPhPcl6bILqJcR6RUkzZtrKHNK
# j85PBm/Kmurx0co5xRxXsXsF3tmp2r+Tt11veA9je+pyzuqE/kRQPn5hF8fIRuea
# h7JVMaaHBTMbRaDcVFioGmCGHUx270yhLapA0eYXpZJv0n62QIMoX9NPcW2EcwhL
# WGAV1IW+TIo/xcprAXBtXCO/mhscgInbMzesdg0uWsboiy4HfeTEzCe9ld54biUK
# TJQu4wqbzkN5SGewOKTd/+c4k5w6yzuUWsk3YZpjWqsgpTlA3zU591uvMFsq0FYd
# A3Py8YsVabLwTxz9d7kpBAHTPRYwDcsKNLGMPc+6MYIF0zCCBc8CAQEwfTBpMQsw
# CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERp
# Z2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIw
# MjEgQ0ExAhAFo6MauDVPnyiWHT0CB2G4MA0GCWCGSAFlAwQCAQUAoIGEMBgGCisG
# AQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJSL
# LG1Nj/1ffcESJJQh5F4nFCcWBysXYMfafG+Bmd0PMA0GCSqGSIb3DQEBAQUABIIB
# gIs3HC7Ew9A8tj8l7zv7Hs3BmUI7WP08lHdx8hAV1Qc/XMdTkklMid5HLUddEd59
# kucjsgYvR7SEwdrxS0nZR61ihGC9wywe5ELsjuhE2ojVhf8cP9RkIfpvt2ckrxNK
# Q96Rw273HvNIYpHQWj7yanuqmw6/VOvqEKEnKZg1LeDPKANRaQ4ztooHJU5Y0lWi
# oYv9YE7OdkNbhF4JGTX0V2yZj+8JeoanX4yo+7h1Ab+HT4qGWxSEYa0rlmppLw6O
# 5uLv+xBgV5Pe1QLKLpBlDN6eCMXEiJdu30qODb2CRWZotJeCao3CJPiRf5GVXMub
# 9gbjfuDLMggWq2ui1OfzkBUPnnLaUe0Z4cuwV69Df49Zy4g3lr4oSPgtfcpFhP7j
# QHfXl375h3wD2cB4GvHDAPtFxuXzrxqyyBDP+LaSvKvs/DAlppIKIsr3aoKKePc6
# iCRmbVUYFqvBgr6F6N/cjFV4R0DWcOm3sEddmtLIa65YsaPWaeYxsGn2E7ynJVRQ
# taGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+V
# h8b56QTjMwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yNTAyMjcyMzI3NTRaMC8GCSqGSIb3DQEJBDEi
# BCCJ42SCjU/DcC0dzQGXBqPwpaNarlwHR73vcXEMRZbU4DANBgkqhkiG9w0BAQEF
# AASCAgBc0TAaJtKpKI3AYqLPlGibiBZpzSDBPmPiZSEtDtJST20/80piX4220qAX
# mQ/swTIc3XV0w+6/jU+VPBbDlISx7WDJPnf5vT8GXgC4EIHLC9XmdHmU4N23IgZE
# KvwHkPgVLzl7ucI5gERxEnrCFDzHXwtCV+XDTHUKBdE5mU69Sn102oqVmrj5Hahh
# n4zi+tgU/C4FbN7CQvbXYOY3d5rKeLOUydAEs7QCd6biPKaUzA2qLfldsfJ1dv9D
# YFoi2Ttu2kPYLJGmO7f8p64WoIiwgoleRJMqh1m0fOAMCW5md8Ef6ATs0wTNltID
# BguIeU6yF1O+Bi944TxRfdfem8ylumZ57lR1hsiq8e/rDkRSJpOkRcgnJKQULlpZ
# /e9oePXWot6lADrnsFRPtcMEPJGzkpGhDKQS2Kp41J+bmzzBn1RY80zVeOH7O7SG
# 12+3+/tR/RplSsJ8+s7kBwZmFt6BiIv46e7cttOWICriIYekilD2irhI/dKtUJVW
# jIx5VValo9BmrkF4F19m5+I5mHzSq2DwI/2EsaBtqJkWBTwq/D/2D/sLTtlAWOdU
# FWC4RlbsXO7ZA7fHvUyE42/VVtqLrWQx9JoBX2c5gVTom7VdLM9UA8YT/HOnmp2/
# WbUsYvVL7oXhRH/pWPyeBu0JdIhEg8Q6gJvNNskV+QtpotbYQw==
# SIG # End signature block