PSAuth.psm1
<#
.EXTERNALHELP PSAuth-help.xml #> #Requires -version 5.0 $moduleRoot = Split-Path ` -Path $MyInvocation.MyCommand.Path ` -Parent #region LocalizedData $culture = 'en-us' if (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath $PSUICulture)) { $culture = $PSUICulture } Import-LocalizedData ` -BindingVariable LocalizedData ` -Filename 'PSAuth.strings.psd1' ` -BaseDirectory $moduleRoot ` -UICulture $culture #endregion #region Functions <# .SYNOPSIS Convert a SecureString back to a string. .PARAMETER SecureString The SecureString to convert back to a string. #> function ConvertFrom-PSAuthSecureString { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.Security.SecureString] $SecureString ) $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) } <# .SYNOPSIS Generate a nonce for use with Oauth. .NOTES This function exists to make unit testing easier. #> function Get-PSAuthNonce { [CmdletBinding()] [OutputType([System.String])] param () return [System.Guid]::NewGuid().Guid -replace '-' } <# .SYNOPSIS Normalize a URI for use in an Oauth signature. .DESCRIPTION Normalize a URI by converting the hostname into all lower case and ensure port is included if not HTTP/HTTPS. .PARAMETER Uri The URI to normalize. #> function Get-PSAuthNormalizedUri { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.Uri] $Uri ) $normalizedUri = ('{0}://{1}' -f $Uri.Scheme, $Uri.Host).ToLower() if (-not (($Uri.Scheme -eq 'http' -and $Uri.Port -eq 80) ` -or ($Uri.Scheme -eq 'https' -and $Uri.Port -eq 443))) { $normalizedUri += ':' + $Uri.Port } $normalizedUri += $Uri.AbsolutePath return $normalizedUri } function Get-PSAuthorizationString { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Uri] $Uri, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $OauthConsumerKey, [Parameter(Mandatory = $true)] [System.Security.SecureString] $OauthConsumerSecret, [Parameter(Mandatory = $false)] [System.String] $OauthAccessToken, [Parameter(Mandatory = $false)] [System.Security.SecureString] $OauthAccessTokenSecret, [Parameter(Mandatory = $false)] [ValidateSet('HMAC-SHA1', 'HMAC-SHA256')] [System.String] $OauthSignatureMethod = 'HMAC-SHA1', [Parameter(Mandatory = $false)] [ValidateSet('1.0')] [System.String] $OauthVersion = '1.0', [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable] $OauthParameters = @{ }, [Parameter(Mandatory = $false)] [ValidateSet('Default', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')] [System.String] $Method = 'Get' ) $Method = $Method.ToUpper() $normalizedUri = Get-PSAuthNormalizedUri -Uri $Uri $oauthTimestamp = Get-PSAuthTimestamp $oauthNonce = Get-PSAuthNonce # Create a hash table containing the parameters to include in the signature $signatureParameters = @{ oauth_consumer_key = $OauthConsumerKey oauth_signature_method = $OauthSignatureMethod oauth_timestamp = $oauthTimestamp oauth_nonce = $oauthNonce oauth_version = $OauthVersion } # If an access token is specified add that to the signature parameters if ($PSBoundParameters.ContainsKey('OauthAccessToken')) { $signatureParameters += @{ oauth_token = $OauthAccessToken } } # Add any optional Oauth parameters to the signature parameters foreach ($oauthParameter in $OauthParameters.GetEnumerator()) { $signatureParameters += @{ $oauthParameter.Name = $oauthParameter.Value } } # If any query string parameters are passed include these in the signature parameters if ($Uri.Query) { foreach ($queryItem in $Uri.Query.TrimStart('?').Split('=')) { $key, $value = $queryItem.split($KeyValueSeparator, 2) $signatureParameters += @{ $key = $value } } } # Serialize all the signature parameters into a string ordered by Name $orderedSignatureParameters = $signatureParameters.GetEnumerator() | Sort-Object -Property Name $paritallySerializedSignatureParameters = $orderedSignatureParameters | Foreach-Object -Process { '{0}={1}' -f $_.Name, $_.Value } $serializedSignatureParameters = $paritallySerializedSignatureParameters -join '&' # Generate the signature $signature = '{0}&{1}&{2}' -f $Method, [System.Uri]::EscapeDataString($normalizedUri), [System.Uri]::EscapeDataString($serializedSignatureParameters) $signatureKey = '{0}&' -f [System.Uri]::EscapeDataString((ConvertFrom-PSAuthSecureString -SecureString $OauthConsumerSecret)) if ($PSBoundParameters.ContainsKey('OauthAccessTokenSecret')) { $signatureKey += [System.Uri]::EscapeDataString((ConvertFrom-PSAuthSecureString -SecureString $OauthAccessTokenSecret)) } # Select the Signature method switch ($OauthSignatureMethod) { 'HMAC-SHA1' { $signatureHashGenerator = New-Object -TypeName System.Security.Cryptography.HMACSHA1 } 'HMAC-SHA256' { $signatureHashGenerator = New-Object -TypeName System.Security.Cryptography.HMACSHA256 } } $signatureHashGenerator.Key = [System.Text.Encoding]::Ascii.GetBytes($signatureKey) $oauthSignature = [System.Convert]::ToBase64String($signatureHashGenerator.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($signature))) $escapedOauthSignature = [System.Uri]::EscapeDataString($oauthSignature) # Now assemble the authorization hash table including parameters $authorizationParameters = @{ oauth_consumer_key = [System.Uri]::EscapeDataString($OauthConsumerKey) oauth_nonce = $oauthNonce oauth_signature = $escapedOauthSignature oauth_signature_method = $OauthSignatureMethod oauth_timestamp = $oauthTimestamp oauth_version = $OauthVersion } if ($PSBoundParameters.ContainsKey('OauthAccessToken')) { $authorizationParameters += @{ oauth_token = $OauthAccessToken } } $orderedAuthorizationParameters = $authorizationParameters.GetEnumerator() | Sort-Object -Property Name $partiallySerializedAuthorizationParameters = $orderedAuthorizationParameters | Foreach-Object -Process { '{0}="{1}"' -f $_.Name, $_.Value } $serializedAuthorizationParameters = $partiallySerializedAuthorizationParameters -join ',' $authorizationString = 'OAuth {0}' -f $serializedAuthorizationParameters Write-Verbose -Message ($LocalizedData.AuthorizationStringGeneratedMessage -f $authorizationString.Replace($escapedOauthSignature,[System.String]::new('*',20))) return $authorizationString } <# .SYNOPSIS Generate a time stamp for use with Oauth. .NOTES This function exists to make unit testing easier. #> function Get-PSAuthTimestamp { [CmdletBinding()] [OutputType([System.Int32])] param () return [System.Int32] ((Get-Date).ToUniversalTime() - (Get-Date -Date '1/1/1970')).TotalSeconds } function Invoke-PSAuthRestMethod { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Uri] $Uri, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $OauthConsumerKey, [Parameter(Mandatory = $true)] [System.Security.SecureString] $OauthConsumerSecret, [Parameter(Mandatory = $false)] [System.String] $OauthAccessToken, [Parameter(Mandatory = $false)] [System.Security.SecureString] $OauthAccessTokenSecret, [Parameter(Mandatory = $false)] [ValidateSet('HMAC-SHA1', 'HMAC-SHA256')] [System.String] $OauthSignatureMethod = 'HMAC-SHA1', [Parameter(Mandatory = $false)] [ValidateSet('1.0')] [System.String] $OauthVersion = '1.0', [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable] $OauthParameters = @{}, [Parameter(Mandatory = $false)] [ValidateSet('Default', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')] [System.String] $Method = 'Get', [Parameter(Mandatory = $false)] [System.String] $ContentType, [Parameter(Mandatory = $false)] [System.String] $Body, [Parameter(Mandatory = $false)] [System.Collections.IDictionary] $Headers, [Parameter(Mandatory = $false)] [System.String] $Proxy, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $ProxyCredential, [Parameter(Mandatory = $false)] [Switch] $ProxyUseDefaultCredentials, [Parameter(Mandatory = $false)] [Switch] $DisableKeepAlive ) $getPSAuthorizationString = @{ } + $PSBoundParameters @( 'Body' 'ContentType' 'DisableKeepAlive' 'Headers' 'Proxy' 'ProxyCredential' 'ProxyUseDefaultCredentials' ) | ForEach-Object -Process { $null = $getPSAuthorizationString.Remove($_) } $authorization = Get-PSAuthorizationString @getPSAuthorizationString # Take all the parameters passed to this function and pass them to $invokeRestMethodParameters = @{ } + $PSBoundParameters $headers += @{ 'Authorization' = $authorization } # Remove parameters that should not be passed to Invoke-RestMethod $null = $invokeRestMethodParameters.Remove('OauthConsumerKey') $null = $invokeRestMethodParameters.Remove('OauthConsumerSecret') $null = $invokeRestMethodParameters.Remove('OauthAccessToken') $null = $invokeRestMethodParameters.Remove('OauthAccessTokenSecret') $null = $invokeRestMethodParameters.Remove('OauthSignatureMethod') $null = $invokeRestMethodParameters.Remove('OauthVersion') $null = $invokeRestMethodParameters.Remove('OauthParameters') if ($method -notin 'POST', 'PUT') { # Remove Body parameter for all methods except POST and PUT $null = $invokeRestMethodParameters.Remove('Body') } $null = $invokeRestMethodParameters['Headers'] = $headers return Invoke-RestMethod @invokeRestMethodParameters } #endregion |