Microsoft Graph get user photos - vba

I try to download the profile picture from some users in my organization. I'm getting the access_token, but in the next step get the error message:
The token contains no permissions, or permissions can not be
understood.
My app has in my point of view all required permissions:
2
Here is my used code:
Sub Test_GetToken()
Dim xml As New MSXML2.XMLHTTP60
Dim url As String
Dim Json As Object
url = "https://login.microsoftonline.com/tenant_id/oauth2/v2.0/token"
xml.Open "POST", url, False
xml.setRequestHeader "application", "x-www-form-urlencoded"
xml.Send ("client_id=1234678&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=12345678&grant_type=client_credentials")
Set Json = JsonConverter.ParseJson(xml.responseText)
access_token = Json("access_token")
token_type = Json("token_type")
expires_in = Json("expires_in")
ext_expires_in = Json("ext_expires_in")
' trying to get the photo
url = "https://graph.microsoft.com/v1.0/users/user1#OUTLOOK.DE/photo/$value"
xml.Open "GET", url, False
xml.setRequestHeader "application", "x-www-form-urlencoded"
xml.setRequestHeader "Content-Type", "text/json"
xml.setRequestHeader "Authorization", token_type & " " & access_token
xml.Send ("")
'Debug.Print token_type & " " & access_token
'Debug.Print xml.getAllResponseHeaders
Debug.Print xml.responseText
Set xml = Nothing
End Sub
Does anybody has an ideas where the mistake is?

There are several things wrong here:
There isn't an application header in HTTP
x-www-form-urlencoded is an incomplete Content-Type (it should be `application/x-www-form-urlencoded).
Your Content-Type should be application/json
You cannot use the Client Credentials OAuth grant to access a Microsoft Account (i.e. a personal #Outlook address). Client Credentials can only access data from users within your tenant. In order to access personal account data, you need to have the user authenticate using either the Authorization Code or Implicit OAuth grants.

I got Admin Consent and now it works. thanks to Marc for improving my code and getting the solution.

Related

Connecting to Web API with Cookie Authentication and CSRF Token

*UPDATE AT THE END
I need help with using an API to authenticate into https://connect.garmin.com/signin/.
I am using VBA and Power Query to automate the collecting of workout data from my Garmin account. As far as I can tell, the website uses cookie based authentication and a CSRF token.
I am testing my API calls in Postman, building the authentication request in VBA, and performing data collection in Power Query with the authenticated cookies. (I would use Power Query for the whole project, but it is not able to return the response headers/authenticated cookies)
I am trying to replicate the browser actions by collecting the cookies from response headers and CSRF token from the HTML body before making the authentication POST request, but I am not having much success. When I try to authenticate using this method the response status is 200 and response body is the login page with a "Something went wrong" message.
I have tried to follow OmegaStripes answer from this question to get a handle on the cookies,
How to set and get JSESSIONID cookie in VBA
Is there a special way to handle CSRF tokens in Postman or VBA MSXML2.ServerXMLHTTP? Is there something I am fundamentally not understanding about cookie authentication?
If you need any further info from me, please let me know. Any help is greatly appreciated!
My modified version of OmegaStripes code is below,
Option Explicit
Sub GetCookie()
Dim sUrl, sRespHeaders, sRespText, aSetHeaders, aList, aSetBody, sCSRFToken, sBody
sBody = "username=USERNAME&password=PASSWORD&embed=false&_csrf="
' get cookie 1
sUrl = "https://connect.garmin.com/signin/"
XmlHttpRequest "GET", sUrl, Array(), "", sRespHeaders, sRespText
ParseResponse "^Set-(Cookie): (\S*?=\S*?);[\s\S]*?$", sRespHeaders, aSetHeaders
' get cookie 2 and CSRF Token
sUrl = "https://sso.garmin.com/sso/signin?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&webhost=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&source=https%3A%2F%2Fconnect.garmin.com%2Fsignin%2F&redirectAfterAccountLoginUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&redirectAfterAccountCreationUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso&locale=en_US&id=gauth-widget&cssUrl=https%3A%2F%2Fconnect.garmin.com%2Fgauth-custom-v1.2-min.css&privacyStatementUrl=https%3A%2F%2Fwww.garmin.com%2Fen-US%2Fprivacy%2Fconnect%2F&clientId=GarminConnect&displayNameShown=false&consumeServiceTicket=false&generateExtraServiceTicket=true&generateTwoExtraServiceTickets=false&generateNoServiceTicket=false&globalOptInShown=true&globalOptInChecked=false&connectLegalTerms=true&locationPromptShown=true&showPassword=true"
XmlHttpRequest "GET", sUrl, aSetHeaders, "", sRespHeaders, sRespText
' parse project names
ParseResponse "^Set-(Cookie): (\S*?=\S*?);[\s\S]*?$", sRespHeaders, aSetHeaders
sCSRFToken = GetCSRFToken("name=" & Chr(34) & "_csrf" & Chr(34) & " value=" & Chr(34), sRespText)
' get authenticated cookies
sUrl = "https://sso.garmin.com/sso/signin?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&webhost=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&source=https%3A%2F%2Fconnect.garmin.com%2Fsignin%2F&redirectAfterAccountLoginUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&redirectAfterAccountCreationUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso&locale=en_US&id=gauth-widget&cssUrl=https%3A%2F%2Fconnect.garmin.com%2Fgauth-custom-v1.2-min.css&privacyStatementUrl=https%3A%2F%2Fwww.garmin.com%2Fen-US%2Fprivacy%2Fconnect%2F&clientId=GarminConnect&displayNameShown=false&consumeServiceTicket=false&generateExtraServiceTicket=true&generateTwoExtraServiceTickets=false&generateNoServiceTicket=false&globalOptInShown=true&globalOptInChecked=false&connectLegalTerms=true&locationPromptShown=true&showPassword=true"
XmlHttpRequest "POST", sUrl, aSetHeaders, sBody & sCSRFToken, sRespHeaders, sRespText
' parse project names
ParseResponse "^Set-(Cookie): (\S*?=\S*?);[\s\S]*?$", sRespHeaders, aSetHeaders
End Sub
Sub XmlHttpRequest(sMethod, sUrl, aSetHeaders, sPayload, sRespHeaders, sRespText)
Dim aHeader
With CreateObject("MSXML2.ServerXMLHTTP")
'.SetOption 2, 13056 ' SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS
.Open sMethod, sUrl, False
For Each aHeader In aSetHeaders
.SetRequestHeader aHeader(0), aHeader(1)
Next
.Send (sPayload)
sRespHeaders = .GetAllResponseHeaders
sRespText = .ResponseText
End With
End Sub
Sub ParseResponse(sPattern, sResponse, aData)
Dim oMatch, aTmp, sSubMatch
If IsEmpty(aData) Then
aData = Array()
End If
With CreateObject("VBScript.RegExp")
.Global = True
.MultiLine = True
.Pattern = sPattern
For Each oMatch In .Execute(sResponse)
If oMatch.SubMatches.Count = 1 Then
PushItem aData, oMatch.SubMatches(0)
Else
aTmp = Array()
For Each sSubMatch In oMatch.SubMatches
PushItem aTmp, sSubMatch
Next
PushItem aData, aTmp
End If
Next
End With
End Sub
Function GetCSRFToken(sPattern, sResponse)
Dim lStart, lLength, sSubMatch
lStart = InStr(1, sResponse, sPattern) + Len(sPattern)
lLength = InStr(lStart, sResponse, Chr(34)) - lStart
GetCSRFToken = Mid(sResponse, lStart, lLength)
End Function
Sub PushItem(aList, vItem)
ReDim Preserve aList(UBound(aList) + 1)
aList(UBound(aList)) = vItem
End Sub
EDIT
I have changed approach to creating an instance of Internet Explorer, and automating the log in process. Not ideal, but I am able to sign in successfully. My issue now is I am not able to retrieve the HttpOnly Cookies required to maintain the logged in session using,
getCookie = objIE.document.Cookie
I have tried another of OmegaStripes answers (coincidentally) Retrieve ALL cookies from Internet Explorer but was unsuccessful.
Any suggestions on how to get this working with MSXML2.ServerXMLHTTP, an IE instance, or any other method would be greatly appreciated!

Token authentication for CURL in EXCEL VBA

A client requested that I put together a tool to take a company domain name and somehow come up with the account name. I found an API that I can work with, but I'm not as familiar with doing this in VBA.
Here is the CURL procedure that the site documentation gives to push through a Domain Name, and receive the company name as a response:
curl 'https://company.clearbit.com/v2/companies/find?domain=segment.com' \
-u sk_b3b05a8924c4f3df86248b4e38421cfa:
After several attempts of receiving the "Please authenticate your request", I've finally gotten to the point where I am receiving an error from the API, I think indicating that I am now correctly calling the API in some way.
Here is my current code. Any idea why I may be receiving this error?
{"error":{"type":"api_error","message":"Sorry, something went wrong. We have been notified."}}
Public Function CallRestAPI2(strUrl1 As String)
TargetURL = "https://company.clearbit.com/v2/companies/find?domain=toplinegroup.com"
Set HTTPReq = CreateObject("WinHttp.WinHttpRequest.5.1")
'HTTPReq.Option(4) = 13056 '
HTTPReq.Open "GET", TargetURL, False
HTTPReq.SetRequestHeader "Content-Type", "application/json"
HTTPReq.SetRequestHeader "Accept", "application/json"
HTTPReq.Send "-u sk_b3b05a8924c4f3df86248b4e38421cfa"
Debug.Print HTTPReq.responseText
End Function
Any idea what I am doing wrong?
Figured this out after more reading in the documentation and it was due to not passing the Authentication header the correct way. Here is my code:
Public Function CallRestAPI3(strUrl1 As String)
Dim xmlhttp As New MSXML2.XMLHTTP60, myurl As String
myurl = "https://company.clearbit.com/v2/companies/find?domain=toplinegroup.com"
xmlhttp.Open "GET", myurl, False
xmlhttp.setRequestHeader "Authorization", "Bearer " + "sk_b3b05a8924c4f3df86248b4e38421cfa"
xmlhttp.Send
Debug.Print (xmlhttp.responseText)
End Function

REST Interacting with BigCommerce from VB EXCEL

NOTE: Further research (and thick skin, and lots of coffee) has lead me here where it suggests that the store cannot receive the uid and pwd in the URL, and I gather from the PHP examples here that the transmission needs to be encrypted. Is this my problem? If so I have not been able to find anything on how to work around this.
I would be happy if the open() command prompted the UID & PWD window to open and I can manually input the values. Does anyone have a suggestion. Thanks!
I am learning how to use the BigCommerce APIs and I am programming in VB / XL to be able to directly post / retrieve from all fields in the store's DB.
I have never coded on this before (although I get around VB OK) and I am stuck. I have the following code:
Const URL As String = "https://www.myurl.com/api/v2/brands.json"
Public Sub Test()
Dim xmlHttp As Object
Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP.6.0")
xmlHttp.Open "GET", URL, False, "myid", "mytoken"
xmlHttp.setRequestHeader "Content-Type", "application/json"
xmlHttp.send
Dim html As MSHTML.HTMLDocument
Set html = New MSHTML.HTMLDocument
html.body.innerHTML = xmlHttp.ResponseText
Range("A1").Value = html.body.innerHTML
End Sub
What I get back is "401" or more precisely:
[{"status":401,"message":"No credentials were supplied in the request."}]
The credentials are valid and working, as if I place the URL in my browser and submit it, the pop up for UID & PWD comes up, and once I place the values I am using in the code, the resulting BRANDS list appears in the browser.
Additionally, if I add a "rand num" to the URL to ensure the returned data is not cached as suggested here, then the request looks like this:
xmlHttp.Open "GET", URL & "&t=" & WorksheetFunction.RandBetween(1, 99), False, `"myid", "mytoken"`
and the response back from BC changes to:
406</STATUS><MESSAGE>The requested content type is not available.</MESSAGE></ERROR></ERRORS>
I have also tried this (where UID and PWD are the correct values):
xmlHttp.Open "GET", URL & "&user=UID" & "&password=PWD", False
and i get:
406</STATUS><MESSAGE>The requested content type is not available.</MESSAGE></ERROR></ERRORS>
Can anyone help me understand pls. I have followed examples at MSDN, but I am still stuck. The BC API site is poor in examples (none actually!) for VB.
Thank you
Sorted! BC required this:
Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP.6.0")
xmlHttp.Open "GET", URL_Cat, UID, PWD
xmlHttp.setRequestHeader "Content-Type", "application/json"
xmlHttp.setRequestHeader "Authorization", "Basic " & Base64Encode(UID & ":" & PWD)
xmlHttp.send
I got Base64Encode code from here. Hope this helps someone.

Not understanding why WinHTTP does NOT authenticate certain HTTPS resource

I'd be extremely grateful for any kind of help that may help me resolving the problem.
From Excel VBA code I need to download & parse CSV file from HTTPS site https://redmine.itransition.com/. I try to use WinHTTP to get the file. However, I can't understand why authentication does not work. Here is the piece of related code:
TargetURL = "https://redmine.itransition.com/projects/pmct/time_entries.csv"
Set HTTPReq = CreateObject("WinHttp.WinHttpRequest.5.1")
HTTPReq.Option(4) = 13056 ' WinHttpRequestOption_SslErrorIgnoreFlags 13056: ignore all err, 0: accept no err
HTTPReq.Open "GET", TargetURL, False
HTTPReq.SetCredentials "UN", "PW", 0
HTTPReq.send
returns the following response (only certain strings are listed):
Content-Type: text/html; charset=utf-8
Status: 406
X-Runtime: 5
However, if I send "Cookie" string from Firefox cookie after successful manual authentication using
HTTPReq.setRequestHeader "Cookie", SetCookieString
HTTPReq.send
I easily get the expected file. Of course I'm not happy with such solution, and want to perform true WinHTTP authentication. However, I can't understand what's wrong or what I miss in my code. Most likely I have to use .SetClientCertificate method, but this is unclear for me - which cert is required?
Or, being more general: which WinHTTP methods or functions I should use for debugging to find out which step is blocking / incorrect and prevents me from correct authentication? I spent 2 weeks seeking through MSDN and various resources, but still have no solution.
Thanks in advance for your suggestions!
The above #Alex K. response was exactly what I was looking for soooo long!
With the help of Firebug and MSDN I finished with 3 requests:
GET request to collect authenticity_token data from login page using RegEx
POST request to authenticate & collect required Cookie string from response
GET request to finally obtain my beloved CSV
The following piece of code which is working as expected:
Set RegX_AuthToken = CreateObject("VBScript.RegExp")
' Below Pattern w/o double-quotes encoded: (?:input name="authenticity_token" type="hidden" value=")(.*)(?:")
RegX_AuthToken.Pattern = "(?:input name=" & Chr(34) & "authenticity_token" & Chr(34) & " type=" & Chr(34) & "hidden" & Chr(34) & " value=" & Chr(34) & ")(.*)(?:" & Chr(34) & ")"
RegX_AuthToken.IgnoreCase = True
RegX_AuthToken.Global = True
TargetURL = "https://redmine.itransition.com/login"
Set HTTPReq = CreateObject("WinHttp.WinHttpRequest.5.1")
HTTPReq.Open "GET", TargetURL, False
HTTPReq.Send
Set Token_Match = RegX_AuthToken.Execute(HTTPReq.ResponseText)
AuthToken = Token_Match.Item(0).SubMatches.Item(0)
PostData = "authenticity_token=" & AuthToken & "&back_url=https://redmine.itransition.com/" & "&username=" & UN & "&password=" & PW & "&login=Login »"
HTTPReq.Open "POST", TargetURL, False
HTTPReq.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
HTTPReq.Send (PostData)
SetCookieString = HTTPReq.GetResponseHeader("Set-Cookie")
TargetURL = "https://redmine.itransition.com/projects/pmct/time_entries.csv"
HTTPReq.Open "GET", TargetURL, False
HTTPReq.setRequestHeader "Cookie", SetCookieString
HTTPReq.Send
The following URL was helpful in building POST request: http://tkang.blogspot.com/2010/09/sending-http-post-request-with-vba.html
You need to load that page with no credentials, grab what looks like
the volatile field authenticity_token from the generated form & post
that along with username & password to /login.
Alex K. - thanks again for the brilliant suggestion! (:
The logon at https://redmine.itransition.com/ is just an HTML form that posts a username & password to a script at /login.
This is not compatible with SetCredentials which is designed for server based authentication schemes like basic/digest/ntlm.
You need to load that page with no credentials, grab what looks like the volatile field authenticity_token from the generated form & post that along with username & password to /login.
If its a session based system it will response with the set-cookie header + data you need to use in subsequent request.
And one more thing (in addition to the above solution) one should be aware of in case of using POST requests similar to the above code: for some obscure reason I still got once in 4-5 times (or even more) the hated 406 response from the website, which means in my case auth is NOT complete. After hours of step-by-step debugging I happily caught the cause: auth token value may have + signs, and from analyzing an arrow of several dozens auth tokens / response codes I discovered that +-containing tokens exactly match 406 codes.
The solution became pretty obvious: safely URL-encode +es for the PostData. With the help of http://www.blooberry.com/indexdot/html/topics/urlencoding.htm I finally came up to the following:
PostData = "authenticity_token=" & Replace(AuthToken, "+", "%2B", vbTextCompare) & _
"&back_url=https://redmine.itransition.com/projects/" & Trim(RedmineProject) & _
"/time_entries" & "&username=" & UN & "&password=" & PW & "&login=Login »"
+es are replaced by %2Bs, and this was that - no more 406s!)
The other special chars do not matter in my case, but the lesson was learned. Hope this will save several hours of life for someone else!

Google OAuth2 Error 400 when exchanging for an Access Token

I am receiving a "400 Bad Request" error when using the following VBA code to exchange a valid Authorization token for an access token in the Google API. Can anyone shed light as to why, I have been struggling with this one for over a week.
Dim http As MSXML2.XMLHTTP
Dim sUrl As String
Dim sUrlHeader As String
Dim svarbody As String
Set http = New MSXML2.XMLHTTP
sUrl = "https://accounts.google.com/o/oauth2/token? HTTP/1.1"
http.Open "POST", sUrl
http.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
svarbody = "code=4%2FXAjmXiUlBXUAUGCnMvNKsxdyuJEJ.8kfzzrqo3wwTuJJVnL49Cc9gML_lbgI&" & _
"client_id=50487549202#-q27v28nvhmjhc0uobq35tjn09lhrh47r.apps.googleusercontent.com&" & _
"redirect_uri=http%3A%2F%2Flocalhost&" & _
"scope=https%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds&" & _
"client_secret=<secret delete for this post>&" & _
"grant_type=authorization_code"
http.send svarbody
Me.Text3 = http.status & vbCrLf & http.statusText & vbCrLf & http.responseText
this is a very very common problem. You may try to solve it doing many approaches and tests... but...
The really one you should try in the first place is:
UPDATE YOUR SERVER/COMPUTER WINDOWS CLOCK, USE time.nist.gov (or brothers). Double-click in your Windows Hour-Date (on the bottom of your screen and change Internet Time in this way)
SntsDev
In my experience using Oauth 2.0 in VBA this error happens when you use the code more than once for requesting a token. The usage sequence I use based on the documentation and experience is:
access GET https://accounts.google.com/o/oauth2/auth once for geting the code
access POST https://accounts.google.com/o/oauth2/token once for geting the token
after that use the token as long as it is valid
It is possible that you need set up billing info in your google account.