Service callout with dynamic host name - apigee

I have a proxy in Apigee that uses a service callout to another proxy in the same environment. I would like to set the URL host for the callout to match the host of the initial request.
For example, if a request is made in the dev environment to:
https://example-dev.apigee.com/awesome-proxy
I need to make a call to:
https://example-dev.apigee.com/support-proxy
In a test environment the first call is to:
https://example-test.apigee.com/awesome-proxy
The support call needs to go to:
https://example-test.apigee.com/support-proxy
Here is how I would like to define the service callout policy:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout async="false" continueOnError="false" enabled="true" name="serviceCallout">
<DisplayName>serviceCallout</DisplayName>
<FaultRules/>
<Properties/>
<Request clearPayload="true" variable="example.request">
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
</Request>
<Response>example.response</Response>
<HTTPTargetConnection>
<Properties/>
<URL>{client.host}/support-proxy</URL>
</HTTPTargetConnection>
</ServiceCallout>
This will not save and complains about no protocol. The help indicates that this must be hard coded:
<HTTPTargetConnection>/<URL> element
The URL to the service being called. While the hostname portion of URL must be hard-coded, you can supply the remainder of the URL dynamically with a variable.
I found a variable to define the URL of a service callout:
servicecallout.{policy-name}.target.url
I attempted to use an assign message policy to dynamically set the variable, as follows:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" name="assignCalloutURL">
<DisplayName>assignCalloutURL</DisplayName>
<FaultRules/>
<Properties/>
<AssignVariable>
<Name>servicecallout.serviceCallout.target.url</Name>
<Value>{client.host}</Value>
<Ref/>
</AssignVariable>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>
This sets the URL to the literal text {client.host}
I have used the assign message policy in a similar fashion for other purposes and it actually resolves the variable listed. I'm not sure what's happening here.

client.host is not the correct variable to use, it returns an IP address 192.168....
I tried a few other variables:
proxy.url returns a strange host, it looks like internal Apigee machine names with a port. The proxy.url host times out when hit directly.
I ended up using virtualhost.aliases and proxy.pathsuffix. Here is the full JavaScript that solved it:
var base = context.getVariable("proxy.basepath");
var aliases = context.getVariable("virtualhost.aliases");
var url = "https://" + aliases[0] + base + "/support-proxy";
context.setVariable("servicecallout.serviceCallout.target.url", url);

Modifying target URLs gets a little goofy when dealing with the linter that ensures URLs have valid values.
This will not save and complains about no protocol.
This is because you're missing the scheme (https:// or http://) before the URL (client.host doesn't include scheme).
This sets the URL to the literal text {client.host}
That bit's not working properly because you need to use the ref tag to retrieve the existing variable:
<AssignVariable>
<Name>servicecallout.serviceCallout.target.url</Name>
<Ref>client.host</Ref>
</AssignVariable>
Now, that might work for the service callout, but it's not probably not going to work for setting the target URL.
I've ended up creating JavaScript policies to handle target.url, since the AssignMessage has been problematic for me:
var scheme = context.getVariable("client.scheme");
var host = context.getVariable("client.host");
var pathsuffix = context.getVariable("proxy.pathsuffix");
var newUrl = scheme + host + pathsuffix;
context.setVariable("target.url", newUrl);

Take look at this - it worked for me.

Related

How to fix 'Cleartext HTTP traffic to x not permitted' in xamarin android

I have an issue in my application Cleartext HTTP traffic to x not permitted.
I've already tried putting android:usesCleartextTraffic="true" in my manifest. But i want to change "android:usesCleartextTraffic" flag to "false" to prevent unencrypted traffic from being sent.
How to solve this?
If at some point you want to move to MAUI (which has no AssemblyInfo.cs), you might want to add UsesCleartextTraffic) to your Application attribute in Platforms/Android/MainApplication.cs:
#if DEBUG // connect to local service on the
[Application(UsesCleartextTraffic = true)] // emulator's host for debugging,
#else // access via http://10.0.2.2
[Application]
#endif
public class MainApplication : MauiApplication
{
...
}
You can fix this with one line of code.
Open AssemblyInfo.cs in your android project under properties and add the code below:
[assembly: Application(UsesCleartextTraffic = true)]
In Maui, expand Platforms/Android and edit MainApplication.cs.
Replace "[Application]", near the top, with "[Application(UsesCleartextTraffic = true)]"
Assuming you are accessing a server that doesn't support HTTPS, then you can create exceptions in your network security config.
You can create a file net_sec_conf.xml like this:
<?xml version="1.0" encoding="utf-8" ?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">api.example.org</domain>
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</domain-config>
</network-security-config>
and then in manifest file add this line:
android:networkSecurityConfig="#xml/net_sec_conf"
(assuming you have put the file in xml folder).
This way cleartext HTTP traffic will only be allowed for the specified domain.
Of course, if the server supports HTTPS, then you just need to change your URL "http://..." to "https://...".

APIGEE API Proxy - How do I prevent a request from hitting the target based on body content

I am trying to prevent requests that contain a json payload with a certain attribute value from hitting my target backend.
For example:
{"status":"pending"}
If the status is "pending", I don't want it to hit my target backend until I see a status of "delivered".
What is the best way to do this?
I have a step in the proxy endpoint preflow that uses a javascript policy to identify the attribute and its value. Now that I know this, how can I prevent the request from hitting the target backend and instead just return a 200 ok to the requester?
As you have the JS to identify the attribute already, now have a policy "Raise-Fault-Attribute" in your preflow after your JS policy.
<Step>
<Condition>(Status is pending) or (Status is null)</Condition>
<Name>Raise-Fault-Attribute</Name>
</Step>
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RaiseFault async="false" continueOnError="false" enabled="true" name="Raise-Fault-Attribute">
<DisplayName>Raise Fault Attribute</DisplayName>
<Properties/>
<FaultResponse>
<Set>
<Headers/>
<Payload contentType="text/xml">
....
</Payload>
<StatusCode>500</StatusCode>
<ReasonPhrase>Server Error</ReasonPhrase>
</Set>
</FaultResponse>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</RaiseFault>
`
Hope this helps...

Unable to Resolve Host Name of SOAP Service

I've added a service reference to a third party API that I have no control over.
The endpoint looks like this (URL masked) and the service is available from this address as I can call it using SOAPUI:
<endpoint address="https://some-api.domain.com/service-name/do-something"
binding="basicHttpBinding" bindingConfiguration="FindPolicyPortSoap11"
contract="FindPolicy.FindPolicyPort" name="FindPolicyPortSoap11" />
When calling the service, I get the error The remote name could not be resolved: 'some-api.domain.com which is technically correct as some-api.domain.com will produce a 404 whereas some-api.domain.com/service-name/do-something works as expected.
What can I do to get this working?
Have you tried setting the defaultproxy to true in system.net tag?
Accordig to MSDN
If the defaultProxy element is empty, the proxy settings from Internet
Explorer will be used. This behavior is different from version 1.1 of
the .NET Framework.
<defaultProxy
enabled="true|false"
useDefaultCredentials="true|false">
<bypasslist> … </bypasslist>
<proxy> … </proxy>
<module> … </module>
</defaultProxy>
https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/network/defaultproxy-element-network-settings
This error related to resolving some-api.domain.com to specific ip address
I would try using nslookup or
http://www.kloth.net/services/nslookup.php
Another option is to add this domain to your hosts file just to test it

How to set custom error message for Quota violation?

I tried multiple options of publishing custom error message for Quota violation. But I was not able to produce custom error message.
Please provide step by step process to generate custom error message. Thanks!
Create a Raise Fault Policy (but don't attach it to a flow):
<RaiseFault async="false" continueOnError="false" enabled="true" name="Fault-Bad-Quota">
<DisplayName>Fault-Bad-Quota</DisplayName>
<FaultRules/>
<Properties/>
<FaultResponse>
<Set>
<Headers/>
<Payload contentType="text/plain">My Custom Fault for {fault.name}</Payload>
<StatusCode>500</StatusCode>
<ReasonPhrase>Server Error</ReasonPhrase>
</Set>
</FaultResponse>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</RaiseFault>
Add a Conditional Fault to your Proxy configuration
<FaultRules>
<FaultRule name="BadQuota">
<Step>
<Name>Fault-Bad-Quota</Name>
</Step>
<Condition>fault.name = "QuotaViolation"</Condition>
</FaultRule>
</FaultRules>
You can add a global fault policy to your proxy default.xml. If you're using the UI go to the word "default" right under your target.

Access .asmx webservice cross domain and load results into cascading dropdown

I have 2 sites.
I also have a webservice.
You can see this in action when I load countrynames in the cascading dropdown on: http://www.mydomain.com/trouwlocaties/zoeken-uitgebreid
However, the same webservice throws an error on: http://otherdomain.com/weddingvenues/search-advanced
As you can see the dropdown shows 'method error -1' and in my Chrome console I see: 500 (Internal Server Error), where the client tries to GET the .asmx service, where on toptrouwen it uses POST (which is as I believe what's supposed to happen and also more secure).
This is the GetCountries webservice:
<System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
<ToolboxItem(False)> _
Public Class geolocation
'<System.Web.Script.Services.ScriptService()> _
'<WebService(Namespace:="http://tempuri.org/")> _
'<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
'<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function GetCountries(ByVal knownCategoryValues As String, ByVal category As String) As CascadingDropDownNameValue()
Dim values As New List(Of CascadingDropDownNameValue)
Dim myConnection As SqlConnection = GetConnection()
Dim cmd As New SqlCommand(String.Format("SELECT id,name as title FROM country order by title asc", Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName), myConnection)
Try
myConnection.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader
Dim CountryName As String
Dim CountryID As Integer
While reader.Read
CountryName = reader("title").ToString
Int32.TryParse(reader("id"), CountryID)
values.Add(New CascadingDropDownNameValue(CountryName, CountryID.ToString))
End While
Catch ex As Exception
Finally
myConnection.Close()
End Try
Return values.ToArray
End Function
End Class
First I tried adding this to my web.config:
<system.web>
<webServices>
<protocols>
<remove name="Documentation"/>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
</system.web>
After doing that, I receiving this in my Chrome console:
Uncaught SyntaxError: Unexpected token <
Where apparently the result was not interpreted as XML, but my guess is JSON. After some Google searches I believed this had to do with the MIME type, but I never found out how to change that to XML for this service.
So I continued searching and found something else, I was reading these posts:
http://social.msdn.microsoft.com/forums/en-us/asmxandxml/thread/F80BDA62-C87A-4BDA-8CB1-F2CFAD1C8891
Uncaught SyntaxError: Unexpected token < -- in jQuery ajax
Where apparently it might be a 'cross-domain issue'.
So I ended up with creating these files:
clientaccesspolicy.xml
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
crossdomain.xml
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<?xml version="1.0" ?>
<cross-domain-policy>
<allow-access-from domain="*" />
<allow-access-from domain="*.otherdomain.com" secure="false" />
</cross-domain-policy>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="GetCountries" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://www.mydomain.com/geolocation.asmx"
binding="basicHttpBinding" name="GeoLocation" />
</client>
</system.serviceModel>
</configuration>
In the first example link that user also added attributes bindingConfiguration="DashboardServiceSoap" and contract="DashboardService.DashboardServiceSoap", but I have no idea what I would have to fill in there for my case.
I'm still stuck, I don't know what is the right track and how to configure my setup.
UPDATE 21-06-2013
Updated my web.config with:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
</customHeaders>
</httpProtocol>
I also tried the following 4 configurations:
<System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
<ToolboxItem(False)> _
Public Class geolocation
Inherits System.Web.Services.WebService
Scenario 1 and 2 With this method definition:
<WebMethod()> _
Public Function GetCountries(ByVal knownCategoryValues As String, ByVal category As String) As CascadingDropDownNameValue()
Scenario 1: WITH protocols section in web.config
<webServices>
<protocols>
<remove name="Documentation"/>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
Works correct on .nl domain
Throws method error -1 on .com domain. Chrome Console shows: Uncaught SyntaxError: Unexpected token < GetCountries:1
Scenario 2: WITHOUT protocols section in web.config
Works correct on .nl domain
Throws method error -1 on .com domain. Chrome Console shows: GET http://www.otherdomain.com/geolocation.asmx/GetCountries?knownCategoryValues=%22%22&category=%22Country%22&callback=Sys._jsonp0 500 (Internal Server Error) ScriptResource.axd:7773
Scenario 3 and 4 with this method definition:
<WebMethod()> _
<ScriptMethod(UseHttpGet:=True, ResponseFormat:=System.ServiceModel.Web.WebMessageFormat.Json)> _
Public Function GetCountries(ByVal knownCategoryValues As String, ByVal category As String) As CascadingDropDownNameValue()
Scenario 3: WITH protocols section in web.config
<webServices>
<protocols>
<remove name="Documentation"/>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
Throws method error 500 on .nl domain. Chrome Console shows: POST http://www.mydomain.com/geolocation.asmx/GetCountries 500 (Internal Server Error) catcher.js:197
Throws method error -1 on .com domain in dropdown. Chrome Console shows: Uncaught SyntaxError: Unexpected token < GetCountries:1
Scenario 4: WITHOUT protocols section in web.config
Throws method error 500 on .nl domain. Chrome Console shows: Failed to load resource: the server responded with a status of 500 (Internal Server Error)
Throws method error -1 on .com domain in dropdown. Chrome Console shows: GET http://www.otherdomain.com/geolocation.asmx/GetCountries?knownCategoryValues=%22%22&category=%22Country%22&callback=Sys._jsonp0 500 (Internal Server Error)
Also I'm not explicity calling the .asmx from script, I let the cascading dropdown do that work for me. Like so:
<asp:DropDownList ID="ddlCountries" CssClass="textbox" AutoPostBack="true" runat="server"></asp:DropDownList>
<cc1:cascadingdropdown ID="cddCountries" runat="server" Category="Country" Enabled="True" LoadingText="<%$Resources:Glossary,loading %>" PromptText="<%$Resources:Glossary,country_choose %>"
ServiceMethod="GetCountries" TargetControlID="ddlCountries">
</cc1:cascadingdropdown>
code-behind
cddCountries.ServicePath = "http://www.mydomain.com/geolocation.asmx"
I don't know if the fact that I'm using these pre-defined elements have anything to do with my issue, and I could better call the .asmx service via script myself and fill the dropdowns. If so: I have no idea how to do so.
You are correct that this is a cross-origin problem. There are several ways of handling this:
You can turn your web service into JSONP as long as the data that needs to go to the web service is not terribly large. The data coming from the service can be as large as you like though. By not terribly large, it must be about 2k characters or less - you can calculate the amount of data that can be sent in one JSONP request by knowing that it is sent as part of the get request from the src attribute of the script tag.
Here's a good SO answer on JSONP with which you may already be familiar:
What is JSONP all about?
UPDATE
Here's an example of doing JSONP in VB.NET:
http://www.sattsoft.com/tutorials/contents/1/14/cross-domain-call-using-ajax-jquery-jsonp-and-vb-net-web-service.html
You can create a sub-domain of www.wunderweddings.com, call it perhaps "api.www.wunderweddings.com" and use DNS to point that subdomain to the right place using either an A or CNAME record. Then you would embed a tiny (invisible) iframe into your client-side page that would point to this new api host (be sure to specify the src as "//api.www.underweddings.com" so as to match http/s of the containing page), and use javascript within the iframe to promote its document.domain to www.wunderweddings.com which you may do through script injection but its easier to just have that page on the server provide the script to do it, then you can communicate freely between the iframe which points to your api and the page containing the iframe which is at www.wunderweddings.com. So code inside the iframe would go access the web service for you, get the data, promote its document.domain, and notify the containing page.
If you know postMessage is always available on your clients (probably not though) you can do the above without changing the document.domain.
Points 2 and 3 above probably sound like a hassle! Especially if you intend to expand the web services you offer and/or the number of domains accessing that service. If so, I would very highly recommend utlizing EasyXDM, it is a wonderful and quite powerful library for doing client-side cross-domain RPC:
http://easyxdm.net/wp/
EasyXDM provides fallbacks from postMessage if it isn't available such as communication through the hash or the name attribute, among a few other things.
You can fix the crossdomain.xml. Now here's where I'm a little rusty but I'll give you my best guess:
UPDATE
You want your crossdomain.xml to like this:
<?xml version="1.0" ?>
<cross-domain-policy>
<allow-access-from domain="*" />
<allow-access-from domain="*.wunderweddings.com" />
</cross-domain-policy>
The first child of the "<cross-domain-policy>", that is "<allow-access-from domain="" />" will make it completely unrestricted whereas "<allow-access-from domain=".wunderweddings.com" />" will make it so only wunderweddings.com and subdomains will be allowed to do a crossdomain call by the browser. You don't need both "allow-access-from" tags, but at least one of them.
I'm not sure why that configuration stuff is in there, it shouldn't be. I completely failed noticing that the first time around, that is almost certainly your problem. Also make sure that crossdomain.xml is being served from the other server, the one with the web service.
So just to clarify, that crossdomain.xml should not have that extra XML at the bottom, the <onfiguration>...</configuration> tags and everything inside of it, all of that is leaking in from someplace and shouldn't be inside crossdomain.xml
FINAL UPDATE
For those reading this answer who have a similar problem, Floran discovered the problem with the invalid character:
this had to be added to the top of the page:
<asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
<Services>
<asp:ServiceReference Path="geolocation.asmx" />
</Services>
</asp:ScriptManagerProxy>

Resources