mustRevalidate in cache apim policy not working - azure

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>

Related

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>

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>

Trouble with Rewriting Azure API Management Basic Authentication

When I rewrite the API Management Basic Authentication for several reasons,
I have the below error and retired the fix.
Is anyone who knows the right API Management policies?
Thank you.
The Policies
<policies>
<inbound>
<set-variable name="isAuthOk"
value="#{
string[] value;
BasicAuthCredentials credit;
if (context.Request.Headers.TryGetValue("Authorization", out value))
{
if(TryParseBasic(value[0], out credit)){
if(credit.UserId == "nelco1"){
return true;
}else{
return false;
}
}
}
else
{
return false;
}
}" />
<base />
<!-- thankx for https://stackoverflow.com/questions/49479416/api-management-basic-authentication -->
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("isAuthOk"))">
</when>
<otherwise>
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Basic realm="ohhhhhhhhh"</value>
</set-header>
<set-body>Wrong username or password</set-body>
</return-response>
</otherwise>
</choose>
<set-backend-service id="apim-generated-policy" backend-id="preaddresscode2" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I have the error when I saved the above policies
One or more fields contain incorrect values:
Error in element 'set-variable' on line 3, column 10: The name 'TryParseBasic' does not exist in the current context
MS Docs
https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions
The method 'TryParseBasic' exists in the document.
Thank you #Thomas.
It worked when I implemented it referring to the JWT token.
I retired using TryParseBasic instead of AsBasic.
<policies>
<inbound>
<set-backend-service id="apim-generated-policy" backend-id="preaddresscode2" />
<rewrite-uri template="/HttpTrigger1" />
<set-variable name="isAuthOk" value="#{
string[] value;
if (context.Request.Headers.TryGetValue("Authorization", out value))
{
BasicAuthCredentials credit = context.Request.Headers.GetValueOrDefault("Authorization","").AsBasic();
if(credit == null){
return false;
}
switch(credit.UserId){
case "UUUUUU1":
// it seems an ugly implementation.
if(credit.Password.Equals("PPPPPP1")){
return true;
}
case "UUUUUU2":
if(credit.Password.Equals("PPPPPP2")){
return true;
}
break;
default:
break;
}
return false;
}
else
{
return false;
}
return true;
}" />
<base />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("isAuthOk"))" />
<otherwise>
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Basic realm="someRealm"</value>
</set-header>
<set-body>Wrong username or password</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

Call stored procedure of CosmosDB from API Management Service

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.

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