Disclaimer: .Net N00b
I've been beating my head against the wall for a couple of days now trying to get the security work with this external vendors web service to no avail. It turns out that they use WSSE digest security, which, in short, adds something like this to the SOAP header:
<wsse:UsernameToken wsu:Id="Example-1">
<wsse:Username> ... </wsse:Username>
<wsse:Password Type="..."> ... </wsse:Password>
<wsse:Nonce EncodingType="..."> ... </wsse:Nonce>
<wsu:Created> ... </wsu:Created>
</wsse:UsernameToken>
I started out by adding the service reference, and through many, many blog posts, stackoverflow questions fiddling with the app.config and the code. I just couldn't seem to get it right. Maybe it isn't easily possible? Maybe I just don't know Visual Studio 2010 and .Net that well, I'm not sure.
Here is what I stopped with in my app.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ServiceHttpBinding" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://vendorurl"
binding="basicHttpBinding" bindingConfiguration="ServiceHttpBinding"
contract="ContractName"
name="ServiceHttpPort">
</endpoint>
</client>
</system.serviceModel>
And the C#:
var someService = new ServiceClient();
someService.ClientCredentials.UserName.UserName = "username";
someService.ClientCredentials.UserName.Password = "passwordgobbletygook/somemorebase64stuff=";
#region Begin Magic
var elements = someService.Endpoint.Binding.CreateBindingElements();
var securityBindingElement = elements.Find<SecurityBindingElement>();
securityBindingElement.IncludeTimestamp = false;
someService.Endpoint.Binding = new CustomBinding(elements);
#endregion
var response = someService.webMethod(param1, param2, param3, param4);
Console.WriteLine(response);
The funny thing is, in the vendors spec, I found that they encourage the use of WSSJ, so I tried it out (in java) and I GOT IT TO WORK IN 2 HOURS
Here is what that looks like:
public class Test implements CallbackHandler {
/**
* #param args
*/
public static void main( final String[] args ) throws Throwable {
SomeService_Service someService_Service = new SomeService_Service();
SomeService someService = someService_Service.getSomeServiceHttpPort();
BindingProvider bindingProvider = (BindingProvider)someService;
Map< String, Object > requestContext = bindingProvider.getRequestContext();
requestContext.put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://vendorurl" );
Client client = ClientProxy.getClient( someService );
Endpoint endpoint = client.getEndpoint();
Map< String, Object > outProps = new HashMap< String, Object >();
outProps.put( WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN );
outProps.put( WSHandlerConstants.USER, "username" );
outProps.put( WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST );
outProps.put( WSHandlerConstants.PW_CALLBACK_REF, new Test() );
WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor( outProps );
endpoint.getOutInterceptors().add( wssOut );
System.out.println( someService.webMethod(param1, param2, param3, param4) );
}
public void handle( final Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[ 0 ];
// set the password for our message.
pc.setPassword( "passwordgobbletygook/somemorebase64stuff=" );
}
}
Has anyone out there in stackoverflow land got this to work in .Net\C#? Is there something obvious I'm missing here?
We've run into this problem before when trying to connect a .NET based component to a JAVA based SOAP service. Our solution doesn't involve any XML construction and is IMHO a bit cleaner than anything else I've seen.
The downside is that you need to download and include an older optional .NET DLL to make it work. The upside is that the code is quite clean and fits naturally into WCF.
The basic implementation looks something like this:
using (OperationContextScope scope = new OperationContextScope(client.InnerChannel))
{
//Class from WSE 3.0
UsernameToken token = new UsernameToken("MY_USERNAME", "MY_PASSWORD", PasswordOption.SendHashed);
//Add Auth to SOAP Header
MessageHeader header
= MessageHeader.CreateHeader(
"Security",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
token.GetXml(new XmlDocument())
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
//Build Request
OrgWS.OrganizationDetailsRequest request = new OrgWS.OrganizationDetailsRequest()
{
ID = 1
};
//Send Request
OrgWS.OrganizationDetail[] response = client.getOrganizationDetail(request);
//Do something with response
}
A full explanation can be found here: http://cxdeveloper.com/article/implementing-ws-security-digest-password-nonce-net-40-wcf
Andy's answer is spot on! Spent most of the say on this, there is a lot out there but this is the ONLY answer that worked for me. Perfect for adding nonce with passwordDigest in SOAP wsse headers. Agree with Nick V, this answer should get more recognition.
BasicHttpBinding myBinding = new BasicHttpBinding();
myBinding.Security.Mode = BasicHttpSecurityMode.Transport;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress ea = new EndpointAddress("****");
WebServiceServiceClient cc = new WebServiceServiceClient(myBinding, ea);
cc.Open();
using (OperationContextScope scope = new OperationContextScope(cc.InnerChannel))
{
//Class from WSE 3.0
UsernameToken token = new UsernameToken("userid", "password", PasswordOption.SendHashed);
//Add Auth to SOAP Header
MessageHeader header
= MessageHeader.CreateHeader(
"Security",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
token.GetXml(new XmlDocument())
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
try
{
//call SOAP methos
}
catch (Exception ex)
{
//catch any errors
}
}
Related
Now I am learning how to build REST webservices - I am trying to build a REST service which communicates through XML (not JSON).
I am using the tutorial here.
http://www.vogella.com/tutorials/REST/article.html
I have created a GET API which returns a List of ToDoes
#GET
#Produces({ MediaType.TEXT_XML })
public List<ToDo> getXML()
{
ArrayList<ToDo> al = new ArrayList<ToDo>();
ToDo t = new ToDo();
t.setSummary("First ToDo");
t.setDescription("This is my first ToDo");
al.add(t);
t.setSummary("2nd Todo");
t.setDescription("This is my 2nd Todo");
al.add(t);
return al;
}
I have a client program which calls this method
String xmlAnswer =
target.path("rest").path("hello").request().accept(MediaType.TEXT_XML).get(String.class);
I get the following response
<?xml version="1.0" encoding="UTF-8" standalone="yes">
<toDoes>
<toDo>
<description>This is my 2nd Todo</description>
<summary>2nd Todo</summary>
</toDo>
<toDo>
<description>This is my 2nd Todo</description>
<summary>2nd Todo</summary></toDo>
</toDoes>
</toDoes>
However, I am sure there is an easier way to program the client - I want to get an array of ToDo objects instead of XML. How do I do this? How to I generate the stub for a client ToDo class & how do I get the answer in the form of an array of ToDo objects?
You can use Spring-web API to call your REST service.
Add Spring-web to your class path, then use the below code:
List<MyBean> beans = null;
RestTemplate template = new RestTemplate()
ParameterizedTypeReference<List<MyBean>> responseType = new ParameterizedTypeReference<List<MyBean>>() {};
Object requestParm = null;
ResponseEntity<List<MyBean>> response = template.exchange("http://example.com",HttpMethod.GET, requestParm, responseType);
if(response.getStatusCode() == HttpStatus.OK && response.hasBody()) {
beans = response.getBody();
}
Based on the comment from #peeskillet, this is what I did.
I took the output XML - used http://xmlgrid.net/xml2xsd.html to create an XSD from it - then used xjc to generate a ToDoes class
ToDoes tds = target.path("rest").path("hello").request()
.accept(MediaType.TEXT_XML).get(ToDoes.class);
List<ToDoes.ToDo> l = tds.getToDo() ;
for (int i = 0; i < l.size(); ++i)
{
System.out.println( "Summary"+i+":" + l.get(i).getSummary());
System.out.println( "Summary"+i+":" + l.get(i).getDescription());
}
It's not clear from the google-http-java-client* docs how you would go about posting a form that has a file field.
For example I'm trying to print a document using the Google Cloud Print API:
HttpRequestFactory httpRequestFactory = getHttpRequestFactory();
Map<String, Object> parameters = Maps.newHashMap();
parameters.put("printerId", printRequest.getPrinterId());
parameters.put("title", printRequest.getTitle());
parameters.put("contentType", printRequest.getContentType());
parameters.put("ticket", new Gson().toJson(printRequest.getOptions()));
MultipartContent content = new MultipartContent();
content.addPart(new MultipartContent.Part(new UrlEncodedContent(parameters)));
content.addPart(new MultipartContent.Part(
new FileContent(printRequest.getContentType(), printRequest.getFile())));
try {
HttpResponse response = httpRequestFactory.buildPostRequest(
SubmitUrl, content).execute();
System.out.println(IOUtils.toString(response.getContent()));
} catch (IOException e) {
String message = String.format();
System.out.println("Error submitting print job: " + e.getMessage());
}
Unfortunately this doesn't work. The API returns the error "Printer Id required for this request." which seems to me like the request isn't properly formed.
What am I doing wrong?
* I'm specifically using the google-http-java-client as it handles automatic refreshing of OAuth tokens etc for me. Please don't reply with solutions that involve using other HTTP clients.
So it looks like I misunderstood how form fields are added to multipart messages. The working code now looks like this
HttpRequestFactory httpRequestFactory = getHttpRequestFactory(username);
Map<String, String> parameters = Maps.newHashMap();
parameters.put("printerid", printRequest.getPrinterId());
parameters.put("title", printRequest.getTitle());
parameters.put("contentType", printRequest.getContentType());
// Map print options into CJT structure
Map<String, Object> options = Maps.newHashMap();
options.put("version", "1.0");
options.put("print", printRequest.getOptions());
parameters.put("ticket", new Gson().toJson(options));
// Add parameters
MultipartContent content = new MultipartContent().setMediaType(
new HttpMediaType("multipart/form-data")
.setParameter("boundary", "__END_OF_PART__"));
for (String name : parameters.keySet()) {
MultipartContent.Part part = new MultipartContent.Part(
new ByteArrayContent(null, parameters.get(name).getBytes()));
part.setHeaders(new HttpHeaders().set(
"Content-Disposition", String.format("form-data; name=\"%s\"", name)));
content.addPart(part);
}
// Add file
FileContent fileContent = new FileContent(
printRequest.getContentType(), printRequest.getFile());
MultipartContent.Part part = new MultipartContent.Part(fileContent);
part.setHeaders(new HttpHeaders().set(
"Content-Disposition",
String.format("form-data; name=\"content\"; filename=\"%s\"", printRequest.getFile().getName())));
content.addPart(part);
try {
HttpResponse response = httpRequestFactory.buildPostRequest(
SubmitUrl, content).execute();
System.out.println(IOUtils.toString(response.getContent()));
} catch (IOException e) {
...
}
The most important parts above were overriding the default HttpMediaType to specify "multipart/form-data" and adding each field as its own part with a "Content-Disposition" header to designate the form field name.
I'm trying to add custom HTTP headers to Axis 1.4 web servers.
I've created a handler which extends BasicHandler:
public class HttpHeaderHandler extends BasicHandler {
.
.
.
#Override
public void invoke(org.apache.axis.MessageContext arg0) throws AxisFault {
LOG.trace("invoke called");
Hashtable ht = (Hashtable)ctx.getProperty(HTTPConstants.RESPONSE_HEADERS);
if(ht == null) {
ht = new Hashtable();
}
ht.put("custom-header", "Hello");
ctx.setProperty(HTTPConstants.RESPONSE_HEADERS, ht);
}
.
.
.
}
I've added the following to server-config.wsdd:
.
.
.
<transport name="http">
<requestFlow>
<handler type="URLMapper" />
<handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler" />
</requestFlow>
<responseFlow>
<handler type="java:com.my.package.HttpHeaderHandler" />
</responseFlow>
</transport>
.
.
.
I can see that the invoke method is being called as the logging is appearing in the log file but the custom header is not being added to the response.
Any suggestions appreciated.
I was able to do this on a org.apache.axis.Stub instance by doing the following:
private Stub setHeaders(Stub stub, Hashtable<String, String> headers){
stub._setProperty(HTTPConstants.REQUEST_HEADERS, headers);
return stub;
}
Note that it is REQUIRED that the value argument to _setProperty() be a java.util.Hashtable (it gets cast later on by Axis when the Stub is used)
I added apikey for request header thanks for #romeara answer here . And it works.
Axis 1.4 sending client request from java.
YourStub stub = new YourStub();
Hashtable<String, String> headers = new Hashtable<String, String>();
headers.put("apikey", "xxxxxxxxxxxxxxxxxxxx");
stub._setProperty(HTTPConstants.REQUEST_HEADERS, headers);
I remember using the stub files generated to add HTTP user and password, check this link and locate the code that says:
_call.setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR, Boolean.FALSE);
http://www.coderanch.com/t/225102/Web-Services/java/Axis-username-password-auth-stubs
That kind of modification works.
This is what we have done
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
/**
* This method is to be used for secure SOAP calls.
* Method created as Axis 1.4 strips the security header which compiling the Java classes.
* #param username
* #param password
* #return SOAP Header
* #throws SOAPException
*/
public static SOAPHeaderElement createCustomSOAPHeader(String username, String password) throws SOAPException {
SOAPHeaderElement oHeaderElement;
SOAPElement oElement;
//Header
oHeaderElement = new SOAPHeaderElement("http://siebel.com/webservices", "Security");
oHeaderElement.setPrefix("web");
oHeaderElement.setMustUnderstand(false);
//Elements for the Header
oElement = oHeaderElement.addChildElement("UsernameToken");
oElement.addTextNode(username);
oElement = oHeaderElement.addChildElement("PasswordText");
oElement.addTextNode(password);
oElement = oHeaderElement.addChildElement("SessionType");
oElement.addTextNode("None");
return oHeaderElement;
}
Hope this helps.
I have an Exchange Server 2007 SP1 and want to create an appointment with the EWS Java API 1.1. I got an Exception that I have to set the time zone definition first.
appointment.setStartTimeZone(new TimeZoneDefinition(){{
setName( "W. Europe Standard Time" );
}});
I tried to set it directly but got this exception:
The time zone definition is invalid or unsupported
I saw some workarounds where you have to edit the Java API (like skipping the TimeZoneDefinition validation) but if its possible I dont want to do any changes there. I hope someone knows how I can set the TimeZoneDefinition properly (without modifying the base Java API).
Edit: In .NET it seems you can set the TimeZoneDefinition directly like:
appointment.StartTimeZone = TimeZoneInfo.Local;
But I cant find anything like this in the Java API
I faced the same problem - and tried mostly everything (besides from editig the java ews api itself) to make Appointments with StartTimeZone work with Exchange 2007 SP1 in my Spring Web Application - without success.
I found comments like:
Unfortunately, Exchange 2007 SP1 does not support the StartTimeZone property of EWS. If you want to use that property, you must use Exchange 2010.
That i should go, look for less "flacky" Java Exchange Framework.
I wasnt pleased and as i heard there is no such problem in the .NET universe i decided to go with the following solution:
I set up a self-hosted Nancy Server.
see the Nancy Documentation
And wrote a simple NancyModule:
namespace WebServiceNancy
{
public class APIModul : NancyModule
{
public APIModul() : base("/")
{
Post["/saveFooApp"] = _ =>
{
var jsonApp = this.Bind<AppData>();
string ewsURL = "https://saveFooApp/ews/exchange.asmx";
System.Uri ewsUri = new System.Uri(ewsURL);
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
service.Url = ewsUri;
service.Credentials = new WebCredentials(jsonApp.Username, jsonApp.Password);
Appointment app = new Appointment(service);
app.Subject = jsonApp.Title;
app.Start = jsonApp.Start;
app.End = jsonApp.End;
app.Save(WellKnownFolderName.Calendar);
return Response.AsText("OK").WithStatusCode(HttpStatusCode.OK);
};
}
}
public class AppData
{
public string Title { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
}
Now i can call this WS from my Spring Controller by passing my Appointment Data as a json Object via RestTemplate:
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startDate = formatter.format(fooMeeting.getMeetingStart());
String endDate = formatter.format(fooMeeting.getMeetingEnd());
JSONObject obj = new JSONObject();
obj.put("title", fooMeeting.getTitle());
obj.put("start", startDate);
obj.put("end", endDate);
obj.put("username", fooUser.getUsername());
obj.put("password", fooUser.getPassword());
RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
JSONSerializer jsonSer = new JSONSerializer();
HttpEntity<String> entity = new HttpEntity<String>(jsonSer.serialize(obj), headers);
ResponseEntity<String> response = rt.exchange("http://localhost:8282/saveFooApp", HttpMethod.POST, entity, String.class);
System.out.println(response.getStatusCode());
Ofc u need to decide if you want to use some kind of password encryption when passing credentials from one server to another - and how you implement your error handling.
but it works like a charm for me
and i am feeling very confident about future requests regarding other EWS funcionalities.
I am trying to POST an image to imageshack using their API and Play Framework's WSRequest object.
My code is as follows:
public static void upload( Picture picture ) throws Exception {
//set file parameter - in this case the image
WS.FileParam fp = new WS.FileParam( picture.asFile, "fileupload");
//set other parameters
Map<String,Object> params = new HashMap<String, Object>();
params.put( "optsize", "resample" );
params.put( "rembar", "yes" );
params.put( "public", "no" );
params.put( "a_username", username );
params.put( "a_password", password );
params.put( "key", a_key );
//POST request
Document doc = WS.url( "http://www.imageshack.us/upload_api.php" )
.setHeader( "Content-Type", picture.contentType )
.mimeType( "multipart/form-data" )
.params( params )
.files( fp )
.post()
.getXml();
}
However, I always reveive the following response from imageshack:
Sorry, but we've detected that unexpected data is received. Required parameter 'fileupload' is missing or your post is not multipart/form-data.
I have tried sending the file as a parameter using a byte array:
params.put( "fileupload", Base64.encode( picture.asBytes ) )
But this also results in the same response from Imageshack.
This is driving me mad. Can anyone point out where I am going wrong or possibly point me in the direction of a better solution? Thanks.
The cause
After a bit of research I found that I had neglected a bit of important information from this question....I am including the Google App Engine module within my app.
According to the Play Framework Google Group the code associated with attaching Files to a WS request when using GAE is actually just commented out. Hence the reason it just doesn't work. So no error thrown for you and no indication why it doesn't work...you just have to work it out.
I have accepted #Gary's answer as it is the correct way to upload an image to imageshack using WS - just not when using GAE.
I don't think you need to specify the content type or mime type directly.
I used the following code to upload successfully.
WS.FileParam fp = new WS.FileParam(
new File("d:\\workspace\\ImageShackTest\\sample_picture.png"), "fileupload");
Map<String,Object> params = new HashMap<String, Object>();
params.put( "optsize", "resample" );
params.put( "rembar", "yes" );
params.put( "public", "yes" );
//params.put( "a_username", username );
//params.put( "a_password", password );
params.put( "key", API_KEY );
//POST request
Document doc = WS.url( "http://www.imageshack.us/upload_api.php" )
.params( params )
.files( fp )
.post()
.getXml();
I think when you attach a file to a request it automatically decides its going to be multipart/form-data.
This is my entire controller (except for the API Key)
package controllers;
import play.*;
import play.mvc.*;
import java.util.*;
import models.*;
import play.libs.*;
import java.io.File;
public class Application extends Controller {
public static void index() { render(); }
private static final String API_KEY = "API KEY REMOVED TO PROTECT THE INNOCENT";
public static void tryUpload() {
WS.FileParam fp = new WS.FileParam( new File("d:\\workspace\\ImageShackTest\\sample_picture.png"), "fileupload");
Map<String,Object> params = new HashMap<String, Object>();
params.put( "optsize", "resample" );
params.put( "rembar", "yes" );
params.put( "public", "yes" );
params.put( "key", API_KEY );
String doc = WS.url( "http://www.imageshack.us/upload_api.php" )
.params( params )
.files( fp )
.post()
.getString();
System.out.println(doc);
index();
}
}
and this is the application.conf file
# This is the main configuration file for the application.
# ~~~~~
application.name=ImageShackTest
application.mode=dev
%prod.application.mode=prod
application.secret=JIVQE8y3y1lCzXRGprFJvoXBdi8Jpa8qE1U1mBIooLLOOYk5yyhAI5cxbEf4q4pl
date.format=yyyy-MM-dd
attachments.path=data/attachments
mail.smtp=mock
I didn't make any other changes. Just browsed to http://localhost:9000/Application.tryUpload and could see the success XML on the play console.
You are setting the content type header incorrectly.
Instead of this:
.setHeader( "Content-Type", picture.contentType )
Try this:
.setHeader( "Content-Type", "multipart/form-data" )