API Authentication
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Mute
- Printer Friendly Page
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Notify Moderator
Hi there,
I’ve been trying to construct an Ansible Playbook (run from our Ansible Automation Platform deployment) which will trigger an Alteryx workflow. Using the information from this page : https://help.alteryx.com/developer-help/server-api-overview I’m still stuck at getting the authentication with the API to work. The guide is a bit unhelpful in that it says just to “Use a 3rd-party signature generation code” – which so far I’ve been unable to find for Ansible.
So I’ve been trying to implement my own version (in Powershell/.NET as that’s the language I’m most comfortable in). My idea being that this powershell code to acquire the validated API tokens for the Oauth flow, and then be used later in the playbook to trigger the actual workflow. Unfortunately what I’ve done doesn’t seem to be liked by the Alteryx deployment we have – as all I’m seeing is an HTTP 401 error returned from the server, with the following content:
{"data":null,"exceptionName":"UnauthorizedException","innerExceptionMessage":"","message":"The provided signature(oauth_signature) is invalid."}
I’m not sure what more troubleshooting I can do though. I’ve been following the standard as laid out here : https://oauth.net/core/1.0a/#auth_step1 – but apparently what my code is doing isn’t what the implementation on the Alteryx side is expecting.
Does anyone have any alternative solutions I could use from Ansible? Or does anyone have any ideas on better troubleshooting to see where my code is going wrong?
Thanks
- Labels:
-
API
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Notify Moderator
Hi @LeeMcrae ! What version of the alteryx server are you on? The oauth1 api end points are deprecated as of 2022.1 which was just released. Oauth2 endpoints are now available as of 21.4 and it's much easier to authenticate.
For oauth1, getting the signature just right is tricky. In my experience, it's usually just something small that is preventing auth(like capitalization, order of elements, encoded vs decoded, etc). Check out my post over here https://community.alteryx.com/t5/Engine-Works/Using-the-Alteryx-API-from-Alteryx/ba-p/318565
If you're able to post your code, that would be helpful!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Notify Moderator
Thanks @patrick_digan for your reply.
The version we are on is 2021.3
I will check out your post thank you, and code is as follows:
The first section between the two horizontal rules is how I’m calling the code, the bulk of the code is below the second horizontal rule, and is the content of the “oauth1.psm1” file which the first bit references in the “Import-Module” line at the beginning.
___________________________________________________________________________________________________________________________
Import-Module .\oauth1.psm1
$Global:Debugging = $true
Set-Config -ConsumerKey "<REDACTED>" `
-ConsumerSecret "<REDACTED>" `
-Url http://bne2-vscsql01/gallery/api/v1/workflows/626772d0969c742ffc3838ab/jobs/
$requestToken = Get-RequestToken
___________________________________________________________________________________________________________________________
#Requires -Version 7
class OauthConfig {
[String]$ConsumerKey
[String]$consumerSecret
[String]$AccessTokenUrl
[String]$AuthorizeTokenUrl
[String]$RequestTokenUrl
# Constructor
OauthConfig([String] $ConsumerKey, [String] $ConsumerSecret, [String]$url) {
$this.ConsumerKey = $ConsumerKey
$this.ConsumerSecret = $ConsumerSecret
$this.AccessTokenUrl = $Url
$this.AuthorizeTokenUrl = $Url
$this.RequestTokenUrl = $Url
}
}
class RequestTokenInfo {
[String]$RequestToken
[String]$RequestTokenSecret
RequestTokenInfo([String]$RequestToken, [String]$RequestTokenSecret) {
$this.RequestToken = $RequestToken
$this.RequestTokenSecret = $RequestTokenSecret
}
}
class AccessTokenInfo {
[String]$AccessToken
[String]$accessTokenSecret
AccessTokenInfo([String]$AccessToken, [String]$accessTokenSecret) {
$this.AccessToken = $AccessToken
$this.AccessTokenSecret = $accessTokenSecret
}
}
Function Set-Config {
param(
[Parameter(Mandatory)]
[string]$ConsumerKey,
[Parameter(Mandatory)]
[string]$ConsumerSecret,
[Parameter(Mandatory)]
[string]$Url
)
$Global:Config = [OauthConfig]::new($ConsumerKey, $ConsumerSecret, $Url)
}
Function New-Nonce{
$NewRandom = [System.Random]::New()
$NewNonce = $NewRandom.Next(1000000000)
[string]$NewNonce
}
Function Get-TimeStamp{
[TimeSpan]$ts = [DateTime]::UtcNow - [DateTime]::new(1970,1,1,0,0,0,0)
[Convert]::ToInt64($ts.TotalSeconds).ToString()
}
Function Get-QueryParameters{
param(
[Parameter(Mandatory)]
[String]$queryString
)
# Remove any leading '?'
if($queryString.StartsWith("?")){
$queryString = $queryString.Remove(0,1)
}
# Declare dictionary object to return
$result = New-Object "System.Collections.Generic.Dictionary[String,String]"
# If the supplied query string is empty, return the empty dictionary
if([String]::IsNullOrEmpty($queryString)){
return $result
}
foreach($s in $queryString.Split('&')){
if(![String]::IsNullOrEmpty($s) -and -not $s.StartsWith("oauth_")){
if($s.IndexOf('=') -gt 1){
$temp = $s.Split('=')
$result.Add($temp[0], $temp[1])
} else {
$result.Add($s, [String]::Empty)
}
}
}
return $result
}
Function Get-NormalizedUrl {
param(
[Parameter(Mandatory)]
[Uri]$uri
)
$normUrl = [String]::Format("{0}://{1}", $uri.Scheme, $uri.Host)
if(!($uri.Scheme -eq "http" -and $uri.Port -eq 80 -or
$uri.Scheme -eq "https" -and $uri.Port -eq 443)){
$normUrl += ":" + $uri.Port
}
$normUrl += $uri.AbsolutePath
$normUrl
}
Function ConcatList {
param(
[Parameter(Mandatory)]
[System.Collections.Generic.IEnumerable[String]]$source,
[Parameter(Mandatory)]
[String]$separator
)
$sb = [Text.StringBuilder]::new()
foreach($s in $source){
if($sb.Length -eq 0){
$sb.Append($s) | Out-Null
} else {
$sb.Append($separator) | Out-Null
$sb.Append($s) | Out-Null
}
}
$sb.ToString()
}
Function Get-RequestUrl{
param(
[Parameter(Mandatory)]
[String]$url
)
$uri = [Uri]::new($url, [UriKind]::Absolute)
$normUrl = [String]::Format("{0}://{1}", $uri.Scheme, $uri.Host)
if(!($uri.Scheme -eq "http" -and $uri.Port -eq 80 -or
$uri.Scheme -eq "https" -and $uri.Port -eq 443)){
$normUrl += ":" + $uri.Port
}
$normUrl += $uri.AbsolutePath
$normUrl
}
Function Get-SignatureBaseString{
param(
[Parameter(Mandatory)]
[String]$method,
[Parameter(Mandatory)]
[String]$url,
[Parameter(Mandatory)]
[System.Collections.Generic.List[String]]$requestParameters
)
$sortedList = [System.Collections.Generic.List[String]]::new($requestParameters)
$sortedList.Sort()
$requestParametersSortedString = ConcatList -source $sortedList -separator "&"
$url = Get-RequestUrl -url $url
$result = $method.ToUpper() + "&" + [Uri]::EscapeDataString($url) + "&" + `
[Uri]::EscapeDataString($requestParametersSortedString)
$result
}
Function Get-Signature{
param(
[Parameter(Mandatory)]
[String]$signatureBaseString,
[Parameter(Mandatory)]
[String]$consumerSecret,
[Parameter(Mandatory=$False)]
[String]$tokenSecret = [String]::Empty
)
$hmacsha1 = [Security.Cryptography.HMACSHA1]::new()
$key = [Uri]::EscapeDataString($consumerSecret) + "&" + ([String]::IsNullOrEmpty($tokenSecret) `
? "" `
: [Uri]::EscapeDataString($tokenSecret))
$hmacsha1.Key = [Text.Encoding]::UTF8.GetBytes($key)
$dataBuffer = [Text.Encoding]::UTF8.GetBytes($signatureBaseString)
$hashBytes = $hmacsha1.ComputeHash($dataBuffer)
$result = [Convert]::ToBase64String($hashBytes)
$result
}
Function Get-AuthorizationHeaderValue{
param(
[Parameter(Mandatory)]
[String]$accessToken,
[Parameter(Mandatory)]
[String]$accessTokenSecret,
[Parameter(Mandatory)]
[String]$url,
[Parameter(Mandatory)]
[Net.Http.HttpMethod]$httpMethod
)
$nonce = New-Nonce
$timeStamp = Get-TimeStamp
$requestParameters = [System.Collections.Generic.List[String]]::new()
$requestParameters.Add("oauth_consumer_key=" + $AccessKey)
$requestParameters.Add("oauth_nonce=" + $nonce)
$requestParameters.Add("oauth_signature_method=HMAC-SHA1")
$requestParameters.Add("oauth_timestamp=" + $timeStamp)
$requestParameters.Add("oauth_token=" + $accessToken)
$requestParameters.Add("oauth_version=1.0")
$requestUri = [Uri]::new($url, [UriKind]::Absolute)
if(![String]::IsNullOrWhiteSpace($requestUri.Query)){
$parameters = Get-QueryParameters -QueryString $requestUri.Query
foreach($kvp in $parameters){
$requestParameters.Add($kvp.Key + "=" + $kvp.Value)
}
}
$signatureBaseString = Get-SignatureBaseString($httpMethod.ToString().ToUpper(), $url, $requestParameters)
$signature = Get-Signature -signatureBaseString $signatureBaseString -secretKey $accessTokenSecret -accesskey $accessToken
$requestParametersForHeader = [System.Collections.Generic.List[String]]::new()
$requestParametersForHeader.Add("oauth_consumer_key=`"" + $accessToken) + "`""
$requestParametersForHeader.Add("oauth_token=`"" + $accessTokenSecret) + "`""
$requestParametersForHeader.Add("oauth_signature_method=`"HMAC-SHA1`"")
$requestParametersForHeader.Add("oauth_timestamp=`"" + $timeStamp + "`"")
$requestParametersForHeader.Add("oauth_nonce=`"" + $nonce + "`"")
$requestParametersForHeader.Add("oauth_version=`"1.0`"")
$requestParametersForHeader.Add("oauth_signature=`"" + [Uri]::EscapeDataString($signature) + "`"")
$result = ConcatList -source $requestParametersForHeader -separator ","
$result
}
Function Get-AuthorizationHeader{
param(
[Parameter(Mandatory)]
[String]$accessToken,
[Parameter(Mandatory)]
[String]$accessTokenSecret,
[Parameter(Mandatory)]
[String]$url,
[Parameter(Mandatory)]
[Net.Http.HttpMethod]$httpMethod
)
$authHeader = [Net.Http.Http.Headers.AuthenticationHeaderValue]::new("OAuth", (Get-AuthorizationHeaderValue -accessToken $accessToken -accessTokenSecret $accessTokenSecret -url $url -httpMethod $httpMethod))
$authHeader
}
Function Send-PostDataBody {
param(
[Parameter(Mandatory)]
[String]$Url,
[Parameter(Mandatory)]
[String]$PostData
)
try {
if($Global:Debugging){
Invoke-RestMethod -Method Post -Uri $Url -Body $PostData -ContentType "application/x-www-form-urlencoded" -Proxy http://fiddler.local:8888
} else {
Invoke-RestMethod -Method Post -Uri $Url -Body $PostData -ContentType "application/x-www-form-urlencoded"
}
} catch {
throw $_
}
}
Function Send-PostDataHeaders {
param(
[Parameter(Mandatory)]
[String]$Url,
[Parameter(Mandatory)]
[String]$PostData
)
$Headers = @{
"Authorization" = [String]::Format("OAuth {0}", $PostData)
}
try {
if($Global:Debugging){
Invoke-RestMethod -Method Post -Uri $Url -Headers $Headers -ContentType "application/json" -Body '{}' -Proxy http://fiddler.local:8888
} else {
Invoke-RestMethod -Method Post -Uri $Url -Headers $Headers -ContentType "application/json" -Body '{}'
}
} catch {
throw $_
}
}
Function Get-RequestToken {
[String]$Nonce = New-Nonce
[String]$TimeStamp = Get-TimeStamp
$requestParameters = [System.Collections.Generic.List[String]]::new()
$requestParameters.Add("oauth_timestamp=" + $TimeStamp)
$requestParameters.Add("oauth_signature_method=HMAC-SHA1")
$requestParameters.Add("oauth_consumer_key=" + $Global:Config.ConsumerKey)
$requestParameters.Add("oauth_version=1.0")
$requestParameters.Add("oauth_nonce=" + $Nonce)
$signatureBaseString = Get-SignatureBaseString -method "POST" -url $Global:Config.RequestTokenUrl -requestParameters $requestParameters
$signature = Get-Signature -signatureBaseString $signatureBaseString -consumerSecret $Global:Config.ConsumerSecret
$responseText = Send-PostDataHeaders -Url $Global:Config.RequestTokenUrl -PostData ((ConcatList -source $requestParameters -separator ",") + ",oauth_signature=" + [Uri]::EscapeDataString($signature))
if(![String]::IsNullOrEmpty($responseText)){
[String]$oauthToken = $null
[String]$oauthTokenSecret = $null
[String]$keyValPairs = $responseText.Split('&')
for($i=0; $i -lt $keyValPairs.Length; $i++){
[String]$splits = $keyValPairs[$i].Split('=')
switch ($splits[0]) {
"oauth_token" { $oauthToken = $splits[1]; Break }
"oauth_token_secret" { $oauthTokenSecret = $splits[1]; Break }
}
}
$RequestTokenInfo = [RequestTokenInfo]::new($oauthToken, $oauthTokenSecret)
return $RequestTokenInfo
}
throw "Empty response text when getting the request token"
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Notify Moderator
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Notify Moderator
Thank you @patrick_digan for taking the time, much appreciated. I'll give it a shot!