Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MFA support #25

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ConnectWiseControlAPI/ConnectWiseControlAPI.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'ConnectWiseControlAPI.psm1'

# Version number of this module.
ModuleVersion = '0.3.3.0'
ModuleVersion = '0.3.5.0'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down Expand Up @@ -69,7 +69,7 @@
# NestedModules = @()

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @('Get-CWCAuditInfo','Get-CWCAuditLog','Connect-CWC','Get-CWCLauncURL','Add-CWCRemoteWorkforceRequiredRole','New-CWCRemoteWorkforceAssignment','New-CWCMFA','Get-CWCLastContact','Get-CWCSession','Get-CWCSessionDetail','Invoke-CWCCommand','Invoke-CWCWake','New-CWCAccessToken','Remove-CWCSession','Update-CWCCustomProperty','Update-CWCSessionName','Get-CWCSecurityConfigurationInfo','New-CWCUser','Remove-CWCUser','Update-CWCUser','Get-CWCSessionGroup')
FunctionsToExport = @('Get-CWCAuditInfo','Get-CWCAuditLog','Connect-CWC','Get-CWCLauncURL','Add-CWCRemoteWorkforceRequiredRole','New-CWCRemoteWorkforceAssignment','New-CWCMFA','Get-CWCLastContact','Get-CWCSession','Get-CWCSessionDetail','Get-CWCSessionDetails','Invoke-CWCCommand','Invoke-CWCWake','New-CWCAccessToken','Remove-CWCSession','Update-CWCCustomProperty','Update-CWCSessionName','Get-CWCSecurityConfigurationInfo','New-CWCUser','Remove-CWCUser','Update-CWCUser','Get-CWCSessionGroup','Test-CWCreq')

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
Expand Down
2 changes: 2 additions & 0 deletions ConnectWiseControlAPI/ConnectWiseControlAPI.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ if([Net.SecurityProtocolType]::Tls) {
}

$script:InternalUserSource = 'InternalMembershipProvider'
$script:defaultGroup = 'All Machines by Company'
$script:timeZoneOffset = New-Timespan -hours (((get-timezone).baseUtcOffset).hours + (Get-Date).IsDaylightSavingTime()) -minutes ((get-timezone).baseUtcOffset).minutes

# Export Public functions ($Public.BaseName) for WIP modules
Export-ModuleMember -Function $Public.Basename
179 changes: 179 additions & 0 deletions ConnectWiseControlAPI/PSGetModuleInfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>Microsoft.PowerShell.Commands.PSRepositoryItemInfo</T>
<T>System.Management.Automation.PSCustomObject</T>
<T>System.Object</T>
</TN>
<MS>
<S N="Name">ConnectWiseControlAPI</S>
<Version N="Version">0.3.5.0</Version>
<S N="Type">Module</S>
<S N="Description">PowerShell wrapper for ConnectWise Control web API</S>
<S N="Author">Chris Taylor</S>
<S N="CompanyName">ChrisTaylorCodes</S>
<S N="Copyright">(c) 2020 Chris Taylor. All rights reserved.</S>
<DT N="PublishedDate">2023-01-24T21:18:08-07:00</DT>
<Obj N="InstalledDate" RefId="1">
<DT>2023-03-27T19:14:57.804093-06:00</DT>
<MS>
<Obj N="DisplayHint" RefId="2">
<TN RefId="1">
<T>Microsoft.PowerShell.Commands.DisplayHintType</T>
<T>System.Enum</T>
<T>System.ValueType</T>
<T>System.Object</T>
</TN>
<ToString>DateTime</ToString>
<I32>2</I32>
</Obj>
</MS>
</Obj>
<Nil N="UpdatedDate" />
<URI N="LicenseUri">https://github.com/christaylorcodes/ConnectWiseControlAPI/blob/master/LICENSE</URI>
<URI N="ProjectUri">https://github.com/christaylorcodes/ConnectWiseControlAPI</URI>
<URI N="IconUri">https://raw.githubusercontent.com/christaylorcodes/ConnectWiseControlAPI/master/Media/control-logo.webp</URI>
<Obj N="Tags" RefId="3">
<TN RefId="2">
<T>System.Object[]</T>
<T>System.Array</T>
<T>System.Object</T>
</TN>
<LST>
<S>ChrisTaylorCodes</S>
<S>ConnectWise</S>
<S>Control</S>
<S>ScreenConnect</S>
<S>API</S>
<S>PSModule</S>
</LST>
</Obj>
<Obj N="Includes" RefId="4">
<TN RefId="3">
<T>System.Collections.Hashtable</T>
<T>System.Object</T>
</TN>
<DCT>
<En>
<S N="Key">Function</S>
<Obj N="Value" RefId="5">
<TNRef RefId="2" />
<LST>
<S>Get-CWCAuditInfo</S>
<S>Get-CWCAuditLog</S>
<S>Connect-CWC</S>
<S>Get-CWCLauncURL</S>
<S>Add-CWCRemoteWorkforceRequiredRole</S>
<S>New-CWCRemoteWorkforceAssignment</S>
<S>New-CWCMFA</S>
<S>Get-CWCLastContact</S>
<S>Get-CWCSession</S>
<S>Get-CWCSessionDetail</S>
<S>Invoke-CWCCommand</S>
<S>Invoke-CWCWake</S>
<S>New-CWCAccessToken</S>
<S>Remove-CWCSession</S>
<S>Update-CWCCustomProperty</S>
<S>Update-CWCSessionName</S>
<S>Get-CWCSecurityConfigurationInfo</S>
<S>New-CWCUser</S>
<S>Remove-CWCUser</S>
<S>Update-CWCUser</S>
<S>Get-CWCSessionGroup</S>
</LST>
</Obj>
</En>
<En>
<S N="Key">Command</S>
<Obj N="Value" RefId="6">
<TNRef RefId="2" />
<LST>
<S>Get-CWCAuditInfo</S>
<S>Get-CWCAuditLog</S>
<S>Connect-CWC</S>
<S>Get-CWCLauncURL</S>
<S>Add-CWCRemoteWorkforceRequiredRole</S>
<S>New-CWCRemoteWorkforceAssignment</S>
<S>New-CWCMFA</S>
<S>Get-CWCLastContact</S>
<S>Get-CWCSession</S>
<S>Get-CWCSessionDetail</S>
<S>Invoke-CWCCommand</S>
<S>Invoke-CWCWake</S>
<S>New-CWCAccessToken</S>
<S>Remove-CWCSession</S>
<S>Update-CWCCustomProperty</S>
<S>Update-CWCSessionName</S>
<S>Get-CWCSecurityConfigurationInfo</S>
<S>New-CWCUser</S>
<S>Remove-CWCUser</S>
<S>Update-CWCUser</S>
<S>Get-CWCSessionGroup</S>
</LST>
</Obj>
</En>
<En>
<S N="Key">Cmdlet</S>
<Obj N="Value" RefId="7">
<TNRef RefId="2" />
<LST />
</Obj>
</En>
<En>
<S N="Key">RoleCapability</S>
<Ref N="Value" RefId="7" />
</En>
<En>
<S N="Key">Workflow</S>
<Ref N="Value" RefId="7" />
</En>
<En>
<S N="Key">DscResource</S>
<Ref N="Value" RefId="7" />
</En>
</DCT>
</Obj>
<Nil N="PowerShellGetFormatVersion" />
<S N="ReleaseNotes">https://github.com/christaylorcodes/ConnectWiseControlAPI/releases</S>
<Obj N="Dependencies" RefId="8">
<TNRef RefId="2" />
<LST />
</Obj>
<S N="RepositorySourceLocation">https://www.powershellgallery.com/api/v2</S>
<S N="Repository">PSGallery</S>
<S N="PackageManagementProvider">NuGet</S>
<Obj N="AdditionalMetadata" RefId="9">
<TN RefId="4">
<T>System.Management.Automation.PSCustomObject</T>
<T>System.Object</T>
</TN>
<MS>
<S N="copyright">(c) 2020 Chris Taylor. All rights reserved.</S>
<S N="description">PowerShell wrapper for ConnectWise Control web API</S>
<S N="requireLicenseAcceptance">False</S>
<S N="releaseNotes">https://github.com/christaylorcodes/ConnectWiseControlAPI/releases</S>
<S N="isLatestVersion">True</S>
<S N="isAbsoluteLatestVersion">True</S>
<S N="versionDownloadCount">9631</S>
<S N="downloadCount">42733</S>
<S N="packageSize">27009</S>
<S N="published">2023-01-24 9:18:08 p.m. -07:00</S>
<S N="created">2023-01-24 9:18:08 p.m. -07:00</S>
<S N="lastUpdated">2023-03-28 1:13:08 a.m. -06:00</S>
<S N="tags">ChrisTaylorCodes ConnectWise Control ScreenConnect API PSModule PSFunction_Get-CWCAuditInfo PSCommand_Get-CWCAuditInfo PSFunction_Get-CWCAuditLog PSCommand_Get-CWCAuditLog PSFunction_Connect-CWC PSCommand_Connect-CWC PSFunction_Get-CWCLauncURL PSCommand_Get-CWCLauncURL PSFunction_Add-CWCRemoteWorkforceRequiredRole PSCommand_Add-CWCRemoteWorkforceRequiredRole PSFunction_New-CWCRemoteWorkforceAssignment PSCommand_New-CWCRemoteWorkforceAssignment PSFunction_New-CWCMFA PSCommand_New-CWCMFA PSFunction_Get-CWCLastContact PSCommand_Get-CWCLastContact PSFunction_Get-CWCSession PSCommand_Get-CWCSession PSFunction_Get-CWCSessionDetail PSCommand_Get-CWCSessionDetail PSFunction_Invoke-CWCCommand PSCommand_Invoke-CWCCommand PSFunction_Invoke-CWCWake PSCommand_Invoke-CWCWake PSFunction_New-CWCAccessToken PSCommand_New-CWCAccessToken PSFunction_Remove-CWCSession PSCommand_Remove-CWCSession PSFunction_Update-CWCCustomProperty PSCommand_Update-CWCCustomProperty PSFunction_Update-CWCSessionName PSCommand_Update-CWCSessionName PSFunction_Get-CWCSecurityConfigurationInfo PSCommand_Get-CWCSecurityConfigurationInfo PSFunction_New-CWCUser PSCommand_New-CWCUser PSFunction_Remove-CWCUser PSCommand_Remove-CWCUser PSFunction_Update-CWCUser PSCommand_Update-CWCUser PSFunction_Get-CWCSessionGroup PSCommand_Get-CWCSessionGroup PSIncludes_Function</S>
<S N="developmentDependency">False</S>
<S N="updated">2023-03-28T01:13:08Z</S>
<S N="NormalizedVersion">0.3.5</S>
<S N="Authors">Chris Taylor</S>
<S N="IsPrerelease">false</S>
<S N="ItemType">Module</S>
<S N="FileList">ConnectWiseControlAPI.nuspec|ConnectWiseControlAPI.Format.ps1xml|en-US\about_ConnectWiseControlAPI.help.txt|Public\AuditService\Get-CWCAuditInfo.ps1|Public\Extensions\Remote Workforce\Add-CWCRemoteWorkforceRequiredRole.ps1|Public\PageService\Get-CWCSession.ps1|Public\PageService\New-CWCAccessToken.ps1|Public\PageService\Update-CWCSessionName.ps1|Public\SecurityService\Remove-CWCUser.ps1|ConnectWiseControlAPI.psd1|en-US\ConnectWiseControlAPI-help.xml|Public\AuditService\Get-CWCAuditLog.ps1|Public\Extensions\Remote Workforce\New-CWCRemoteWorkforceAssignment.ps1|Public\PageService\Get-CWCSessionDetail.ps1|Public\PageService\Remove-CWCSession.ps1|Public\SecurityService\Get-CWCSecurityConfigurationInfo.ps1|Public\SecurityService\Update-CWCUser.ps1|ConnectWiseControlAPI.psm1|Private\Invoke-CWCWebRequest.ps1|Public\Authentication\Connect-CWC.ps1|Public\Helpers\New-CWCMFA.ps1|Public\PageService\Invoke-CWCCommand.ps1|Public\PageService\Update-CWCCustomProperty.ps1|Public\SecurityService\New-CWCUser.ps1|Public\SessionGroupService\Get-CWCSessionGroup.ps1|Private\Join-Url.ps1|Public\Extensions\Launcher\Get-CWCLauncURL.ps1|Public\PageService\Get-CWCLastContact.ps1|Public\PageService\Invoke-CWCWake.ps1</S>
<S N="GUID">f94fa996-0f01-4c5c-9cd9-3227728ebacb</S>
<S N="PowerShellVersion">3.0</S>
<S N="CompanyName">ChrisTaylorCodes</S>
</MS>
</Obj>
<S N="InstalledLocation">/home/api_operator/.local/share/powershell/Modules/ConnectWiseControlAPI/0.3.5.0</S>
</MS>
</Obj>
</Objs>
26 changes: 26 additions & 0 deletions ConnectWiseControlAPI/Private/Convert-FromEpoch.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Function Convert-FromEpoch () {
<#
.SYNOPSIS
Convert linux epoch time to a DateTime object

.DESCRIPTION
Pipe a string

.PARAMETER unixTime
Epoch time in a string

.INPUTS
Pipe in the epoch time

.OUTPUTS
A DateTime Object - UTC time

.EXAMPLE
$time = $unixTime | fromEpoch
#>
[cmdletBinding()]
param (
[parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)][string]$unixTIme
)
Return (Get-Date 01.01.1970)+([System.TimeSpan]::fromseconds($unixTime))
}
3 changes: 3 additions & 0 deletions ConnectWiseControlAPI/Private/Get-EpochNow.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Function Get-EpochNow () {
Return (New-TimeSpan (Get-Date 01.01.1970) (Get-Date)).TotalSeconds
}
80 changes: 80 additions & 0 deletions ConnectWiseControlAPI/Private/Get-OTP.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# https://github.com/HumanEquivalentUnit/PowerShell-Misc/blob/master/GoogleAuthenticator.psm1
function Get-OTP {
[CmdletBinding()]
Param (
# BASE32 encoded Secret e.g. 5WYYADYB5DK2BIOV
[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=0)]
[securestring]$secureSecret,

# OTP time window in seconds
$TimeWindow = 30
)
$secret = $secureSecret | ConvertFrom-SecureString -AsPlainText
$Base32Charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
# Convert the secret from BASE32 to a byte array
# via a BigInteger so we can use its bit-shifting support,
# instead of having to handle byte boundaries in code.
$bigInteger = [Numerics.BigInteger]::Zero
foreach ($char in ($secret.ToUpper() -replace '[^A-Z2-7]').GetEnumerator()) {
$bigInteger = ($bigInteger -shl 5) -bor ($Base32Charset.IndexOf($char))
}

[byte[]]$secretAsBytes = $bigInteger.ToByteArray()


# BigInteger sometimes adds a 0 byte to the end,
# if the positive number could be mistaken as a two's complement negative number.
# If it happens, we need to remove it.
if ($secretAsBytes[-1] -eq 0) {
$secretAsBytes = $secretAsBytes[0..($secretAsBytes.Count - 2)]
}


# BigInteger stores bytes in Little-Endian order,
# but we need them in Big-Endian order.
[array]::Reverse($secretAsBytes)


# Unix epoch time in UTC and divide by the window time,
# so the PIN won't change for that many seconds
$epochTime = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()

# Convert the time to a big-endian byte array
$timeBytes = [BitConverter]::GetBytes([int64][math]::Floor($epochTime / $TimeWindow))
if ([BitConverter]::IsLittleEndian) {
[array]::Reverse($timeBytes)
}

# Do the HMAC calculation with the default SHA1
# Google Authenticator app does support other hash algorithms, this code doesn't
$hmacGen = [Security.Cryptography.HMACSHA1]::new($secretAsBytes)
$hash = $hmacGen.ComputeHash($timeBytes)


# The hash value is SHA1 size but we want a 6 digit PIN
# the TOTP protocol has a calculation to do that
#
# Google Authenticator app may support other PIN lengths, this code doesn't

# take half the last byte
$offset = $hash[$hash.Length-1] -band 0xF

# use it as an index into the hash bytes and take 4 bytes from there, #
# big-endian needed
$fourBytes = $hash[$offset..($offset+3)]
if ([BitConverter]::IsLittleEndian) {
[array]::Reverse($fourBytes)
}

# Remove the most significant bit
$num = [BitConverter]::ToInt32($fourBytes, 0) -band 0x7FFFFFFF

# remainder of dividing by 1M
# pad to 6 digits with leading zero(s)
# and put a space for nice readability
$PIN = ($num % 1000000).ToString().PadLeft(6, '0')
Return [PSCustomObject] @{
'code' = $PIN
'timeout' = ($TimeWindow - ($epochTime % $TimeWindow))
}
}
3 changes: 2 additions & 1 deletion ConnectWiseControlAPI/Private/Invoke-CWCWebRequest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
$ErrorMessage += "----> Run 'Connect-CWC' to initialize the connection before issuing other CWC cmdlets."
return Write-Error ($ErrorMessage | Out-String)
}


$script:cwcserverconnection.Headers.'X-One-Time-Password' = $(Get-OTP $script:cwcserverconnection.Secret).Code
$BaseURI = "https://$($script:CWCServerConnection.Server)"
$Arguments.URI = Join-Url $BaseURI $Arguments.Endpoint
$Arguments.remove('Endpoint')
Expand Down
33 changes: 19 additions & 14 deletions ConnectWiseControlAPI/Public/Authentication/Connect-CWC.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,46 @@ function Connect-CWC {
[string]$Server,
[Parameter(Mandatory = $True)]
[pscredential]$Credentials,
[securestring]$secret,
[switch]$Force
)

if ($script:CWCServerConnection -and !$Force) {
Write-Verbose "Using cached Authentication information."
return
}

$Server = $Server -replace("http.*:\/\/",'')
$EncodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$($Credentials.UserName):$($Credentials.GetNetworkCredential().Password)"))
$Headers = @{
'authorization' = "Basic $EncodedCredentials"
'content-type' = "application/json; charset=utf-8"
'origin' = "https://$Server"
}

if ($secret) {
$Headers.'X-One-Time-Password' = (Get-OTP $secret -verbose).code
}
$FrontPage = Invoke-WebRequest -Uri $Headers.origin -Headers $Headers -UseBasicParsing
$Regex = [Regex]'(?<=antiForgeryToken":")(.*)(?=","isUserAdministrator)'
$Match = $Regex.Match($FrontPage.content)
if($Match.Success){ $Headers.'x-anti-forgery-token' = $Match.Value.ToString() }
else{ Write-Verbose 'Unable to find anti forgery token. Some commands may not work.' }

if ($Match.Success) {
$Headers.'x-anti-forgery-token' = $Match.Value.ToString()
} else {
Write-Verbose 'Unable to find anti forgery token. Some commands may not work.'
}
Write-Verbose "Result:"
Write-Verbose $FrontPage.headers
$LoginResult = $FrontPage.headers.'X-Login-Result'
$script:CWCServerConnection = @{
Server = $Server
Headers = $Headers
Secret = $secret
}
Write-Verbose ($script:CWCServerConnection | Out-String)

try{
$null = Get-CWCSessionGroup -ErrorAction Stop
Write-Verbose '$CWCServerConnection, variable initialized.'
}
catch{
if ($LoginResult) {
Remove-Variable CWCServerConnection -Scope script
Write-Verbose 'Authentication failed.'
Write-Error $_
Throw ("Login failed: " + $LoginResult)
} else {
Write-Verbose "Login Success. "
Write-Verbose $Script:CWCServerConnection
Return $true
}
}
Loading