apim policy to call and print it the value from json - azure

I am facing challenges to print the value from json body in azure apim policy
{    "value": [        {            "counterKey": "abc123",            "periodKey": "0_86400",            "periodStartTime": "2023-02-10T00:00:00Z",            "periodEndTime": "2023-02-11T00:00:00Z",            "value": {                "callsCount": 4,                "kbTransferred": 0.36328125            }        }    ],    "count": 1,    "nextLink": null}
I want to print the "callsCount": 4, through api policy
<send-request ignore-error="true" timeout="20" response-variable-name="apref" mode="new">
<set-url>myurl</set-url>
<set-method>GET</set-method>
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["PSItoken"])</value>
</set-header>
</send-request>
<set-header name="NumberOfCounts" exists-action="override">
<value>#{
var jsonBody = ((IResponse)context.Variables["apref"]).Body.As<JObject>()["value"].ToString();
JArray jBody = JArray.Parse(jsonBody);
return String.Join("", jBody.Select(i => i.ToString()));
}</value>
</set-header>
<set-variable name="NumberOfCounts" value="#(context.Request.Headers.GetValueOrDefault("NumberOfCounts"))" />
What exactly am i missing in my policy ?

Whenever printing api response use the response variable name and add return-response policy:
<return-response response-variable-name="apref">
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value></set-header>
<set-body>#{
var jsonBody = ((IResponse)context.Variables["apref"]).Body.As<JObject>()["value"].ToString();
JArray jBody = JArray.Parse(jsonBody);
return String.Join("", jBody.Select(i => i.ToString()));
}
</set-body>
</return-response>

Related

Unable to return a response using return-response policy in Azure API management

I have a condition like:
In the outbound I will check the response of the GET API which is where aAPI policies are there. If the body has a key with entityStatus which takes values 0 or 1, if 0 then output simple message else need to send-request to another API endpoint which is POST API.
I used API policies as below:
<set-variable name="id" value="" />
<set-variable name="newRequest" value="#(context.Request.Body?.As<JObject>
(preserveContent: true))" />
<choose>
<when condition="#(context.Response.StatusCode == 200 &&
(int)context.Response.Body?.As<JObject>(true)["entityStatus"] == 1)">
<send-request mode="new" timeout="20" response-variable-name="id" ignore-error="false">
<set-url>#($"https://api.dev/external/workrequest/wr")</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization","scheme param"))</value>
</set-header>
<set-body>#{ var document = (JObject)context.Variables["newRequest"];
return document?.ToString();
}</set-body>
</send-request>
<return-response response-variable-name="id">
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#((((IResponse)context.Variables["id"]).Body.As<JObject>()).ToString())</set-body>
</return-response>
</when>
<when condition="#(context.Response.StatusCode == 200 && (int)context.Response.Body?.As<JObject>(true)["entityStatus"] == 0)">
<return-response>
<set-status code="500" reason="VOID" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value></set-header>
<set-body>The record is void</set-body>
</return-response>
</when>
<otherwise />
The response it returned was good when entityStatus was 0 but when entityStatus is 1 it was returning a 400 error. The error is like:
{
"type": "https://httpstatuses.io/400",
"title": "Bad Request",
"status": 400,
"traceId": "00-317b53ba7a76f19b6153ca6ab14c5190-e9c27cf9162e2414-00"
}
I just need the response from the POST API which I am sending the request to and how can I do this?
I got a solution, I was using below variable that will take request body:
<set-variable name="newRequest" value="#(context.Request.Body?.As<JObject>(preserveContent: true))" />
I changed it by using:
set-variable name="newRequest" value="#(context.Response.Body.As<JObject>(preserveContent: true))" />

Liquid body inside the return response policy [duplicate]

This question already has answers here:
Azure API Management - Liquid Transformation does no longer work
(2 answers)
Closed last month.
Does anyone know how to access the request body from inside the <set-body template = "liquid"> statement when using the <return response> policy?
When I do like this the body seem to be empty:
<inbound>
<base />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<return-response>
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body template="liquid">
{
"name":"{{body.firstname}}"
}
</set-body>
</return-response>
</inbound>
The request body is:
{"firstname" : "John Doe"}`
Expected result would be:
{"name":"John Doe"}
The result is:
{"name":""}
Microsoft support told me that the policy return-response does not support set-body with liquid template.
You can do a workaround by modifying the body before using it in return-response.
Inbound policy:
<inbound>
<base />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body template="liquid">
{
"name":"{{body.firstname}}"
}
</set-body>
<return-response>
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#{
string body = context.Request.Body.As<string>(true);
return body;
}</set-body>
</return-response>
</inbound>
Request:
POST https://rfqapiservicey27itmeb4cf7q.azure-api.net/sample/liquid HTTP/1.1
Host: rfqapiservicey27itmeb4cf7q.azure-api.net
Content-Type: application/json
{"firstname" : "John Doe"}
Response:
HTTP/1.1 200 OK
content-length: 35
content-type: application/json
date: Mon, 09 Jan 2023 04:21:51 GMT
request-context: appId=cid-v1:a10dc7c9-c354-40a2-acf3-1401681f7808
vary: Origin
{
"name": "John Doe"
}

In API Management Policy, check if a header value is within a list/array

I have an API management inbound policy where I can grab a users id from within a JWT. For the purpose of some testing, I then want API management to check in the policy, "Is this ID within the list of tester IDs that are allowed to access here"
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="myurl" />
</validate-jwt>
<set-header name="header-value-userId" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()?.Claims.GetValueOrDefault("oid"))</value>
</set-header>
<!--
PSEUDOCDOE below to describe my intention
How can I check that the abover header-value-userId value is within a hardcoded list at this point?
#{
string[] userList = ["user1", "user2", "user3"];
var match = userList.FirstOrDefault(x => x.Contains(header-value-userId));
if(match == null)
return bad request
}
-->
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Thanks for any help.
--- UPDATE ---
Thanks to the answer from Markus Meyer I now have this working. See below my now full working example.
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="myurl" />
</validate-jwt>
<set-variable name="isValidUser" value="#{
var email = context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()?.Claims.GetValueOrDefault("emails");
string[] emailList = new string[] { "email1#gmail.com", "email2#gmail.com" };
var match = emailList.Any(x => x.Contains(email));
if(match == true)
{
return true;
}
return false;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("isValidUser") == false)">
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#("{\"status\": \"" + "User not valid" + "\"}")</set-body>
</return-response>
</when>
<otherwise>
<return-response>
<set-status code="200" reason="Valid" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#("{\"status\": \"" + "User valid" + "\"}")</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Using the choose policy allows you to check a boolean condition.
In the current example, the condition is set in the set-variable policy
return-response will return your wanted response.
Complete example:
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="myurl" />
</validate-jwt>
<set-header name="header-value-userId" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()?.Claims.GetValueOrDefault("oid"))</value>
</set-header>
<set-variable name="isValidUser" value="#{
string[] userList = new string[] { "user1", "user2", "user3" };
var match = userList.FirstOrDefault(x => x.Contains("header-value-userId"));
if(match == null)
{
return true;
}
return false;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("isValidUser") == false)">
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#("{\"status\": \"" + "User not valid" + "\"}")</set-body>
</return-response>
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
A few things to note:
This this the correct code for the string[]:
string[] userList = new string[] { "user1", "user2", "user3" };
There's no need to store the usereId into the header. The value can also be stored in a variable.

Set-AzApiManagementPolicy with a value from a function

I am trying to use Azure Pipeline to build my API Management infrastructure automatically and have successfully added the API and API Operation but having trouble defining the Operation specific Policy.
I have this policy that I based on a very useful article https://www.serverlessnotes.com/docs/expose-service-bus-queue-through-api-management
<policies>
<inbound>
<base />
<set-variable name="sasToken" value="#{
return "bob";
}" />
<set-header name="Authorization" exists-action="override">
<value>#(context.Variables.GetValueOrDefault<string>("sasToken"))</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" />
<set-header name="BrokerProperties" exists-action="override">
<value>#{
return string.Format("{{\"SessionId\":\"{0}\"}}", "bob");
}</value>
</set-header>
<set-backend-service base-url="https://i365intfnapidevtbcoresb.servicebus.windows.net/i365intfnapidevtbcoresbqueue" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
There is actually more code in the value bits, but for illustration I've removed them.
However if I put this in a separate file (or even an inline variable) and run the Azure Powershell command
Set-AzApiManagementPolicy -Context $apim_context -ApiId $apiId -OperationId addmessage -PolicyFilePath <path to policy xml file>
Where the $ values are variables I declared previously.
I get the error
Error Details:
[Code= ValidationError, Message= 'bob' is an unexpected token. Expecting white space. Line 5, position 21., Target=
representation]
Basically, I cant work out how to format the function thats in the value attribute or value elements. The bit starting with #{}.
I can enter the policy via the Azure API Management screen no problem, but cant do it via the Set-AzApiManagementPolicy command.
Any ideas on how to format it.
Thanks.
It will escape it for you if you use the Format parameter.
Set-AzApiManagementPolicy -Context $apim_context -Format "application/vnd.ms-azure-apim.policy.raw+xml" -ApiId $apiId -OperationId addmessage -Policy $policyString
Solved it in the end, You have to encode some of the characters to make it work. For example #quot; instead of "
So I have in my Azure Powershell script:
$policyString = '
<policies>
<inbound>
<base />
<set-variable name="sasToken" value="#{
string resourceUri = "$(azure.servicebusqueueendpoint)";
string keyName = "$(azure.servicebusaccesspolicykey)";
string key = "$(azure.servicebusaccesspolicyvalue)";
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;
}" />
<set-header name="Authorization" exists-action="override">
<value>#((string)context.Variables.GetValueOrDefault("sasToken"))</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" />
<set-header name="BrokerProperties" exists-action="override">
<value>#{
return string.Format("{{\"SessionId\":\"{0}\"}}", "1");
}</value>
</set-header>
<set-backend-service base-url="$(azure.servicebusqueueendpoint)" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
'
Set-AzApiManagementPolicy -Context $apim_context -ApiId $apiId -OperationId addmessage -Policy $policyString
and this now seems to do the trick.
You would probably also need to do the same for c# things like:
context.Variables.GetValueOrDefault<string>
would be
context.Variables.GetValueOrDefault<string>
to make it work otherwise it conflicts with the xml
Thanks mclayton for pointing me in the right direction.

Azure API Management policy to get token with query parameters

I am looking to implement an Azure API Management policy for bank account validation and as part of that API I want to call out to a token endpoint and pass that into the bank account validation. The problem I have is around setting the inbound send-request policy to accept the query parameters from NamedValues/KeyVault.
The URL for the token validation is as below:
https://apps.applyfinancial.co.uk/validate-api/rest/authenticate?username=USERNAME.com&password=PASSWORD
I tried using the set-query-parameter policy but it appears that this is not allowed within the send-request node based on the below validation error:
Error in element 'send-request' on line 16, column 10: The element
'send-request' has invalid child element 'set-query-parameter'. List
of possible elements expected: 'set-header, set-body,
authentication-certificate, authentication-token,
authentication-token-store, authentication-managed-identity, proxy'.
One or more fields contain incorrect values:;Error in element
'send-request' on line 16, column 10: The element 'send-request' has
invalid child element 'set-query-parameter'. List of possible elements
expected: 'set-header, set-body, authentication-certificate,
authentication-token, authentication-token-store,
authentication-managed-identity, proxy'.
POLICY
<policies>
<inbound>
<!-- Send request to Token Server to validate token (see RFC 7662) -->
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
<set-url>https://apps.applyfinancial.co.uk/validate-api/rest/authenticate</set-url>
<set-method>POST</set-method>
<set-query-parameter name="username" exists-action="override">
<value>{{BankValidationUsername}}</value>
</set-query-parameter>
<set-query-parameter name="password" exists-action="override">
<value>{{BankValidationPassword}}</value>
</set-query-parameter>
</send-request>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
My question is how do you set query parameters in the send-request section of an API policy?
OK,
You can't set a query parameter within the scope of the send-request but you can do it within the ionbound policy. Also it seems better to pull the KeyVault hosted Named Values in to variables and use them in the request that way.
<policies>
<inbound>
<rewrite-uri template="/" />
<set-variable name="username" value="{{BankValidationUsername}}" />
<set-variable name="password" value="{{BankValidationPassword}}" />
<set-variable name="errorresponse" value="" />
<send-request mode="new" response-variable-name="tokenstate" ignore-error="false">
<set-url>#($"https://apps.applyfinancial.co.uk/validate-api/rest/authenticate?username={(string)context.Variables["username"]}&password={(string)context.Variables["password"]}")</set-url>
<set-method>POST</set-method>
</send-request>
<set-query-parameter name="token" exists-action="override">
<value>#((string)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["token"])</value>
</set-query-parameter>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</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>

Resources