manipulate response in apim outbound policies - azure

I have SOAP service that I have converted to rest in apim and returns the response in json using the following outbound policy:
<outbound>
<base />
<xml-to-json kind="direct" apply="always" consider-accept-header="false" />
</outbound>
Response output is like below:
"response": {"currentABNRecord": {
"ABN":
{
"identifierValue": "xxxxx",
"identifierStatusCode": "ACT",
"issuingPartyCode": null,
"replacedIndicator": "N"
}
}
}
My requirement is to "manipulate" conditionally data by modifying "identifierStatusCode" and appending a new attribute "termsAnConditions" when "identifierStatusCode" is "ACT" and if it is DEL to "DELETED" to something else like below :
"response": {"currentABNRecord": {
"ABN":
{
"identifierValue": "xxxxx",
"identifierStatusCode": "ACT",
"issuingPartyCode": null,
"replacedIndicator": "N",
"termsAnConditions": "Some terms and conditions"
}
}
}
How can I call an azure function(perform all the conditional logic in it ) in the outbound policy and return the modified object. Is this the correct approach or can I do it policies.

Related

"JWT Validation Failed: JWT not present.." in Azure API Management Service

For reference I am attempting to reproduce the solution talked about here: https://www.tech-findings.com/2020/02/securing-logic-app-with-azure-active-directory.html to use API Management to secure an Azure Logic App.
I am getting a JWT Error. When I visit the app url in the browser it gives:
{ "statusCode": 404, "message": "Resource not found" }
In the API Management Service test I get:
HTTP/1.1 401 Unauthorized
Following the trace through it shows:
validate-jwt (-0.111 ms)
{
"message": "JWT Validation Failed: JWT not present.."
}
I did some googling and tried the solutions at:
JWT validation failure error in azure apim
and
https://learn.microsoft.com/en-us/answers/questions/108008/azure-apim-jwt-token-validation-policy.html
Here is the inbound policy of from the API Management design:
<policies>
<inbound>
<base />
<set-method id="apim-generated-policy">POST</set-method>
<rewrite-uri id="apim-generated-policy" template="/request/paths/invoke//?api-version=2016-06-01&sp=/triggers/request/run&sv=1.0&sig={{[[LOGIC APP NAME]]_request-invoke_XXXXXXXXXXXXXXXXXXXXXXXX}}" />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Request is not authorized or token failed" require-expiration-time="false" require-scheme="Bearer" require-signed-tokens="true">
<openid-config url="https://login.windows.net/[[TENANT NAME]].onmicrosoft.com/.well-known/openid-configuration" />
<audiences>
<audience>[[THE ID OF A REGISTERED APP]]</audience>
</audiences>
</validate-jwt>
<set-header name="Authorization" exists-action="delete" />
<set-header name="apim-generated-policy" exists-action="delete" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
This is the manifest of the registered app:
{
"id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"acceptMappedClaims": null,
"accessTokenAcceptedVersion": 2,
"addIns": [],
"allowPublicClient": null,
"appId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"appRoles": [],
"oauth2AllowUrlPathMatching": false,
"createdDateTime": "2020-12-22T19:48:36Z",
"disabledByMicrosoftStatus": null,
"groupMembershipClaims": null,
"identifierUris": [
"api://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
],
"informationalUrls": {
"termsOfService": null,
"support": null,
"privacy": null,
"marketing": null
},
"keyCredentials": [],
"knownClientApplications": [],
"logoUrl": null,
"logoutUrl": null,
"name": "LabsTestApp",
"oauth2AllowIdTokenImplicitFlow": false,
"oauth2AllowImplicitFlow": false,
"oauth2Permissions": [],
"oauth2RequirePostResponse": false,
"optionalClaims": null,
"orgRestrictions": [],
"parentalControlSettings": {
"countriesBlockedForMinors": [],
"legalAgeGroupRule": "Allow"
},
"passwordCredentials": [],
"preAuthorizedApplications": [],
"publisherDomain": "[[TENANT NAME]].onmicrosoft.com",
"replyUrlsWithType": [],
"requiredResourceAccess": [],
"samlMetadataUrl": null,
"signInUrl": null,
"signInAudience": "AzureADandPersonalMicrosoftAccount",
"tags": [],
"tokenEncryptionKeyId": null
}
Hoping you can help out - point me in the right direction.
For this question, there are more than one problem in your steps.
1. You mentioned the error { "statusCode": 404, "message": "Resource not found" } when you request the url in browser. The reason is when you request it in browser, it request with Get method but the url should be request with Post method. So it shows 404 not found.
2. When you test in API Management service, it shows 401 Unauthorized. The reason for this error is you did not provide the access token or the access token you provided is invalid. The steps in the document you mentioned are incomplete, please refer to the steps below:
1). First please make sure you have completed all of the steps in the document you provided.
2). Then go to the app you registered in azure ad and click "Manifest" tab, add a appRole in the json of "Manifest".
You can specify a name(anything you want) for this role, I named the role as Writer as the screenshot above shows. And you can also specify a "id"(in GUID format) as the value of the id field in appRole. For more details of add appRole, you can refer to this document.
3). You need to register another app in azure ad as the client app. Do same register operation as your document shows to register the other app, I registered the app and named huryGetToken4. Go to this app and click "API permissions" tab, click "Add a permission" and find the original app you registered, then add the permission Writer.
After add the Writer permission, you also need to click "Grant admin consent for xxx".
Then click "Certificates & secrets" tab, click "New client secret" to generate a client secret. Copy this secret because it will just show one time.
4). Then you need to get access token, please refer to the screenshot below to request for access token.
In the screenshot above, you need to replace the <tenant id> with your tenant id in the host url. And you also need to input the first three parameters. The last parameter grant_type is static.
5). Request for the access token, you will get the response like below screenshot.
Copy the value of access_token and paste it to this page to decode the token, you can see the claim roles with Writer permission in it. This claim is what you need to check in the <validate-jwt> policy in your APIM.
6). Go to your apim and click the pencil icon of validate-jwt policy.
7). Edit the "Reauired claims" like screenshot below:
8). After that, you can test the api in APIM service. Add a header with key: Authorization, value: Bearer <your access token>(note there is a blank between Bearer and access token).

Need to modify response using Azure APIM set-body policy

I am relatively new to Azure APIM and I have this backend which I am able to invoke which returns the response in format
{
"data": {
"attributes": {
"municipalities": []
}
}
}
I want to modify the response so that the data is returned in the format
{
"data": {
"municipalities": []
}
}
I have tried using the set body liquid template
<outbound>
<set-body template="liquid">
{
"data": {
"municipalities": {{body.data.attributes.municipalities}}
}
}</set-body>
</outbound>
But the response I get is just
{
"data": {
"municipalities":
}
}
If someone can point out to me what I am doing wrong or if there is a better way to do this?
I also tried to use the below code just to check if I am able to retrieve the "data" attribute but I got the below error in the trace part of Azure APIM Test
<outbound>
<set-body>
#{
JObject inBody = context.Request.Body.As<JObject>();
return inBody["data"].ToString();
}
</set-body>
</outbound>
ERROR:
{
"messages": [
{
"message": "Expression evaluation failed.",
"expression": " \n JObject inBody = context.Request.Body.As<JObject>();\n return inBody[\"data\"].ToString(); \n",
"details": "Object reference not set to an instance of an object."
},
"Expression evaluation failed. Object reference not set to an instance of an object.",
"Object reference not set to an instance of an object."
]
}
As body.data.attributes.municipalities is a array, we can't put it "municipalities": directly. For your requirement of modify the format of the json data, we need to write every property of each array item. Below is my steps for your reference.
My json is:
{
"data": {
"attributes": {
"municipalities": [
{
"name": "hury",
"email": "hury#mail.com"
},
{
"name": "mike",
"email": "mike#email.com"
}
]
}
}
}
And my liquid template show as:
==============================Update============================
First the code JObject inBody = context.Request.Body.As<JObject>(); you shared can't get the response data. You should use JObject inBody = context.Response.Body.As<JObject>();.
Then for your question about "Is there an easier way to remove .attributes part, I provide a solution below for your reference.
Do not use liquid template, use Replace to replace "attributes": { with empty and use Substring to remove the last }.
<set-body>#{
JObject inBody = context.Response.Body.As<JObject>();
string str = inBody.ToString();
string str1 = str.Replace("\"attributes\": {", "");
string result = str1.Substring(0, str1.Length-1);
return result;
}</set-body>
Notice: This approach requires a high degree of specification for your response data format.
I also managed to get the desired output with the below code, so that the code is generic and doesnt need to do any string operations.
<set-body>#{
JToken inBody = context.Response.Body.As<JToken>()["data"]["attributes"];
JObject jsonObject = new JObject{
["data"] = inBody
};
return jsonObject.ToString();
}</set-body>

Azure API management - how to access product information

In APIM, I am trying to access the Product information at API level policy, as i have to execute some logic based on product name. I am using the below code
<policies>
<inbound>
<set-variable name="ProductName" value="#{
return context.Product.Name;
}" />
But, when trying to post request from postman, i can see the below exception in trace.
{
source: "set-variable",
timestamp: "2020-08-19T14:42:24.4936554Z",
elapsed: "00:00:00.0358409",
data:- {
messages:- [
-{
message: "Expression evaluation failed.",
expression: " return context.Product.Name; ",
details: "Object reference not set to an instance of an object."
},
"Expression evaluation failed. Object reference not set to an instance of an object.",
"Object reference not set to an instance of an object."
]
}
}
Why it is null ? is this the case, that i can't access the property in inbound scope. Need guidance. Or, is there any other way i can access the Product.Name property.
Thank you.
You can get the Product Name with #(context.Product.Name).
<inbound>
<base />
<set-variable name="aaa" value="#(context.Product.Name)" />
<set-body template="liquid">
{
"success": true,
"var1": {{context.Variables["aaa"]}}
}
</set-body>
</inbound>
In test, set the product name as Starter and you will get the snapshot as below.

Client Certificate is not being passed on by Azure Application Gateway

I've picked up something that someone else has set up.
We have an API Management instance, siting behind an Application Gateway, which has a policy on an API:
<inbound>
<choose>
<when condition="#(context.Request.Certificate == null)">
<return-response>
<set-status code="403" reason="Client certificate required..d1PD" />
</return-response>
</when>
</choose>
<choose>
<when condition="#(!context.Request.Certificate.Verify())">
<return-response>
<set-status code="403" reason="Client certificate cannot be verified..d2PD " />
</return-response>
</when>
</choose>
<choose>
<when condition="#(!context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint))">
<return-response>
<set-status code="403" reason="Client certificate is untrusted or invalid..d3PD" />
</return-response>
</when>
</choose>
<base />
</inbound>
In Postman I am passing a cert and key. The Postman Console shows
Client Certificate:
keyPath:"C:\selfsigned\internalscm.X.com.key"
pemPath:"C:\selfsigned\internalscm.X.com.crt"
pfxPath:""
I'm passing Ocp-Apim-Trace in the header of the request so am getting a trace back which contains:
traceEntries {2}
inbound [10]
..
6 {4}
source : authentication-certificate
timestamp : 2019-08-06T08:55:31.3435485Z
elapsed : 00:00:00.0006857
data {2}
message : Certificate was attached to request per configuration.
certificate {...}
7 {4}
source : choose
timestamp : 2019-08-06T08:55:31.3435485Z
elapsed : 00:00:00.0007011
data {3}
message : Expression was successfully evaluated.
expression : context.Request.Certificate == null
value : true
UPDATE:
The authentication-certificate assessment is of the backend's certificate and is nothing to do with the client certificate (.key and .crt) that Postman claims to be including in the request (the same result is returned if I pass a pfx and password instead of .key and .crt).
When I hit the API that the Gateway is protecting, I can see in the trace that it is processing the client certificate (and returning a 200):
{
"source": "client-certificate-handler",
"timestamp": "2019-08-09T15:47:46.3825928Z",
"elapsed": "00:00:00.0005974",
"data": "Requesting client certificate because next handler requires access to it."
},
{
"source": "client-certificate-handler",
"timestamp": "2019-08-09T15:47:46.6950495Z",
"elapsed": "00:00:00.3225172",
"data": "Client certificate thumbprint '6C03F4E7999999999999999999999999' received."
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.6950495Z",
"elapsed": "00:00:00.3225288",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "context.Request.Certificate == null",
"value": false
}
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.9606395Z",
"elapsed": "00:00:00.5849700",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "!context.Request.Certificate.Verify()",
"value": false
}
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.9606395Z",
"elapsed": "00:00:00.5850060",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "!context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint)",
"value": false
}
}
So it looks like AppGateway is dropping the client certificate.
The trace doesn't give me enough to start deducing why the client certificate (assuming that Postman is transmitting it to the Gateway as it does the API) is being dropped. Where should I start?
For ref, when I remove the policy the request is processed as expected.
I'm not sure if you can make AppGateway pass the certificate - you need to check their docs. The reason I'm skeptical is bacuse the whole idea of AppGateway is to look into traffic and provide protection by doing that. And the only way to d othat is to terminate SSL connection at AppGateway level. See here for more information: https://learn.microsoft.com/en-us/azure/application-gateway/ssl-overview there are two modes for AppGateway: SSL temination when AppGateway makes HTTP (not HTTPS) calls to backend, and SSL end-to-end when AppGateway uses own SSL certificate to connect to backend.
Some client certificate information can be passed to backend via server variables: https://learn.microsoft.com/en-us/azure/application-gateway/rewrite-http-headers-url#mutual-authentication-server-variables

Policy rewrite-uri To Append Context Variable in Azure APIM

What is the approach to a simple append to the url of a context variable such as context.Variables["accountKey"] during a policy rewrite?
The end result should be /accounts/232.
I have success earlier in setting it
set-variable (0.003 ms)
{
"message": "Context variable was successfully set.",
"name": "accountKey",
"value": "232"
}
One of the things tried:
<policies>
<inbound>
<base />
<rewrite-uri template="/accounts/{accountKey}" />
</inbound>
But I get this error
> Error Receive
> rewrite-uri (0.260 ms) {
> "messages": [
> null,
> "Variable accountKey has no value.",
> "Variable accountKey has no value."
> ] }
Configure the inbound rule in the policy as follows:
<inbound>
<base />
<set-variable name="accountKey" value="232" />
<rewrite-uri template="#{
return "/account/" + context.Variables.GetValueOrDefault<string>("accountKey");
}"/>
</inbound>
{} in rewrite-uri are for query string parameters in the original request URL.
Find more details about rewrite-uri section in the Microsoft docs at Rewrite URL - API Management transformation policies.
I ended up using this call as shown as an alternative:
<rewrite-uri template="#($"/account/{(string)context.Variables["accountKey"]}/")" />
In my case, my URL was
https://test.com/send/
I need to add query string from context variable name (name = myName)
https://test.com/send/myName
This is working for me:
<rewrite-uri template="#($"{(string)context.Variables["name"]}")" />

Resources