Call stored procedure of CosmosDB from API Management Service - azure

I would like for one of my APIs in the API Management Service to call a stored procedure in CosmosDB and return its result. It seems there is not much documentation around the subject.
My attempt so far :
Frontend : GET method with three parameters
Inbound processing : I adapted the code from this question as follows
<policies>
<inbound>
<base />
<set-variable name="Content-Type" value="application/query+json" />
<set-variable name="x-ms-documentdb-isquery" value="True" />
<set-variable name="x-ms-documentdb-query-enablecrosspartition" value="False" />
<set-variable name="x-ms-max-item-count" value="1000" />
<set-variable name="x-ms-version" value="2017-02-22" />
<set-variable name="x-ms-date" value="#( DateTime.UtcNow.ToString("R") )" />
<set-header name="Content-Type" exists-action="override">
<value>#((string)context.Variables["Content-Type"])</value>
</set-header>
<set-header name="x-ms-documentdb-isquery" exists-action="override">
<value>#((string)context.Variables["x-ms-documentdb-isquery"])</value>
</set-header>
<set-header name="x-ms-documentdb-query-enablecrosspartition" exists-action="override">
<value>#((string)context.Variables["x-ms-documentdb-query-enablecrosspartition"])</value>
</set-header>
<set-header name="x-ms-max-item-count" exists-action="override">
<value>#((string)context.Variables["x-ms-max-item-count"])</value>
</set-header>
<set-header name="x-ms-version" exists-action="override">
<value>#((string)context.Variables["x-ms-version"])</value>
</set-header>
<set-header name="x-ms-documentdb-partitionkey" exists-action="override">
<value>#("[\""+context.Subscription.Id+"\"]")</value>
</set-header>
<set-header name="x-ms-date" exists-action="override">
<value>#( (string)context.Variables["x-ms-date"] )</value>
</set-header>
<set-variable name="StringToSign" value="#(string.Format("post\nsprocs\ndbs/{myDBName}/colls/{myCollName}\n{0}\n\n", ((string)context.Variables["x-ms-date"]).ToLowerInvariant()))" />
<set-variable name="cosmosreadwritekey" value="{{MyMasterKeyInReadWriteMode}}" />
<set-variable name="SharedKey" value="#{
// https://learn.microsoft.com/en-us/rest/api/documentdb/access-control-on-documentdb-resources#constructkeytoken
System.Security.Cryptography.HMACSHA256 hasher = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String((string)context.Variables["cosmosreadwritekey"]));
return Convert.ToBase64String(hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes((string)context.Variables["StringToSign"])));
}" />
<set-variable name="Authorization" value="#(string.Format("type=master&ver=1.0&sig={0}", ((string)context.Variables["SharedKey"]).Replace("&","%26").Replace("+","%2B").Replace("=","%3D")))" />
<set-header name="Authorization" exists-action="override">
<value>#((string)context.Variables["Authorization"])</value>
</set-header>
<set-backend-service base-url="https://{myCosmosName}.documents.azure.com" />
<rewrite-uri template="/dbs/{myDBName}/colls/{myCollName}/sprocs" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Backend: HTTPS endgoing with the following URL : https://{myCosmosName}.documents.azure.com/
However, when I test the API I systematically get the following error message :
The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\nsprocs\ndbs/{myDBName}/colls/{myDBName}\ntue, 18 sep 2018 09:31:58 gmt\n\n'\r\nActivityId: 64586521-cf37-44e5-80a4-bac0a9d3b261, Microsoft.Azure.Documents.Common/2.0.0.0
My questions from there:
How can I make this call work for my stored procedure?
How can I pass the API parameters to the call to the stored procedure?

For sending parameters, take a look at this. It looks like there is something wrong setting Authorization token, refer to this.

Related

mustRevalidate in cache apim policy not working

I am trying to implement cache remove in the policy . When i am testing it , i am getting one response as false where as i must receive it true
I am passing the header in the test section as : Cache-Control = maxAge & maxAge= 0
I am getting mustRevalidate as false where as i should receive it as true and perform cache-remove-value part . I have pasted the image also .
What exactly am i missing in policy ?
<policies>
<inbound>
<rewrite-uri template="#{
return ("/api/todo");
}" />
<set-header name="Cache-Control" exists-action="override">
<value>public, maxAge=3600</value>
</set-header>
<choose>
<when condition="#(context.Variables.GetValueOrDefault<string>("maxAge") != "0")">
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" must-revalidate="true" caching-type="internal">
<vary-by-header>Accept</vary-by-header>
<vary-by-header>Accept-Charset</vary-by-header>
<vary-by-header>Accept-Encoding</vary-by-header>
<vary-by-header>Accept-Language</vary-by-header>
</cache-lookup>
</when>
</choose>
<set-variable name="cacheKey" value="#(context.Request.Headers["maxAge"].FirstOrDefault())" />
<set-variable name="mustRevalidate" value="#(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("maxAge"))" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == true)">
<cache-remove-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" />
</when>
</choose>
<cache-lookup-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" variable-name="cachedResponse" />
</inbound>
<backend>
<forward-request timeout="100" />
</backend>
<outbound>
<cache-store caching-mode="cache-on" duration="60" />
<cache-store-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" value="cacheKey" duration="60" />
</outbound>
<on-error>
</on-error>
</po
licies>

Azure API management configuring external caching policy

We are using Azure API Management to publish, monitor and maintain APIs. Also we have implemented B2C login for Authentication and Authorization.
I'm trying to configure external cache for APIs. Somehow caching policy is not working. I refer following link https://learn.microsoft.com/en-us/azure/api-management/api-management-sample-cache-by-key
Based on logged in user tenant id, we want to store the template in cache and retrieve later for next request.
Here is the policy I have written.
<policies>
<inbound>
<set-variable name="tenantId" value="#(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Subject)" />
<cache-lookup-value key="#("templates-" + context.Variables["tenantId"])" variable-name="templates" />
<choose>
<when condition="#(!context.Variables.ContainsKey("tenantId"))">
<send-request mode="new" response-variable-name="templateResponse" timeout="15" ignore-error="true">
<set-url>https://abc.azure-api.net/api/notification/templates/?api-version=v1</set-url>
<set-method>GET</set-method>
</send-request>
<set-variable name="templates" value="#(((IResponse)context.Variables["templateResponse"]).Body.As<string>())" />
<cache-store-value key="#("templates-" + context.Variables["tenantId"])" value="#((string)context.Variables["templates"])" duration="10000" />
</when>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
As long as there is an authorization header with the request then APIM will not cache the request unless you enable the private response caching.
You can set the allow-private-response-caching attribute to ture in order to enable private response caching https://learn.microsoft.com/en-us/azure/api-management/api-management-caching-policies#elements.
if with that it does not continue to work then I recommend file a support ticket to the MS team.
I made some mistake while writing policy. Here is the corrected policy.
<policies>
<inbound>
<set-variable name="userId" value="#(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Subject)" />
<cache-lookup-value key="#("templates-" + context.Variables["userId"])" variable-name="templates" caching-type="external" />
<choose>
<when condition="#(!context.Variables.ContainsKey("templates"))">
<send-request mode="new" response-variable-name="templateResponse" timeout="15" ignore-error="true">
<set-url>https://appname.azurewebsites.net/api/templates</set-url>
<set-method>GET</set-method>
<set-header name="Authorization" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization",""))</value>
</set-header>
</send-request>
<set-variable name="templates" value="#(((IResponse)context.Variables["templateResponse"]).Body.As<string>())" />
<cache-store-value key="#("templates-" + context.Variables["userId"])" value="#((string)context.Variables["templates"])" duration="10000" />
</when>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<set-body>#((string)context.Variables["templates"])</set-body>
<base />
</outbound>
<on-error>
<base />
</on-error>

Azure API Management Policy to add item to service bus - should return error

I have the following APIM policy:-
<policies>
<inbound>
<base />
<set-variable name="payload" value="#(context.Request.Body?.As<JObject>(true))" />
<!-- Put it onto the service bus -->
<rewrite-uri template="/transaction/messages2" copy-unmatched-params="true" />
<cache-lookup-value key="emlsbsas" variable-name="cachedSasToken" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<string>("cachedSasToken") == null)">
<cache-store-value key="emlsbsas" value="#{
string resourceUri = "{{Transaction_Uri}}";
string keyName = "{{Transaction_KeyName}}";
string key = "{{Transaction_Key}}";
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 120);
string stringToSign = System.Uri.EscapeDataString(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = String.Format("SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
System.Uri.EscapeDataString(resourceUri),
System.Uri.EscapeDataString(signature), expiry, keyName);
return sasToken;
}" duration="10" />
<cache-lookup-value key="emlsbsas" variable-name="cachedSasToken" />
</when>
</choose>
<set-header name="Authorization" exists-action="override">
<value>#(context.Variables.GetValueOrDefault<string>("cachedSasToken"))</value>
</set-header>
<set-header name="Content-type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<return-response>
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#{
var json = new JObject(
new JProperty("id", context.Request.Body?.As<JObject>(true)["id"])
);
return json.ToString();
}</set-body>
</return-response>
</outbound>
<on-error>
<set-header name="ErrorSource" exists-action="override">
<value>#(context.LastError.Source)</value>
</set-header>
<set-header name="ErrorReason" exists-action="override">
<value>#(context.LastError.Reason)</value>
</set-header>
<set-header name="ErrorMessage" exists-action="override">
<value>#(context.LastError.Message)</value>
</set-header>
<set-header name="ErrorScope" exists-action="override">
<value>#(context.LastError.Scope)</value>
</set-header>
<set-header name="ErrorSection" exists-action="override">
<value>#(context.LastError.Section)</value>
</set-header>
<set-header name="ErrorPath" exists-action="override">
<value>#(context.LastError.Path)</value>
</set-header>
<set-header name="ErrorPolicyId" exists-action="override">
<value>#(context.LastError.PolicyId)</value>
</set-header>
<set-header name="ErrorStatusCode" exists-action="override">
<value>#(context.Response.StatusCode.ToString())</value>
</set-header>
<base />
</on-error>
</policies>
I return a 200 to the caller with the ID of the message that was sent.
What I want to happen when an error occurs is to return an error and not a 200
I have tried to test with an invalid URL. This is invalid
/transaction/messages2
but still a 200 is returned
How do I get it to return an error when the service bus is unavailable?
We directly check for errors in outbound section:
<outbound>
<base />
<choose>
<when condition="#(context.Response.StatusCode >= 400 && context.Response.StatusCode < 503)">
<set-status code="500" reason="Queue failure" />
</when>
<when condition="#(context.Response.StatusCode == 503)">
<set-status code="503" reason="Service unavailable" />
<set-header name="Retry-After" exists-action="override">
<value>300</value>
</set-header>
</when>
<otherwise />
</choose>
<xml-to-json kind="direct" apply="content-type-xml" consider-accept-header="false" />
</outbound>

How to choose to set back-end using rewrite-uri to two different logic apps in Azure API Management?

I'm trying to choose between two different logic apps using a when condition:
<inbound>
<set-variable name="CompanyID" value="#((string)context.Request.Headers.GetValueOrDefault("CompanyID"))" />
<set-variable name="AuthKey" value="#((string)context.Request.Headers.GetValueOrDefault("AuthKey"))" />
<base />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<string>("CompanyID") == "1" && context.Variables.GetValueOrDefault<string>("AuthKey") == "1")">
<set-method id="apim-generated-policy">GET</set-method>
<rewrite-uri id="apim-generated-policy" template="/manual/paths/invoke/?api-version=2016-06-01&sp=/triggers/manual/run&sv=1.0&sig={{apibrokerlogicapp_manual-invoke_5d36ff21ed}}" />
<set-header id="apim-generated-policy" name="Ocp-Apim-Subscription-Key" exists-action="delete" />
</when>
<when condition="#(context.Variables.GetValueOrDefault<string>("CompanyID") == "2" && context.Variables.GetValueOrDefault<string>("AuthKey") == "2")">
<set-method id="apim-generated-policy">GET</set-method>
<rewrite-uri id="apim-generated-policy" template="/manual/paths/invoke/?api-version=2016-06-01&sp=/triggers/manual/run&sv=1.0&sig={{apibrokerlogicapp_manual-invoke_5d36ff21ed}}" />
<set-header id="apim-generated-policy" name="Ocp-Apim-Subscription-Key" exists-action="delete" />
</when>
<otherwise />
</choose>
</inbound>
My two variables "CompanyID" and "AuthKey" decides which logic app to execute, in the case above, both the same logic app will be executed due to this line:
<rewrite-uri id="apim-generated-policy" template="/manual/paths/invoke/?api-version=2016-06-01&sp=/triggers/manual/run&sv=1.0&sig={{apibrokerlogicapp_manual-invoke_5d36ff21ed}}"
The line above will execute logic app 1, but how to execute logic app 2? Where get I find the following URL in my logic App so I can change this in my expression?
/manual/paths/invoke/?api-version=2016-06-01&sp=/triggers/manual/run&sv=1.0&sig={{apibrokerlogicapp_manual-invoke_5d36ff21ed
Hope someone can help!
Thanks in advance!
You also need to set the corresponding back-end-services (you find these in the all operations policy of the APIs imported from LogicApps) and check that the query parameters also match correspondingly:
<inbound>
<set-variable name="CompanyID" value="#((string)context.Request.Headers.GetValueOrDefault("CompanyID"))" />
<set-variable name="AuthKey" value="#((string)context.Request.Headers.GetValueOrDefault("AuthKey"))" />
<base />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<string>("CompanyID") == "1" && context.Variables.GetValueOrDefault<string>("AuthKey") == "1")">
<set-backend-service backend-id="LogicApp_kw1_kw" />
<set-method>GET</set-method>
<rewrite-uri template="/manual/paths/invoke/?api-version=2016-06-01&sp=/triggers/manual/run&sv=1.0&sig={{kw1_manual-invoke_5d680fe2da5ce8c03b53263b}}" />
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
</when>
<when condition="#(context.Variables.GetValueOrDefault<string>("CompanyID") == "2" && context.Variables.GetValueOrDefault<string>("AuthKey") == "2")">
<set-backend-service backend-id="LogicApp_kw2_kw" />
<set-method>GET</set-method>
<rewrite-uri template="/manual/paths/invoke/?api-version=2016-06-01&sp=/triggers/manual/run&sv=1.0&sig={{kw2_manual-invoke_5d68100a2fe4c33527ceaf4d}}" />
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
</when>
<otherwise />
</choose>
</inbound>

Use Azure Api Management as a passthrough

I would like to create a policy in Azure API Management that forwards all calls that start with the path "proxy/search" to another url. However, i don't want to have to import/create endpoints in APIM for every possibility since this makes it a maintenance nightmare. For example..
GET https://whatever.azure-api.net/proxy/search?q=dogs
GET https://whatever.azure-api.net/proxy/search/categories?q=dogs
GET https://whatever.azure-api.net/proxy/search/categories/x/y/z/etc....?q=blah
to the corresponding...
GET https://mysearchapi.com/?q=dogs
GET https://mysearchapi.com/categories?q=dogs
GET https://mysearchapi.com/categories/x/y/z/etc....?q=blah
I've built the policy below but it looks like APIM wants exact routes to map from it to the backend. I don't want to do this because this proxy may be forwarding to many, many routes apis etc...
<policies>
<inbound>
<base />
<set-variable name="baseUrlSearch" value="https://mysearchapi.com/" />
<set-variable name="matchSearch" value="proxy/search" />
<set-variable name="isRoutingComplete" value="false" />
<set-variable name="apiVersionDefaultSearch" value="1.0" />
<choose>
<when condition="#{return context.Request.Url.Path.Contains(context.Variables.GetValueOrDefault<string>("matchSearch"));}">
<set-backend-service base-url="#(context.Variables.GetValueOrDefault<string>("baseUrlSearch"))" />
<rewrite-uri template="#(context.Request.Url.Path.Replace(context.Variables.GetValueOrDefault<string>("matchSearch"), ""))" />
<set-header name="Api-Version" exists-action="skip">
<value>#(context.Variables.GetValueOrDefault<string>("apiVersionDefaultSearch"))</value>
</set-header>
<set-variable name="isRoutingComplete" value="true" />
</when>
<when condition="#(!context.Variables.GetValueOrDefault<bool>("isRoutingComplete"))">
<return-response>
<set-status code="400" reason="Bad Request Through Proxy" />
</return-response>
</when>
</choose>
</inbound>
<outbound>
<base />
</outbound>
</policies>
You are making your life much harder than it needs to be. Simply create an operation that uses /proxy/* as the template and it will match to all the URLs you identified.
Then just create a policy for that operation that does set-backend-service.
Adding to Darrel Miller's answer, here is how i got it working...
Adding an operation...
{
"name": "Search_GET",
"method": "GET",
"urlTemplate": "/search/*",
"policies": null
}
Adding a policy for that operation...
<policies>
<inbound>
<base />
<set-backend-service base-url="https://mysearchapi.com/" />
<rewrite-uri template="#(context.Request.Url.Path.Replace("search/", ""))" />
<set-header name="Api-Version" exists-action="skip">
<value>1.0</value>
</set-header>
</inbound>
<outbound>
<base />
</outbound>
</policies>

Resources