how do you request a new downloadUrl from graph-onedrive? - microsoft-graph-api

In my C# code with the Graph SDK, I am testing "download large file" with a method generously provided here: Download large files from OneDrive using Microsoft Graph SDK
However, even though I explicitly request the DriveItem from the service just prior to calling this method (which then uses the downloadUrl from the AdditionalData in the DriveItem), I am getting a response indicating JWT Token Expired.
I assume this token is embedded in the pre-authenticated downloadUrl. Further, I theorize that this token is either single-use or it expires relatively quickly...both reasons for me to explicitly request the DriveItem just before attempting to utilize that URL.
How do I ensure that the service is sending me back a "fresh" downloadUrl? Are there some no-cache headers or something I should explicitly include in my DriveItem request?
As mentioned, even though I have the DriveItem object local, I am explicitly re-fetching it from the service to ensure I have the latest properties, and a new downloadUrl. (But this is not working.) Using the Request object without any extra Headers (or other odata parameters) is apparently not enough to trigger the generation of a new URL.
This does not happen every test. I believe it happens when re-running a test with the same DriveItem in a short time-window. Again, I'm not sure if the JWT token is single-use or time-expired, but if it's expired in any case, I think the service should automatically generate a new one. If that's not practical, just understanding how to explicitly ask for a new one is certainly effective too!

Continuing to debug and try different things, I believe I have found the answer to my question...
Sending a known-false ETag in the request for the DriveItem seems to force the service to send me a new copy. Example code as follows:
//we need a "recent" copy of this object, because the downloadURL is only good for a limited time...
IList<HeaderOption> opts = new List<HeaderOption>();
opts.Add(new HeaderOption("Cache-Control", "no-cache, no-store"));
opts.Add(new HeaderOption("if-none-match", "xyz")); //never match
DriveItem item = await client.Me.Drive.Items[Id].Request(opts).GetAsync();
if (item == null)
{
log.Warn("Could not fetch existing driveItem? " + Id);
return null;
}
object downloadUrl = null;
item.AdditionalData?.TryGetValue(#"#microsoft.graph.downloadUrl", out downloadUrl);
I am still testing this, but initial tests show it to work. I will update this answer when my testing is reasonably conclusive.
Still "bonus points" for anyone who can identify the specific expiration of these URLs (time, number of uses, etc.). Then we can actually check locally whether our object is "stale" and only re-fetch when necessary.

Related

Workbox: how to remove a request from the cache

I'm using staleWhileRevalidate Workbox v3 strategy for all of my API Get calls. However, sometimes I want to remove a particular request from the cache at runtime.
Example: I have a yes/no state that can be updated by the client. Initially, the UI displays the "no" state (taken from the cache) while executing a request that also returns a "no" result. The result of the request is cached by Workbox.
Now, the user switches it to "yes", the system updates the server and sends another query after some time to get the latest value. But the cache still stores the "no" value which is displayed in the UI while the new request returns "yes" which is now stored in the cache.
What I need here is to remove the "no" result from the cache right when I send the update request from the server. How do I do that? How can I access the Workbox's cache from the page code?
Workbox uses the browser's Cache Storage API for its caching. You can access the same caches directly, either via the window context or inside of a service worker, from your own code.
It's easier to do this if you configure your Workbox strategy to use a specific cache name, rather than the default cache name, since you'd otherwise need to reverse-engineer some of the Workbox logic to create the default cache name inside of the window context. Assuming you know the cache name and the URL to delete, you can call the following function from within the window context to use the Cache Storage API to delete the entry:
async function deleteFromCache(cacheName, urlOrPath) {
const cache = await caches.open(cacheName);
await cache.delete(urlOrPath);
}
// Later:
await deleteFromCache('runtime-cache', '/path/to/entry');

Take all data from Zapier Storage

I need to get all the data from StoreClient (Javascript). The format of the data on the below picture, they are in Zapier Storage.
const store = StoreClient('mysecret');
const value = await store.getMany('mykey'); // or store.get('mykey')
return {result: value}
This code works well. But I need to take and process all stored keys in a loop along with all their child value. I did not find a way :(
I tried store.list_pop(key), but the lists have a different storage format. And the data is not retrieved.
I would recommend using Zapier's storage API which will allow you to retrieve all stored data through a GET request to https://store.zapier.com/api/records. I often have to do the same thing and this works for me.
Have a look at their documentation here. I typically code in Python using the requests library. But I'm sure you could achieve similar results using an ajax or fetch request in Javascript.
EDIT
If I am understanding your question correctly you are trying to 'GET' all of your data stored in Zapier's storage client. As per their API documentation:
Zapier stores your data as a dictionary object which can hold key value pairs. Those values can also be nested dictionaries or lists. Regardless of how you store the data (simple key value pairs, nested lists, nested dictionaries, or some combination of the preceding) a 'GET' request will return the entire object. As stated before I typically use Python's request library to handle HTTP requests but I was able to achieve the same result using a Javascript fetch request. I setup a dummy storage account at https://store.zapier.com/api/records?secret=dog to test and illustrate how this works. See my code below.
var url = "https://store.zapier.com/api/records?secret=dog";
const res = await fetch(url);
const body = await res.json()
return {JSON : body}
Unfortunately, due to my lack of familiarity with Javascript I had to bake the secret into the url, which I don't think is ideal, but for the purposes of this example it does the job. See my output below.
As you can see the 'GET' request returned all data stored in my Zapier storage account. I simply returned the data I retrieved from the 'GET' request but you could of course loop through the results and execute logic as needed. This does not modify any of the data stored, what I often do is pull in all of my stored date with a 'GET' request, modify it, delete the old storage, and 'POST' my modified storage information. This allows me to limit my requests to two calls rather than modifying each individual value.

How to handle tile request errors from ol.source.WMTS - Openlayers 3

By using the obvious event handler::
layer.getSource().on('tileloaderror', function (ev) {
console.log(ev);
});
I get an output rather useless, since it doesn't seem to contain any information regarding HTTP Response body/headers etc.
Is there a way to access those kind of information after a failed tile request?
In my implementation, access token must be updated, thus response code is necessary.
If not possible after all, maybe there is a way to create a custom tile load request (maybe implement my own "tileloadfunction" or something).

ADALiOS - how to refresh accessToken silently?

I'm using ADALiOS v3.0.0-pre.2 to connect to Azure AD B2C and authorize a given user. I successfully obtain an accessToken for the user, who gets prompted with UI to login in the process. I use the acquireTokenWithScopes method on ADAuthenticationContext instance to do so.
Somewhere down the line, I want to make sure that the accessToken I obtained earlier is still valid and hasn't been revoked. So, I use acquireTokenSilentWithScopes to check. However, I get an immediate error back saying:
Error raised: 10. Additional Information: Domain: ADAuthenticationErrorDomain Details: The user credentials are need to obtain access token. Please call the non-silent acquireTokenWithResource methods.
What's the right usage of this API such that the token gets silently refreshed or throws an error only when it has been revoked on the server side?
I've managed to beat acquireTokenSilentWithScopes into submission by making the following changes to ADALiOS v3.0.0-pre.2.
Change #1:
ADUserIdentifier has the following class method:
+(BOOL) identifier:(ADUserIdentifier*)identifier matchesInfo:(ADProfileInfo*)info
In it, there are the following lines of code:
NSString* matchString = [identifier userIdMatchString:info];
if (!matchString || [matchString isEqualToString:identifier.userId])
{
return YES;
}
For one reason or another, matchString can sometimes come back as NSNull and calling isEqualToString: method on it will throw. I changed it thusly:
id matchString = [identifier userIdMatchString:info];
if (!matchString || ![matchString isKindOfClass:[NSString class]] || [matchString isEqualToString:identifier.userId])
{
return YES;
}
This seems like a legit bug in the framework that's worth fixing.
Change #2:
When a token is received from AD, ADALiOS tries to store that value in the cache. At some point, it calls ADTokenCacheStoreItem's userCacheKey property, which is defined as follows:
-(NSString*)userCacheKey
{
switch (_identifierType)
{
case OptionalDisplayableId:
case RequiredDisplayableId:
return _profileInfo.username;
case UniqueId:
return _profileInfo.subject;
}
}
In my case, I use RequiredDisplayableId to identify users. In the switch statement above, that translates to _profileInfo.username, which, in turn, returns the preferred_username value from the user profile dictionary. For me that value is not set. So, userCacheKey returns NSNull and the caching mechanism fails.
The values that are set in the user profile dictionary are name and tid. This could be a server misconfiguration, but I worked around the issue by changing the return value of this method to _profileInfo.friendlyName (which maps to name in the user profile dictionary).
Change #3:
The ADKeychainTokenCacheStore, which I use as the concrete ADTokenCacheStoring cache of choice, exposes a sharedGroup property that allows multiple applications to share common keychain secrets. By default, sharedGroup is set to com.microsoft.adalcache. However, since the class is currently private, there is no way to override this value. Also, having that value set requires the iOS app to declare the shared group name in its entitlements. Without these entitlements properly configured, setting values into the keychain fails. So, to work around this issue, I manually set the default sharedGroup value to nil in the ADKeychainTokenCacheStore class itself. I suspect eventually this class will be exposed by the framework as public, but currently that's not the case so I had to hack into it.
Change #4
When I request an auth token from the AD server via the ADALiOS framework, I do so using a policy and a set of scopes. The framework code uses this policy/scope pair to create a lookup key and see if any tokens for that key have already been cached. If none are found, the code contacts the server as expected. Once the server returns an auth token, the framework attempts to cache the value. It constructs a brand new policy/scope key object. However, this time, it uses the policy and scope values that are returned by the server, not the ones I passed in. And, for some reason, the server returns those values to nil. As a result, the new policy/scope key that gets constructed for storage is valid but different from the one I used to look up the cached token initially. So, while the caching operation succeeds, next time I try to look up the auth token using my valid policy/scope pair, the lookup fails.
This may, once again, be a server misconfiguration issue.
Regardless, to fix the problem, I now reset the policy and scope values in the response from the server to the original values I used to generate the server request in the first place. This happens in the following method in ADAuthenticationContext(TokenCaching):
- (void)updateCacheToResult:(ADAuthenticationResult*)result
cacheInstance:(id<ADTokenCacheStoring>)tokenCacheStoreInstance
cacheItem:(ADTokenCacheStoreItem*)cacheItem
withRefreshToken:(NSString*)refreshToken
After all these changes, getting the AD auth token and refreshing it silently seems to work as expected. I'm a little worried about how much I needed to hack into the codebase to make it work. It would be helpful if some MS folks could direct me as to whether these changes were warranted or whether there is a more straight-forward solution.
UPDATE:
It turns out that you don't need to hack into ADKeychainTokenCacheStore directly (change #3 above). The ADAutheticationSettings class exposes a method for you to do so thusly:
[[ADAuthenticationSettings sharedInstance] setSharedCacheKeychainGroup:nil];
I'm Brandon Werner from the Azure Active Directory team. I answered this question here: https://stackoverflow.com/a/44170226/1232116 for the specific question asked.

Multiple objects waiting for the same API response

I have an API code, which loads a data necessary for my application.
It's as simple as:
- (void) getDataForKey:(NSString*) key onSuccess:(id (^)())completionBlock
I cache data returned from server, so next calls of that functions should not do network request, until there is some data missing for given key, then I need to load it again from server side.
Everything was okey as long as I had one request per screen, but right now I have a case where I need to do that for every cell on one screen.
Problem is my caching doesn't work because before the response comes in from the first one, 5-6 more are created at the same time.
What could be a solution here to not create multiple network request and make other calls waiting for the first one ?
You can try to make a RequestManager class. Use dictionary to cache the requesting request.
If the next request is the same type as first one, don't make a new request but return the first one. If you choose this solution, you need to manager a completionBlock list then you will be able to send result to all requesters.
If the next request is the same type as first one, waiting in another thread until the first one done. Then make a new request, you API will read cache automatically. Your must make sure your codes are thread-safe.
Or you can use operation queues to do this. Some documents:
Apple: Operation Queues
Soheil Azarpour: How To Use NSOperations and NSOperationQueues
May be there will be so many time consuming solutions for this. I have a trick. Create a BOOL in AppDelegate, its default is FALSE. When you receive first response, then set it TRUE. So when you go to other screen and before making request just check value of your BOOL variable in if condition. If its TRUE means response received so go for it otherwise in else don't do anything.

Resources