How to implement in VBA a REST(ful) API for a moodle Database? - vba

I am trying to handle moodle data from our schools MS-Access database using VBA-code to post xml.objects that I also successfully used for an API communicating with Telegram (the messenger service).
I tried to implement the following code from an example for using the RESTful-API into my VBA-code:
curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Accept: application/json" \ -H 'Authorization: {token}' \ -d'options[ids][0]=6' \ "https://localhost/webservice/restful/server.php/core_course_get_courses"
This is how my VBA-code looks like:
Private Sub btnTestMoodleApi_Click()
Dim objRequest As Object
Dim strResult As String
Dim strPostData As String
Dim strURL As String
Dim strToken As String
Set objRequest = CreateObject("MSXML2.XMLHTTP")
strURL = xxx
strToken = xxx
strPostData = "options[ids][0]=432"
With objRequest
.Open "POST", strURL & "/webservice/restful/server.php?wstoken={" & strToken & "}/core_course_get_courses"
.setRequestHeader "Content-Type", " application/x-www-form-urlencoded"
.setRequestHeader "Accept", "application/json"
.setRequestHeader "Authorization", "{strToken}"
.Send (strPostData)
strResult = .responseText
Debug.Print strResult
End With
End Sub
The error I get in MS-Access is rather useless to me (also changing some aspects as described below did not change the error message):
" -runtime error 2147483638: The data necessary to complete this operation is not yet available
I suspect the following error sources:
a) I thought that "-H" means header, but the ".setRequestHeader" method just accepts one variable and value. I guessed maybe I can use it several times. But I am not sure if that works or how else I could declare a header in a xml.object.
b) I guess that "-d" means data, I had no idea what to do with it, thus I put into the .send() method. I think that is where the html body goes. I could be utterly wrong...
c) I nested the token for my API into the URL, because I saw it like this in another example. However the original instructions for the moodle-plugin do not have the token in the URL (only in the Authorization Header). I tried both ways, it did not work either way...
I would be really glad if someone with experience in vba could help me, how to implement API instructions into the vba code or at least point me in the right direction. Actually I do not really need this particular core function but the more complex ones to create courses etc. But I thought it was best to start with an easy example as I don't know much about API/ xml/ php/ html etc..
Thanks for reading
Arndt David

If the data is not yet available it seems like a loop to wait for results works
While objRequest.readyState <> 4
DoEvents
Wend
Besides that access to the webservice works from vba. Another problem is the implementation of the Authorization Header which is probably a formatting problem that I will clearify in another post.

Related

Format this GET request in VB.NET code behind?

I am trying a "GET" method to request a count of activities from the Accelo API here:
https://api.accelo.com/docs/?http#count-activities
And although i've used a very similar POST method to successfully get the access token using an authentication method, I cannot for the life of me figure out how to get the count of activities. The API says to use "GET" and past the access token as "bearer..." and I've also tried doing a post and getting the stream first, tried sending in some data and accessing the "list activities" endpoint instead...nothing is working. everything I do returns the error "400. Bad Request."
I've tried passing data in a query string format directly in the URI, and tried passing no data since its a GET function. It looks to me like I'm following the API exactly.
Dim data2 = Nothing ' Encoding.UTF8.GetBytes(jsonstring)
Dim _list = GetListOfActivities(New Uri("https://example.api.accelo.com/api/v0/activities/count.xml"), data2, _AccessToken)
Dim reqa As WebRequest = WebRequest.Create(uri)
' reqa.Method = "GET"
reqa.Headers.Add("GET", "/api/v0/activities/count.xml HTTP/1.1")
reqa.Headers.Add("Authorization", "Bearer " & _AccessToken)
reqa.ContentType = "application/x-www-form-urlencoded"
'reqa.ContentLength = jsonDataBytes.Length
' Dim streama = reqa.GetRequestStream()
' streama.Write(jsonDataBytes, 0, jsonDataBytes.Length)
'streama.Close()
Dim responsea As WebResponse = reqa.GetResponse()
Console.WriteLine((CType(responsea, HttpWebResponse)).StatusDescription)
I must be formatting the request wrong - please help?
My problem turned out to be something stupid, and literally beyond the scope of what I posted here. I had specified a "scope" in my initial request to get the access token that was set to read-only "staff" data (I had copied-and-pasted their example online into my code, for other parameters like grant type, and I brought the scope along with it), and in this scenario here I was trying to access "activities" data and not "staff" data. I would have thought I'd get a permissions-related error, instead of "bad request" which confused me, but anyway it works now.
The above code - actually with this line:
reqa.Method = "GET"
instead of this line:
reqa.Headers.Add("GET", "/api/v0/activities/count.xml HTTP/1.1")
Works just fine since I changed my scope to read(all) in my initial web method getting the access token.

Convert curl to vb.net/c#

How can I convert the code below to vb.net/c#?
I've tried the various examples found online but cant seem to get it going?
curl -H "Accept: application/json+v6" -H "x-api-key: <api_key>" \https://some.thing.uk/fred/prices\?productcode=ZZ99ABC
I expect it to return some results but I keep getting Forbidden (403).
I think RestSharp will be your best bet. It will work for C# or vb.net. I am fairly new to the use of RestSharp but it has worked well for me. You might have to tweak the code below a little bit since I can't test this answer without an api key but it should get you started. You will also need to install RestSharp via Nuget first and then import it into your class.
Dim key As String = 'your api key'
Dim client As New RestClient("https://some.thing.uk/fred/prices")
Dim pagesrequest = New RestRequest("\?productcode=ZZ99ABC" & "&x-api-key:" & key, Method.GET)
Dim response As IRestResponse = client.Execute(pagesrequest)
Dim textresponse As String = response.Content
'Display the response so you can check it.
textbox1.text=textresponse
Also, "\?productcode=ZZ99ABC" doesn't look right to me. You might want to try it withoug the "\".

VBA http get request response is gibberish

I tried to search for a quick answer to this but I did not see a response...so I apologize if this is redundant.
I am pretty new to VBA and only use it to pull data from APIs to make my life easier.
My question is about the response im getting from a particular API. HEre is the code im using:
Dim URL As String: URL = "API URL HERE in json format"
Dim Http As New WinHttpRequest
Dim Resp As String
Http.Open "GET", URL, False
Http.Send
Resp = Http.ResponseText
Debug.Print Resp
So the Resp text is complete gibberish...is this a security thing? I pass a security key successfully when I use it in the browser so I assumed it was not that.... I have used this exact method on numerous APIs but this is the first time I have seen this garbled response. FYI the url I am using works just fine in a browser.
The Resp looks like this for example:
gõi:?ñq¢²^2?7AÄ??æºz³Gs=ΠÜ?¬«¤%?$ÖÉ'q¯¼|?¼²ôue¦½Þ"HË!ø5[4]s½?·Þ.OÛÃBh×?4"rÊÊ[r7
Thanks for any help!
Khauna

401 Status Returned on Access Token Errors

While a 401 Unauthorized may seem spiffy for these ("Access token is missing or invalid") it can throw many a client HTTP stack into prompting the user for credentials, something that won't succeed anyway since normal HTTP authentication mechanisms are not in play.
While I can detour that using another client library that I can direct not to attempt auto-auth or user prompting (and have done so) this seems to violate RFC 7235 as far as I can tell.
I suspect that a 403 Forbidden would be more compliant here and less grief for API users. Most of them probably just see any non-2XX status and immediately run to look for a JSON "error" reponse body.
I have a detour so I'm not complaining, but something seems fishy here. Surely I'm missing something? Is it common practice now to use the 401 in this manner for REST-like HTTP APIs?
More detail
This works as long as the proper auth token is used, but causes a GUI prompt for user/pw if a bad token is used:
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
.Item("title") = txtTitle.Text
.Item("body") = txtBody.Text
End With
With XMLHTTP
.abort 'Clean up previously failed request if any.
.open "POST", PBConfig.Item("CreatePushUrl"), True
.setRequestHeader "Access-Token", PBConfig.Item("AccessToken")
.setRequestHeader "Content-Type", "application/json"
.onreadystatechange = SinkRSChange
.send JsonBag.JSON
End With
If the prompt is canceled by the user then the 401 gets reported to the code.
In light of information below I tried sending the auth token as a user ID value. However this raises a prompt even if the auth token is correct:
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
.Item("title") = txtTitle.Text
.Item("body") = txtBody.Text
End With
With XMLHTTP
.abort 'Clean up previously failed request if any.
.open "POST", PBConfig.Item("CreatePushUrl"), True, PBConfig.Item("AccessToken")
.setRequestHeader "Content-Type", "application/json"
.onreadystatechange = SinkRSChange
.send JsonBag.JSON
End With
If the user manually enters the valid auth token into the prompt as the user ID the request then succeeds.
Based on new information below
This can be made to work by explictly sending a "." as password:
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
.Item("title") = txtTitle.Text
.Item("body") = txtBody.Text
End With
With XMLHTTP
.abort 'Clean up previously failed request if any.
.open "POST", PBConfig.Item("CreatePushUrl"), True, PBConfig.Item("AccessToken"), "."
.setRequestHeader "Content-Type", "application/json"
.onreadystatechange = SinkRSChange
.send JsonBag.JSON
End With
Correct token value works, bad token value returns the 401 where it can be handled. No credentials prompt dialogs now.
Normal HTTP authentication mechanisms are technically in play. The api even asks your browser for credentials so you can do requests in your browser (someone actually requested that).
HTTP libraries that have special behavior for 401s do seem to be a problem, but the one time it happened I was been able to disable the magic 401 handling. I have no idea who is in violation of RFC 7235 here. RFC 2616 10.4.2 seems to indicate that the current behavior is "correct". Do you have a list of HTTP clients that prompt the user for credentials?
Maybe a 403 makes more sense here, but Stripe at least seems to use a 401: https://stripe.com/docs/api#errors and they are all about the REST. Switching to a 403 would break all existing clients as well. Most clients actually don't look at the JSON body oddly enough, they just look at the status code.
I think if I make another HTTP API it will have only 200/400/500 status codes with POST of JSON encoded bodies and JSON responses.
Alternative:
If support for downlevel versions of Windows is not required you can use the WinHttp.WinHttpRequest object as a replacement for the MSXML2.XMLHTTP object used in the question examples above.
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a copy.
JsonBag("title") = txtTitle.Text
JsonBag("body") = txtBody.Text
With WinHttp
.Abort 'Clean up previously failed request if any.
.Open "POST", PBConfig("CreatePushUrl"), True
.SetAutoLogonPolicy AutoLogonPolicy_Never
.SetRequestHeader "Access-Token", PBConfig("AccessToken")
.SetRequestHeader "Content-Type", "application/json"
.Send JsonBag.JSON
End With
The key to this is .SetAutoLogonPolicy AutoLogonPolicy_Never which we don't have available with the older class.
Note that this example makes use of the fact that a JsonBag has .Item() as its default property... just in case you were wondering about that difference in this code snippet compared to previous ones. It has no bearing on the use of WinHttp and could have been written this way in the earlier snippets as well.

VBA HTTP GET request - cookies with colons

I am trying to send an HTTP GET request in VBA which includes a cookie containing a colon character, like so:
objReq.Open "GET", "http://my.url.com?foo=bar", False
objReq.setRequestHeader "Cookie", "abcd=cookie:containing:colons"
objReq.Send
Depending on what object type I use for objReq, however the request gets treated differently.
The following object type works:
Dim objReq As MSXML2.ServerXMLHTTP
Set objReq = New MSXML2.ServerXMLHTTP
Unfortunately, I need to use a different object type (as MSXML2.ServerXMLHTTP can't capture sufficient detail about HTTP redirects). From what I've read, I need to use Winhttp.WinHttpRequest, MSXML2.ServerXMLHTTP40, or MSXML2.ServerXMLHTTP60, but using any of those objects results in the following error when including colons in the cookie value.
I have tried replacing the colons with Chr(58), %3A, and double-quoting within the string to no avail. I have also tried adding a 'Content-Type' header with various character encodings, but that doesn't seem to work either.
Anyone know how I can send a cookie value containing colons using the Winhttp.WinHttpRequest, MSXML2.ServerXMLHTTP40, or MSXML2.ServerXMLHTTP60 objects?
PS: Alternatively, if anyone knows how I can get the ending URL of a redirect sequence when using MSXML2.ServerXMLHTTP, that would work as well! Winhttp.WinHttpRequest would allow me to capture a 302 status code, and MSXML2.ServerXMLHTTP40 or MSXML2.ServerXMLHTTP60 would allow me to use GetOption(-1), but MSXML2.ServerXMLHTTP doesn't support either of these methods (from what I can tell).
I did a bit of testing with WinHttpRequest and I came up with the following code:
Dim objReq As WinHttp.WinHttpRequest
Set objReq = New WinHttp.WinHttpRequest
objReq.Option(WinHttpRequestOption_EnableRedirects) = True
objReq.Open "GET", "http://www.example.com", False
objReq.setRequestHeader "Cookie", "abcd=cookie:containing:colons"
objReq.send
I did notice i got the same error that you posted when I forgot to include the "http://" in the url.
I hope this helps!