Rename Query Parameter in Outbound Request in Azure API Management - azure

Using policies in Azure API Management, I'm trying to rename a query parameter, but it does not work. If I change copy-unmatched-params="false" to copy-unmatched-params="true" then it works, but the behaviour becomes that all unmatched parameters will get passed to the backend API which would allow clients to inject their own query params into the backend request which we do not want.
Everything else is fine.
I want to transform a request that comes in that looks like this:
https://{site}/search?query=dylan
To:
https://{backend-site}documents?api-version=2018-1-11&amount=1000&searchFields=Album,Artist&search=dylan
The only part that doesn't work is transforming the query parameter to be named "search" instead of query without allowing all parameters to be passed on from the inbound querystring. How can I fix that?
<policies>
<inbound>
<rewrite-uri template="/" copy-unmatched-params="false" />
<set-header name="api-key" exists-action="override">
<value>THIS-IS-API-KEI</value>
</set-header>
<set-query-parameter name="api-version" exists-action="override">
<value>2018-1-11</value>
</set-query-parameter>
<set-query-parameter name="amount" exists-action="override">
<value>1000</value>
</set-query-parameter>
<set-query-parameter name="searchFields" exists-action="override">
<value>Album,Artist</value>
</set-query-parameter>
<set-query-parameter name="search" exists-action="override">
<value>#(context.Request.Url.Query.GetValueOrDefault("query"))</value>
</set-query-parameter>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

The reason why you're getting empty value from your last expression is because by that time your URI already rewritten to "/" and only "api-version", "amount", and "searchFields" query parameters are set. There are a few ways to go about that:
Refer to original Url: #(context.Request.OriginalUrl.Query.GetValueOrDefault("query"))
Add query to operation URI template - search?query={query} - and refer it from rewrite-uri policy: <rewrite-uri template="/?query={query}" copy-unmatched-params="false" />. The downside is that "query" parameter becomes required, so any request without it will result in 404.

Try using variables. Assign the value to variable in the beginning and use the variable for assigning new query parameter

You may rename parameter in the query by using string of the URL and simple replace method. In that case, the parameter wouldn't be mandatory.
<inbound>
<base />
<rewrite-uri template="#{
return "/some-url-here-or-your-previously-constructed-url" + context.Request.OriginalUrl.QueryString
.Replace("old-name", "new-name");
}" copy-unmatched-params="false" />
</inbound>

Related

Azure API Management CSRF Policy

I'm trying to create an inbound policy to pass my x-csrf-token to a Post method. However, when i check the tracing, i'm getting null value for the x-csrf-token.
I'm using the snippet given here in the https://github.com/Azure/api-management-policy-snippets/blob/master/examples/Get%20X-CSRF%20token%20from%20SAP%20gateway%20using%20send%20request.policy.xml.
Have anyone come across this issue before?
<!--
IMPORTANT:
- Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.
- To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.
- To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.
- To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar.
- To remove a policy, delete the corresponding policy statement from the policy document.
- Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope.
- Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope.
- Policies are applied in the order of their appearance, from the top down.
- Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope.
-->
<!-- The policy defined in this file shows how to implement X-CSRF pattern used by many APIs. The example is specific to SAP Gateway. -->
<!-- Detailed description of the scenario and solution can be found on: -->
<!-- https://github.com/MartinPankraz/AzureSAPODataReader. -->
<policies>
<inbound>
<base />
<authentication-basic username="{{SAP-test-user}}" password="{{SAP-test-PW}}" />
<set-variable name="checkRequestUrl" value="#(context.Request.Url.ToString())" />
<rewrite-uri template="/" />
<choose>
<!-- CSRF-token only required for every operation other than GET or HEAD -->
<when condition="#(context.Request.Method != "GET" && context.Request.Method != "HEAD")">
<!-- Creating a HEAD subrequest to save request overhead and get the SAP CSRF token and cookie.-->
<send-request mode="new" response-variable-name="SAPCSRFToken" timeout="10" ignore-error="false">
<set-url>#(context.Request.Url.ToString())</set-url>
<set-method>HEAD</set-method>
<set-header name="X-CSRF-Token" exists-action="override">
<value>Fetch</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization"))</value>
</set-header>
</send-request>
<!-- Extract the token and cookie from the "SAPCSRFToken" and set as header in the POST request. -->
<choose>
<when condition="#(((IResponse)context.Variables["SAPCSRFToken"]).StatusCode == 200)">
<set-header name="X-CSRF-Token" exists-action="override">
<value>#(((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("x-csrf-token"))</value>
</set-header>
<set-header name="Cookie" exists-action="override">
<value>#{
string rawcookie = ((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("Set-Cookie");
string[] cookies = rawcookie.Split(';');
/* new session sends a XSRF cookie */
string xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("sap-XSRF"));
/* existing sessions sends a SessionID. No other cases anticipated at this point. Please create a GitHub Pull-Request if you encounter uncovered settings. */
if(xsrftoken == null){
xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("SAP_SESSIONID"));
}
return xsrftoken.Split(',')[1];}</value>
</set-header>
</when>
</choose>
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

Use azure Apim to Call an Api that uses OAuth2 token

With Apim i'm trying to call a backend Api that needs a OAuth2 validation. This question are more or less similair to this: Azure API Management: Oauth2 with backend API
But there are no good answer here...
I have been reading alot about policies and caching.
But can't seem to set it up correctly. I hope to be able to cal the apim, and then the apim calls the backend api to get a token and with that token call an Api to get some output data.
I also found one where i had to setup some policies in the backend-part..
Can anyone help me set up the policies ?
my policy is like:
<policies>
<inbound>
<base />
<set-variable name="originBearer" value="#(context.Request.Headers.GetValueOrDefault("Authorization", "empty_token").Split(' ')[0].ToString())" />
<send-request ignore-error="true" timeout="20" response-variable-name="bearerToken" mode="new">
<set-url>{{lookupAccessTokenUrl}}</set-url>
<set-method>GET</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
<set-body>#{
return "client_id={{HLR-app-client-id}}&scope={{HLR-scope}}&client_secret={{HLR-secret}}&assertion="+(string)context.Variables["originBearer"]+"&grant_type=urn:ietf:params:oauth:grant-type:client_credentials&requested_token_use=on_behalf_of";
}</set-body>
</send-request>
<set-variable name="requestResponseToken" value="#((String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"])" />
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["requestResponseToken"])</value>
</set-header>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I found the answer to my own Question :-)
I try to comment on each line, but if you take alle the code and put it together you get a policy to handle Oauth2 in a backend api.
In the inbound section, the cache-lookup-value
Assigns the value in cache to the context variable called “bearerToken”.
On first entry, the cache value will be null and the variable will not be
created.
<inbound>
<cache-lookup-value key="cacheAccessToken" variable-name="bearerToken" />
Create a variable that contains clientid and secret - needed to call the api
<set-variable name="user-password" value="{{HLR-Clientid}}:{{HLR-Secret}}"
/>
<choose>
Checks if the context variable collection contains a key called
“bearerToken” and if not found executes the code between the opening and closing
“” XML elements.
<when condition="#(!context.Variables.ContainsKey("bearerToken"))">
Initiates the request to the OAuth endpoint with a response
timeout of 20 seconds. This will put the response message into the variable
called “oauthResponse”
<send-request mode="new" response-variable-name="oauthResponse" timeout="20" ignore-error="false">
<set-url>{{lookupAccessTokenUrl}}</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
here you define your header Authorization and use the variable that contains clientid and password
<set-header name="Authorization" exists-action="override">
<value>#("Basic " + system.Convert.ToBase64String(Encoding.UTF8.GetBytes((string)context.Variables["user-password"])))</value>
</set-header>
<set-body>#("grant_type=client_credentials&scope={{HLR-Scope}}")</set-body>
</send-request>
Casts the response as a JSON object to allow the retrieval of the “access_token” value using an indexer and assigns it to the context variable “accessToken”.
<set-variable name="AccessToken" value="#((string)((IResponse)context.Variables["oauthResponse"]).Body.As<JObject>()["access_token"])" />
Store result in cache and where we add the contents of the variable “accessToken” into cache for a period of 3600 seconds.
<cache-store-value key="cacheAccessToken" value="#((string)context.Variables["AccessToken"])" duration="3600" />
Set the variable in a context-variable, then it can be used right now
<set-variable name="bearerToken" value="#((string)context.Variables["AccessToken"])" />
</when>
</choose>
<base />
</inbound>
<backend>
<!--Creates the request to the backend web service. Here we are placing the response from the web service into the variable called “transferWSResponse”.-->
<send-request mode="copy" response-variable-name="transferWSResponse" timeout="60" ignore-error="false">
<set-method>GET</set-method>
<!--Is the creating the “Authorization” header to be sent with the request.-->
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["bearerToken"])</value>
</set-header>
<!--Removes the APIM subscription from being forwarded to the backend web service.-->
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
</send-request>
</backend>
<outbound>
<!--Now we need to return the response message from the backend web service to the caller. This is done in the “<outbound>” policy section. Here we just simply return the value of the variable “transferWSResponse” back to the caller-->
<return-response response-variable-name="transferWSResponse" />
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

How to use a variable when returning response in policy definition?

I'm configuring inbound policies in an instance of Azure API Management.
First, I set a variable:
<set-variable name="var1" value="" />
Then I send a request
<send-request mode="new" response-variable-name="var1" timeout="20" ignore-error="false">
Which returns a JSON. When testing I get the following message in trace tab:
GET request to 'https://my-api.azure-api.net/api/data' has been sent, result stored in 'var1' variable.
I guess the send-request policy works and the result is stored in the variable.
Then I want to return a response (still in inbound, I get 500 when trying to do it in outbound):
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>
{
"success": true,
"var1": context.Variables["var1"]
}
</set-body>
</return-response>
My problem is it doesn't work... It just renders context.Variables["var1"].
And so does:
#context.Variables["var1"]
#{ context.Variables.GetValueOrDefault<string>("var1") }
#context.Variables.GetValueOrDefault("var1")
All of them are rendered as written, no value is being extracted.
Edit: I also tried adding a placeholder string and then using
<find-and-replace from="Placeholder" to="context.Variables.GetValueOrDefault("var1")" />
And try to place it in inbound and outbound alike. But this policy did not launch.
It's a JSON object that I want to append to the response (small detail: in reality I have this issue with multiple variables).
My question is: how can I add my declared variable to the response?
There are two ways you can go about this. You could to use policy expressions for that: https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions. The thing to remember is that they can only be used to construct whole value for policy, not part of it, so:
<set-body>#("{\"success\": true, \"var1\": " + ((IResponse)context.Variables["var1"]).Body.As<string>() + "}"</set-body>
Or with set-body policy you could use liquid template:
<set-variable name="var1body" value="#((IResponse)context.Variables["var1"]).Body.As<string>())" />
<set-body template="liquid">
{
"success": true,
"var1": {{context.Variables["var1body"]}}
}
</set-body>
I assume you have some sidecar request going on additionally to your main request flow.
This sample adds the response from send-request to the response body of the main request:
<policies>
<inbound>
<base />
<!-- main request -->
<set-backend-service base-url="https://reqres.in" />
<rewrite-uri template="/api/users/2" />
<!-- sidecar request -->
<send-request mode="new" response-variable-name="var1" timeout="20" ignore-error="true">
<set-url>https://reqres.in/api/unkown/2</set-url>
<set-method>GET</set-method>
</send-request>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-body template="none">#{
var body = context.Response.Body.As<JObject>(true);
body["var1"] = ((IResponse)context.Variables["var1"]).Body.As<JObject>();
return body.ToString();
}</set-body>
</outbound>
<on-error>
<base />
</on-error>
</policies>

Azure API Management returning 200 status without response body

I already have checked other solutions available on the forum and this one too
REST API service when called from azure APIM returning empty response body with Status code 200
I'm trying to call an authentication service which returns status codes (200,401 and 404)
If it returns 200 then I want my request to be forwarded to the backend otherwise return only status code back to the client.
This is what I added in my policy
<policies>
<inbound>
<base />
<check-header name="username" failed-check-httpcode="401" failed-check-error-message="unauthorised" ignore-case="true" />
<cors>
<allowed-origins>
<origin>*</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
</allowed-methods>
</cors>
<send-request mode="new" response-variable-name="receivedResp" timeout="20" ignore-error="true">
<set-url>https://authenticate1.azurewebsites.net/auth</set-url>
<set-method>GET</set-method>
<set-header name="username" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("username",""))</value>
</set-header>
<set-body />
</send-request>
<return-response response-variable-name="receivedResp" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
Now problem is return is returning received response in all cases, when i added a conditional policy i still was not able to return response received from backend.
Can anyone tell how to check status code and return response received from the server in case of 200 only?
The <send-request/> policy returns the response variable of type context.Response. So you can use the methods and variables available for that object. See the below reference to find it.
https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ref-context-response
In your case, you can add <choose/> policy and test the
receivedResp.StatusCode != 200. Then you can return based on the response.
Hope it helps

Loop through multiple values in Set-Header policy

Wondering if there's a way to loop through all values for a particular header and modify each one. I have the need to do a string replace on each value of a header and I want to avoid assuming how many values for that header there will be. I do not see a way to create a loop in API Management policies.
 <outbound>
<base />
<choose>
<when condition="#(context.Response.Headers.ContainsKey("Set-Cookie"))">
<set-header name="Set-Cookie" exists-action="override">
<value - How can I loop through these?>#{
var currentHeader = context.Response.Headers["Set-Cookie"][i];
//perform necessary string.replace
}</value>
</set-header>
</when>
</choose>
</outbound>

Resources