I have an API Gateway (C#, net.Core 3.1, Ocelot), everything is working fine but now I'm trying to configure different routes for upstream and downstream, because my Gateway retrieve information during the process and send this information to the final API.
In upstream I don't have a placeholder {userid}, but I want to have it in downstream.
Here is my ocelot.json:
"DownstreamPathTemplate": "/api/User/{userid}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 44301
}
],
"UpstreamPathTemplate": "/api/User/",
"UpstreamHttpMethod": [ "Get" ],
And there is how I'm adding in the middleware the placeholder value:
if (context.DownstreamRequest.OriginalString.Contains("User"))
{
context.DownstreamRequest.AbsolutePath =
context.DownstreamRequest.AbsolutePath + userid; //this variable is valued before
}
So, to be more clear, here is an example:
I get called at http://localhost:44358/api/User/ (mygateway Upstream), from some logics I get the userid that made this call, for example Andrew, and I want to redirect the request to my API http://localhost:44301/api/User/Andrew (mygateway Downstream).
Everything is fine, except at my API the userid is coming as {userid} and not has userid value (Andrew).
You can implement using Claims Transformation feature of Ocelot.
e.g.
"AddQueriesToRequest": {
"LocationId": "Claims[LocationId] > value",
}
I managed to do it with this code in the config:
"DownstreamPathTemplate": "/api/User/{userid}",
"DownstreamScheme": "http",
"ChangeDownstreamPathTemplate": {
"userid": "Claims[userId] > value"
},
"UpstreamPathTemplate": "/api/User/",
Related
Hello Now I am trying to use Ocelot gateway Normally I have one api in different server such as
https://server_domain.net/kpiDashboardApi
without gateway if I send request directly to below link , it works
https://server_domain.net/kpiDashboardApi/Report/DocumentTypeCounts
I want to reach endpoint in this api from Ocelot in my local I use below configuration json
{
"Routes": [
{
"DownstreamPathTemplate": "/kpiDashboardApi/Report/DocumentTypeCounts",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "https://server_domain.net/kpiDashboardApi",
"Port": 443
}
],
"UpstreamPathTemplate": "/Report/DocumentTypeCounts",
"UpstreamHttpMethod": [ "Post" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:5001"
}
}
when U send request to https://localhost:5001/kpiDashboardApi/Report/DocumentTypeCounts
it return 404 and as error return in console below
warn:
Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
requestId: 0HMFMPNHV9VHO:00000001, previousRequestId: no previous request id, message: DownstreamRouteFinderMiddleware setting
pipeline errors. IDownstreamRouteFinder returned Error Code:
UnableToFindDownstreamRouteError Message: Failed to match Route
configuration for upstream path:
/kpiDashboardApi/Report/DocumentTypeCounts, verb: POST. warn:
Ocelot.Responder.Middleware.ResponderMiddleware[0]
requestId: 0HMFMPNHV9VHO:00000001, previousRequestId: no previous request id, message: Error Code:
UnableToFindDownstreamRouteError Message: Failed to match Route
configuration for upstream path:
/kpiDashboardApi/Report/DocumentTypeCounts, verb: POST. errors found
in ResponderMiddleware. Setting error response for request
path:/kpiDashboardApi/Report/DocumentTypeCounts, request method: POST
I can't find the missing part . Where is my mistake?
Thanks in advance
I think you mixed Downstream & Upstream up.
Try such config (note values for Host and UpstreamPathTemplate):
{
"Routes": [
{
"DownstreamPathTemplate": "/kpiDashboardApi/Report/DocumentTypeCounts",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "https://server_domain.net",
"Port": 443
}
],
"UpstreamPathTemplate": "/kpiDashboardApi/Report/DocumentTypeCounts",
"UpstreamHttpMethod": [ "Post" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:5001"
}
}
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.
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"
]
},
...
]
}
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.
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.