Camel exchange expired via jetty continuation - jetty

Is there a possibility in Apache Camel to register a handler for managing exchanges that cannot be written to jetty endpoint http response because continuation timeout has been reached?

I'll just add my notes on that because I made it available in my project by modifying CamelContinuationServlet in the if (continuation.isExpired()) block like this
if (continuation.isExpired()) {
String id = (String) continuation.getAttribute(EXCHANGE_ATTRIBUTE_ID);
// remember this id as expired
expiredExchanges.put(id, id);
log.warn("Continuation expired of exchangeId: {}", id);
consumer.getBinding().doWriteExceptionResponse(new TimeoutException(), response);
return;
}
in combination with a custom HttpBinding called ErrorHandlingHttpBinding in my code like this
public class ErrorHandlingHttpBinding extends DefaultHttpBinding {
#Override
public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException {
if (exception instanceof TimeoutException) {
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
response.getWriter().write("Continuation timed out...");
} else {
super.doWriteExceptionResponse(exception, response);
}
}
}
registered as spring bean with id="errorHandlingHttpBinding" and referred in the component string as jetty:http://localhost:21010/?useContinuation=true&continuationTimeout=1&httpBindingRef=errorHandlingHttpBinding.

No this is not possible. Maybe you need to set a higher timeout if you have some slow processing exchanges.
You are welcome to dive in the Jetty APIs to see if you can find a hook for such a onTimeout event and see what it takes to support that in camel-jetty.

Related

Camel Restlet sends response to client asynchronously

I have a scenario that I'm using camel-restlet component to receive post requests, I'm forwarding these requests to an external web service, after receiving the response code from the external service, I need to add this response code to my own response to the client asynchronously.
Im trying to save the response object to a hashMap where key is an unique serial number generated based on the request content, once upon receiving the response from external web service, I can retrieve the response object from the hashMap using this unique key. Seems like restlet saves the response to exchange.getOut() message and sends back to the client synchronously which is not something I want. Not setting an out message would give me a nullPointerException.
route Class:
public class ReceiveRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("restlet:http://localhost:8083/api/atmp?restletMethod=post")
.to("activemq:queue:requestReceiveQueue");
from("activemq:queue:requestReceiveQueue")
.process(new RequestProcessor())
.to("activemq:queue:requestSendQueue");
from("activemq:queue:requestSendQueue")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.bean("responseProcessor");
}
}
requestProcessor class:
public class RequestProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
byte[] bytes = (byte[])message.getBody();
String body = new String(bytes);
String atmpId = GUIDGenerator.generateAtmpSerialNumber();
String terIndentifier = GUIDGenerator.generateTerminalIdentifier(body);
MapLookupHelper.insertResponse(atmpId, terIndentifier, exchange);
Map<String, Object> messageMap = new HashMap<String, Object>();
messageMap = FormatUtil.parseJson(body);
messageMap.put("ATMPId", atmpId);
exchange.getIn().setBody(messageMap.toString());
}
}
responseProcessor class
#Component
public class ResponseProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String responseCode = in.getHeader(Exchange.HTTP_RESPONSE_CODE).toString();
String body = in.getBody().toString();
Map<String, Object> resMap = new HashMap<String, Object>();
resMap = FormatUtil.parseJson(body);
String atmpId = resMap.get("ATMPId").toString();
Exchange ex = MapLookupHelper.getOutMessage(atmpId);
ex.getOut().setHeader("HostResponseCode", responseCode);
ex.getOut().setBody(resMap.toString());
}
}
I'm new to Apache Camel and would like to know if restlet is the right way to go, if not, any suggestion on how I can handle async responses to client in Camel? Is AsyncProcessor only solution to such scenario?
I think it's not issue of restlet. Your exchange pattern is InOut, that's why all jms-endpoint's waiting synchronously result of your .bean("responseProcessor").
Even if you change pattern to InOnly your client will not receive response asynchronously. I think you should make another route's architecture, like below:
from("restlet:http://localhost:8083/api/atmp_asyncRequest?restletMethod=post")
.process(exchange -> {
exchange.setProperty("uniqueRequestId", GUIDGenerator.generateAtmpSerialNumber());
})
.inOnly("seda:requestReceiveQueue")// here starts async processing of your request
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// make here response for client with generated request id
});
from("seda:requestReceiveQueue")
.process(exchange -> {
// prepare\process request if need
})
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// save somewhere prepared response for client bound to generated request id
});
from("restlet:http://localhost:8083/api/atmp_getResponse?restletMethod=post")
.process(exchange -> {
String requestId = ;//extract request id from client's request
Object body = ;//find response that you saved asynchronously by extracted request id
// if response not found, then async processing request not ended, so you should send message to client to continue polling
exchange.getIn().setBody(body);
});
That will work if you haven't callback server for async responses on client's side.
Also you can use Seda component instead of jms, for queueing tasks between routes.

Same-Site flag for session cookie in Spring Security

Is it possible to set Same-site Cookie flag in Spring Security?
And if not, is it on a roadmap to add support, please? There is already support in some browsers (i.e. Chrome).
New Tomcat version support SameSite cookies via TomcatContextCustomizer. So you should only customize tomcat CookieProcessor, e.g. for Spring Boot:
#Configuration
public class MvcConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer sameSiteCookiesConfig() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
context.setCookieProcessor(cookieProcessor);
};
}
}
For SameSiteCookies.NONE be aware, that cookies are also Secure (SSL used), otherwise they couldn't be applied.
By default since Chrome 80 cookies considered as SameSite=Lax!
See SameSite Cookie in Spring Boot and SameSite cookie recipes.
For nginx proxy it could be solved easily in nginx config:
if ($scheme = http) {
return 301 https://$http_host$request_uri;
}
proxy_cookie_path / "/; secure; SameSite=None";
UPDATE from #madbreaks:
proxy_cookie_flags iso proxy_cookie_path
proxy_cookie_flags ~ secure samesite=none;
Instead of a Filter, In your Authentication Success Handler, you can mention in this way.
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
clearAuthenticationAttributes(request);
addSameSiteCookieAttribute(response);
handle(request, response);
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
}
}
It was mentioned in one of the answers. Couldn't find the link after I've implemented it.
All possible solutions here failed for me. Every time I tried a filter or interceptor, the Set-Cookie header had not yet been added. The only way I was able to make this work was by adding Spring Session and adding this bean into one of my #Configuration files:
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
return serializer;
}
Anyway hope this helps someone else in my same situation.
You can always set cookie values by yourself in the Java world if you can get an instance of the HttpServletResponse.
Then you can do:
response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")
In spring-security you can easily do this with a filter, here is an example:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");
chain.doFilter(request, response);
}
}
Add this filter to your SecurityConfig like this:
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class)
Or via XML:
<http>
<custom-filter after="BASIC_AUTH_FILTER" ref="myFilter" />
</http>
<beans:bean id="myFilter" class="org.bla.CustomFilter"/>
It isn't possible. There is support for this feature in Spring Session: https://spring.io/blog/2018/10/31/spring-session-bean-ga-released
I came up with a solution similar to Ron's one. But there is one important thing to note:
Cookies for cross-site usage must specify SameSite=None; Secure
to enable inclusion in third party context.
So I've included Secure attribute in header. Also, you don't have to override all three methods when you don't use them. It is only required when you are implementing HandlerInterceptor.
import org.apache.commons.lang.StringUtils;
public class CookiesInterceptor extends HandlerInterceptorAdapter {
final String sameSiteAttribute = "; SameSite=None";
final String secureAttribute = "; Secure";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
addEtagHeader(request, response);
Collection<String> setCookieHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);
if (setCookieHeaders == null || setCookieHeaders.isEmpty())
return;
setCookieHeaders
.stream()
.filter(StringUtils::isNotBlank)
.map(header -> {
if (header.toLowerCase().contains("samesite")) {
return header;
} else {
return header.concat(sameSiteAttribute);
}
})
.map(header -> {
if (header.toLowerCase().contains("secure")) {
return header;
} else {
return header.concat(secureAttribute);
}
})
.forEach(finalHeader -> response.setHeader(HttpHeaders.SET_COOKIE, finalHeader));
}
}
I used xml in my project so I had to add this to my configuration file:
<mvc:interceptors>
<bean class="com.zoetis.widgetserver.mvc.CookiesInterceptor"/>
</mvc:interceptors>
Using the interceptor in SpringBoot.
I'm looking for a resolution for adding SameSite as you, and I only want to add the attribute to the existing "Set-Cookie" instead of creating a new "Set-Cookie".
I have tried several ways to meet this requirement, including:
adding a custom filter as #unwichtich said,
and more I overrode basicAuthenticationFilter. It does add the SameSite attribute. While the timing when Spring will add the "Set-Cookie" is hard to catch. I thought in onAuthenticationSuccess() method, the response must have this header, but it doesn't. I'm not sure whether it's the fault of my custom basicAuthenticationFilter's order.
using cookieSerializer, but the spring-session version comes up to a problem. Seems only the latest version support it, but I still can't figure out the version number should be added into the dependency list.
Unfortunately, none of them above can add the samesite well as expected.
Finally, I found the interceptor in spring can help me to make it.
It took me a week to get it. Hope this can help you if anyone has the same problem.
#Component
public class CookieServiceInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
//check whether it has "set-cookie" in the response, if it has, then add "SameSite" attribute
//it should be found in the response of the first successful login
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
}
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {
}
}
and you also need to make this interceptor work in your application, which means you should add a bean as below:
#Autowired
CookieServiceInterceptor cookieServiceInterceptor;
#Bean
public MappedInterceptor myInterceptor() {
return new MappedInterceptor(null, cookieServiceInterceptor);
}
This interceptor has a flaw, it can't add samesite when the request is redirected(ex.return 302) or failed(ex. return 401), while it makes my app fail when SSO. Eventually, I have to use the Tomcat cookie, because I don't embed tomcat in my springboot app. I add
<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>
in a context.xml under /META-INF of my app. It will add SameSite attribute in set-cookie header for each response. Note that this behavior is possible since Tomcat 9.0.21 and 8.5.42. according to https://stackoverflow.com/a/57622508/4033979
For Spring Webflux (reactive environment) this worked for me:
#Configuration
#EnableSpringWebSession
public class SessionModule {
#Bean
public ReactiveSessionRepository<MapSession> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
#Bean
public WebSessionIdResolver webSessionIdResolver() {
CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
resolver.setCookieName("SESSION");
resolver.addCookieInitializer((builder) -> {
builder.path("/")
.httpOnly(true)
.secure(true)
.sameSite("None; Secure");
});
return resolver;
}
}
You can add cookie by yourself by using ResponseCookie and adding it to your HttpServletResponse.
ResponseCookie cookie = ResponseCookie.from("cookiename", "cookieValue")
.maxAge(3600) // one hour
.domain("test.com")
.sameSite("None")
.secure(true)
.path("/")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
I have tested this solution for spring-webmvc without spring-security, but I think it should also work for spring-boot.
Using the SessionRepositoryFilter bean from spring-session-core
You can extend default java HttpSession with a spring Session and replace JSESSIONID cookie with a custom one, like this:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
Additional spring Session cookie flags can be set using DefaultCookieSerializer:
#Configuration
#EnableSpringHttpSession
public class WebAppConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext
.addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(null, false, "/*");
}
#Bean
public MapSessionRepository sessionRepository() {
final Map<String, Session> sessions = new ConcurrentHashMap<>();
MapSessionRepository sessionRepository =
new MapSessionRepository(sessions) {
#Override
public void save(MapSession session) {
sessions.entrySet().stream()
.filter(entry -> entry.getValue().isExpired())
.forEach(entry -> sessions.remove(entry.getKey()));
super.save(session);
}
};
sessionRepository.setDefaultMaxInactiveInterval(60*5);
return sessionRepository;
}
#Bean
public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
SessionRepositoryFilter<?> sessionRepositoryFilter =
new SessionRepositoryFilter<>(sessionRepository);
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("JSESSIONID");
cookieSerializer.setSameSite("None");
cookieSerializer.setUseSecureCookie(true);
CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
new CookieHttpSessionIdResolver();
cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
sessionRepositoryFilter.setHttpSessionIdResolver(cookieHttpSessionIdResolver);
return sessionRepositoryFilter;
}
}
I have extended a bit MapSessionRepository implementation, since it does NOT support firing SessionDeletedEvent or SessionExpiredEvent - I have added clearing of expired sessions before adding new ones. I think this might be enough for a small application.
Apparently, with spring boot you can write this and it gets picked up.
#Configuration
public static class WebConfig implements WebMvcConfigurer {
#Bean
public CookieSameSiteSupplier cookieSameSiteSupplier(){
return CookieSameSiteSupplier.ofNone();
}
}
Or ... even simpler, spring boot since 2.6.0 supports setting it in application.properties.
Spring documentation about SameSite Cookies
server.servlet.session.cookie.same-site = none

Accessing Session in HttpRequest Pipelines

I'm trying to access a query string parameter and save it to a Session variable. Since the solution I'm working on has several base layouts, the simplest approach would be to add this to a pipeline handler. However, my code is failing because args.Context.Session is null:
public class SaveQueryStringToSession : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
string queryString = WebUtil.GetQueryString("parm1");
if (queryString.Length <= 0)
return;
args.Context.Session["parm1"] = queryString;
}
}
This occurs when this method is inserted into either the HttpRequestBegin or HttpRequestEnd pipeline. Curious to know why, and if there is a standard workaround or pattern to use here. (Yes, I will add a null check. No need to point that out.)
I'm running Sitecore Sitecore.NET 6.4.1 (rev. 110720) on IIS 7.5 (Integrated .Net 2.0)
Possibly relevant links:
What is the first global.asax event that Session is accessible assuming the context handler is IRequiresSessionState or IReadOnlySessionState?
http://intothecore.cassidy.dk/2009/02/session-state-and-integrated-pipeline.html
The HttpRequestBegin pipeline is wired up to the HttpApplication.BeginRequest event, and this event is fired before the HttpSession object has been instantiated. Using the HttpRequestEnd pipeline does not work because the HttpSession object has already been disposed by the time the HttpApplication.EndRequest event is fired.
The session becomes available after the PostAcquireRequestState event is fired. To intercept this, create a class that implements IHttpModule, and add it to the <httpModules> element in Web.config. The HttpModule code will need to check if the request requires session state, as attempting to read the session for a static resource request will throw an exception.
Here is HttpModule code that accesses the Session and QueryString:
public class MyHttpModule :IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAcquireRequestState += RequestHandler;
}
public void Dispose()
{
//
}
public void RequestHandler(object sender, EventArgs e)
{
var app = (HttpApplication) sender;
if (app.Context.Handler is IRequiresSessionState)
{
var session = app.Session;
var queryString = app.Request.QueryString["test"];
session["test"] = queryString;
}
}
}
It is worth noting that Sitecore's HttpRequestBegin and HttpRequestEnd pipelines are wired to ASP.NET via an HttpModule:
<add type="Sitecore.Nexus.Web.HttpModule,Sitecore.Nexus"
name="SitecoreHttpModule" />
Thanks to #ddysart for pointing me in the right direction, and to this answer for the correct event to listen for.
Actually instead of httpRequestBegin or HttpRequestEnd you can use httpRequestProcessed during which sitecore process the HttpRequest so you can access the Session.
You will be able to use the same code you have provided earlier.
public class SaveQueryStringToSession : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
string queryString = WebUtil.GetQueryString("parm1");
if (queryString.Length <= 0)
return;
args.Context.Session["parm1"] = queryString;
}
}

Servlet Filter as Security Proxy for Web Services

Good time.
Suppose there are 8 web-services in the one application. 5 of them require authorization (a client must to provide a JSESSIONID cookie and a corresponding session must not be invalidated), other 3 can be called without the jsessionid cookie. My naive solution is to write a servlet filter which intercepts requests and retrieve their pathInfos (all the services have the same url structure: /service/serviceSuffix). There is a enum which contains the serviceSuffix of each web service that requires authorization. When the request is retrieved the pathInfo is collected; if this pathInfo is contained in the enum and there is the corresponding valid session the request is sent ahead to the filter chain. Otherwise, an error is sent back to a client. After a while I've realized that it is needed to add the possibility to retrieve the wsdl and xsds for the concrete service. So that, two more check were added.
public class SecurityFilter implements Filter {
public static final String WSDL = "wsdl";
public static final String XSD = "xsd=";
/**
* Wittingly left empty
*/
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse)response;
String pathInfo = servletRequest.getPathInfo();
String queryString = servletRequest.getQueryString();
if (pathInfo != null && SecureWebServices.contains(pathInfo)) {
if (queryString != null && (queryString.equals(WSDL) || queryString.startsWith(XSD))) {
// wsdl or xsd is requested
chain.doFilter(request, response);
} else {
// a web service's method is called
HttpSession requestSession = servletRequest.getSession(false);
if (requestSession != null) { // the session is valid
chain.doFilter(request, response);
} else {
servletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
} else {
chain.doFilter(request, response);
}
}
/**
* Wittingly left empty
*/
public void destroy() {}
}
It seems that it is not very secure, because if the request's pathInfo is not in the enum, this request is passed on (just in case of some unexpected system calls).
Could you, please, suggest what to do, how to increase the security level. I want to build a configurable system (that is why I have the enum. It is possible just to add a path there to secure the web service and it is not required to duplicate the security code in the each web service). How to increase
Maybe I do not understand but.
jsessionid has nothink to do with security. you simply just get it.
Next I am not sure if you want authentication or authorization. The code as provided will not provide you with security features.
I suppose you are interested in authentication anyway. Security logic can be provided with standard web container features. Just send in authentication data in the header of request and you are done. web container can be configured to secure only selected resources (urls)

MonoTouch Web Service Request over SSL Gets 'authentication or decryption has failed'

A web service request over SSL raises a WebException on Monotouch v4.0.4.1:
'Error getting response stream (Write: The authentication or decryption has failed)'
Since the server's SSL certificate is self-signed (and btw I think it is not X.509), I am bypassing the certificate validation using ServicePointManager.ServerCertificateValidationCallback. The exact same code works fine on Windows .NET, where the web service call returns the correct result. On Monotouch adding a Writeline shows that the ServerCertificateValidationCallback delegate code is never reached.
Note: Although probably not relevant, the content of the request is SOAP with embedded WS-Security UsernameToken.
Has anyone got something like this to work on MonoTouch? Have seen reports of similar symptom but no resolution. The code and stacktrace are below, any comment appreciated. Can email a self-contained test case if wanted.
I gather there is an alternative approach using certmgr.exe to store the self-signed server certificate in the local trust store, but can't seem to find that app in the MonoTouch distribution. Could anyone point me to it?
..
public class Application
{
static void Main (string[] args)
{
UIApplication.Main (args);
}
}
// The name AppDelegate is referenced in the MainWindow.xib file.
public partial class AppDelegate : UIApplicationDelegate
{
// This method is invoked when the application has loaded its UI and its ready to run
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
// If you have defined a view, add it here:
// window.AddSubview (navigationController.View);
string soapResponse;
string soapRequest = #" SOAP envelope is here but omitted for brevity ";
soapResponse = WebService.Invoke("myOperation", soapRequest);
window.MakeKeyAndVisible ();
return true;
}
// This method is required in iPhoneOS 3.0
public override void OnActivated (UIApplication application)
{
}
}
public class WebService
{
public static string Invoke(string operation, string soapRequest)
// Input parameters:
// operation = WS operation name
// soapRequest = SOAP XML request
// Output parameter:
// SOAP XML response
{
HttpWebResponse response;
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, ssl) => true;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://myserver.com:7570/MyEndpoint");
request.Method = "POST";
request.Headers.Add("SOAPAction", "/MyEndpoint/" + operation);
request.ContentType = "text/xml;charset=UTF-8";
request.UserAgent = "Smartphone";
request.ContentLength = soapRequest.Length;
request.GetRequestStream().Write(System.Text.Encoding.UTF8.GetBytes(soapRequest), 0, soapRequest.Length);
request.GetRequestStream().Close();
response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
catch (WebException e)
{
throw new WebException(e.Message);
}
}
}
Stack trace (some names changed to protect the innocent, original available on request):
WS.WebService.Invoke (operation="myOperation", soapRequest="<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" \n\txmlns:ns1=\"http://mycompany/Common/Primitives/v1\" \n\txmlns:ns2=\"http://mycompany/Common/actions/externals/Order/v1\" \n\txmlns:ns3=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n\t<SOAP-ENV:Header> <wsse:Security SOAP-ENV:mustUnderstand=\"1\" \n\txmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"> \n\t<wsse:UsernameToken wsu:Id=\"UsernameToken-1\" \n\txmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"> \n\t<wsse:Username>myusername</wsse:Username> <wsse:Password \n\tType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">mypw</wsse:Password> \n\t<wsse:Nonce>{0}</wsse:Nonce> \n\t<wsu:Created xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">{1}</wsu:Created> \n\t</wsse:UsernameToken> </wsse:Security> \n\t</SOAP-ENV:Header><SOAP-ENV:Body><ns2:tp_getOrderDetailRequest><ns2:header><ns1:source>TEAM</ns1:source>\n\t<ns1:userAccessKey>12345678901234567</ns1:userAccessKey></ns2:header>\n\t<ns2:OrderId>myid1</ns2:OrderId>\n\t<ns2:OrderId>myid2</ns2:OrderId>\n\t</ns2:tp_getOrderDetailRequest>\n\t</SOAP-ENV:Body>\n\t</SOAP-ENV:Envelope>") in /Users/billf/Projects/WS/WS/Main.cs:103
WS.AppDelegate.FinishedLaunching (app={MonoTouch.UIKit.UIApplication}, options=(null)) in /Users/billf/Projects/WS/WS/Main.cs:52
MonoTouch.UIKit.UIApplication.Main (args={string[0]}, principalClassName=(null), delegateClassName=(null)) in /Developer/MonoTouch/Source/monotouch/monotouch/UIKit/UIApplication.cs:26
MonoTouch.UIKit.UIApplication.Main (args={string[0]}) in /Developer/MonoTouch/Source/monotouch/monotouch/UIKit/UIApplication.cs:31
WS.Application.Main (args={string[0]}) in /Users/billf/Projects/WS/WS/Main.cs:18
MonoTouch (just like Mono) does not support TLS_DH* cipher suites (like TLS_DHE_DSS_WITH_AES_128_CBC_SHA).
When a server is configured to accept only them then the negotiation stage fails very early (an Alert is received from the server after the Client Hello message is sent) which explains why the callback was never called.
Ensure your server allows the more traditional cipher suites, e.g. the very secure (but slow) TLS_RSA_WITH_AES_256_CBC_SHA or the faster (and very common) Cipher Suite: TLS_RSA_WITH_RC4_128_[MD5|SHA], and Mono[Touch] should work well using them.
Note that this is unrelated to SOAP or web-services (and even X.509 certificates) - it's just plain SSL.
1) An untrusted root certificate is not the only problem that could result in this exception.
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, ssl) => true;
Add a Console.WriteLine in there so you'll see if it gets called (or not).
throw new WebException(e.Message);
and another here, with full stack trace (not just the Message property).
2) Each application is isolated. This means that:
applications cannot updates the global iOS certificate stores (that would create security issues);
if a certmgr tool existed (for MT) it could only use a local (mono) store that would be usable only for itself (which would not be of any help for your own apps)