PoshCode Archive  Artifact [4a972bf9dc]

Artifact 4a972bf9dc40c2d60bc4bd31aed2120c528310d256cc858ad3344e7361e841d1:

  • File PowerOAuth.ps1 — part of check-in [dd61e66498] at 2018-06-10 13:09:17 on branch trunk — This is the second release, but still very raw. Supports OAuth via Installed Application Authentication (a modified form of OAuth where the consumer fetches access tokens using a username and password instead of a request token) ... now includes a sample app for fetching stuff off Yammer. (user: Joel Bennett size: 19111)

# encoding: ascii
# api: powershell
# title: PowerOAuth
# description: This is the second release, but still very raw. Supports OAuth via Installed Application Authentication (a modified form of OAuth where the consumer fetches access tokens using a username and password instead of a request token) ... now includes a sample app for fetching stuff off Yammer.
# version: 1.1
# type: module
# author: Joel Bennett
# license: CC0
# function: Get-OAuthBase
# x-poshcode-id: 2376
# x-derived-from-id: 2377
# x-archived: 2012-11-30T03:27:31
# x-published: 2012-11-19T22:03:00
#
#
#requires -Version 2.0
##requires -Module HttpRest -Version 1.2
# http://poshcode.org/1262
Set-StrictMode -Version 2.0
$null = [Reflection.Assembly]::LoadWithPartialName('System.Web')
$safeChars = [char[]]'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~'

function Get-OAuthBase {
#.Synopsis
#  Get the basic OAuth Authorization values
#.Parameter ConsumerKey
#  The OAuth Consumer Key
#.Parameter AccessToken
#  The OAuth Access token
#.Parameter AccessVerifier
#  The OAuth Access verifier from the user's interaction with the web site ...
#.ReturnValue
#  A HashTable containing the key-value pairs needed for further requests, including the 
#  oauth_consumer_key, oauth_signature_method, oauth_timestamp, oauth_nonce, oauth_version, and optionally, the oauth_token.
PARAM( 
   [Parameter(Mandatory=$true)]$ConsumerKey
,  [Parameter(Mandatory=$false)]$AccessToken 
,  [Parameter(Mandatory=$false)]$AccessVerifier
,  [Parameter(Mandatory=$false)]
   [ValidateSet("HMAC-SHA1", "PLAINTEXT")]
   [String]
   $SignatureMethod = "HMAC-SHA1"
)
END {
   @{ 
      oauth_consumer_key      = Format-OAuth $ConsumerKey
      oauth_signature_method  = $SignatureMethod # The signature method the Consumer used to sign the request
      oauth_timestamp         = [int]((Get-Date).ToUniversalTime() - (Get-Date 1/1/1970)).TotalSeconds
      oauth_nonce             = [guid]::NewGuid().GUID -replace '-'
      oauth_version           = '1.0' # OPTIONAL: If present, value MUST be 1.0
   } + $(if($AccessToken){ @{oauth_token = $AccessToken } } else { @{} }
   ) + $(if($AccessVerifier){ @{oauth_verifier = $AccessVerifier } } else { @{} }) # $FFAccessToken.oauth_token
}}

function Format-OAuth {
#.Synopsis 
#  UrlEncode, but with upper-case hex
#.Parameter value
#  The text to encode
PARAM([Parameter(ValueFromPipeline=$true)][string]$value)
PROCESS {
   $result = New-Object System.Text.StringBuilder
   foreach($c in $value.ToCharArray()){
      if($safeChars -notcontains $c) {
         $null = $result.Append( ('%{0:X2}' -f [int]$c) )
      } else {
         $null = $result.Append($c)
      }
   }
   write-output $result.ToString()
}}

function ConvertTo-Hashtable {
#.Synopsis
#  Convert a query string into a hashtable
#.Parameter string
#  The query string to convert
#.Parameter PairSeparator
#  The string separating each set of key=value pairs
#.Parameter KeyValueSeparator
#  The string separating the keys from the values
#.ReturnValue
#  The hashtable created from reading the query string
PARAM(
   [Parameter(ValueFromPipeline=$true, Position=0, Mandatory=$true)]
   [String]
   $string
,
   [Parameter(Position=1)]
   [String]
   $PairSeparator = '&'
,  
   [Parameter(Position=2)]
   [String]
   $KeyValueSeparator = '='
)
PROCESS {
   $result = @{}
   foreach($w in $string.split($pairSeparator) ) {
      $k,$v = $w.split($KeyValueSeparator,2)
      $result.Add($k,$v)
   }
   write-output $result
}
}

function ConvertFrom-Hashtable {
#.Synopsis
#  Convert a hashtable into a query string
#.Description
#  Converts a hashtable into a query string by joining the keys to the values, and then joining all the pairs together
#.Parameter values
#  The hashtable to convert
#.Parameter PairSeparator
#  The string used to concatenate the sets of key=value pairs, defaults to "&"
#.Parameter KeyValueSeparator
#  The string used to concatenate the keys to the values, defaults to "="
#.ReturnValue
#  The query string created by joining keys to values and then joining them all together into a single string
PARAM(
   [Parameter(ValueFromPipeline=$true, Position=0, Mandatory=$true)]
   [Hashtable]
   $Values
,
   [Parameter(Position=1)]
   [String]
   $pairSeparator = '&'
,  
   [Parameter(Position=2)]
   [String]
   $KeyValueSeparator = '='
,
   [string[]]$Sort
)
PROCESS {
   [string]::join($pairSeparator, @(
      if($Sort) {
         foreach( $kv in $Values.GetEnumerator() | Sort $Sort) {
            if($kv.Name) {
               '{0}{1}{2}' -f $kv.Name, $KeyValueSeparator, $kv.Value
            }
         }
      } else {
         foreach( $kv in $Values.GetEnumerator()) {
            if($kv.Name) {
               '{0}{1}{2}' -f $kv.Name, $KeyValueSeparator, $kv.Value
            }
         }
      }
   ))
}}

function ConvertTo-OAuthSignatureBase {
#.Synopsis
#  An internal function to build up the string needed for the OAuth Signature
PARAM(
   [Parameter(Mandatory=$true)]
   [Uri]
   $Uri
,
   [Parameter(Mandatory=$false)]
   [hashtable]
   $Parameters =@{}
,
   [Parameter(Mandatory=$false)]
   [ValidateSet("POST", "GET", "PUT", "DELETE", "HEAD", "OPTIONS")]
   [string]
   $Verb = "GET"
#  ,
   #  [System.Management.Automation.PSCredential]
   #  $Credential
)
BEGIN {
   trap { continue }
   if(!$Uri.IsAbsoluteUri) {
      $Uri= Join-Url $global:url $Uri
      Write-Verbose "Relative URL, appending to $($global:url) to get: $Uri"
   }
}
END {
      $normalizedUrl = ("{0}://{1}" -f $Uri.Scheme, $Uri.Host).ToLower()
      if (!(($Uri.Scheme -eq "http" -and $Uri.Port -eq 80) -or ($Uri.Scheme -eq "https" -and $Uri.Port -eq 443)))
      {
          $normalizedUrl += ":" + $Uri.Port
      }
      
      $normalizedUrl += $Uri.AbsolutePath
      write-output $normalizedUrl

      if($Uri.Query) { 
         $Parameters += $(ConvertTo-Hashtable $Uri.Query.trim("?"))
      }
      $normalizedRequestParameters = Format-OAuth (ConvertFrom-Hashtable $Parameters -Sort "Name","Value")
      
      ## DEBUG  Write-Host $normalizedRequestParameters -fore yellow
      write-output $normalizedRequestParameters
      
      $result = New-Object System.Text.StringBuilder
      $null = $result.AppendFormat("{0}&", $verb.ToUpper() )
      $null = $result.AppendFormat("{0}&", $(Format-OAuth $normalizedUrl) )
      $null = $result.AppendFormat("{0}", $normalizedRequestParameters)

      ## DEBUG  Write-Host $result.ToString() -fore cyan
      write-output $result.ToString();
   }
}

function Get-OAuthSignature {
#.Synopsis
#  An internal function to calculate the OAuth Signature using the HMAC-SHA1 algorithm
#.Parameter Verb
#  The HTTP verb which will be invoked is the first part of the OAuth Signature string (defaults to GET)
#.Parameter Uri
#  The URI which will be queried is the second part of the OAuth Signature string
#.Parameter Parameters
#  All of the parameters which will be passed (regardless of how they will be passed) in hashtable format. The OAuth Base Authorization parameters are not included here, they will be set by this function
#.Parameter ConsumerKey
#  The OAuth Consumer Key (a key specific to the application requesting access)
#.Parameter ConsumerSecret
#  The OAuth Consumer Secret (the secret part of the application's consumer key shouldn't be given to end users)
#.Parameter AccessToken
#  The OAuth Access Token (if you're already authenticated)
#.Parameter ConsumerSecret
#  The OAuth Access Secret (the secret part of the access token, if you're already authenticated)
PARAM( 
   [Parameter(Mandatory=$true)]
   [Uri]
   $Uri
,
   [Parameter(Mandatory=$false)]
   [hashtable]
   $Parameters =@{}
,
   [Parameter(Mandatory=$true)]
   [string]
   $ConsumerKey = "key"
, 
   [Parameter(Mandatory=$true)]
   [string]
   $ConsumerSecret = "secret"
, 
   [Parameter(Mandatory=$false)]
   [string]
   $AccessToken = ""
, 
   [Parameter(Mandatory=$false)]
   [string]
   $AccessSecret = ""
, 
   [Parameter(Mandatory=$false)]
   [string]
   $AccessVerifier = ""
, 
   [Parameter(Mandatory=$false)]
   [ValidateSet("POST", "GET", "PUT", "DELETE", "HEAD", "OPTIONS")]
   [string]
   $Verb = "GET"
,
   [ValidateSet("HMAC-SHA1", "PLAINTEXT")]
   [String]
   $SignatureMethod = "HMAC-SHA1"
#  ,
   #  [System.Management.Automation.PSCredential]
   #  $Credential
)
END {
   @($Parameters.Keys) | % {
      $Parameters.$_ = @($Parameters.$_) | %{ Format-OAuth $_ }
   }

   $Parameters += Get-OAuthBase $ConsumerKey $AccessToken $AccessVerifier -SignatureMethod $SignatureMethod

   $url, $query, $sigbase = ConvertTo-OAuthSignatureBase -Uri $Uri -Parameters $Parameters -Verb $Verb 
   Write-Verbose ([System.Web.HttpUtility]::URLDecode( $sigbase ) -split "&" -join "`n")

   Write-Output $url, $Parameters
   if($SignatureMethod -eq "HMAC-SHA1") {
      $sha = new-object System.Security.Cryptography.HMACSHA1
      $sha.Key =  [System.Text.Encoding]::Ascii.GetBytes( ('{0}&{1}' -f $(Format-OAuth $ConsumerSecret),$(Format-OAuth $AccessSecret)) )
      Write-Output $([Convert]::ToBase64String( $sha.ComputeHash( [System.Text.Encoding]::Ascii.GetBytes( $sigbase ) ) ))
   } else {
      Write-Output ('{0}&{1}' -f $(Format-OAuth $ConsumerSecret),$(Format-OAuth $AccessSecret)) 
   }
}}

function Invoke-OAuthHttp {
#.Synopsis
#  The primary OAuth function
#.Parameter Uri
#  The URI which will be invoked against
#.Parameter Parameters
#  All of the additional parameters which will be passed (regardless of how they will be passed) in hashtable format. Obviously the OAuth Base access parameters are not included here, they will be set by this function
#.Parameter ConsumerKey
#  The OAuth Consumer Key (a key specific to the application requesting access)
#.Parameter ConsumerSecret
#  The OAuth Consumer Secret (the secret part of the application's consumer key shouldn't be given to end users)
#.Parameter AccessToken
#  The OAuth Access Token (if you're already authenticated)
#.Parameter ConsumerSecret
#  The OAuth Access Secret (the secret part of the access token, if you're already authenticated)
#.Parameter Verb
#  The HTTP verb which will be invoked (defaults to GET)
[CmdletBinding()]
PARAM(
   [Parameter(Mandatory=$true)]
   [Uri]
   $Uri
,
   [Parameter(Mandatory=$false)]
   [HashTable]
   $Parameters =@{}
,
   [Parameter(Mandatory=$true)]
   [string]
   $ConsumerKey = "key"
, 
   [Parameter(Mandatory=$true)]
   [string]
   $ConsumerSecret = "secret"
, 
   [Parameter(Mandatory=$false)]
   [string]
   $AccessToken = ""
, 
   [Parameter(Mandatory=$false)]
   [string]
   $AccessSecret = ""
,
   [Parameter(Mandatory=$false)]
   [string]
   $AccessVerifier = ""
, 
   [Parameter(Mandatory=$false)]
   [ValidateSet("POST", "GET", "PUT", "DELETE", "HEAD", "OPTIONS")]
   [string]
   $Verb = "GET"
,
   [ValidateSet("HMAC-SHA1", "PLAINTEXT")]
   [String]
   $SignatureMethod = "HMAC-SHA1"
, 
   [ValidateSet("Xml", "File", "Text","Bytes")]
   [Alias("As")]
   $Output = "Xml"
,
   [AllowEmptyString()]
   [string]$Path
)
END {
   $parameters.format = "xml"
   

   if($AccessToken -and $AccessSecret) {
      $script:url, $script:Headers, $script:sig = Get-OAuthSignature -Uri $Uri -Parameters $Parameters `
                                                -ConsumerKey    $ConsumerKey    `
                                                -ConsumerSecret $ConsumerSecret `
                                                -AccessToken    $AccessToken    `
                                                -AccessSecret   $AccessSecret   `
                                                -AccessVerifier $AccessVerifier `
                                                -Verb $Verb.ToUpper()           `
                                                -SignatureMethod $SignatureMethod
   } else {
      $script:url, $script:Headers, $script:sig = Get-OAuthSignature -Uri $Uri -Parameters $Parameters `
                                                -ConsumerKey    $ConsumerKey    `
                                                -ConsumerSecret $ConsumerSecret `
                                                -Verb $Verb.ToUpper()           `
                                                -SignatureMethod $SignatureMethod
   }
   
   $Headers += @{ oauth_signature = Format-OAuth $sig }
   $Parameters.Keys | %{ $Headers.Remove($_) }
   $WithHeader = @{ Authorization="OAuth {0}`"" -f $(ConvertFrom-Hashtable $Headers "`", " "=`"") }

   Write-Verbose $( $WithHeader | fl | Out-String )
   
   switch($Verb) {
      "POST" {
         $plug = Get-DreamPlug $Uri.AbsoluteUri -Headers $WithHeader
         $global:debugplug = $plug
         $script:result = Invoke-Http "POST" -Plug $plug -Content ([HashTable]$Parameters) | Receive-Http $Output -Path $Path
      }
      "GET" {
         $plug = Get-DreamPlug $Uri.AbsoluteUri -Headers $WithHeader -With $Parameters
         $global:debugplug = $plug
         $script:result = Invoke-Http "GET" -Plug $plug | Receive-Http $Output -Path $Path
      }
   }
      
   ## Freakydeaky magic pulls an access hashtable out of it's hat
   if(!$AccessToken -and $result -is [System.Xml.XmlDocument] -and $result.SelectNodes("html") -and $result.SelectNodes("html") -match "oauth_token_secret") {
      $result.html | ConvertTo-Hashtable
   } elseif($result -is [System.Xml.XmlDocument]){
      $result.SelectSingleNode("//*")
   } else {
      $result
   }
}
}

# http`://oauth.net/core/1.0#auth_step3
#
# A good place to practice: http`://term.ie/oauth/example/
#
# The reason I started playing:  http`://friendfeed.com/api/documentation
#                                http`://friendfeed.com/api/applications/6a3c26fe1af047bb9553b3098bee5867
#
# The docs for Yammer were also involved:  https://www.yammer.com/api_doc.html
#
# One other resource I found helpful (I had to make a few enhancements to HttpRest):
# http`://blog.developer.mindtouch.com/2009/08/05/async-io-dream-vs-parallel-extensions/
# And thanks to http`://oauth.googlecode.com/svn/code/csharp/
# And thanks to http`://oauth.googlecode.com/svn/code/python/oauth/







####################################################################################################
### This is *MY* application stuff
$global:PoshOAuthToken = @{ 
   oauth_consumer_key    = # You must put your oauth key here
   oauth_consumer_secret = # You must put your oauth secret here
}
$global:OAuthUris = @{
   AccessToken  = "https://www.yammer.com/oauth/access_token"
   RequestToken = "https://www.yammer.com/oauth/request_token"
   Authorize    = "https://www.yammer.com/oauth/access_token"
}

function Get-OAuthAccessToken {
[CmdletBinding()]
param()
#.Synopsis
#  OAuth Web Browser Authentication
   if(!(Test-Path Variable:Global:OAuthAccessToken) -or !$global:OAuthAccessToken) {
   
      $RequestToken = Invoke-OAuthHttp                            `
        -Uri             $OAuthUris.RequestToken                  `
        -ConsumerKey     $PoshOAuthToken.oauth_consumer_key       `
        -ConsumerSecret  $PoshOAuthToken.oauth_consumer_secret    `
        -Verb "POST" -SignatureMethod 'HMAC-SHA1'
   
      if(!$RequestToken) { return }
      
      $Global:DebugToken = $RequestToken
      Write-Warning "Starting browser for interactive authorization"
      $AuthURL = ("https://www.yammer.com/oauth/authorize?oauth_token={0}" -f $RequestToken.oauth_token)
      Start $AuthURL
      
      $AccessVerifier = Read-Host "Please enter the token from the web site: $AuthURL"

      $global:OAuthAccessToken = Invoke-OAuthHttp                           `
        -Uri             $OAuthUris.AccessToken                   `
        -ConsumerKey     $PoshOAuthToken.oauth_consumer_key       `
        -ConsumerSecret  $PoshOAuthToken.oauth_consumer_secret    `
        -AccessToken     $RequestToken.oauth_token            `
        -AccessSecret    $RequestToken.oauth_token_secret     `
        -AccessVerifier  $AccessVerifier                          `
        -Verb "POST"
   }
   return $global:OAuthAccessToken
}


function Invoke-OAuthorized {
[CmdletBinding()]
PARAM(
   [Parameter(Mandatory=$true)]
   [Uri]
   $Uri
,
   [Parameter(Mandatory=$false,ValueFromPipeline=$true)]
   [hashtable]
   $Parameters =@{}
,
   [Parameter(Mandatory=$false)]
   [IO.FileInfo[]]
   $File
,
   [Parameter(Mandatory=$false)]
   [ValidateSet("POST", "GET", "PUT", "DELETE", "HEAD", "OPTIONS")]
   [string]
   $Verb = "GET"
, 
   [ValidateSet("Xml", "File", "Text","Bytes")]
   [Alias("As")]
   $Output = "Xml"
,
   [AllowEmptyString()]
   [string]$Path
)
BEGIN {
   if(!(Test-Path Variable:Global:OAuthAccessToken) -or !$global:OAuthAccessToken) {
      Write-Warning "Must fetch new Access Token"
      $Global:OAuthAccessToken = Get-OAuthAccessToken
   }
}
PROCESS {
   Write-Verbose "Fetching URI: $Uri"
   Invoke-OAuthHttp -Parameters $Parameters -Uri $Uri                            `
                    -ConsumerKey    $Global:PoshOAuthToken.oauth_consumer_key    `
                    -ConsumerSecret $Global:PoshOAuthToken.oauth_consumer_secret `
                    -AccessToken    $Global:OAuthAccessToken.oauth_token         `
                    -AccessSecret   $Global:OAuthAccessToken.oauth_token_secret  `
                    -Verb $Verb -Output $Output -Path $Path
}}


function Get-AccessTokenForIAA {
#.Synopsis
#  Installed Application Authentication
param($Credential)
   if(!(Test-Path Variable:Global:IAAccessToken)) {
      if(!$Credential) { $Credential = Get-Credential }
      
      $global:IAAccessToken = Invoke-OAuthHttp -Parameters @{
         ff_username   = $Credential.GetNetworkCredential().Username # the user's username
         ff_password   = $Credential.GetNetworkCredential().Password # the user's password
      } -Uri             $FriendFeedUris.AccessToken `
        -ConsumerKey     $global:PoshFFToken.oauth_consumer_key `
        -ConsumerSecret  $global:PoshFFToken.oauth_consumer_secret -Verb "POST"
   }   
   return $global:IAAccessToken
}



#  function get-messages {
   #  Invoke-OAuthorized https://www.yammer.com/api/v1/messages | tee -var messages
   #  $global:newestyammer = $messages.messages.message[0].id
   #  while($messages.messages.message.count -eq 20) {
      #  $global:oldestyammer = $messages.messages.message[-1].id
      #  sleep -sec 10
      #  Invoke-OAuthorized https://www.yammer.com/api/v1/messages -parameter @{ older_than = $oldestyammer } | tee -var messages
      #  if(!$?) { break }
   #  }
#  }

#  get-messages | Select sender-id, thread-id, attachments, created-at, client-type | Export-CSV Messages.csv