Microsoft Graph: Uploading files to shared library instead of user's library? - onedrive

In the documentation about Upload, these request options are listed:
PUT /me/drive/items/{parent-id}:/{filename}:/content
PUT /me/drive/root:/{parent-path}/{filename}:/content
PUT /me/drive/items/{parent-id}/children/{filename}/content
PUT /groups/<id>/drive/items/<parent-id>/children/<filename>/content
In the documentation about Resumable Uploads, Create an upload session show these options:
POST /me/drive/root:/{path-to-item}:/createUploadSession
POST /me/drive/items/{parent-item-id}:/{filename}:/createUploadSession
What if I have to upload to other (not 'me') drive? For example, the default shared library (https://mycompany.sharepoint.com/Shared%20Documents), which for any other porpoise besides uploading, the documentation says can be accessed like this:
/drives/{drive-id}/items/{item-id}
So, my question is: Is it possible to upload to "/Shared Documents"? If so, which is the right syntax for the PUT (small file) or POST (upload session)?
Perhaps something similar to this? (I just made this up, and it doesn't work)
PUT /drives/{drive-id}/items/{parent-id}/{filename}:/content
or (in case of an upload session):
POST /drives/{drive-id}/items/{parent-item-id}:/{filename}:/createUploadSession
For example, in the Graph Explorer, the response for something like this:
/v1.0/drives/THEDRIVEID/items/THEFOLDERID:/whatever.jpg:/createUploadSession
was:
{
"error": {
"code": "invalidRequest",
"message": "A valid path must be provided.",
"innerError": {
"request-id": "THERETURNEDID",
"date": "THERETURNEDDATE"
}
}
}
EDIT: Because the company's root can also be accessed as /drive/root/, I also tried (with no luck, in both 1.0 and beta):
/drive/root:/whatever.jpg:/createUploadSession
/drive/root/whatever.jpg:/createUploadSession

This is possible, but you need the right app permissions to be able to read/create files outside of the user's OneDrive. Instead of requesting Files.ReadWrite, you would need to request Sites.ReadWrite.All and then use /v1.0/drive/root:/file.bin:/createUploadSession.
You can also use the SharePoint Sites API in Microsoft Graph to access team site document libraries (other than the root site). However, this API is still in the beta version and should not be used in production apps.

Related

Whatsapp Cloud API Update Profile Picture

I'm trying to upload an image as profile picture using WhatsApp Cloud API *.
After creating an application using WhatsApp Cloud API I'm not allowed to access neither using the regular application nor using Business Application. It says something like "try again in one hour". So I have to implement everything using the API.
After reading the docs and importing Postman Endpoints I found the one called Business Profiles > Update Business Profile
https://graph.facebook.com/{{Version}}/{{Phone-Number-ID}}/whatsapp_business_profile
It has a field "profile_picture_url"and I have tried POSTing media https://graph.facebook.com/{{Version}}/{{Phone-Number-ID}}/media and then with the given ID y used https://graph.facebook.com/{{Version}}/{{Media-ID}} to get the URL but it didn't work. The rest of the information is updated successfully
{
"messaging_product": "whatsapp",
"address": "",
"description": "Simple Bot",
"email": "...#gmail.com",
"websites": [
"https://..."
],
"profile_picture_url": "https://lookaside.fbsbx.com/..."
}
However if I try to send someone using the ID and the endpoint https://graph.facebook.com/{{Version}}/{{Phone-Number-ID}}/messages it works fine.
And if I use Download Media Content with the URL in Postman it works fine too.
I don't know if I have misunderstood something or if it can't be done using the API.
It is mentioned in the Cloud API Documentation:
profile_picture_url (Optional): URL of the profile picture generated from a call to the Resumable Upload API.
But, i got it working using profile_picture_handle instead of profile_picture_url. So how do we get the profile_picture_handle?
Prerequisite:
Graph API token here. Or use your WhatsApp Cloud API token.
App ID, go Apps > Your App > Settings (sidebar menu) > Basic
Update Photo Profile:
Call POST https://graph.facebook.com/v14.0/{{appId}}/uploads?access_token={{token}}&file_length={{fileSizeInByte}}&file_type=image/jpeg
Save the session id you got, upload:XXXXXX?sig=XXXXXX.
Call POST https://graph.facebook.com/v14.0/{{sessionId}}, with the headers: Authorization=OAuth {{token}}, file_offset=0, Host=graph.facebook.com, Connection=close, Content-Type=multipart/form-data, and include your image file in the request body with type binary. If you using Postman, see image below (This is what I missed for hours).
Save the handle result you got, 4::XXX==:XXXXXX.
Finally, call POST https://graph.facebook.com/{{Version}}/{{Phone-Number-ID}}/whatsapp_business_profile, with json request body: {"messaging_product": "whatsapp", "profile_picture_handle": "4::XXX==:XXXXXX"}
That's it, You can check the profile picture.
The last step you have to add your taken by selecting "Bearer" or else it will give you error. I had hard time on the last ones, do all the steps and then go to the following link and it should help.
https://web.postman.co/workspace/My-Workspace~a4ddb3b8-02a3-4132-8384-66e63e149b7b/request/22958165-506a0542-c845-41ac-b3fb-b8209fd6f53b

PUT request to google cloud storage signed URL generated by Shopify throws error MalformedSecurityHeader

I'm trying to upload a .glb file to a product in a Shopify store through Shopify GraphQL Admin API. For that, it first returns a google cloud storage signed URL, to where I should upload my file through an HTTP PUT request. After uploading, I should attach the same URL to the product with another API call.
This question is about that file uploading to the cloud storage signed URL. I include all these details to make this question easy to be getting answered. So, please read till the end.
What data Shopify provides me with is mentioned below.
{
"data": {
"stagedUploadsCreate": {
"stagedTargets": [
{
"parameters": [
{
"name": "GoogleAccessId",
"value": "threed-model-service--6bgx7cbe#shopify-applications.iam.gserviceaccount.com"
},
{
"name": "key",
"value": "models/a6436c066064bac3/windmill.glb"
},
{
"name": "policy",
"value": "eyJleHBpcmF0aW9uIjoiMjAyMC0wNy0yMVQwOToxNjoxMFoiLCJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJ0aHJlZWQtbW9kZWxzLXByb2R1Y3Rpb24iXSxbImVxIiwiJGtleSIsIm1vZGVscy9hNjQzNmMwNjYwNjRiYWMzL3dpbmRtaWxsLmdsYiJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDE5NzE3MiwxOTcxNzJdXX0="
},
{
"name": "signature",
"value": "vz+OdcEmD9Kbv2FbXdxWNUk59XO2GmXzhvtDswXbDQNcyZpUufI85z5x2PFGv/XZ+tSBsl/S393pmy0Bu9xG7oVgOZcMIWEbOIm9kXgQunbjKQY3Ff3BBpMocB0xazzlYmckZozdJ8ZZkyox/c/gEe1QaxqW4+419iufuFHy4Bp3LL/aUr+ATNChwn9Dn8+XnHMOckZxDlbiggcF3dx+yBuTFia8FneaVSiU0M5DIWmHqHb2YDCV0KtEP6jfTj/PQVUjS8pn8EGhrRaMx7Q2A5G8Pycgc9H35hqJnnUKCTa3AYeyI45RbhddYnIWw9YrAADXuQYlVCo6LYBHjxsCWA=="
}
],
"resourceUrl": "https://storage.googleapis.com/threed-models-production/models/a6436c066064bac3/windmill.glb?external_model3d_id=bW9kZWwzZC00MDg5Ng==",
"url": "https://storage.googleapis.com/threed-models-production/models/a6436c066064bac3/windmill.glb?external_model3d_id=bW9kZWwzZC00MDg5Ng=="
}
],
"userErrors": []
}
}
}
Using these parameters, I construct a signed URL as follows.
resourceUrl+"&signature="+signature+"&key="+key+"&policy="+policy+"&GoogleAccessId="+GoogleAccessId
Eg:
https://storage.googleapis.com/threed-models-production/models/a6436c066064bac3/windmill.glb?external_model3d_id=bW9kZWwzZC00MDg5Ng==&signature=vz+OdcEmD9Kbv2FbXdxWNUk59XO2GmXzhvtDswXbDQNcyZpUufI85z5x2PFGv/XZ+tSBsl/S393pmy0Bu9xG7oVgOZcMIWEbOIm9kXgQunbjKQY3Ff3BBpMocB0xazzlYmckZozdJ8ZZkyox/c/gEe1QaxqW4+419iufuFHy4Bp3LL/aUr+ATNChwn9Dn8+XnHMOckZxDlbiggcF3dx+yBuTFia8FneaVSiU0M5DIWmHqHb2YDCV0KtEP6jfTj/PQVUjS8pn8EGhrRaMx7Q2A5G8Pycgc9H35hqJnnUKCTa3AYeyI45RbhddYnIWw9YrAADXuQYlVCo6LYBHjxsCWA==&key=models/a6436c066064bac3/windmill.glb&policy=eyJleHBpcmF0aW9uIjoiMjAyMC0wNy0yMVQwOToxNjoxMFoiLCJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJ0aHJlZWQtbW9kZWxzLXByb2R1Y3Rpb24iXSxbImVxIiwiJGtleSIsIm1vZGVscy9hNjQzNmMwNjYwNjRiYWMzL3dpbmRtaWxsLmdsYiJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDE5NzE3MiwxOTcxNzJdXX0=&GoogleAccessId=threed-model-service--6bgx7cbe#shopify-applications.iam.gserviceaccount.com
Then I try to make a PUT request to this URL with the .glb file in POSTman as shown in this image -->
with the following headers.
But I don't get a success response. In fact, I get a 400 error with the following message.
<?xml version='1.0' encoding='UTF-8'?>
<Error>
<Code>MalformedSecurityHeader</Code>
<Message>Your request has a malformed header.</Message>
<ParameterName>signature</ParameterName>
<Details>Signature was not base64 encoded</Details>
</Error>
Can someone point me out what I'm doing wrong here? I have been dealing with this error for days and read a lot of questions and articles, but couldn't get this to work. Therefore, any helpful suggestion is highly appreciated.
Few suggestions for you to try:
When generating the initial request (Shopify did this in this case), GCS usually includes HTTP headers of that request and includes them in the request signature and expects the upload request (the one you're trying to do) to match same HTTP header values.
This means you are advised to include only those headers Shopify specified in their request in your signed key upload request and nothing extra. So first try removing all HTTP headers that are not documented by Shopify (e.g. ones added by POSTMAN) and ensure all headers values follow Shopify docs (e.g. Content-Type should match)
You need to ensure signature parameters are in the expected names and formats:
signature value field is named: x-goog-signature and not signature (This is the likely reason for the error you're getting that GCS can't find the expected param)
you likely have to provide a signing algorithm key as well (x-goog-algorithm)
you might need other keys as well depending on other factors like authentication and so
I'd say these should be documented by Shopify SDK and/or examples
One suggestion to simplify/speed up things for you might be to use Google Cloud Utils (gsutil command line tool) to create a signed url request and then try to reproduce the same behaviour in your code
For more information see these links:
https://cloud.google.com/storage/docs/access-control/signed-urls
https://cloud.google.com/storage/docs/access-control/signing-urls-with-helpers#gsutil
Then after familiazing yourself with signed url format, you can check this for some sample code on how to do in your own code:
https://cloud.google.com/storage/docs/access-control/signing-urls-manually
For your reference when reading the docs, the type of Signed URL you're trying to create is non-resumable upload (which uses a single PUT request) rather than resumable (which uses an initial POST plus a series of PUTs)
Sorry I haven't worked with Shopify but the above is from my experience working with GCS signed urls

Cannot access SharedWithMe Folders via microsoft graph api

I am trying to download the items of a shared folder following the v1.0 reference guide. I am able to retrieve the basic details by using the /v1.0/me/drive/sharedWithMe request.
sharedWithMe Response
However when I try and request the contents of the folder using the '/drives/remoteItem.driveId/items/id' request I get a 'itemNotFound' error code.
/drives/b!SuVijei8UUiLXWR4XeJPFe-SS3gbCDVJuXZLcatX7Ikm3BPqZMVJTLOW4rsDD7B2/items/01GB75254SYRXS4C7C25HLIWFMXFY7HWB3
"error": {
"code": "itemNotFound",
"message": "The resource could not be found.",
"innerError": {
"request-id": "2a0591bd-6fdf-4bf8-a5f3-baca24fd1930",
"date": "2016-08-29T14:03:25"
}
Any ideas what is going on?
Thanks!
EDIT:
I was able to retrieve SharedWithMe folders data by using the '/drives/remoteitem.parentReference.driveId/items/remoteItem.id/children'
It seems also very important to use a scope of Files.Read.All (which seems to be currently undocumented)
It looks like you are almost there. All you need is the /content segment.
Expected: /drives/remoteItem.parentReference.driveId/items/id/content
Your actual: /drives/remoteItem.parentReference.driveId/items/id
You'll get redirected to the resource for download. I think the documentation should be updated
And just to make sure, the base url is a bit different:
To get the shared item content: /v1.0/drives/drive-id/items/item-id/content
To get the list of shared items: /v1.0/me/drive/sharedWithMe

Copy operation on new OneDrive API

I've been stumbling on this issue for a while. I'm trying to copy file which is located at OneDrive's folder into another folder within the same account. (i.e: from user's root folder to one of it's subfolder).
According to OneDrive Copy API, I need to call this REST API:
POST /drive/items/{item-id}/action.copy
where {item-id} is the file's id which I want to copy. For this request, I've using this content:
{
"parentReference": {
"id": [destination-folder-id]
}
}
This http call was work just fine. I've got the desired result which is:
HTTP/1.1 202 Accepted
Location: https://onedrive.com/monitor/[generated-alphanumeric-characters]
When I following the link at Location header, I got the async job status which is:
{
"operation": "ItemCopy",
"percentageComplete": 0,
"status": "notStarted",
"statusDescription": "Completed 0/0 files; 0/0 bytes"
}
The problem is, no matter how many times I wait and call this link, it gives the exact response, which it's status was notStarted. I also try to reproduce this using OneDrive's API Console and it shows exact behaviors. I don't have any problem when copying an empty folder. This problem only occurs on files. Does anyone know why? Is there anything I should do to start that async job?
It seems that no one knows about this problem. For now, I have to use the old OneDrive API (formerly SkyDrive API) only for this copy operation, like this example:
COPY https://apis.live.net/v5.0/file.a6b2a7e8f2515e5e.A6B2A7E8F2515E5E!126
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"destination": "folder.a6b2a7e8f2515e5e.A6B2A7E8F2515E5E!114"
}
The things after file. and folder. is user id followed by item id (file id for the item or folder id for destination). Note that this only works on file. We cannot copy folder. If success, it will return a status code of Created.
Oh, and ... In order to use this API, we need to have wl.skydrive_update in the scope when authenticating our app.
I won't mark this answer as accepted answer since this is only a workaround for this issue. It does not solve the real problem.
UPDATE per April 12:
Ok. According to this bug tracking, this API should working by now (the referenced comment are posted on April 10. So I'll mark this question as answered.

Retrieving and viewing google docs using gdata in objective-c

I've been trying to find an answer for this for a while and I have not come across something that explains whether it is possible or not:
I have a bunch of documents in Google Docs and I'm retrieving the list of them using gdata library. I have tried a bunch of different ways to display/open just a single one when I select one from the list but I have not been able to find what kind of request to do or url to send with the request.
The files are public and the share settings are set to public but they won't show up.
Can anyone help me out figure out how to open a Google Docs document (any file format) either in an UIWebView or in the native app?
EDIT Ok, I think there was a bit of confusion as to this question. I have a list of documents from a user's feed in which I store the resourceID of the documents in my database. These documents are public and therefore should be allowed to be opened and viewed by others. Say another user comes in and wants to view documents from another person. I call the resourceID into the viewer url and it still says it needs authorization. I figured, since the document is public, a user that has not logged in (therefore no auth token) should be able to view the document still. Just like google docs works?
The DocsSampleWindowController included with the Objective-C library includes a method that shows how to open a document using its HTML link:
- (IBAction)viewSelectedDocClicked:(id)sender {
NSURL *docURL = [[[self selectedDoc] HTMLLink] URL];
if (docURL) {
[[NSWorkspace sharedWorkspace] openURL:docURL];
}
}
I have no idea about Objective-C but I can tell you in general terms..
Just send an authenticated request at the below uri:
Be sure to replace YourDocument'sResourceID with the resource ID of the document that you want to read..
https://docs.google.com/feeds/download/documents/export/Export?id=**YourDocument'sResourceID**&exportFormat=txt&format=txt
you can also play with the variable format in the above uri.. txt gives you the simple text and no styles..