AuthCommands.ps1
#requires -Version 5.1 $expires = @( [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin, [KeeperSecurity.Authentication.TwoFactorDuration]::Every30Days, [KeeperSecurity.Authentication.TwoFactorDuration]::Forever) 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-Output $_ -ForegroundColor Red } Catch { Write-Output $_ -ForegroundColor Red } } } 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-Output $_ -ForegroundColor Red } } } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) { Try { $auth.step.VerifyPassword($action).GetAwaiter().GetResult() | Out-Null } Catch [KeeperSecurity.Authentication.KeeperAuthFailed] { Write-Output 'Invalid password' -ForegroundColor Red } Catch { Write-Output $_ -ForegroundColor Red } } 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 NewLogin Do not use Last Login information .Parameter SsoPassword Use Master Password for SSO account .Parameter Server Change default keeper server #> [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 ) Disconnect-Keeper -Resume | Out-Null $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($Server, $storage.Servers) $endpoint.DeviceName = 'PowerShell Commander' $endpoint.ClientVersion = 'c16.1.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" } else { while (-not $Username) { $Username = Read-Host -Prompt $namePrompt.PadLeft(20, ' ') } } 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]) { $securedPassword = Read-Host -Prompt $prompt -AsSecureString if ($securedPassword.Length -gt 0) { $action = [Net.NetworkCredential]::new('', $securedPassword).Password } else { $action = '' } } else { $action = Read-Host -Prompt $prompt } 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-Output $authFlow.Step.Message -ForegroundColor Red } 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.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 |