Download Binary File using WinHTTP.WinHTTPrequest with SSO Authentication, No Password, Multiple Redirects - vba

Using VB and WinHTTP.WinHTTPrequest.5.1, I need to automate the download of a binary file for users that resides on a network share, requiring SSO authentication, without hardcoding or requiring a user to enter their password.
I have been reviewing solutions on the web for awhile now and the following VB is the closest I've gotten. I am having problems with the multiple redirects, which I am downloading instead of the file.
Public Sub download_SCData_test()
On Error GoTo err_me
Dim fData '() As Byte
Dim count As Long
Dim fileNum As Long
Dim ado_strm As Object
Dim winHTTP As New winHTTP.WinHttpRequest
Dim destPath As String
Dim destPath2 As String
Dim fileURL As String
Dim mainURL1 As String
Dim mainURL2 As String
' HttpRequest SetCredentials flags
' It might also be necessary to supply credentials to the proxy if you connect to the Internet through a proxy that requires authentication.
Const CREDENTIALS_FOR_SERVER = 0
Const CREDENTIALS_FOR_PROXY = 1
Const HTTPREQUEST_PROXYSETTING_PROXY = 2
mainURL1 = "http://wsso.someplace.on.the.web:XXXX/redirect.html?URL=https://someplace.on.the.web/quality/data_metrics/mac/"
mainURL2 = "http://wsso.someplace.on.the.web:XXXX/redirect.html?URL=https://someplace.on.the.web/quality/data_metrics/mac/"
'fileURL = "http://wsso.someplace.on.the.web:XXXX/redirect.html?URL=https://someplace.on.the.web/quality/data_metrics/mac/data.xlsb"
fileURL = "\\serverXXX\webdata\quality\data_metrics\mac\data.xlsb"
destPath = "C:\Temp\data.xlsb"
destPath2 = "C:\Temp\data2.xlsb"
With winHTTP
.SetProxy proxysetting:=HTTPREQUEST_PROXYSETTING_PROXY, ProxyServer:="wsso.someplace.on.the.web:XXXX", BypassList:="*.someplace.on.the.web"
.Option(Option:=WinHttpRequestOption_SslErrorIgnoreFlags) = 13056
.Option(Option:=WinHttpRequestOption_MaxAutomaticRedirects) = 20 'default 10
.Option(Option:=WinHttpRequestOption_EnableHttpsToHttpRedirects) = True
.Option(Option:=WinHttpRequestOption_EnableRedirects) = True
.Option(Option:=WinHttpRequestOption_RevertImpersonationOverSsl) = True
.SetTimeouts 30000, 30000, 30000, 30000 'ms - resolve, connect, send, receive
' Send a request to the server and wait for a response.
'POST authentication string to the main website address not to the direct file address
.Open Method:="POST", URL:=mainURL1, async:=False
'.SetCredentials UserName:="server\user", Password:="pass", Flags:=CREDENTIALS_FOR_SERVER ' this line has no effect
'strAuthenticate = "start-url=%2F&user=" & myuser & "&password=" & mypass & "&switch=Log+In"
.setRequestHeader Header:="Content-Type", Value:="application/x-www-form-urlencoded"
.setRequestHeader Header:="Date", Value:=Date
.send 'body:=strAuthenticate
If Not .WaitForResponse(TimeOut:=30000) Then MsgBox "timeout!": GoTo exit_me
Sleep 2000
.Open Method:="POST", URL:=mainURL2, async:=False
.send 'body:=strAuthenticate
If Not .WaitForResponse(TimeOut:=30000) Then MsgBox "timeout!": GoTo exit_me
Sleep 2000
.Open Method:="GET", URL:=fileURL, async:=True
.send
If Not .WaitForResponse(TimeOut:=30000) Then MsgBox "timeout!": GoTo exit_me
Sleep 2000
Do While InStr(1, .responseText, "function WSSORedirect()", vbTextCompare)
Sleep 2000
count = count + 1: If count > 2 Then Exit Do
Debug.Print InStr(1, .responseText, "function WSSORedirect()", vbTextCompare)
If InStr(1, .responseText, "function WSSORedirect()", vbTextCompare) < 1 Then MsgBox "any luck?"
.Open Method:="GET", URL:=fileURL, async:=True
.send
If Not .WaitForResponse(TimeOut:=30000) Then MsgBox "timeout!": GoTo exit_me
Debug.Print count
Sleep 2000
Loop
Sleep 2000
fData = .responseBody
' Display the results of the request.
Debug.Print "Credentials: "
Debug.Print .Status & " " & .StatusText
Debug.Print .getAllResponseHeaders
End With
If Dir(destPath) <> vbNullString Then Kill destPath
fileNum = FreeFile
Open destPath For Binary Access Write As #fileNum
Put #fileNum, 1, fData
Close #fileNum
If Dir(destPath2) <> vbNullString Then Kill destPath2
Set strm = CreateObject("ADODB.Stream")
With strm
.Type = 1
.Open
.Write winHTTP.responseBody
.SaveToFile destPath2, 2 'overwrite
End With
MsgBox "Completed. Check 'C:\Temp\'.", vbInformation, "execution completed"
exit_me:
On Error Resume Next
Set winHTTP = Nothing
Exit Sub
eerr_me:
Err.clear
Resume Next
End Sub
Response Headers
Credentials:
200 OK
Connection: Keep-Alive
Date: Sun, 21 Feb 2016 22:52:06 GMT
Keep-Alive: timeout=15, max=495
Content-Length: 1975
Content-Type: text/html
Last-Modified: Fri, 17 Aug 2012 17:01:12 GMT
Accept-Ranges: bytes
ETag: "XXXXXX-XXX-XXXXXXXXXXXXX"
Server: Apache/X.X.XX (Unix) mod_ssl/X.X.XX OpenSSL/X.X.XX XXX/X
Resultant download is still a WSSO redirect page not the file.
<html>
<head>
<script language="javascript" type="text/javascript">
WSSORedirect();
function WSSORedirect() {
var destinationURL = window.location.search;
if (destinationURL.substring(0, 5).toUpperCase() != "?URL=") {
destinationURL = "";
}
else {
destinationURL = destinationURL.substring(5, destinationURL.length);
}
if (destinationURL == "") {
document.writeln("redirect.html error - no URL. Usage: redirect.html?URL=[destination URL]");
document.close();
return;
}
location.replace(destinationURL);
}
</script>
</head>
</html>

Related

Sending a HTTP POST with a file >2GB from VBA

I try to upload a file (POST request) to a web service through their API in VBA. I get an "out of memory error" if the file is too large.
Unfortunately the web service does not support chunked transfer (which seems to be deprecated since http/2 anyway). Is there a way to "stream" the file without loading it into memory at once before sending?
I would like to avoid calling curl.exe, because it's cleaner.
Dim url As String
Dim dataStream As Object
Const adTypeBinary = 1
url = "https://api-endpoint.domain.com"
Dim oWinHttpReq As Object
Set oWinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
With oWinHttpReq
.Open "POST", url, False
.setRequestHeader "Content-Type", "application/octet-stream"
.setRequestHeader "Authorization", "Bearer " & AUTH_TOKEN
Set dataStream = CreateObject("ADODB.Stream")
dataStream.Type = adTypeBinary
dataStream.Open
dataStream.LoadFromFile filePath
.send dataStream.Read
End With
I tried Win32 file API as well but cannot transfer the output to the HTTP post.
/edit: I think I came closer. The solution seems to be chunked transfer - by setting the Header "Transfer-Encoding" to "chunked". Looks like you have to handle the chunk structure yourself because WinHTTP 5.1 does only support chunked download, not chunked upload. How do I use the winhttp api with "transfer-encoding: chunked"
But when I set the "Transfer-Encoding" Header, the .send method does not seem to be present anymore
/edit: I developed this function with the help of ChatGPT, but the HttpSendRequest fails...
Public Sub UploadFileChunkedLarge(filePath As String, url As String)
Dim CHUNK_SIZE As Long
CHUNK_SIZE = CLng(1024) * CLng(1024) ' 1 MB
Dim hSession As Long
Dim hRequest As Long
Dim hConnection As Long
Dim lngRetVal As Long
Dim strBoundary As String
Dim strPost As String
Dim strHeader As String
Dim varData() As Byte
Dim lngIndex As Long
Dim lngSize As Long
Dim lngBytesRead As Long
Dim result As Boolean
' Set the boundary for the POST data
strBoundary = "---------------------------7d93b2a700d04"
' Open the file for binary access
Open filePath For Binary Access Read As #1
' Get the file size
lngSize = LOF(1)
' Create the session
hSession = InternetOpen("Upload", INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
' Create the request
hConnection = InternetConnect(hSession, url, INTERNET_DEFAULT_HTTP_PORT, vbNullString, vbNullString, INTERNET_SERVICE_HTTP, 0, 0)
' HttpOpenRequest hRequest, "POST", "", "HTTP/1.1", "", "", INTERNET_FLAG_NO_CACHE_WRITE Or INTERNET_FLAG_NO_AUTH, 0
hRequest = HttpOpenRequest(hConnection, "POST", "", "HTTP/1.1", "", "", INTERNET_FLAG_NO_CACHE_WRITE, 0)
' Add the headers
strHeader = "Content-Type: multipart/form-data; boundary=" & strBoundary & vbCrLf
strHeader = strHeader & "Authorization: Bearer " & KDRIVE_TOKEN & vbCrLf
strHeader = strHeader & "Content-Length: " & lngSize & vbCrLf & vbCrLf
result = HttpAddRequestHeaders(hRequest, strHeader, Len(strHeader), HTTP_ADDREQ_FLAG_ADD)
Debug.Print WININET_GetLastError
' Send the request
result = HttpSendRequest(hRequest, vbNullString, 0, vbNullString, 0)
Debug.Print WININET_GetLastError
' Send the file data in chunks
Do While Not EOF(1)
' Read the next chunk of data
ReDim varData(CHUNK_SIZE)
lngBytesRead = LOF(1) - Loc(1)
If lngBytesRead > CHUNK_SIZE Then
lngBytesRead = CHUNK_SIZE
End If
Get #1, , varData
' Send the chunk
result = InternetWriteFile(hRequest, varData(0), lngBytesRead, lngIndex)
Loop
' Close the file
Close #1
' Close the request
InternetCloseHandle hRequest
' Close the session
InternetCloseHandle hSession
End Sub

Unable to upload a text file using vba [duplicate]

This question already has an answer here:
Convert CURL command line to VBA
(1 answer)
Closed 2 years ago.
I'm trying to upload a tiny text file in a website using vba. When I run the script I encounter this error {"success":false,"error":400,"message":"Trouble uploading file"}. I mimicked the same approach using vba that I did and found success using python. I got rid of the headers altogether in python so I suppose multipart headers is not that important to upload the file successfully.
Using vba (I got the above error):
Sub UploadFile()
Dim Http As New XMLHTTP60, sPostData$
Dim nFile&, baBuffer() As Byte
nFile = FreeFile
Open "C:\Users\WCS\Desktop\some_text.txt" For Binary Access Read As nFile
If LOF(nFile) > 0 Then
ReDim baBuffer(0 To LOF(nFile) - 1) As Byte
Get nFile, , baBuffer
sPostData = StrConv(baBuffer, vbUnicode)
End If
Close nFile
' MsgBox sPostData 'to examine if it is able to print the text
With Http
.Open "POST", "https://file.io/"
.setRequestHeader "x-requested-with", "XMLHttpRequest"
.send ("file=" & sPostData)
Debug.Print .responseText
End With
End Sub
Using vba (another way but got the same error):
Sub UploadFile()
Dim Http As New XMLHTTP60, sPostData$
With CreateObject("ADODB.Stream")
.Charset = "utf-8"
.Open
.LoadFromFile ("C:\Users\WCS\Desktop\some_text.txt")
sPostData = .ReadText()
End With
With Http
.Open "POST", "https://file.io/"
.setRequestHeader "x-requested-with", "XMLHttpRequest"
.send ("file=" & sPostData)
Debug.Print .responseText
End With
End Sub
Using python (I got success):
import requests
url = 'https://file.io/'
files = {
'file': open('some_text.txt','rb')
}
def upload_file(link):
res = requests.post(link,files=files)
print(res.content)
if __name__ == '__main__':
upload_file(url)
Btw, this is what the text file contains hi there!!!
If the file was textfile, you can store the contents in a variable and in this case to send the contents as text
Sub UploadFile()
Dim http As New XMLHTTP60, sPostData$
With CreateObject("ADODB.Stream")
.Charset = "UTF-8"
.Open
.LoadFromFile (ThisWorkbook.Path & "\Sample.txt")
sPostData = .ReadText()
End With
With http
.Open "POST", "https://file.io"
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.send ("text=" & sPostData)
Debug.Print .responseText
End With
End Sub
The code depends on the code derived from this LINK

REST API access via VBA returns "Invalid ID/Key" error

I am attempting to access the Appointments-Plus.com API via VBA code and am consistently being told I'm giving it an invalid site-id/key. I'm using Access from the Office365 version.
This is the relevant documentation for this API call.
When I use Postman to test out the API, I am able to successfully connect and I get data back. The URL Postman puts together is this:
https://ws.appointment-plus.com/Locations/GetLocations?Authorization:Basic=<site-ID>:<Key>&response_type=xml
My VBA code is this:
Public Sub RESTtestBigURL()
Dim responseType As String
responseType = "response_type=json"
Dim restRequest As WinHttp.WinHttpRequest
Set restRequest = New WinHttp.WinHttpRequest
Dim restResult As String
With restRequest
.Open "POST", "https://ws.appointment-plus.com/Locations/GetLocations?Authorization:Basic=<site-ID>:<Key>&response_type=xml", False
.Send
.WaitForResponse
Debug.Print ".ResponseText: " & .ResponseText
Debug.Print ".Status: " & .Status
Debug.Print ".StatusText: " & .StatusText
Debug.Print ".ResponseBody: " & .ResponseBody
End With
End Sub
I know that the first question is "are you sure you've got the <site-ID> and <key> correct???" Yes - I've copy/pasted the entire URL from Postman into my VBA code, and I've had another couple of pairs of eyeballs review it to confirm that they're still the same.
When I run that code, I get:
.ResponseText: <?xml version="1.0" encoding="utf-8" ?>
<APResponse>
<resource>customers</resource>
<action>getcustomers</action>
<request></request>
<result>fail</result>
<count>0</count>
<errors>
<error><![CDATA[Web Services authentication failed: invalid Site ID or API Key]]></error>
</errors>
</APResponse>
I've tried several other methods of accessing the API, all of which are giving me the same "Invalid ID/Key" error:
Public Sub SecondRESTtestMSXML()
Dim restRequest As MSXML2.XMLHTTP60
Set restRequest = New MSXML2.XMLHTTP60
With restRequest
.Open "GET", URL & REQUEST_GET_LOCATIONS, True
.SetRequestHeader "Authorization", "Basic" & SITE_ID & ":" & API_KEY
.SetRequestHeader "response_type", "xml"
.SetRequestHeader "Accept-Encoding", "application/xml"
.Send "{""response_type"":""JSON""&""location"":""582""}"
While .ReadyState <> 4
DoEvents
Wend
Debug.Print ".ResponseText: " & .ResponseText
Debug.Print ".Status: " & .Status
Debug.Print ".StatusText: " & .StatusText
Debug.Print ".ResponseBody: " & .ResponseBody
End With
End Sub
There is a suggestion that this is a duplicate of another question that was resolved by Base64-encoding. However, this method, while it wasn't explicit, shows that I have attempted that, too. I've added the Base64Encode function code that is called from here.
Public Sub RESTtest()
Dim restRequest As WinHttp.WinHttpRequest
Set restRequest = New WinHttp.WinHttpRequest
Dim restResult As String
With restRequest
.Open "POST", URL & REQUEST_GET_LOCATIONS, True
.SetRequestHeader "Authorization", "Basic " & SITE_ID & ":" & Base64Encode(API_KEY)
' Note call to Base64Encode() on this line ---------------- ----- ^^^^^^^^^^^^
.Option(WinHttpRequestOption_EnableRedirects) = False
.Send "{""response_type"":""JSON""}"
.WaitForResponse
Debug.Print ".ResponseText: " & .ResponseText
Debug.Print ".Status: " & .Status
Debug.Print ".StatusText: " & .StatusText
Debug.Print ".ResponseBody: " & .ResponseBody
End With
End Sub
Public Function Base64Encode(ByVal inputText As String) As String
Dim xmlDoc As Object
Dim docNode As Variant
Set xmlDoc = CreateObject("Msxml2.DOMDocument.3.0")
Set docNode = xmlDoc.createElement("base64")
docNode.DataType = "bin.base64"
docNode.nodeTypedValue = Stream_StringToBinary(inputText)
Base64Encode = docNode.Text
Set docNode = Nothing
Set xmlDoc = Nothing
End Function
Notes:
URL, REQUEST_GET_LOCATIONS, SITE_ID, and API_KEY are constants declared globally in this module for testing purposes. They, too, have all been copy/pasta'd and reviewed by several people for typos.
You may note that there are requests for responses in both XML and JSON - they're both giving me the same response.
I do have a support ticket open with Appt Plus, but I'm hoping I might get a faster response here.
Are there any obvious errors that anyone sees in this code? Are there any suggestions for other methods to attempt to call the API and get results? I've had a suggestion to write a DLL in C# and call that, however, I don't have the time to learn enough C# to make that happen, so switching languages isn't really an option here.
Additional notes:
I tried this using curl in a Powershell session, and it gives me the same result:
PS H:\> curl -method Post -uri "https://ws.appointment-plus.com/Locations/GetLocations?Authorization:Basic=<ID>:<key>&response_type=json"
The result:
StatusCode : 200
StatusDescription : OK
Content : {"resource":"locations",
"action":"getlocations",
"request":"",
"result":"fail",
"count":"0"
,"errors":[
"Web Services authentication failed: invalid Site ID or API ...
RawContent : HTTP/1.1 200 OK
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Date: Mon, 26 Aug 2019 17:28:41 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Set-Cooki...
Forms : {}
Headers : {[Pragma, no-cache], [Cache-Control, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0], [Date, Mon, 26 Aug 2019 17:28:41 GMT], [Expires, Thu, 19 Nov 1981 08:52:00 GMT]...}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 207
Per the exchanges in the comments, the main issue appears to be how the basic authorization header was being formed.
For future readers, the format for the authorization header is:
.SetRequestHeader "Authorization", "Basic " & Base64Encode(SITE_ID & ":" & API_KEY)
Also, another issue you may run into is related here. Linebreaks are inserted into the Base64 encoded string with the current approach, which won't play nice with most (if not all) APIs. A suggested fix for this would be something like:
Public Function Base64Encode(ByVal inputText As String, Optional removeBlankLines = True) As String
Dim xmlDoc As Object
Dim docNode As Variant
Set xmlDoc = CreateObject("Msxml2.DOMDocument.3.0")
Set docNode = xmlDoc.createElement("base64")
docNode.DataType = "bin.base64"
docNode.nodeTypedValue = Stream_StringToBinary(inputText)
Base64Encode = docNode.Text
Set docNode = Nothing
Set xmlDoc = Nothing
'remove blank line characters ASCII --> 10,13,10 + 13
If removeBlankLines Then Base64Encode = Replace(Replace(Replace(Base64Encode, vbCrLf, vbNullString), vbLf, vbNullString), vbCr, vbNullString)
End Function

Adding Parameters to VBA HTTP Post Request

I want to request a token from a web service. It requires I make an HTTP "POST" request using an authorization code.
I need to include this code, among other parameters in my request.
Any detail I find online formats the request in Java as follows (all IDs are faked):
POST /services/oauth2/token HTTP/1.1
Host: "YourURL.com"
grant_type=authorization_code&code=aPrxsmIEeqM9PiQroGEWx1UiMQd95_5JUZ
VEhsOFhS8EVvbfYBBJli2W5fn3zbo.8hojaNW_1g%3D%3D&client_id=3MVG9lKcPoNI
NVBIPJjdw1J9LLM82HnFVVX19KY1uA5mu0QqEWhqKpoW3svG3XHrXDiCQjK1mdgAvhCs
cA9GE&client_secret=1955279925675241571&
redirect_uri=https%3A%2F%2Fwww.mysite.com%2Fcode_callback.jsp
How do I produce a request like this?
Below are the relevant components of my code:
Dim request As WinHttp.WinHttpRequest
Dim
client_id,
redirect_uri,
grant_type,
client_secret,
authcode,
result,
token_url,
As String
Sub testmod()
Set request = New WinHttp.WinHttpRequest
client_id = "MyClientID"
client_secret = "MyClientSecret"
grant_type = "authorization_code"
redirect_uri = "MyRedirectURI"
authcode = "MyAuthorizationCode"
token_url = "MyTokenURL" <--- No specified query string appended
With request
.Open method:="POST", Url:=token_url
''''Including POST Params with Send method''''
.Send ("{""code"":" & authcode &
",""grant_type"":authorization_code,""client_id"":" & client_id &
",""client_secret"":" & client_secret & ",""redirect_uri"":" &
redirect_uri & "}")
''''This returns error code 400 denoting a bad request''''
Debug.Print .StatusText
end with
end sub
Any idea why these parameters are causing this request to fail?
I don't know what API you are referring to, whereas there is a new API in which the oldest 'guide' is dated 'Mar' presumably 2019.
https://developer.tdameritrade.com/apis
https://developer.tdameritrade.com/guides
Wherein there is NO reference to the "&client_secret=" being needed !.
In the 'latest' API, you request the 'code' as follows directly into your browser. It is good got a very few minutes.
https://auth.tdameritrade.com/oauth?
client_id=XXXX#AMER.OAUTHAP&response_type=code&redirect_uri=https://192.168.0.100
The response appears in the browser's entry, not in the body, You have to decode the response to use the 'code'. The RefreshToken (90 days valid) & AccessToken (30 minutes valid) are used as the are returned in the ResponseText
To get the 90 day RefreshToken and the first AccessToken
This is VBA which calls Javascript.
Private Sub Get_RefreshToken() 'Good for 90 days, then needs a new 'code', see above, also get the first AccessToken which are good for 30 minutes
Dim code As String 'dcoded, not URL coded 'WAITS for the RESPONSE, NO callback
Dim shtSheetToWork As Worksheet
Set shtSheetToWork = ActiveWorkbook.Sheets("AUTH") '<<== may NEED change
With shtSheetToWork
authorizationcode = .Range(3, "C") // dump into Excel and decode by rows JSON 'split'
Dim xmlhttp As Object
Dim scriptControl As Object
Dim Response, JsonObj As Object
Set xmlhttp = CreateObject("MSXML2.serverXMLHTTP")
Set scriptControl = CreateObject("MSScriptControl.ScriptControl")
scriptControl.Language = "JScript"
authUrl = "https://api.tdameritrade.com/v1/oauth2/token"
xmlhttp.Open "Post", authUrl, False
xmlhttp.Send "{grant_type: authorization_code, authorizationcode: ,access_type: offline, client_id: .UserId, redirect_uri: .URLredirect}"
Response = scriptControl.Eval(xmlhttp.responseText)
.Range(4, "C") = Response.refresh_token 'RefreshToken
xmlhttp.setRequestHeader "Authorization", Response.refresh_token
xmlhttp.Send
MsgBox (xmlhttp.responseText)
Select Case xmlhttp.Status
Case 200
Dim i As Integer
Dim strKey As String
Dim strVal As Variant
Dim JsonData As Variant
JsonObj = JsonDate.Parse(xmlhttp.responseText)
Cells(colstr, toprow - 1) = JsonObj
i = 1
Do While Trim(Cells(i, 1)) <> ""
Name = Split(Cells(i, 1).Text, ":")
If Name = "RefreshToken" Then .RefreshToken = Name: .nextRefreshToken = DateAdd("d", 90, Now)
If Name = "AccessToken" Then .AccessToken = Name: .nextAccessToken = DateAdd("m", 30, Now)
Case 400
MsgBox (" validation problem suthorization 'CODE' ")
Stop
Case 401
MsgBox (" Invalid credentials ")
Stop
Case 403
MsgBox (" caller doesn't have access to the account ")
Stop
Case 405
MsgBox (" Response without Allow Header")
Stop
Case 500
MsgBox (" unexpected server error ")
Stop
Case 503
MsgBox ("temporary problem responding, RETRYING !! ")
' WAIT A MINUTE AND RETRY
End Select
Set xmlhttp = Nothing
Set JsonObj = Nothing
End With
End Sub
Private Sub AccessToken() 'WAITS for the RESPONSE, NO callback
Dim code As String 'dcoded, not URL coded
Dim shtSheetToWork As Worksheet
Set shtSheetToWork = ActiveWorkbook.Sheets("AUTH") '<<== may NEED change
With shtSheetToWork
Dim xmlhttp As Object
Dim scriptControl As Object
Dim Response, JsonObj As Object
Set xmlhttp = CreateObject("MSXML2.serverXMLHTTP")
Set scriptControl = CreateObject("MSScriptControl.ScriptControl")
scriptControl.Language = "JScript"
authUrl = "https://api.tdameritrade.com/v1/oauth2/token"
xmlhttp.Open "Post", authUrl, False
xmlhttp.Send "{grant_type: refresh_token, authorizationcode: .RefreshToken, access_type: , client_id: .MYUserId, redirect_uri: }"
Response = scriptControl.Eval(xmlhttp.responseText)
.AccessToken = Response.refresh_token
xmlhttp.setRequestHeader "Authorization", RefreshToken
xmlhttp.Send
'MsgBox (xmlhttp.responseText)
Select Case xmlhttp.Status
Case 200
Dim i As Integer
Private strKey As String
Private strVal As Variant
Private Data As Variant
JsonObj = Json.Parse(xmlhttp.responseText)
Cells(colstr, toprow - 1) = JsonObj
NextText = Cells(colstr, toprow - 1)
JsonObj = Nothing
i = 1
Do While Trim(Cells(i, 1)) <> ""
Name = Split(Cells(i, 1).Text, ":")
If Name = "RefreshToken" Then .RefreshToken = Name: .nextRefreshToken = DateAdd("d", 90, Now)
If Name = "AccessToken" Then .AccessToken = Name: .nextAccessToken = DateAdd("m", 30, Now)
Case 400
MsgBox (" validation problem suthorization 'CODE' ")
Stop
Case 401
MsgBox (" Invalid credentials ")
Stop
Case 403
MsgBox (" caller doesn't have access to the account ")
Stop
Case 405
MsgBox (" Response without Allow Header")
Stop
Case 500
MsgBox (" unexpected server error ")
Stop
Case 503
MsgBox ("temporary problem responding, RETRYING !! ")
' WAIT A MINUTE AND RETRY
End Select
Next i
Set xmlhttp = Nothing
End With
End Sub

VBA setRequestHeader "Authorization" failing

I am trying to connect to a Web Database with the following code, but it does not seem to work when automated in VBA. The login and password are fine as I can connect manually with them.
is it possible that the Object: "WinHttp.WinHttpRequest.5.1" does not work with this sort of database connection? Or maybe am I missing a parameter in my Connect sub? Any help on this matter would be greatly appreciated.
Sub Connect()
Dim oHttp As Object
Set oHttp = CreateObject("WinHttp.WinHttpRequest.5.1")
Call oHttp.Open("GET", "http://qrdweb/mg/loan/loans.html?show=all", False)
oHttp.setRequestHeader "Content-Type", "application/xml"
oHttp.setRequestHeader "Accept", "application/xml"
oHttp.setRequestHeader "Authorization", "Basic " + Base64Encode("login123" + ":" + "pass123")
Call oHttp.send
Sheets("Sheet1").Cells(1, 1).Value = oHttp.getAllResponseHeaders
Sheets("Sheet1").Cells(1, 2).Value = oHttp.ResponseText
End Sub
Private Function Base64Encode(sText)
Dim oXML, oNode
Set oXML = CreateObject("Msxml2.DOMDocument.3.0")
Set oNode = oXML.createElement("base64")
oNode.DataType = "bin.base64"
oNode.nodeTypedValue = StringToBinary(sText)
Base64Encode = oNode.Text
Set oNode = Nothing
Set oXML = Nothing
End Function
Private Function StringToBinary(Text)
Const adTypeText = 2
Const adTypeBinary = 1
Dim BinaryStream
Set BinaryStream = CreateObject("ADODB.Stream")
BinaryStream.Type = adTypeText
BinaryStream.Charset = "us-ascii"
BinaryStream.Open
BinaryStream.WriteText Text
'Change stream type To binary
BinaryStream.Position = 0
BinaryStream.Type = adTypeBinary
'Ignore first two bytes - sign of
BinaryStream.Position = 0
StringToBinary = BinaryStream.Read
Set BinaryStream = Nothing
End Function
The oHttp.getAllResponseHeaders displaying the getAllresponseHeaders outputs the following information:
Cache-Control: must-revalidate,no-cache,no-store
Connection: keep-alive
Date: Fri, 24 Feb 2017 17:19:54 GMT
Content-Length: 30633
Content-Type: text/html;charset=ISO-8859-1
Server: nginx/1.11.6
WWW-Authenticate: Digest realm="QRDWEB-MNM", domain="", nonce="aB5DLmvuCfok9Zo112jo4S0evgOuXntE", algorithm=MD5, qop="auth", stale=true
While the oHttp.ResponseText displaying the ResponseText outputs the following information:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 401 Server Error</title>
</head>
<body>
Edit 1
When I comment out the 3 lines of code containing: oHttp.setRequestHeader, and changing the line: Set oHttp = CreateObject("WinHttp.WinHttpRequest.5.1") by Set oHttp = CreateObject("MSXML2.XMLHTTP"), a pop up appears for a login and password. If I fill in the information the following responses are different:
The oHttp.getAllResponseHeaders displaying the getAllresponseHeaders outputs the following information:
Server: nginx/1.11.6
Date: Fri, 24 Feb 2017 18:19:02 GMT
Transfer-Encoding: chunked
Connection: keep-alive
While the oHttp.ResponseText displaying the ResponseText outputs the following information:
<html>
<head>
<title>M&M - Loan Viewer</title>
<script language="javascript" type="text/javascript">
function showTransactionComments(loanId, date, type, commentsTableWidth) {
//alert(loanId + " " + date + " " + type + " " + commentsTableWidth);
if (window.ActiveXObject) {
return;
Edit 2
I am now attempting to integrate Digest Authentication into VBA with the following sub and I get 2 possible outcomes: The first outcome is the same 401 error when using the wrong login info and the return is immediate. However, when I provide the proper login info, the operation times out... What could be causing that?
Sub digest()
Dim http As New WinHttpRequest
Dim strResponse As String
Set http = New WinHttpRequest
http.Open "GET", "http://qrdweb/mg/loan/loans.html?show=all", False
http.SetCredentials "login123", "pass123", HTTPREQUEST_SETCREDENTIALS_FOR_SERVER
http.send
Sheets("Sheet1").Cells(1, 1).Value = http.getAllResponseHeaders
Sheets("Sheet1").Cells(1, 2).Value = http.ResponseText
http.Open "PROPFIND", "http://qrdweb/mg/loan/loans.html?show=all", False
http.send
End Sub
Per the Microsoft docs, the JScript example, it looks like authentication requires two sucessive Open/Send pairs on the same connection. The first tells the HTTP request object that Digest authentication is required, and the second actually does it. Try this (not tested):
Sub digest()
Dim http As WinHttpRequest ' *** Not "New" - you do it below
Dim strResponse As String
Set http = New WinHttpRequest
http.Open "GET", "http://qrdweb/mg/loan/loans.html?show=all", False
http.Send ' *** Try it without authentication first
if http.Status <> 401 then Exit Sub ' *** Or do something else
http.Open "GET", "http://qrdweb/mg/loan/loans.html?show=all", False
' *** Another Open, same as the JScript example
http.SetCredentials "login123", "pass123", HTTPREQUEST_SETCREDENTIALS_FOR_SERVER
http.Send
MsgBox CStr(http.Status) & ": " & http.StatusText ' *** Just to check
Sheets("Sheet1").Cells(1, 1).Value = http.getAllResponseHeaders
Sheets("Sheet1").Cells(1, 2).Value = http.ResponseText
' *** Not sure what these two lines are for --- I have commented them out
'http.Open "PROPFIND", "http://qrdweb/mg/loan/loans.html?show=all", False
'http.send
End Sub