curl to Invoke-RestMethod conversion

This topic contains 16 replies, has 5 voices, and was last updated by Profile photo of Daniel Krebs Daniel Krebs 3 months ago.

  • Author
    Posts
  • #52021
    Profile photo of Zuldan
    Zuldan
    Participant

    I'm trying to convert this curl command to Invoke-RestMethod.

    The curl command is...

    curl --insecure -i  -H "Content-type: application/x-www-form-urlencoded" -H "Accept: appliation/json" --data  "grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string" -X POST https://192.168.110.90/api/common/1.0/oauth/token

    So far I have the following but I'm not sure where to put 'Accept: appliation/json'? Any assistance would be greatly appreciated.

    $Params = @{
        ContentType = 'application/x-www-form-urlencoded' 
        Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
        Method = 'Post'
        URI = 'https://192.168.110.90/api/common/1.0/oauth/token'
    }
    
    Invoke-RestMethod @Params
    • This topic was modified 3 months, 1 week ago by Profile photo of Zuldan Zuldan.
    • This topic was modified 3 months, 1 week ago by Profile photo of Zuldan Zuldan.
  • #52025
    Profile photo of Daniel Krebs
    Daniel Krebs
    Participant

    Please check if one of the examples below work for you.

    Example 1:

    $Params = @{
        ContentType = 'application/x-www-form-urlencoded' 
        Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
        Method = 'Post'
        URI = 'https://192.168.110.90/api/common/1.0/oauth/token'
        Headers = @{'accept'='application/json'}
    }
    
    Invoke-RestMethod @Params
    

    Example 2:

    $Params = @{
        Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
        Method = 'Post'
        URI = 'https://192.168.110.90/api/common/1.0/oauth/token'
        Headers = @{'accept'='application/json';'content-type'='application/x-www-form-urlencoded'}
    }
    
    Invoke-RestMethod @Params
    
    • This reply was modified 3 months, 1 week ago by Profile photo of Daniel Krebs Daniel Krebs.
  • #52041
    Profile photo of Zuldan
    Zuldan
    Participant

    Hi Daniel, thanks for the quick reply.

    Both examples gives me "Invoke-RestMethod : The remote server returned an error: (400) Bad Request."

    I suspect the issue is with Body. RESTful is a pain in the ass.

  • #52047
    Profile photo of Daniel Krebs
    Daniel Krebs
    Participant

    Please check if either of the following works instead.

    $Params = @{
        ContentType = 'application/x-www-form-urlencoded' 
        Method = 'Post'
        URI = 'https://192.168.110.90/api/common/1.0/oauth/token?grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
        Headers = @{'accept'='application/json'}
    }
    Invoke-RestMethod @Params
    
    $Params = @{
        Method = 'Post'
        URI = 'https://192.168.110.90/api/common/1.0/oauth/token?grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
        Headers = @{'accept'='application/json';'content-type'='application/x-www-form-urlencoded'}
    }
    Invoke-RestMethod @Params
    
  • #52178
    Profile photo of Zuldan
    Zuldan
    Participant

    Still getting the (400) Bad Request error Daniel. Thank you so much for your suggestions. I'm going to open a case with the vendor.

  • #52192
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    Try this. Specifying the headers this way has worked well for me using other RESTful APIs.

    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add('Content-type', 'application/x-www-form-urlencoded'")
    $headers.Add('Accept', 'application/json')
    
    $args = @{
        Uri = "https://192.168.110.90/api/common/1.0/oauth/token"
        Method = "Post"
        Headers = $headers
        Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
    }
    
    Invoke-RestMethod @args
    
    • This reply was modified 3 months, 1 week ago by Profile photo of Curtis Smith Curtis Smith.
  • #52222
    Profile photo of Zuldan
    Zuldan
    Participant

    Thanks for the suggestion Curtis. Still no luck. Same 400 error.

    The REST API document (PDF) is here

    It says...

    All access to protected resources requires a valid access token. To obtain an access token, the client must send
    a POST request with the access code. (See the oauth section under Resources for the API.)
    The Steelhead appliance will issue an access token that is valid for the next one hour time period and return that
    token in the body of the POST. If the client script runs for over an hour, the appliance must generate another
    access token when the old one expires. An expired token results in an error with HTTP code 401 and error_id
    AUTH_EXPIRED_TOKEN.
    Finally, the Authorization HTTP Header must be used to pass the token for API authentication. The following
    format should be used:
    Authorization: Bearer 'Access Token encoded in base64 format'
    Example: Authorization: Bearer eyJhdWQiOiAiaHR0cHM6Ly9nZW4tdnNoNDMubGFiLm5idHRlY2guY29tAifQ==

    Below the full doco with the example I was providing before. Maybe you guys will see something I'm missing.

    Issue

    How do you make REST API calls on the Steelhead ?

    Solution

    Starting with RioS 8.5 Steelheads support REST API Access. To enable this feature
    •In the Steelhead web GUI ,
    Configure > Security > REST API Access –> Enable REST API Access
    •Preconfigure the access code
    Configure > Security > REST API Access to display the REST API Access page. –> Click Add Access Code –> Generate New Access Code/Import Existing Access Code –> Add
    •Copy the access code copied from the Management Console REST API Access page into the configuration file of your external script. The script uses the access code to make a call to the Steelhead appliance to request an access token.

    The appliance/system validates the access code and returns an access token for use by the script. Generally the access token is kept by the script for a session only, but note that the script can make many requests using the same access token. These access tokens have some lifetime—usually around an hour —in which they are valid. When they expire, the access code must fetch a new access token. The script uses the access token to make REST API calls with the appliance/system.

    Using CURL to obtain a token

    [user@tbsl6 ~]$ curl –insecure -i -H "Content-type: application/x-www-form-urlencoded" -H "Accept: appliation/json" –data "grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string" -X POST https://192.168.110.90/api/common/1.0/oauth/token

    HTTP/1.1 200 OK
    Date: Fri, 27 Jun 2014 21:33:37 GMT

    Server: PasteWSGIServer/0.5 Python/2.7.3
    Content-type: application/json
    Vary: Accept-Encoding,User-Agent
    Transfer-Encoding: chunked
    {

    "access_token": "eyJhdWQiOiAiaHR0cHM6Ly90......QwMzkwODQxNyIsICJpYXQiOiAiMTQwMzkwNDgxNyJ9",
    "allowed_signature_types": [
    "none"
    ],
    "expires_in": 3600,
    "state": "state_string",
    "token_type": "bearer"
    }

    Note:

    The assertion string, which is of a format 'a.b.c', where a = base64('{"alg":"none"}'), b = base64(access code) and c = empty string. base64() is the function that encodes the string passed to base64 format. Use the corresponding function in your language of implementation. Also, in 'a', the algorithm is 'none', because the only signature method currently supported is 'none'.

    In the example above ,

    assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.

    a= 'eyJhbGciOiJub25lIn0K' ==> b64encode('{"alg":"none"}\n')
    b= access code from Steelhead
    c= empty string

    Making a REST API authenticated call using CURL

    [user@tbsl6 ~]$ curl –insecure -i -H "Content-type: application/x-www-form-urlencoded" -H "Accept: application/json" -H "Authorization: Bearer eyJhdWQiOiAiaHR0cHM6Ly90......QwMzkwODQxNyIsICJpYXQiOiAiMTQwMzkwNDgxNyJ9" https://192.168.110.90/api/rfwk/1.0/system/uptime

    HTTP/1.1 200 OK
    Date: Fri, 27 Jun 2014 21:45:07 GMT
    Server: PasteWSGIServer/0.5 Python/2.7.3
    Content-type: application/json
    Vary: Accept-Encoding,User-Agent
    Transfer-Encoding: chunked
    {
    "uptime": "3735163904"
    }

    • This reply was modified 3 months, 1 week ago by Profile photo of Zuldan Zuldan.
    • This reply was modified 3 months, 1 week ago by Profile photo of Zuldan Zuldan.
    • This reply was modified 3 months, 1 week ago by Profile photo of Zuldan Zuldan.
  • #52227
    Profile photo of Zuldan
    Zuldan
    Participant

    and here is a Python example...I'm wondering if it's because I'm not doing base64 encoding.

    import os,sys
    import base64
    import json,time
    import httplib, urllib
    
    def base64_encode(s):
       return base64.urlsafe_b64encode(s)
    
    def GET(sh,token,url):
       auth_token="Bearer " + ''.join(token)
       headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "application/json","Authorization": auth_token}
       conn = httplib.HTTPSConnection(sh)
       conn.request("GET", url, auth_token, headers)
       response_data = conn.getresponse().read()
       print "GET:",url," response:\n", response_data
    
    # Main
    steelhead_ip="X.X.X.X"
    access_code="eyJhdWQiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaC9hcGkvY29tbW9uLzEuMC90b2tlbiIsICJpc3MiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaCIsICJwcm4iOiAiYWRtaW4iLCAianRpIjogIjI2NjA5YzM0LWE1ZDAtNGZmZS05YWViLThjMjZhMzMxMTY1MiIsICJleHAiOiAiMCIsICJpYXQiOiAiMTQwMjY4MjQ5NSJ9"
    header_encoded = base64_encode("{\"alg\":\"none\"}\n")
    assertion_string=''.join([header_encoded,'.',access_code,'.',''])
    
    # Create OAuth request
    header = {"Content-type": "application/x-www-form-urlencoded","Accept": "application/json"}
    body = 'grant_type=%s&assertion=%s&state=%s' % ('access_code', assertion_string, 'state_string')
    
    conn = httplib.HTTPSConnection(steelhead_ip)
    conn.request("POST", "/api/common/1.0/oauth/token", body, header)
    response = conn.getresponse()
    rdata = response.read()
    conn.close()
    
    jdata=json.loads(rdata)
    if response.status == 200:
        access_token=jdata['access_token']
        print "\naccess_token=%s\nExpires in=%s\n" % (jdata['access_token'], jdata['expires_in'])
    else:
        print response.status, response.reason
    
    print "Rest API calls using token"
    
    GET(steelhead_ip,access_token,"/api/rfwk/1.0/system/uptime")
    GET(steelhead_ip,access_token,"/api/sh/1.0/status/health")
    GET(steelhead_ip,access_token,"/api/common/1.0/info")
  • #52301
    Profile photo of Daniel Krebs
    Daniel Krebs
    Participant

    Thanks for the link to the PDF and the sample Python code.

    According to the document the API expects a JSON request body too which does not make sense if you look at the Curl and Python examples. However, I've created a couple of examples below of which one will hopefully work for you.

    As I understand (page 3 of the PDF), you'll need to go into appliance portal and generate an access code, and replace the place holders in my examples before going ahead. Obviously the access code provided in the Curl and Python examples won't work for the appliance you've access to.

    I hope that helps. I can offer you to have a Zoom screen sharing session for free if you're still stuck with getting this to work. Unfortunately, I don't have access to this kind of appliance. Please let us know.

    Example 1a – JSON request body and access code with Base64 encoding:

    $uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
    $algorithm = "{`"alg`":`"none`"}\n"
    $accessCode = 'PasteAccessCodeFromAppliancePortalHere'
    
    $algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))
    $accessCodeBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($accessCode))
    
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers = @{'accept'='application/json'}
        Body = @{
            'grant_type' = 'access_code'
            'assertion' = '{0}.{1}.' -f $algorithmBase64, $accessCodeBase64
            'state' = 'state_string'
        } | ConvertTo-Json
        Method = 'Post'
        URI = $uri
    }
    Invoke-RestMethod @params
    

    Example 1b – JSON request body and access code w/o Base64 encoding:

    $uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
    $algorithm = "{`"alg`":`"none`"}\n"
    $accessCode = 'PasteAccessCodeFromAppliancePortalHere'
    
    $algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))
    
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers = @{'accept'='application/json'}
        Body = @{
            'grant_type' = 'access_code'
            'assertion' = '{0}.{1}.' -f $algorithmBase64, $accessCode
            'state' = 'state_string'
        } | ConvertTo-Json
        Method = 'Post'
        URI = $uri
    }
    Invoke-RestMethod @params
    

    Example 2a – URI request body format and access code with Base64 encoding:

    $uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
    $algorithm = "{`"alg`":`"none`"}\n"
    $accessCode = 'PasteAccessCodeFromAppliancePortalHere'
    
    $algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))
    $accessCodeBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($accessCode))
    
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers = @{'accept'='application/json'}
        Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64, 
            $accessCodeBase64
        Method = 'Post'
        URI = $uri
    }
    Invoke-RestMethod @params
    

    Example 2b – URI request body format and access code w/o Base64 encoding:

    $uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
    $algorithm = "{`"alg`":`"none`"}\n"
    $accessCode = 'PasteAccessCodeFromAppliancePortalHere'
    
    $algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))
    
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers = @{'accept'='application/json'}
        Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64, 
            $accessCode
        Method = 'Post'
        URI = $uri
    }
    
    Invoke-RestMethod @params
    
  • #52315
    Profile photo of Gareth Shipp
    Gareth Shipp
    Participant

    @daniel, thank you so much for all the new examples. I've been using the access code generated by the appliance the whole time. Thanks for checking. Unfortunately all of them returned the same 400 error. I think we're heading in the right direction though.

    As for the Zoom screen share offer...I am interested. What timezone are you in? I'm in GMT 10:00+.

  • #52319
    Profile photo of Max Kozlov
    Max Kozlov
    Participant

    want to mention: access code in your python example is base64 encoded json string.
    may be you need to properly fill needed properties before base64 encode it ?

    PS D:\> $access_code="eyJhdWQiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaC9hcGkvY29tbW9uLzEuMC90b2tlbiIsICJpc3MiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaCIs
    ICJwcm4iOiAiYWRtaW4iLCAianRpIjogIjI2NjA5YzM0LWE1ZDAtNGZmZS05YWViLThjMjZhMzMxMTY1MiIsICJleHAiOiAiMCIsICJpYXQiOiAiMTQwMjY4MjQ5NSJ9"
    >>>
    PS D:\> [text.encoding]::utf8.getstring([Convert]::FromBase64String($access_code))
    {"aud": "https://tb-serversh/api/common/1.0/token", "iss": "https://tb-serversh", "prn": "admin", "jti": "26609c34-a5d0-4ffe-9aeb-8c26a3311652", "exp": "0", "iat": "1402682495"}
    

    btw, may be your problem is encoding ? as far as I see, server want utf8, may be you sent your strings in unicode?

    • This reply was modified 3 months ago by Profile photo of Max Kozlov Max Kozlov.
  • #52322
    Profile photo of Max Kozlov
    Max Kozlov
    Participant

    ... not unicode... its Default in Daniel's code

    • This reply was modified 3 months ago by Profile photo of Max Kozlov Max Kozlov.
  • #52325
    Profile photo of Zuldan
    Zuldan
    Participant

    fyi, the post from Gareth Shipp is me. For some strange reason Powershell.org is now automatically logging me into the forums with what Outlook account I'm logged in with in another browser. I can no longer choose what email address to use when logging into the forums. Weird!

  • #52327
    Profile photo of Daniel Krebs
    Daniel Krebs
    Participant

    Gareth, no problem.

    I am GMT+7 for another week. Are you on LinkedIn, Twitter, Facebook or an open Slack team like DevOps Chat, hangsops or PowerShell? To connect up and schedule a Zoom session without sharing our email addresses here.

    My contact details:
    https://www.linkedin.com/in/krebsdaniel
    https://twitter.com/Dan1el42
    https://www.facebook.com/danielkrebs42
    Slack user name: dan1el42

    Cheers,
    Daniel

  • #52329
    Profile photo of Zuldan
    Zuldan
    Participant

    I'm on the Powershell Slack. Should be online within 45 min. If you're on too then, great!

  • #52951
    Profile photo of Gareth Shipp
    Gareth Shipp
    Participant

    @daniel, thank you so much for the session today. You've solved the mystery!

    Fyi to everyone else....

    Powershell was not using the correct base64 encoding. By copying the base64 code from a Python we managed to make a REST call to the appliance.

    I've tried all the other types with [Convert]::ToBase64String([System.Text.Encoding]:: but still cannot match what Python produces. I'll have to investigate further.

    Final code below:

    $uri = 'https://mydevice/api/common/1.0/oauth/token'
    $algorithm = "{`"alg`":`"none`"}"
    $accessCode = 'myacesscode'
    
    $algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($algorithm))
    
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers = @{'accept'='application/json'}
        #Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64,$accessCode
        Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f 'eyJhbGciOiJub25lIn0K',$accessCode
        Method = 'Post'
        URI = $uri
    }
  • #52953
    Profile photo of Daniel Krebs
    Daniel Krebs
    Participant

    Thanks Gareth.

    I've figured out why PowerShell returned a different Base64 string for the algorithm soon after our call had ended. I've made a mistake and missed to convert all escape characters from Python to PowerShell.

    Wrong:
    $algorithm = "{`"alg`":`"none`"}\n"

    Correct:
    $algorithm = "{`"alg`":`"none`"}`n"

    $uri = 'https://mydevice/api/common/1.0/oauth/token'
    $algorithm = "{`"alg`":`"none`"}`n"
    $accessCode = 'myacesscode'
    
    $algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($algorithm))
    
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers = @{'accept'='application/json'}
        Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64, $accessCode
        Method = 'Post'
        URI = $uri
    }
    

You must be logged in to reply to this topic.