3
votes

I'm working against the OneLogin REST API and can't seem to get any calls with a PUT method working. When I test in Postman, I can pass a raw JSON body like this:

{
   "role_id_array":  [
                        115028
                     ]
}

to the endpoint:

https://api.us.onelogin.com/api/1/users//add_roles

This works just fine. However, when I attempt to do the same with PowerShell's Invoke-RestMethod command, I'm getting 400 Bad Request errors. My code looks like this:

$Splat = @{
    Method      = $Method
    Uri         = https://api.us.onelogin.com/api/1/users/12345678/add_roles
    ContentType = "application/json"
    Headers     = @{authorization = "bearer:$($Token.access_token)"}
    Body        = @{role_id_array = @(123456)}
}

Invoke-RestMethod @Splat

So far I've had no issues with any GET calls, only with PUT. Also, if I run the Body hashtable that I'm passing in through ConvertTo-Json, the output looks just like the above working example. Does anyone have any thoughts on why this doesn't work?

2
Try converting body to JSON ahead of time. I'm not certain that Invoke-RestMethod will automatically convert the Body payload to JSON. I've had to do this for other REST endpoints before. To be clear, you'd run $Body = @{role_id_array = @(123456)} | ConvertTo-JSON in it's own line, and then provide that as a separate param, or within your splat.FoxDeploy
According to the docs on developers.onelogin.com/api-docs/1/users/assign-role-to-user, the response object should also have a message telling you why it's a bad request.Eris
@FoxDeploy I believe Invoke-RestMethod does actually handle the JSON for you, but I have also tried converting before passing the body value. It doesn't appear to make a difference in the end result.Matt McNabb
@Eris I can't seem to wrangle any of the more advanced response details out of the errors. It seems that Invoke-RestMethod and Invoke-WebRequest both handle any non-200 responses as errors and they seem to hide the entire repsonse body from youMatt McNabb

2 Answers

9
votes

I was able to get a PUT to work!

How to get rich error response data from Invoke-RestMethod

First, I used this blog post here which has an excellent method to retrieve the full error message of an Invoke-RestMethod or WebRequest cmdlet.

In his method, first define a Function called Failure.

function Failure {
    $global:helpme = $body
    $global:helpmoref = $moref
    $global:result = $_.Exception.Response.GetResponseStream()
    $global:reader = New-Object System.IO.StreamReader($global:result)
    $global:responseBody = $global:reader.ReadToEnd();
    Write-Host -BackgroundColor:Black -ForegroundColor:Red "Status: A system exception was caught."
    Write-Host -BackgroundColor:Black -ForegroundColor:Red $global:responsebody
    Write-Host -BackgroundColor:Black -ForegroundColor:Red "The request body has been saved to `$global:helpme"
    break
}

Then, wrap all of your Invoke-RestMethod calls in a try Catch block like this.

try { 
    $e = Invoke-WebRequest 'https://api.us.onelogin.com/api/1/users/$id' `
        -Headers  @{ Authorization = "bearer:$token" } `
        -Body ( @{ phone = "7709746046" } | ConvertTo-Json ) `
        -Method Put `
        -ErrorAction:Stop `
        -ContentType 'application/json' 
} 
catch {Failure}

Now when you run into an error, you can see the actual message, like this

> Status: A system exception was caught.
{"status":{"error":true,"code":400,"type":"bad request","message":{"description":"notes is not a valid attribute for user model","attribute":"notes"}}}
The request body has been saved to $global:helpme

This was super helpful in helping me get rid of the errors I was encountering, and I was able to update a user entry using a PUT verb and get it to work.

Fixing your issue

I only had to make two changes to your code to get this to work.

First, put quotes around the URI, as your example code didn't have them and you must have quotes around strings in a hashtable.

Finally, pipe your body content to ConvertTo-JSON, otherwise the data is sent over as a string, as you found in Fiddler.

With those two changes, here's my request and the response

$Splat = @{
    Method      = 'PUT'
    Uri         = 'https://api.us.onelogin.com/api/1/users/27697924/add_roles'
    ContentType = "application/json"
    Headers     = @{authorization = "bearer:$token" }
    Body        = @{role_id_array = @(143175)} | ConvertTo-Json
}

try {Invoke-RestMethod @Splat -ErrorAction Stop }
catch {Failure}

Here's the response:

status                                                 
------                                                 
@{type=success; message=Success; error=False; code=200}

Update: we did it, this has now been fixed!

If you think PowerShell should present the actual server response for a non 200 status code, then help draw attention to this open issue on the PowerShell project page on Github.

Add your feedback or thumbs up it, and we might be able to get this changed in a future release of the language.

This issue is now fixed as of PowerShell v6.1

-1
votes

Ok, so I broke down and installed Fiddler and I think I'm onto something. It appears that the PowerShell cmdlets don't know how to handle arrays for the application/json contenttype. I'm not certain that this would be the case with XML as I haven't tested that.

I changed the body to use straight-up json strings and it worked:

$Body = "{ `"role_id_array`": [123456] }"

If I used a hashtable with the value set to an array like this:

$Body = @{role_id_array = 115028,128732}

Then I would get a 400 failure response. Fiddler showed me what was going on - here's the raw request:

PUT https://api.us.onelogin.com/api/1/users/12345678/add_roles HTTP/1.1 authorization: bearer:3c93ae2-redacted- User-Agent: Mozilla/5.0 (Windows NT; Windows NT 6.3; en-US) WindowsPowerShell/5.1.14394.1000 Content-Type: application/json Host: api.us.onelogin.com Content-Length: 33 role_id_array=System.Object%5b%5d

Notice that the array was not properly converted and instead Invoke-RestMethod simply folded it up into a typename definition, similar to the way Export-Csv will handle array property values.

So I was thinking that I had a problem with using the PUT method because all my GET calls were working, but that was a red herring. The real problem is dealing with a REST endpoint that expects array values for it's request parameter values.

EDIT: I found someone else who had posted this issue back in 2014, but his resolution was to set the -ContentType parameter to 'application/json':

Powershell v4 Invoke-RestMethod body sends System.Object

I've done this and am still having the same problem. Is this a bug?