Office 365 - multitenant application constantly gives unauthorized - azure

For some reason the following code gives an unauthorized exception, can anyone tell me what i'm doing wrong?
In the screenshot below you can find the contents of "theToken", which has been received through Office365 and saved in a database ( 1 minute before excecution)
Async Function CalendarDemo() As System.Threading.Tasks.Task(Of ActionResult)
Dim tokenData = UserTokenService.GetToken(HttpContext.User.Identity.Name)
Dim theToken = tokenData.ReadItems(0)
Dim myCalendars As List(Of ViewModels.Office365.MyCalendar) = New List(Of ViewModels.Office365.MyCalendar)
Try
Dim accessToken As Func(Of String) = Function() As String
Return theToken.AccessToken
End Function
Dim discClient As New DiscoveryClient(New Uri("https://api.office.com/discovery/v1.0/me/"), accessToken)
Dim dcr = Await discClient.DiscoverCapabilityAsync("Calendar") 'This is where the code breaks, the code worked in the past. Till i split up the authorization and the fetching of the Calendar
ViewBag.ResourceId = dcr.ServiceResourceId
Dim exClient As New OutlookServicesClient(dcr.ServiceEndpointUri, Function()
Return Helpers.Office365Helpers.ReturnStringAsAsync(theToken.AccessToken)
End Function)
Dim calendarResults = Await exClient.[Me].Events.ExecuteAsync()
Dim calenderResults = Await exClient.Me.Calendar.Events.ExecuteAsync()
Do
Dim calendars = calendarResults.CurrentPage
For Each calendar In calendars
Dim newCalendar = New ViewModels.Office365.MyCalendar(calendar)
myCalendars.Add(newCalendar)
Next
calendarResults = Await calendarResults.GetNextPageAsync()
Loop While calendarResults IsNot Nothing
Catch exception As AdalException
' handle token acquisition failure
If exception.ErrorCode = AdalError.FailedToAcquireTokenSilently Then
'authContext.TokenCache.Clear()
'Redirect naar login of refresh token
ViewBag.ErrorMessage = "AuthorizationRequired"
End If
Catch exception As DiscoveryFailedException
End Try
Return View(myCalendars)
End Function
The exception is : "Exception of type ' Microsoft.Office365.Discovery.DiscoveryFailedException' was thrown." - and the ErrorCode is 'Unauthorized'
According to the answer below, i changed the following code:
Dim exClient As New OutlookServicesClient(New Uri("https://outlook.office365.com/api/v1.0/"), Function()
Return Helpers.Office365Helpers.ReturnStringAsAsync(theToken.AccessToken)
End Function)
Dim calendarResults = Await exClient.[Me].Events.Take(10).ExecuteAsync()
Where my helper is :
Public Module Office365Helpers
Public Async Function ReturnStringAsAsync(ToReturnString As String) As System.Threading.Tasks.Task(Of String)
Return Await System.Threading.Tasks.Task.Delay(1).ContinueWith(Function(dl) ToReturnString)
End Function
End Module
But it doesn't return any value when it's loading.. It's like the api call is in a continuous loop.
Identitical question but shorter:
I have a Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache, how can i query the Microsoft Calendar API using the DLL's and vb.net to get the data.

In the ODataException, in the response, you can view the header values.
There is an error there that explains my fault, it said something in the lines of : "Inconsistent resource". Which could be true. I found out that i didn't use https://outlook.office365.com/api/v1.0/ when requesting the token. But used it when requesting data.
So i received invalid tokens for what i tried to do.

Your resource should be https://api.office.com/discovery/ for calling the Discovery service. However, since you're calling the Calendar API, you could just skip discovery. The endpoint is fixed at https://outlook.office365.com/api/v1.0/.
If you get stuck, try the .NET tutorial on https://dev.outlook.com.

Related

Is it possible to group function calls in VBA?

I have a function that looks something like:
Public Function GetData(DataType As String) As String
Dim Client As New WebClient
Client.BaseUrl = "http://url/to/get/data"
Dim Response As New WebResponse
Set Response = Client.GetJson(DataType)
GetInstruments = Response.Data("data")
End Function
It's a simple HTTP GET that returns a value based on an argument.
My problem is that I'm trying to execute this function for many different cells at once in Excel (i.e. =GetData(A$1)) that leads to hundreds of HTTP calls which is very slow.
Is there a way that in VBA that I am able to intercept function calls so I can then make a single and quick HTTP call and then return all the data at once?
You can use global variables in a module to cache and reuse alread downloaded data.
First easy to digest example using simple Collection:
Private someCollection As Collection
Public Function GetData() As Integer
' Make sure that data is already read/created
If someCollection Is Nothing Then
' If we didn't get any data, then get it
Set someCollection = New Collection
someCollection.Add (1)
End If
' Get data :)
GetData = someCollection(1)
End Function
Now, applying this logic to your problem you could do:
Private Response As WebResponse
Public Function GetData(DataType As String) As String
' You can alter check to see if URL has changed.
' In order to do that just store URL in some global variable
If Response Is Nothing Then
Dim Client As New WebClient
Client.BaseUrl = "http://url/to/get/data"
Set Response = Client.GetJson(DataType)
End If
GetInstruments = Response.Data("data")
End Function
Of course, all this code goes into module.

Async VBA Function

I have a VBA function that is supposed to get some information from the user's cell, make a POST request with that info, then print the response in the output cell.
It's required that the user be able to make about 2000 requests at a time, so I thought to make the requests async to help improve performance.
As it stands right now, I have a function ConnectToAPI that makes the asynchronous request, then passes the response off to a callback function. The problem I'm having is that the data lives in the callback function, but I need it in the query function in order to return it.
Function Query(ID, quote, field)
Application.Volatile
Query = ConnectToAPI(ID)
Some logic with parsed data from callback
End Function
Function ConnectToAPI(ID)
Dim Request As New WebRequest
Dim Client As New WebClient
Client.BaseUrl = "http://www.endpoint.com"
Dim Wrapper As New WebAsyncWrapper
Dim Wrapper.Client = Client
Dim Body As New Dictionary
Body.Add "ID", ID
Set Request.Body = Body
Request.Method = HttpPost
ConnectToAPI = Wrapper.ExecuteAsync Request, "CallbackFunction"
End Function
Function CallbackFunction
Callback = Parsed Data
End function
So ultimately in the query function, I want to write
Query = (Parsed Data From the Callback)
How can I pass the data from the callback back up to query?
It is important that the cell have the Query function in it. The data updates frequently, so we want clients to be able to calculate the workbook to get the newest data.
With what I currently have, my thought process is that the callback will pass the data back to ConnectToAPI, then that will be passed up to Query. However, my function returns 0 and I think this might be that the parsed data is not available once the function tries to return.
For reference, I am using the VBA-Web library
https://github.com/VBA-tools/VBA-Web
VBA-Web/src/WebAsyncWrapper.cls
WebAsyncWrapper.ExecuteAsync has an optional parameter: CallbackArgs. Use this parameter to pass back you an ID or a cell address.
ExecuteAsync has an example callback function that receives an Array of arguments.
Here is how you can get the information back to the function for processing.
Sub ConnectToAPI(ID As Variant, quote As Variant, field As Variant, CellAddress As Variant)
Dim Request As New WebRequest
Dim Client As New WebClient
Client.BaseUrl = "http://www.endpoint.com"
Dim Wrapper As New WebAsyncWrapper
Dim Body As New Dictionary
Body.Add "ID", ID
Set Request.Body = Body
Request.Method = HttpPost
Set Wrapper.Client = Client
Wrapper.ExecuteAsync Request, "Callback", Array(ID, CellAddress)
End Sub
Public Function Callback(Response As WebResponse, Args As Variant)
Dim ID As Variant, CellAddress As Variant
ID = Args(0)
CellAddress = Args(1)
With Worksheets("Web Requests")
.Range(CellAddress).Value = Response
.Range(CellAddress).Offset(0, 1).Value = ID
End With
End Function
MSDN - Application.Volatile Method (Excel)
Marks a user-defined function as volatile. A volatile function must be recalculated whenever calculation occurs in any cells on the worksheet. A nonvolatile function is recalculated only when the input variables change. This method has no effect if it's not inside a user-defined function used to calculate a worksheet cell.
I would not recommend trying to have a UDF that can be used as a worksheet function to return the web-requests. Application.Volatile will cause all 2000 queries to refresh every time a value is changed. When the first query updates all the other queries will refresh. This will cause an infinite loop and crash the application.
Function Query(ID, quote, field)
Application.Volatile
Query = ConnectToAPI(ID)
Some logic with parsed data from callback
End Function
Using the Worksheet_Change event would give the users the ability to update the information without the problems associated with Application.Volatile.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Columns("A")) Is Nothing Then
If Target.Count = 1 Then
Debug.Print Target.Value, Target.Address
End If
End If
End Sub
I ended up populating a dictionary with the response values from the API call, then recursively calling the query function in the callback.
Query checks if the response value is in the dictionary, if it is, then it returns it. If not, it connects to the api, the callback puts the value in the dictionary, and also calls the query function again.

Dim As Dictionary. Compile Error: User-defined type not defined

Can you please assist with this problem?
Whenever I run this macro, it stops at:
Dim authResult As Dictionary
With an error message of:
Compile error: User-defined type not defined.
I have not used the dictionary type before and I am trying to re-use this code from a sample macro.
The aim of this script is to use excel to make rest calls to a website so that I can download historic data. I am currently stuck at the login section.
Sub Login()
Dim userName As String
Dim password As String
Dim apiKey As String
userName = "username"
password = "password"
apiKey = "key123"
'activityTextbox.Text = ""
'clearData
Dim authResult As Dictionary
Set authResult = restClient.authenticateAccount(userName, password, apiKey)
If Not authResult Is Nothing Then
'appendActivity "Connected"
' Configure Excel to pull streaming updates as often as possible
Application.RTD.ThrottleInterval = 0
' Uncomment for real-time prices - this is very CPU intensive
' Buffer interval defaults to 500ms
'Application.WorksheetFunction.RTD "IG.api.excel.RTD.IGApiRTDServer", "", "bufferInterval", "0"
' Set manual refresh to true from very remote locations
' Application.WorksheetFunction.RTD "IG.api.excel.RTD.IGApiRTDServer", "", "manualRefresh", "true"
' This will require manually calling refresh to update lighstreamer subscriptions, i.e.
' Application.WorksheetFunction.RTD "IG.api.excel.RTD.IGApiRTDServer", "", "refresh"
Dim maxPriceRequestsPerSecond As Double
maxPriceRequestsPerSecond = 0 ' all available updates
If restClient.streamingAuthentication(maxPriceRequestsPerSecond) Then
m_loggedIn = True
'populateWatchlists
'populateAccounts
'manualStreamingRefresh
'Else
' appendActivity "Lightstreamer connection failure"
End If
Else
MsgBox "Authentication failed"
End If
End Sub
Thanks in advance.
Cheers,
Joe
Add a reference to Microsoft Scripting Runtime as #YowE3k said:
In the VBA Editor:
Tools -> References
Find Microsoft Scripting Runtime
Check it
Click okay

Multitasking TcpListener.AcceptSocket

In the code below, StartSocketListener has been started as a Thread. Sometimes, the processing of a message takes long enough that a couple of messages stack up (or at least that appears to be the case). I thought I'd take the socket and start another thread with it. I thought that passing the _HostConnection into the task would make concurrent instances of MessageFunction thread safe. Not so, it appears. I regularly get two kinds of errors: one is at _HostReader.ReadString, where I either read past the end of the stream or read only a portion of the stream (as if something else has consumed part of it). The other is at _HostWriter.Write, where I get "an established connection was aborted." Is it obvious where I've failed to get the thread safety that I was expecting? [I am a C# developer, so the VB lambda may look awkward :) ]
Private Sub StartSocketListener()
Dim _Listener As TcpListener = Nothing
Dim _HostConnection As Socket
Try
_Listener = New TcpListener(IPAddress.Parse(GetIPAddress), _TCPListenerPort)
_Listener.Start()
While _KeepListening
_HostConnection = _Listener.AcceptSocket
Dim MessageFunction = Sub(socket As Socket)
Dim _ClientIP As String = socket.RemoteEndPoint.ToString
Dim _HostSocketStream As NetworkStream = New NetworkStream(socket)
Dim _HostWriter As BinaryWriter = New BinaryWriter(_HostSocketStream)
Dim _HostReader As BinaryReader = New BinaryReader(_HostSocketStream)
Try
Dim _MsgXML As String = _HostReader.ReadString
If _MsgXML.Trim <> "" Then
If _KeepListening Then
Dim _RetXML As String = ProcessMessage(_MsgXML, _ClientIP)
Try
_HostWriter.Write(_RetXML)
Catch ex As Exception
...
End Try
End If
End If
Catch ex As Exception
...
End Try
_HostReader.Close()
_HostWriter.Close()
_HostSocketStream.Close()
socket.Close()
End Sub
Task.Factory.StartNew(Sub() MessageFunction(_HostConnection))
End While
_Listener.Stop()
Catch ex As Exception
....
End Try
End Sub

SAP RFC call returns "Error 0" in RETURN parameter from vb

Hi everybody and thanks in advance.
I'm trying to call a SAP BAPI using RFC from vb but I'm having some problem to get the result of the call.
The BAPI "BAPI_GL_ACC_EXISTENCECHECK" (from General Ledger Account module) has two parameters,
COMPANYCODE and GLACCT, and a RETURN parameter.
I wrote this piece of code to make the call and I had no problem to establish the SAP Connection (I use the SAP Logon Control OLE/COM object to do the job), and I tried to make the RFC call.
Also in this case I make the call without problems (it seems, not sure about it ...), because the RFC call returns true and no exception.
However, looking through the objReturn object/parameter, it has a value "Error 0" in it.
I was expecting a complex structure like the BAPIRETURN object in SAP or something similar if the account doesn't exist.
Tried to search with Google and SAP forums but I haven't found a real solution to my problem, so here I am to ask you all if you have some idea to solve this problem (maybe I'm only making a wrong call!!! I'm quite a newbie on SAP integration ...).
BTW, Final Notes: after a lot of RFC_NO_AUTHORIZATION on the SAP side, they gave me a SAP_ALL / S_RFC authorization (sort of, not a SAP expert) and the error RFC_NO_AUTHORIZATION disappeared, but not the Error 0 return
Dim sapConn As Object
Dim objRfcFunc As Object
Dim SAPMandante As String
Dim SAPUtente As String
Dim SAPPassword As String
Dim SAPLingua As String
Dim SAPApplicationServer As String
Dim SAPNumeroSistema As Variant
Dim SAPIDSistema As String
Dim SAPRouter As String
Dim FlagInsertLogin As Integer
Dim FlagLogin As Variant
On Error GoTo ErrorHandler
Set sapConn = CreateObject("SAP.Functions") 'Create ActiveX object
'Silent Logon
SAPMandante = "xxx"
SAPUtente = "yyyy"
SAPPassword = "zzzzzz"
SAPLingua = "IT"
SAPApplicationServer = "www.xxx.com"
SAPNumeroSistema = x
SAPIDSistema = "zzz"
SAPRouter = ""
FlagLogin = SilentLogin(sapConn, SAPMandante, SAPUtente, SAPPassword, SAPLingua, SAPApplicationServer, SAPNumeroSistema, SAPIDSistema, SAPRouter) 'IT WORKS, NO PROBLEM HERE
If FlagLogin = False Then
'Explicit Logon
If sapConn.Connection.logon(0, False) <> True Then
MsgBox "Cannot Log on to SAP", 16, "Query Interrupted"
sapConn.Connection.logoff
Set sapConn = Nothing
InsertCash = False
Exit Sub
End If
End If
'BAPI RFC Call
Set objRfcFunc = sapConn.Add("BAPI_GL_ACC_EXISTENCECHECK")
objRfcFunc.exports("COMPANYCODE") = "C100"
objRfcFunc.exports("GLACCT") = "0000000001" 'Inexistent
Rem *** BAPI CALL ***
If objRfcFunc.Call = False Then
ErrorMsg = objRfcFunc.Exception 'Message collection
MsgBox ErrorMsg, 16, "Errore"
sapConn.Connection.logoff
Exit Sub
else
Dim objReturn As Object
Set objReturn = objRfcFunc.imports("RETURN")
End If
You need to put
Dim objReturn As Object
Set objReturn = objRfcFunc.imports("RETURN")
BEFORE objRfcFunc.Call
i.e. you must state what you're importing from the function before you call it. I usually put it alongside the .exports() lines.
The problem was solved, please take a look at this thread ...
http://sap.ittoolbox.com/groups/technical-functional/sap-dev/sap-rfc-call-returns-error-in-return-parameter-from-vb-before-the-rfc-call-4894968#M4902486

Resources