Shared OneDrive Directory From Application Authentication - c#

I have an app that processes a bunch of data and generates some results. Currently the app emails the results, but this can be cumbersome and the email can get too big, so I'm looking to have the app store the results to a shared OneDrive folder. The app runs without user interaction.
I've been looking into multiple samples for the Microsoft.Graph sdk. I'v been able to authenticate using an application and the ConfidentialClient workflow, but I'm not sure how to find/access a shared directory in OneDrive. Currently just the root drive request doesn't return any children or anything useful. I could access the shared drive when I used the API to login as my user, but that was using a share link for my user. Do I need to generate a share link somehow for the app or not tied to a user? Or is there some other way to find a shared drive?
Here's the code creating my GraphServiceClient:
public static GraphServiceClient GetAuthenticatedClient()
{
if (graphClient == null)
{
ConfidentialClientApp = ConfidentialClientApplicationBuilder.Create(clientId).WithTenantId(FormBrowser.MsaTenantId).WithClientSecret(FormBrowser.MsaClientSecret).Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(ConfidentialClientApp);
graphClient = new GraphServiceClient(authProvider);
}
return graphClient;
}
Then I've tried some of these different calls just to make sure it is authenticating correctly:
//var shares = await this.graphClient.Shares[encodedUrl].Root.Request().Expand(expandValue).GetAsync();
//ProcessFolder(shares);
var drive = graphClient.Drive.Request().GetAsync();
ProcessFolder(await this.graphClient.Drive.Root.Request().Expand(expandValue).GetAsync());
Here's a sample JSON response from the Drive.Root request:
{
"createdDateTime": "2013-11-07T19:59:00+00:00",
"lastModifiedDateTime": "2019-09-15T02:12:23+00:00",
"name": "root",
"webUrl": "https://<company>.sharepoint.com/Documents",
"fileSystemInfo": {
"createdDateTime": "2013-11-07T19:59:00+00:00",
"lastModifiedDateTime": "2019-09-15T02:12:23+00:00"
},
"folder": {
"childCount": 0
},
"parentReference": {
"driveId": "<stuff>",
"driveType": "documentLibrary"
},
"root": {
},
"size": 0,
"children": [
],
"thumbnails": [
],
"id": "<stuff>",
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#drive/root(thumbnails(),children(thumbnails()))/$entity",
"children#odata.context": "https://graph.microsoft.com/v1.0/$metadata#drive/root/children(thumbnails())",
"thumbnails#odata.context": "https://graph.microsoft.com/v1.0/$metadata#drive/root/thumbnails",
"responseHeaders": {
"Transfer-Encoding": [
"chunked"
],
"Vary": [
"Accept-Encoding"
],
"request-id": [
"<id>"
],
"client-request-id": [
"<id>"
],
"x-ms-ags-diagnostic": [
"{\"ServerInfo\":{\"DataCenter\":\"North Central US\",\"Slice\":\"SliceC\",\"Ring\":\"1\",\"ScaleUnit\":\"000\",\"RoleInstance\":\"<stuff>\",\"ADSiteName\":\"<stuff>\"}}"
],
"OData-Version": [
"4.0"
],
"Duration": [
"259.0577"
],
"Strict-Transport-Security": [
"max-age=31536000"
],
"Cache-Control": [
"private"
],
"Date": [
"Thu, 19 Sep 2019 14:06:27 GMT"
]
},
"statusCode": "OK"
}

So I was able to get there through a round-about way, if someone knows an easier way I'd really appreciate it. Here are the steps I took:
1) Authenticated with my user and loaded the info using the sharing url:
string sharingUrl = "<url>";
string base64Value = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sharingUrl));
string encodedUrl = "u!" + base64Value.TrimEnd('=').Replace('/', '_').Replace('+', '-');
var shares = await this.graphClient.Shares[encodedUrl].Root.Request().Expand(expandValue).GetAsync();
Once I got the response for that I noted the "driveId" for the drive. Then when I authenticate using my confidentialclient, I can specify the drive in my request:
await this.graphClient.Drives["<driveId from 1>"].Root.Request().Expand(expandValue).GetAsync()
I'm wondering if there's an easier way to find those driveId's, like from the sharepoint site?
Also, it looks like when I get the sharing link from Sharepoint, if I switch the link from "specific people" to "People in " then I can use the Shares to get the drive items.

Related

Application with AAD App Id is not authorized to generate notifications about [...] to the recipient

Good morning,
I'm trying to use the Microsoft graph's API to send notifications in Microsoft Teams feed.
I've created a C# APP and gave all Authorization following the official Guide:
Here
But I couldn't get the things done. So I've chosen to make things a little bit easier by cutting out the whole C# App using only Postman. The result is that I get the same error...
(ATTEMPT 1 WITH POSTMAN) -> This is the body of my request:
{
"topic": {
"source": "entityUrl",
"value": "https://graph.microsoft.com/beta/users/{MY_USER_ID}/teamwork/installedApps/{APP_ID}"
},
"activityType": "taskCreated",
"previewText": {
"content": "New Task Created"
},
"templateParameters": [
{
"name": "taskId",
"value": "Task 12322"
}
]
}
This is my call:
https://graph.microsoft.com:443/beta/users/{MY_USER_ID}/teamwork/microsoft.graph.sendActivityNotification
As result I get this:
{
"error": {
"code": "Forbidden",
"message": "Application with AAD App Id '{APP_ID}' is not authorized to generate notifications about 'https://graph.microsoft.com/beta/users/{MY_USER_ID}/teamwork/installedApps/{APP_ID}' to the recipient.",
"innerError": {
"date": "{date}",
"request-id": "{request-id}",
"client-request-id": "{client-request-id}"
}
}
}
(ATTEMPT 2 WITH POSTMAN) -> This is the body of my request (which is the same as the 1st one):
{
"topic": {
"source": "entityUrl",
"value": "https://graph.microsoft.com/beta/users/{MY_USER_ID}/teamwork/installedApps/{APP_ID}"
},
"activityType": "taskCreated",
"previewText": {
"content": "New Task Created"
},
"templateParameters": [
{
"name": "taskId",
"value": "Task 12322"
}
]
}
This is my call (the things which change):
https://graph.microsoft.com/beta/users/{MY_USER_ID}/teamwork/installedApps/{APP_ID}
and this is the error:
{
"error": {
"code": "UnknownError",
"message": "",
"innerError": {
"date": "{date}",
"request-id": "{request-id}",
"client-request-id": "{client-request-id}"
}
}
}
I think the right call is the first one, but I'm not sure...
I've already given the permission required by the guide I've linked above (TeamsActivity.Send 'delegated' and TeamsActivity.Send 'application').
I'm sure I'm missing something but from GraphExplorer I can't even test the right call/understand which permission I'm missing
Thanks in advance,
Giovanni
---------EDIT 1---------
As requested by Carl Zhao here my Access token, I had to censor some data but I think It will readable in the same way:
{
"typ": "JWT",
"nonce": "FHxXnlAuHU14c4czPflNLrniH-4d-4ZdRNawgEx6LQg",
"alg": "RS256",
"x5t": "nOo3ZDrODXEK1jKWhXslHR_KXEg",
"kid": "nOo3ZDrODXEK1jKWhXslHR_KXEg"
}.{
"aud": "00000003-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/9e892716-96e0-4bf4-a58a-e43212bfab33/",
"iat": 1614156685,
"nbf": 1614156685,
"exp": 1614160585,
"acct": 0,
"acr": "1",
"acrs": [
"urn:user:registersecurityinfo",
"urn:microsoft:req1",
"urn:microsoft:req2",
"urn:microsoft:req3",
"c1",
"c2",
"c3",
"c4",
"c5",
"c6",
"c7",
"c8",
"c9",
"c10",
"c11",
"c12",
"c13",
"c14",
"c15",
"c16",
"c17",
"c18",
"c19",
"c20",
"c21",
"c22",
"c23",
"c24",
"c25"
],
"aio": "E2ZgYBBXWFm5tc8nMm5J68RHD6qmJ31f8GpGeqzcg8/+XlvTE40B",
"amr": [
"pwd"
],
"app_displayname": "NotificationSender",
"appid": "{APP_ID}",
"appidacr": "0",
"family_name": "{My_surname}",
"given_name": "{My_name}",
"idtyp": "user",
"ipaddr": "{IP}",
"name": "{My_surname} {My_name}",
"oid": "{MY_USER_ID}",
"platf": "14",
"puid": "10032001002D8362",
"rh": "0.AREAFieJnuCW9EuliuQyEr-rM44Tv1nRFotKsOAymqy2XUwRALk.",
"scp": "email IdentityProvider.Read.All IdentityProvider.ReadWrite.All IdentityRiskEvent.Read.All IdentityRiskEvent.ReadWrite.All IdentityRiskyUser.Read.All IdentityRiskyUser.ReadWrite.All IdentityUserFlow.Read.All IdentityUserFlow.ReadWrite.All Notifications.ReadWrite.CreatedByApp openid profile TeamsActivity.Read TeamsActivity.Send TeamsApp.ReadWrite User.Read User.ReadBasic.All UserNotification.ReadWrite.CreatedByApp",
"sub": "EmlIIgfTtCx92uXpG26JS3A4s30BF-L_q08y7WpK45g",
"tenant_region_scope": "EU",
"tid": "9e892716-96e0-4bf4-a58a-e43212bfab33",
"unique_name": "{My_Mail}",
"upn": "{My_Mail}",
"uti": "hfEx4TU7lEOdFnLyO38VAA",
"ver": "1.0",
"wids": [
"7be44c8a-adaf-4e2a-84d6-ab2649e08a13",
"158c047a-c907-4556-b7ef-446551a6b5f7",
"baf37b3a-610e-45da-9e62-d9d1e5e8914b",
"c4e39bd9-1100-46d3-8c65-fb160da0071f",
"9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3",
"b79fbf4d-3ef9-4689-8143-76b194e85509"
],
"xms_st": {
"sub": "xFCFf1A82XccwsRgTd7olVqOBVUE9pZ5d6QDyWjbojc"
},
"xms_tcdt": 1428499913
}.[Signature]
Sorry but I have to post this as an answer since I don't have enough rep to comment. More of a guide than an answer.
Prerequisite: Your C# app is registered in Azure, with Graph API permissions for TeamsActivity.Send
I was able to get this to work by doing the following:
Using the Teams "Developer Portal" app, I went to the "apps" tab and added a new app. Under "app features", I added an activity feed notification, and then I published as an app package (to upload as an org app later, you'll see why in the next step).
For some reason the app (client) id I entered in the previous step did not get put into my manifest file (maybe because I didn't add an SSO provider? I'm not sure). So I modified the teams app manifest to include my C# app's client id, adding this:
"webApplicationInfo": {
"id": "{my C# app's client id}",
"applicationPermissions": [
"TeamsActivity.Send"
]
}
I zipped up my files again and uploaded the package to the Teams admin center.
I was then able to install the custom app I had uploaded to my Teams app.
After installing the app, I tried testing my C# app but neither the id nor externalid values were working for my "APP_ID", was getting the same exception as you mentioned. So I opened Graph Explorer, and found out the id using this query: https://graph.microsoft.com/beta/me/teamwork/installedApps?$expand=teamsApp (it will be a long string, not a GUID).
Once I used that id, everything worked.
From my understanding, the Teams app and your C# app are essentially 2 different registered apps that are tied together using the client id of your C# app, which I assume is just for authentication purposes. I guess if you don't have your C# app's client id in the Teams app's manifest, it can't authenticate, which seemed to be my issue. Also make sure your recipient has the app installed as that can also cause this exception.

MYOB Essentials Invoice API unable to push invoice

I am using RestSharp to push an invoice to MYOB.
RestClient myobPostInvoicesClient = new RestClient("https://api.myob.com/");
RestRequest myobPostInvoicesRequest = new RestRequest("au/essentials/businesses/" + business_uid + "/sale/invoices", Method.POST);
myobPostInvoicesRequest.AddHeader("Authorization", "Bearer " + access_token);
myobPostInvoicesRequest.AddHeader("x-myobapi-key", clientId);
myobPostInvoicesRequest.AddHeader("x-myobapi-version", "v0");
myobPostInvoicesRequest.AddHeader("Content-Type", "application/json");
The JSON I am sending to the endpoint is as below
{{
"contact": {
"uid": "26939970"
},
"invoiceNumber": "IV00000000082",
"issueDate": "2020-06-07T09:00:00",
"dueDate": "2020-07-07T09:00:00",
"gstInclusive": "true",
"status": "Open",
"lines": [
{
"unitOfMeasure": "Qty",
"quantity": 5.0,
"unitPrice": 1000.0,
"total": 5000.0,
"taxType": {
"uid": "10"
},
"account": {
"uid": "9"
},
"description": "Test Description"
}
]
}}
The Response I am getting back from the MYOB Invoice API endpoint is
"{\"errors\":[{\"field\":\"\",\"message\":\"Forbidden\",\"code\":\"403\"}]}"
The access token and client id are both valid and I am following the structure of the Invoice based on the below link
https://developer.myob.com/api/essentials-accounting/endpoints/sale/invoices/
The ones I have included in the request where the fields that were previously marked as required but MYOB have modified the UI.
Just for reference I can GET contacts, accounts and taxtypes from MYOB, just getting the Forbidden 403 message back trying to POST an Invoice.
Any help you could provide would be very much appreciated.
If you are getting 403 Forbidden, you need to check the permissions on the account that you are using to make the post call.
See here to read about the permissions of the account
Except from link above
How do I check a user's access permissions
To find out exactly what rights the current user has, and to ensure they have the right permissions for your application to function correctly make a GET request to the {{company_file_uri}}/{{company_file_id}}/CurrentUser endpoint.
Following response tells you what permissions the user has on each url
{
"UserAccess": [
{
"ResourcePath": "https://{{company_file_uri}}/{{company_file_id}}/Banking/BankAccount/",
"Access": [
"GET"
]
},
{
"ResourcePath": "https://{{company_file_uri}}/{{company_file_id}}/Banking/ReceiveMoneyTxn/",
"Access": [
"GET",
"POST",
"PUT",
"DELETE"
]
},
...
]
}

INVALID_USERID when calling Docusign API from live environment

I am getting below error when calling Docusign API from a C# web api. Able to get the access token but when creating the envelope this error is being received.
Is there any issue with clientUserId because it worked without any hiccups in sandbox. What value do I need to pass in it ? From all the sources, I gather it just indicates that this request is an embedded one. If we have to pass a specific userId in this field how to get it when passing it for envelope creation.
Response:
{
"errorCode": "INVALID_USERID",
"message": "Invalid UserId."
}
Below is the request which we are passing
{
"documents": [
{
"documentId": "1",
"fileExtension": "pdf",
"name": "Trial - OL.pdf"
}
],
"emailSubject": "Docusign Digital Signature",
"recipients": {
"signers": [
{
"clientUserId": "1001",
"email": "XXXX",
"name": "XXXX",
"recipientId": "1",
"routingOrder": "1",
"tabs": {
"signHereTabs": [
{
"anchorIgnoreIfNotPresent": "false",
"anchorString": "XXXX",
"anchorUnits": "inches",
"anchorXOffset": "0",
"anchorYOffset": "-0.25"
}
]
}
}
]
},
"status": "sent"
}
There is no error while retreiving access token
The error is not about clientUser but about the userId of the user.
After you finished Go-Live, the account is different, the user is different, and the URLs for the environments are all different when you migrate from the developer sandbox to the production environment.
If you got a token using JWT, remember that one of the things you used was the userId of the impersonated users.
You cannot use the token generator tokens in production.
Production environment doesn't have a single URL like demo.docusign.net. It can be many different URLs and you have to first figure out what it is before making API calls.

Google action doesn't provide user.profile.payload object after successful account linking

I followed this guide in order to create account linking in my app
https://developers.google.com/actions/identity/google-sign-in#json
I'm able to verify the user's jwt decoder and send back a response that the user is authorised. Then, according to the guide, in the next request, I should get the user's profile payload (user.profile.payload in the json structure) but It's missing from the next request. More than that, I get the tokenId for jwt verification again.
I think that what i miss here is in the possibleIntent object but I'm not sure, as I didn't see any documentation for that, because I work with asp.net server. There are SDKs with documentation for java and nodeJS only
this is the request provided for the sign in the contain the tokenId
{
"user": {
"locale": "en-US",
"lastSeen": "2019-07-11T14:18:10Z",
"idToken": "<tokenId>",
"userVerificationStatus": "VERIFIED"
},
"conversation": {
"conversationId": "ABwppHH9uZfcKj6pS6A6wItKC1dOXuZJ5oFYt2Og7cqrElSQYC9bv-aV7iQ5FDYaJPp-fa7tQNhc2yS0fw3QBu-M",
"type": "ACTIVE",
"conversationToken": "e0e78f40-a207-49c2-9050-50c6ed526c24"
},
"inputs": [
{
"intent": "actions.intent.SIGN_IN",
"rawInputs": [
{
"inputType": "KEYBOARD"
}
],
"arguments": [
{
"name": "SIGN_IN",
"extension": {
"#type": "type.googleapis.com/google.actions.v2.SignInValue",
"status": "OK"
}
},
{
"name": "text"
}
]
}
],
"surface": {
"capabilities": [
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.ACCOUNT_LINKING"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
},
{
"name": "actions.capability.WEB_BROWSER"
}
]
},
"isInSandbox": true,
"requestType": "SIMULATOR"
}
this is the response that i provide after verifying the user.
I tried it with both intents actions.intent.TEXT and actions.intent.SIGN_IN but with no success. the next request is provided with the user.idToken property again instead of the user.profile (that should contain the payload)
{
"conversationToken": "b09d915e-6df9-496d-acde-b76858cd95b4",
"expectUserResponse": true,
"expectedInputs": [
{
"inputPrompt": {
"richInitialPrompt": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Hi",
"displayText": "Hi"
}
}
],
"suggestions": []
}
},
"possibleIntents": [
{
"intent": "actions.intent.TEXT",
"inputValueData": {
"#type": "type.googleapis.com/google.actions.v2.SignInValue",
"status": "OK"
}
}
]
}
]
}
The user.profile attribute you're talking about is something that is provided via the actions-on-google library for JavaScript. It isn't in the JSON that you will receive. But...
You don't need it because the basic profile information (name, email, and Google ID) is encoded in the user.idToken. That string, which will be sent to you for every request, is just a JWT token which you can verify and decode. The profile will be in the "payload" section.
I don't know c#, but https://jwt.io/ contains a list of libraries which can decode the JWT string for you so you can read the "payload".
Keep in mind that you don't need to verify the token each time (although if you do it right, this shouldn't be expensive), but that you can decode it to get the information that you're looking for.
If you don't want to decode it, you can decode it when you first verify it, get the information you need, and store that information in the userStorage string (assuming you don't expect it to change).

AWS DynamoDB Write permissions aren't working

I'm doing a proof of concept with connecting to dynamodb, but I appear to be having issues with permissions.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:*"
],
"Resource": [
"arn:aws:dynamodb:us-west-1:172777662485:table/*"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${www.amazon.com:user_id}"
]
}
}
}
]
}
The code:
DynamoDBContext context = new DynamoDBContext(new AmazonDynamoDBClient("...", "...", new AmazonDynamoDBConfig() { RegionEndpoint = RegionEndpoint.USWest1, MaxErrorRetry = 6 }));
var writeContext = context.CreateBatchWrite<Music>();
writeContext.AddPutItem(new Music() { Artist = "Test Artist", SongTitle = "SongTitle" });
writeContext.Execute();
The error I get is:
arn:aws:iam::...:user/DynamoDBTestUser is not authorized to perform: dynamodb:BatchWriteItem on resource: arn:aws:dynamodb:us-west-1:172777662485:table/Music
Does anyone see anything wrong here?
thanks
Your policy appears to be based on some of the samples from this document:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html
However, your code does not appear to be adding a record with a partition key matching the username.
So there are 2 possible solutions, depending on your desired outcome.
Solution 1:
Assuming you simply want to insert records into the table, you can use a policy such as:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:*"
],
"Resource": "*"
}]
}
This policy grants the user fully DynamoDB access to any DynamoDB resource.
Solution 2:
If you want to restrict access to the table limiting the user to read/put records only for them, then you need to ensure your primary key attribute in your Music structure is populated with your IAM user name.

Categories

Resources