I require some help on doing a rewrite uri policy. I was able to extract information from payload and change the initial request URL. Have extracted the insurer_id from body and passed it to my backend -> mybackend.com/api/relationship/deny/{insurer_id}
I was able to extract it and send it to my backend, I have a bit of a trouble formatting once the request has been posted
I have a current payload:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code: [ " "Deny\",",
],
"additionalComments": "This is a test"
}
This is my Current code:
<policies>
<inbound>
<base />
<set-variable name="insurerId" value="#{
var body = context.Request.Body.As<JObject>(true);
return body["insurer_id"].Value<string>();
}" />
<set-variable name="status_code" value="#{
return context.Request.Body.As<JObject>(preserveContent:true)["status_code"].ToString(Newtonsoft.Json.Formatting.None);
}" />
<rewrite-uri template="#("/api/relationship/deny/" + context.Variables.GetValueOrDefault<string>("insurerId") + "?denyReason=" + context.Variables.GetValueOrDefault<string>("status_code"))" copy-unmatched-params="false" />
<set-backend-service base-url="https://testbackend.azure-api.net" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
What I am trying to achieve works, but when I do a post, the payload doesn't look what is to be expected.
Expected Payload:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code": "Deny",
"additionalComments": "This is a test"
}
Unformatted payload:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code": "[\"Deny\"]",
"additionalComments": "This is a test"
}
You have to read the first value of your JArray and assign it to the variable:
<set-variable name="status_code" value="#{
var body = context.Request.Body.As<JObject>(true);
var statusCode = body["status_code"] as JArray;
if(statusCode != null && statusCode.Count > 0)
{
return statusCode.FirstOrDefault();
}
return null;
}" />
But therefore you have to fix your invalid JSON:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code": [ "Deny"],
"additionalComments": "This is a test"
}
A query string consists of a key-value pair:
https://example.com/over/there?name=ferret
name is the key
ferret is the value for it
In your example, there's no key:
? + denyReason=context.Variables.GetValueOrDefault<string>("status_code")
and also the ? is a string. So it has to be put between quotation marks.
Fixed with sample query-key status_code:
<rewrite-uri template="#("/api/relationship/deny/" + context.Variables.GetValueOrDefault<string>("insurerId") + "?statuscode=" + context.Variables.GetValueOrDefault<string>("status_code"))" copy-unmatched-params="false" />
Complete policy:
<policies>
<inbound>
<base />
<set-variable name="insurerId" value="#{
var body = context.Request.Body.As<JObject>(true);
return body["insurer_id"].Value<string>();
}" />
<set-variable name="status_code" value="#{
var body = context.Request.Body.As<JObject>(true);
var statusCode = body["status_code"] as JArray;
if(statusCode != null && statusCode.Count > 0)
{
return statusCode.FirstOrDefault();
}
return null;
}" />
<rewrite-uri template="#("/api/relationship/deny/" + context.Variables.GetValueOrDefault<string>("insurerId") + "?statuscode=" + context.Variables.GetValueOrDefault<string>("status_code"))" copy-unmatched-params="false" />
<set-backend-service base-url="https://testbackend.azure-api.net" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Related
I try to check request body like this.
<set-variable name="isBody" value="#{
bool rtn_result = false;
if (context.Request.Body == null)
{
rtn_result = true;
}
return rtn_result;
}" />
but 'rtn_result' return false always
I followed the document and they told me
"context.Request Body: IMessageBody or null if request doesn't have a body."
What I mistake?
What is the right thing?
What should I do?
Thx.
Your if condition does not make sense.
It returns true if there's no body.
Please change it to:
if (context.Request.Body != null)
{
rtn_result = true;
}
Complete policy:
<policies>
<inbound>
<base />
<set-variable name="isBody" value="#{
bool rtn_result = false;
if (context.Request.Body != null)
{
rtn_result = true;
}
return rtn_result;
}" />
<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 response = new JObject();
response["hasBody"] = context.Variables.GetValueOrDefault<bool>("isBody");
return response.ToString();
}</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Result with body:
Result without body:
I am trying to create a policy, that will pull the insurer ID out of the input body and put it in the URL as shown in the picture below.
Trying to clean up request body and reconstruct URL so the request can successfuly post to our approve endpoint
Expected Result:
https://apimanagement.test.com/consto-123/api/relationship/approve/{id}
Request body:
{
"insurer_name": "Tony",
"insurer_id": "12345",
"comments": "This is test"
}
You have to extract the insurer_id from the request-body and store it as a variable:
<set-variable name="insurerId" value="#{
var body = context.Request.Body.As<JObject>(true);
return body["insurer_id"].Value<string>();
}" />
Afterwards you can use rewrite-uri with this variable and forward the request to the blackened.
The complete policy:
<policies>
<inbound>
<base />
<set-variable name="insurerId" value="#{
var body = context.Request.Body.As<JObject>(true);
return body["insurer_id"].Value<string>();
}" />
<rewrite-uri template="#("/api/relationship/approve/" + context.Variables.GetValueOrDefault<string>("insurerId"))" copy-unmatched-params="false" />
<set-backend-service base-url="https://rfqapiservicey27itmeb4cf7q.azure-api.net" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I am trying to check if a param exists in the body of the POST request.
But i get the following error :
{
"messages": [
{
"message": "Expression evaluation failed.",
"expression": "(string)context.Variables[\"param1\"] != null || ((string)context.Variables[\"param2\"] != null) || ((string)context.Variables[\"param3\"] != null) || ((string)context.Variables[\"param4\"] != null)",
"details": "Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String'."
},
"Expression evaluation failed. Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String'.",
"Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String'."
]
}
I define values like this :
<set-variable name="param1" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param1", null))" />
<set-variable name="param2" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param2", null))" />
<set-variable name="param3" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param3", null))" />
<set-variable name="param4" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param4", null))" />
This is my test code :
<when condition="#((string)context.Variables["param"] != null || ((string)context.Variables["param2"] != null)
|| ((string)context.Variables["param3"] != null) || ((string)context.Variables["param4"] != null))">
...
</when>
This is what I send in the post body :
{"param2":false}
I also tried with
context.Request.Body.As<JObject>
but I got the same error.
And also with just :
<set-variable name="param1" value="#(context.Request.Body.As<JObject>(preserveContent: true).GetValue("param1"))" />
What should I do?
This solution works for following request body:
{
"param2": "",
"param3": null,
"param4": "lorem"
}
The policy checks if a parameter exists, is null or is empty.
In case, the validation fails, a message will be set and returned.
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param1"] == null || context.Request.Body.As<JObject>(true)["param1"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param1"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param1 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param2"] == null || context.Request.Body.As<JObject>(true)["param2"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param2"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param2 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param3"] == null || context.Request.Body.As<JObject>(true)["param3"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param3"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param3 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param4"] == null || context.Request.Body.As<JObject>(true)["param4"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param4"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param4 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(!String.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("message")))">
<return-response>
<set-status code="400" reason="Bad Request" />
<set-body>#{
return context.Variables.GetValueOrDefault<string>("message");
}</set-body>
</return-response>
</when>
</choose>
<return-response>
<set-status code="200" reason="OK" />
<set-body>ok</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Sample with Postman:
Different solution
API Management has also validation policies.
By defining the OpenApi schema with required, pattern,..., the policy is able to validate it.
OpenApi:
paths:
/validate2:
post:
summary: Validate2
description: Validate2
operationId: validate2
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Request'
example:
param1: string
param2: string
param3: string
param4: string
responses:
'200':
description: OK
components:
schemas:
Request:
required:
- param1
- param2
- param3
- param4
type: object
properties:
param1:
type: string
param2:
type: string
param3:
type: string
param4:
type: string
Policy:
<policies>
<inbound>
<base />
<validate-content unspecified-content-type-action="prevent" max-size="102400" size-exceeded-action="ignore" errors-variable-name="validateContent">
<content type="application/json" validate-as="json" action="prevent" />
</validate-content>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Response:
{
"statusCode": 400,
"message": "Body of the request does not conform to the definition which is associated with the content type application/json. Invalid type. Expected String but got Null. Line: 3, Position: 18"
}
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.
Is there any way to decrypt a bearer token in an API management policy in order to create a condition it's acr_values, for example a tenant.
Looking at the MS documentation it does not seem possible, I would be looking to achieve something like:
<when condition="#(context.Request.Headers["Authorization"] --DO MAGIC HERE-- .acr_values["tenant"] == "contoso" ">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
Alternatively something like the example here but for setting the backed service:
http://devjourney.com/blog/2017/03/23/extract-jwt-claims-in-azure-api-management-policy/
Documentation I've read:
https://learn.microsoft.com/en-us/azure/api-management/api-management-transformation-policies#example-4
https://learn.microsoft.com/en-us/azure/api-management/policies/authorize-request-based-on-jwt-claims?toc=api-management/toc.json#policy
Did you try .AsJwt() method (https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables):
<policies>
<inbound>
<base />
<set-header name="tenant" exists-action="append">
<value>#{
var jwt = context.Request.Headers.GetValueOrDefault("Authorization").AsJwt();
return jwt?.Claims.GetValueOrDefault("tenant") ?? "unknown";
}</value>
</set-header>
<choose>
<when condition="#(context.Request.Headers.GetValueOrDefault("tenant", "unknown") == "some-tenant" )">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
Also I'm not sure if you need it as a header to backend request, if not consider using set-variable policy.
Ok so I got it working in a very hacky way, you can set vales of the decrypted token in the header and then set conditions on that header.
<policies>
<inbound>
<base />
<set-header name="tenant" exists-action="append">
<value>#{
string tenant = "unknown";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
tenant = (jwt.Claims.GetValueOrDefault("tenant", "unknown"));
}
}
}
return tenant;
}</value>
</set-header>
<choose>
<when condition="#(context.Request.Headers.GetValueOrDefault("tenant", "unknown") == "some-tenant" )">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
A few years have passed since this has been answered, but as I found a less verbose solution, without actually modifying the request headers, i thought it would be nice to share for others:
<set-variable name="tenant" value="#{
var authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
return authHeader.AsJwt()?.Claims.GetValueOrDefault("tenant", "");
}" />
...
<choose>
<when condition="#(context.Variables.GetValueOrDefault("tenant", "") == "your-tenant-id")">