I'm trying to delete a file from a Sharepoint list in Java and running into some issues. I'm using a batch element described here
I'm able to make the request, but the results that come back are null and the file is not deleted (I don't get any errors).
Here is the code for the UpdateListItems.Update that I'm using:
UpdateListItems.Updates updates = new UpdateListItems.Updates();
updates.getContent().add(this.generateXmlNode(
"<Batch PreCalc='True' OnError='Continue' ListVersion='1' ListName='" + spMoveRequest.getListName() + "'>" +
"<Method ID='1' Cmd='Delete'>" +
"<Field Name='ID'>5</Field>" +//this must be where we specify the file
"</Method>" +
"</Batch>"
));
I'm then making a method call on the listSoap object like this:
UpdateListItemsResult updateResult = listSoap.updateListItems("<my list name here>", updates);
I've also tried many variations like using the GUID instead of the actual list name and using
<Field Name='FileRef'><my file url here></Field>
to identify the file.
Nothing seems to be working, and I'm not getting any useful feedback either.
The generateXmlNode method that I'm using looks like this:
protected Node generateXmlNode(String sXML) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document documentOptions = builder.parse(new InputSource(new StringReader(sXML)));
Node elementOptions = documentOptions.getDocumentElement();
return elementOptions;
}
but I've used this in the past when retrieving sharepoint lists without problems.
What am I missing here?
An other solution is to use HTTP DELETE method to delete a file directly to the Sharepoint with NTLMv2 authentication
For that you can use Apache HTTP Client:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.3</version>
</dependency>
And to permit NTLMv2 authentication you need JCIF library.
<dependency>
<groupId>jcifs</groupId>
<artifactId>jcifs</artifactId>
<version>1.3.17</version>
</dependency>
First we need to write a wrapper to permit Apache HTTP Client to use JCIF for NTLMv2 support :
public final class JCIFSEngine implements NTLMEngine {
private static final int TYPE_1_FLAGS =
NtlmFlags.NTLMSSP_NEGOTIATE_56
| NtlmFlags.NTLMSSP_NEGOTIATE_128
| NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2
| NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN
| NtlmFlags.NTLMSSP_REQUEST_TARGET;
#Override
public String generateType1Msg(final String domain, final String workstation)
throws NTLMEngineException {
final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
return Base64.encode(type1Message.toByteArray());
}
#Override
public String generateType3Msg(final String username, final String password,
final String domain, final String workstation, final String challenge)
throws NTLMEngineException {
Type2Message type2Message;
try {
type2Message = new Type2Message(Base64.decode(challenge));
} catch (final IOException exception) {
throw new NTLMEngineException("Invalid NTLM type 2 message", exception);
}
final int type2Flags = type2Message.getFlags();
final int type3Flags = type2Flags
& (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
final Type3Message type3Message = new Type3Message(type2Message, password, domain,
username, workstation, type3Flags);
return Base64.encode(type3Message.toByteArray());
}
}
Reference
The main code to execute HTTP DELETE with authentication:
try {
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
DefaultHttpClient httpclient = new DefaultHttpClient(params);
//Register JCIF NTLMv2 to manage ntlm auth.
httpclient.getAuthSchemes().register("ntlm", new AuthSchemeFactory() {
#Override
public AuthScheme newInstance(HttpParams hp) {
return new NTLMScheme(new JCIFSEngine());
}
});
//Provide login/password
httpclient.getCredentialsProvider().setCredentials(
AuthScope.ANY,
new NTCredentials([LOGIN], [PASSWORD], "", [DOMAIN]));
//Create HTTP PUT Request
HttpPut request = new HttpDelete("http://[server]/[site]/[folder]/[fileName]");
return httpclient.execute(request);
} catch (IOException ex) {
//...
}
Linked question to upload a file to Sharepoint : How do I upload a document to SharePoint with Java?
You can take a look of this project i've developed to provide a working sharepoint rest api client usable in a very easy way. Take a look in github here:
https://github.com/kikovalle/PLGSharepointRestAPI-java
Related
We have an old application written in Java that uses and old version of wss4j to generate the SOAP requests and xFire to transmit those requests.
This project uses a few dependencies that I believe are relevant:
wss4j (1.6.5)
xfire-all (1.2.6)
xfire-jsr181-api (1.0-M1)
Frankly, I have little experience with SOAP and zero experience with this wss4j library.
From looking online, I was unable to find a lot of helpful resources for this older version of wss4j.
With all of the developers who worked on this project having left the company before I joined, I'm hoping to find some help from the vast knowledge found on SO.
A change in the SOAP service has been made by an external team for which we need to modify our client.
We need to be able to add a BinarySecurityToken to the Security section of the header as well as an arbitrary element to the Header.
From what I've read online, the BinarySecurityToken is supposed to be used to encode certificate data, so I'm not sure if we're trying to use it correctly, but frankly we don't have a choice as the change has been made to the service and we have to adapt the client to meet it's new requirements :(
Below is an example or what our application currently sends; it does not include a BinarySecurityToken in the Security element or an authMethod element in the Header element:
<soap:Header>
<wsse:Security soap:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-b92109ac-4a13-444d-b8e5-19c400f5910f">
<wsse:Username>some_username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">some_password</wsse:Password>
<wsse:Nonce>Daaaaa1bBbbbbBb2cccccc==</wsse:Nonce>
<wsu:Created>2022-09-19T09:10:31Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
<wsa:Action>some_text</wsa:Action>
<wsa:MessageID>some_text_value</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>some_text_value</wsa:Address>
</wsa:ReplyTo>
<wsa:To endpointID="some_text_value">http://172.31.9.216:443/P/V3_20090316L/XML/FICR_AR062004CA</wsa:To>
<wsa:From endpointID="some_text_value">
<wsa:Address>some_text_value</wsa:Address>
</wsa:From>
<language>en</language>
</soap:Header>
The expected SOAP request must contain a header that looks like the below, it must include a BinarySecurityToken in the Security element and an authMethod element in the Header element:
<soap:Header>
<wsse:Security soap:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-b92109ac-4a13-444d-b8e5-19c400f5910f">
<wsse:Username>some_username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"> </wsse:Password>
<wsse:Nonce>Daaaaa1bBbbbbBb2cccccc==</wsse:Nonce>
<wsu:Created>2022-09-19T09:10:31Z</wsu:Created>
</wsse:UsernameToken>
<wsse:BinarySecurityToken ValueType="authToken" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-c0c6c469-235a-42e5-869c-cec77b1c2dbd">some_token_which_will_be_base64_encoded</wsse:BinarySecurityToken>
</wsse:Security>
<wsa:Action>some_text</wsa:Action>
<wsa:MessageID>some_text_value</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>some_text_value</wsa:Address>
</wsa:ReplyTo>
<wsa:To endpointID="some_text_value">http://172.31.9.216:443/P/V3_20090316L/XML/FICR_AR062004CA</wsa:To>
<wsa:From endpointID="some_text_value">
<wsa:Address>some_text_value</wsa:Address>
</wsa:From>
<language>en</language>
<ehr:authMethod xmlns:ehr="some_text_value">authToken</ehr:authMethod>
</soap:Header>
Below an a sample of the current code:
I cleaned-up the code a little to make it legible and added full class-declarations where I thought important.
Transmitter.java
public class Transmitter
{
public org.w3c.dom.Document sendRequest(
Document reqMsg,
String serverUrlAndLocation,
String action,
String msgId,
String receiverNetAddr,
String senderNetAddr,
String toEndpointID,
String fromEndpointID,
String username,
String authToken,
String testCase
)
final EHIP_routingServiceClient prclient = new EHIP_routingServiceClient(serverUrlAndLocation);
final EHIP_routingPortType prpt = prclient.getEHIP_routingPort0();
final org.codehaus.xfire.client.Client client = org.codehaus.xfire.client.Client.getInstance(prpt);
String interactionId = stripVersion(action);
action = "urn:hl7-org:v3:" + action;
final RxDOMInHandler rxHandler = new RxDOMInHandler(testCase);
client.addInHandler (rxHandler); // creates the DOM for the SOAP msg;
client.addOutHandler (new org.codehaus.xfire.util.dom.DOMOutHandler.DOMOutHandler());
client.addFaultHandler(new org.codehaus.xfire.util.dom.DOMOutHandler.DOMOutHandler());
client.addInHandler (new org.codehaus.xfire.util.LoggingHandler());
client.addOutHandler (new RxLoggingHandler(testCase, interactionId));
client.addFaultHandler(new RxLoggingHandler(testCase, interactionId));
this.enableUsernameTokenEClaims(
client,
username,
authToken
);
this.enableWSAddressing(
client,
action,
msgId,
receiverNetAddr,
senderNetAddr,
toEndpointID,
fromEndpointID
);
client.addOutHandler(new RxSoapActionOutHandler(action, false));
client.addOutHandler(new RxAdjustBodyOutHandler(m_api.getCreationTimeFormatFlag()));
final Object[] params = new Object[] {reqMsg};
Object[] ret;
try
{
ret = client.invoke("processRequest", params);
}
catch(XFireFault fault)
{
if (fault.getCause() instanceof WstxEOFException)
{
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = factory.newDocumentBuilder();
ret = new Document[1];
ret[0] = documentBuilder.newDocument();
}
else
{
throw fault;
}
}
return rxHandler.getResponse();
}
private void enableUsernameTokenEClaims(
Client client,
String username,
String authToken
)
{
final java.util.Map<String, Object> usernameTokenConfig = new HashMap<String, Object>();
usernameTokenConfig.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
usernameTokenConfig.put(WSHandlerConstants.USER, username);
usernameTokenConfig.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
final RxPasswordHandler pwdHandler = new RxPasswordHandler();
pwdHandler.setPassword(authToken, true);
usernameTokenConfig.put(WSHandlerConstants.PW_CALLBACK_REF, pwdHandler);
client.addOutHandler(new WSS4JOutHandler(usernameTokenConfig));
}
private void enableWSAddressing(
org.codehaus.xfire.client.Client client,
String action,
String msgId,
String receiverNetAddr,
String senderNetAddr,
String toEndpointID,
String fromEndpointID
)
{
final RxAddressingHeadersFactory200508 factory = new RxAddressingHeadersFactory200508(
m_api.getLanguage(),
toEndpointID,
fromEndpointID
);
final AddressingHeaders ah = new AddressingHeaders();
if (msgId == null || msgId.trim().equals(""))
{
ah.setMessageID(new RandomGUID(true).toString());
}
else
{
ah.setMessageID(msgId);
}
final EndpointReference epr1 = factory.createEClaimsDefaultEPR(senderNetAddr);
ah.setFrom(epr1);
ah.setReplyTo(factory.createDefaultEPR());
ah.setTo(receiverNetAddr);
ah.setAction(action);
final java.util.Map<String, Object> properties = new HashMap<String, Object> ();
properties.put(AddressingInHandler.ADRESSING_HEADERS.toString(), ah);
properties.put(AddressingInHandler.ADRESSING_FACTORY.toString(), factory);
client.addOutHandler(new RxAddressingOutHandler(properties));
}
}
RxDOMInHandler
public class RxDOMInHandler extends org.codehaus.xfire.util.dom.DOMInHandler
{
public static final String DOM_MESSAGE = "dom.message";
private String interactionId = null;
private String testCase = null;
private org.w3c.dom.Document response = null;
public RxDOMInHandler()
{
super();
setPhase(org.codehaus.xfire.handler.Phase.PARSE);
before(org.codehaus.xfire.soap.handler.ReadHeadersHandler.class.getName());
}
public RxDOMInHandler(String testCase)
{
super();
setPhase(org.codehaus.xfire.handler.Phase.PARSE);
before(org.codehaus.xfire.soap.handler.ReadHeadersHandler.class.getName());
this.testCase = testCase;
}
public void invoke(org.codehaus.xfire.MessageContext context)
throws Exception
{
org.w3c.dom.Document doc = null;
javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setIgnoringComments(false);
dbf.setIgnoringElementContentWhitespace(false);
dbf.setNamespaceAware(true);
dbf.setCoalescing(false);
org.codehaus.stax2.XMLStreamReader2 reader2 = (org.codehaus.stax2.XMLStreamReader2) context.getInMessage().getXMLStreamReader();
doc = org.codehaus.xfire.util.STAXUtils.read(dbf.newDocumentBuilder(), reader2, false);
this.response = doc;
context.getInMessage().setProperty(DOM_MESSAGE, doc);
context.getInMessage().setXMLStreamReader(new org.codehaus.xfire.util.stax.W3CDOMStreamReader(doc.getDocumentElement()));
this.interactionId = RxDOMUtil.getInteractionIdFromDoc(doc);
RxDOMUtil.writeXmlToFile("xmls/SOAP_Response_" + this.interactionId + "_" + this.testCase + ".xml", doc);
}
public org.w3c.dom.Document getResponse()
{
return response;
}
}
I tried modifying the enableUsernameTokenEClaims method to add a BinarySecurityToken, but so far I have not been able to get that to work.
I have already successfully make this same change to another client of this service, but that client was written in VB and the libraries being used there were either more flexible or less strict.
Based on speaking with coworkers with more experience with these libraries and SOAP in general, it seems that this version of the wss4j library just doesn't support BinarySecurityToken :(
As such we're going to go a different route to transmit the tokens.
I am using lightcouch for my spring boot application and I need to be able to query my CouchDb database for documents based on a provided filter. Since these filters can always be different, I can't make use of a preset view. I am looking for something that would work in a similar way of the normal find like below:
public List<MyEntity> getEntities(MyFilter myFilter)
{
return dbClient.find(resourceFilter, MyEntity.class);
}
myFilter would be a Map object which I would use to query documents based on certain values provided in this map. Is it possible? Is there an approach to achieve what I want? Thanks
LightCouch provides a method for querying CouchDB using mango selectors.
See CouchDbClientBase.java
/**
* Find documents using a declarative JSON querying syntax.
* #param <T> The class type.
* #param jsonQuery The JSON query string.
* #param classOfT The class of type T.
* #return The result of the query as a {#code List<T> }
* #throws CouchDbException If the query failed to execute or the request is invalid.
*/
public <T> List<T> findDocs(String jsonQuery, Class<T> classOfT) { ...
LightCouch internal API allows for a user-defined raw HTTP request to execute against a database. This can be done through the CouchDbClient#executeRequest method.
I'm not using LightCouch in my Java project but Apache HTTPClient together with GSON. Below example assumes CouchDB being installed on your local computer and both, user and password to be "admin". It could be easily adapted to use CouchDbClient#executeRequest.
The mangoSelector parameter in the find method must comply with the CouchDB selector syntax.
public class CouchDBAccess {
private static final String BASE_URL = "http://localhost:5984/";
private static final Gson GSON = new GsonBuilder().create();
private final Header[] httpHeaders;
public CouchDBAccess() {
this.httpHeaders = new Header[] { //
new BasicHeader("Accept", "application/json"), //
new BasicHeader("Content-type", "application/json"), //
new BasicHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("admin:admin".getBytes())) //
};
}
FindResult find(String dbName, String mangoSelector) throws IOException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
HttpPost httpPost = new HttpPost(BASE_URL + dbName + "/_find");
httpPost.setHeaders(httpHeaders);
httpPost.setEntity(new StringEntity(mangoSelector, ContentType.APPLICATION_JSON));
HttpResponse response = client.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
return GSON.fromJson(extractContent(response), FindResult.class);
} else {
// handle invalid response
}
}
}
private String extractContent(HttpResponse response) throws IOException {
StringWriter writer = new StringWriter();
IOUtils.copy(response.getEntity().getContent(), writer, defaultCharset());
return writer.toString();
}
}
class FindResult {
MyEntity[] docs;
}
A corresponding jUnit test method could look as follows:
#Test
public void testFind() throws IOException {
String mangoSelector = "{\"selector\": {\"Host\": \"local drive\"}}";
FindResult findResult = couchDBAccess.find("data_1", mangoSelector);
assertEquals(100, findResult.docs.length); // or whatever you expect
}
Have been struggling for last few days with this error Authentication of type {http://service.soap.xcompany.com}AuthenticationHeader had undefined attribute {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id while invoking a service call from a C# WCF client (targeting .Net 4.5 framework) to a Java Soap Service hosted externally with end-to-end encryption (both client and service certificates are used). When I tested the service using SoapUI with a JKS file, request was processed successfully.
So to see what's difference between the two requests, I did the followings:
Used Fiddler Inspector to capture two requests, one from SoapUI which was successful and one from C# which failed with 500 error
Extracted these two Xml messages into two C# classes (named them RequestByJava and RequestByDotNet, respectively) using the VS2017 feature Edit/Paste Special/Paste Xml as Classes.
Use XmlSerializer to de-serialize the two requests into the two objects of the types created in 2) and compared their properties.
With the Soap error message in mind, I narrowed down the difference between two Authentication headers - interestingly there is one extra property "Id" in the RequestByDotNet object whereas the RequestByJava object does not have. And the 500 Soap error message seemed to indicate that there was a schema validation error due to that undefined element "Id"
Also noticed that the RequestByDotNet.Header.Security.BinarySecurityToken.ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" but RequestByJava (SoapUI) has a different ValueType "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"
Another difference, not sure it matters, is that the Request from .net codes has a "mustunderstand" value under the Header.Security set to true while the one from Java does not.
My questions are:
Why is the difference?
How can this be fixed without having to write a Java client?
Some codes used binding and endpoint behavior:
private static CustomBinding BuildCustomBinding()
{
var binding = new CustomBinding();
var textMessageEncoding = new TextMessageEncodingBindingElement()
{
MessageVersion = MessageVersion.Soap11
};
var securityBindingElement =
SecurityBindingElement.CreateMutualCertificateBindingElement(
MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10, true);
binding.Elements.AddRange(textMessageEncoding, securityBindingElement, new HttpsTransportBindingElement());
return binding;
}
private static void CallAccountService()
{
//credential for test
const string applId = "testuser";
const string pwd = "password";
//for client certificate, import client.pfx to LocalMachine's Trusted Root Certification Authorities and make sure the thumbprint matches
var client = new NOLWSAccountSvc.WSAccountv1Client(BuildCustomBinding(), GetAccountServiceEndpointAddress());
client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine,
StoreName.Root, X509FindType.FindByThumbprint, "thumbprintvalue");
//for service certificate, import service-provider.cer to same store location and store name and make sure the thumbprint matches
client.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.Root,
X509FindType.FindByThumbprint, "thumprintvalue");
client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.PeerOrChainTrust;
client.Open();
var header = new NOLWSAccountSvc.AuthenticationHeader()
{
application_id = applId,
password = pwd
};
var getActiveAccountsFunc = new NOLWSAccountSvc.getActiveAccounts() { applRef = "softact-dev", resetRows = true };
try
{
var response = client.getActiveAccounts(header, getActiveAccountsFunc);
Console.WriteLine(response.moreData);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
client.Close();
}
}
Thanks for your time! Your help will be highly appreciated.
#jdweng Yes, I did; here were two request bodies, first from .Net and 2nd from SoapUI:
.Net Request:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><h:Authentication u:Id="_2" xmlns:h="http://service.soap.xcompany.com" xmlns="http://service.soap.xcompany.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><application_id>testuserid</application_id><password>testpassword</password></h:Authentication><ActivityId CorrelationId="d7085e6f-b757-46e8-b3eb-319a51d568a3" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">00000000-0000-0000-0000-000000000000</ActivityId><VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo8DAzaQVkApDpl1Tc1YTHQwAAAAAMbeMEvBLCUqoD7kEDPHDKYukgggNOf5FtHBB/Sa7ggkACQAA</VsDebuggerCausalityData><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><o:BinarySecurityToken u:Id="uuid-eb310312-396a-4d00-8922-f77de97138cb-3" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">MIIDYzCCAkugAwIBAgIEaGKzJDANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJ1czEPMA0GA1UEChMGU3ByaW50MREwDwYDVQQLEwhQcm9qZWN0czEMMAoGA1UECxMDQk1QMQwwCgYDVQQLEwNUUEExEzARBgNV</o:BinarySecurityToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#_1"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>WCpRwVjx89ceVctR8lp9LNGKHeA=</DigestValue></Reference><Reference URI="#_2"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>8/PErh8BL9To5zazpP9CbPFTAa8=</DigestValue></Reference></SignedInfo><SignatureValue>hOtpz7lXvZPPbBD6sV1hxyx3Hc39vj0q2GYKMd8oQbgTbbuKC7QKcZOjktqUxayrzc6h/V0j7Kx3APPONe4F3A2581nK4AQ72yYonsaeXQW0yzSxW/VTsN04uoqCP6IpKXqlAz40VeWGUPJOeGthCKy/9A+NSuqS</SignatureValue><KeyInfo><o:SecurityTokenReference><o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-eb310312-396a-4d00-8922-f77de97138cb-3"/></o:SecurityTokenReference></KeyInfo></Signature></o:Security></s:Header><s:Body u:Id="_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><getActiveAccounts xmlns="http://service.soap.xcompany.com"><applRef>dev</applRef><resetRows>false</resetRows></getActiveAccounts></s:Body></s:Envelope>
SoapUI Request:
(somehow it won't let me past whole xml here.. )
Well, my colleague helped me figure out way to remove the extra headers from the request before it was posted to the Java SOAP service endpoint - the key was to use IClientMessageInspector and implement some logic in the BeforeSendRequest to remove the unwanted headers that were rejected by the service provider; then add a custom FormattingBehavior class to inherit from IEndpointBheavior and in the IEndPointBehavior.ApplyClientBehavior, attach the MyClientMessageInspector; finally add the customer endpoint behavior to the web service client. Here are the codes:
Where and how to remove unwanted request headers:
public class MyClientMessageInspector : IClientMessageInspector
{
public MyClientMessageInspector(ServiceEndpoint endpoint)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
//Console.WriteLine(request.ToString());
var lstUnwantedStuff = new[]
{
new KeyValuePair<string, string>("Action", "http://www.w3.org/2005/08/addressing"),
new KeyValuePair<string, string>("VsDebuggerCausalityData",
"http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink")
};
foreach (var kv in lstUnwantedStuff)
{
var indexOfUnwantedHeader = request.Headers.FindHeader(kv.Key, kv.Value);
if (indexOfUnwantedHeader>=0)
{
request.Headers.RemoveAt(indexOfUnwantedHeader);
}
}
...
Where and how to use the custom ClientMessageInspector:
internal class MyFaultFormatterBehavior : IEndpointBehavior
{
...
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new MyClientMessageInspector(endpoint));
}
}
Where and how to attach custom EndpointBehavior:
private static void CallAccountService()
{
var client = new WSAccountv1Client(BuildCustomBinding(), GetAccountServiceEndpointAddress());
//Set client certificate
client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine,
StoreName.Root, X509FindType.FindByThumbprint, "xxxxxxxxxx");
//for service certificate
client.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople,
X509FindType.FindByThumbprint, "xxxxxxxxxxxxxxxxy");
client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.PeerOrChainTrust;
//add faultformattingbehavior so we can intercept the fault reply message
client.Endpoint.EndpointBehaviors.Add(new MyFaultFormatterBehavior());
client.Open();
var header = new AuthenticationHeader()
{
application_id = applId,
password = pwd
};
var getActiveAccountsFunc = new getActiveAccounts() { applRef = "test", resetRows = true };
try
{
//MyClientMessageInspector.BeforeSendRequest is entered when this called is made
var response = client.getActiveAccounts(header, getActiveAccountsFunc);
Console.WriteLine(response.moreData);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
client.Close();
}
}
What else?
In the proxy classes, need to set the Authentication ProtectionLevel to None while on the Service level it needs to be set as ProtectionLevel.Sign:
Request level:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(IsWrapped = false)]
public partial class getActiveAccountsRequest
{
[System.ServiceModel.MessageHeaderAttribute(Namespace = "http://service.xcompany.com"
, ProtectionLevel = System.Net.Security.ProtectionLevel.None
)]
public AuthenticationHeader Authentication;
Service (Interface) Level:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace = "http://service.xcompany.com",
ConfigurationName = "WSAccount"
, ProtectionLevel = ProtectionLevel.Sign
)]
public interface WSAccount
{
I have a weird question. I am working on a Java project for work, where we need to make HTTP GET/POST calls to our WEB API. I wanted to make a WebAPI testing project in C#; run it locally (localhost on some random port) and make sure I am sending the right stuff. That way I could control what was sent back(success, errors, JSON, XML, and different variables like that).
Here is some key stuff I have so far:
Client-Java code:
public String sendAPIRequest( HttpRequestMethod method, String apiURI, String payload) throws IOException
{
// Method is GET, POST....
// apiURL specific API navigating to.
// pauload is the html body.
if(payload == null)
{
payload = "";
}
// Establish a connection.
String strURL = String.format("%s%s", this.BaseURL, apiURI);
URL url = new URL(strURL);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Accept-Charset", this.CHARSET);
conn.setRequestMethod(method.toString());
conn.setRequestProperty("Content-Type", "text/json;charset=" + this.CHARSET);
conn.setRequestProperty("User-Agent","Mozilla/5.0 ( compatible ) ");
conn.setRequestProperty("Accept","*/*");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
// write the payload out, if it exists.
//if(payload != null)
{
try(OutputStream output = conn.getOutputStream())
{
output.write(payload.getBytes(CHARSET));
}
}
// read the response.
StringBuilder response = new StringBuilder();
InputStream input = conn.getInputStream();
try(Scanner inputScanner = new Scanner(input))
{
while(inputScanner.hasNextLine())
{
response.append((inputScanner));
}
}
return response.toString();
}
public String CheckForApplicableLicenses(String dCode, String key)
{
String result;
try
{
String APICall = String.format("/license/find_matching?d_code=%s&key=%s", dCode, key);
String Response = API.sendAPIRequest(HttpRequestMethod.GET, APICall);
// TODO Parse the String Response JSON/XMl.
result = Response;
}
catch(Exception ex)
{
// TODO: incorporate some sort of logging and error handling.
result = ex.toString();
}
return result;
}
Server-C#.Net code (tested with fiddler, and in the browser):
[Route("api/[controller]")]
public class LicenseController : Controller
{
[HttpGet]
[Route("find_matching")]
public IEnumerable<string> find_matching(string d_code = "", string key = "")
{
return new string[] { d_code, key };
}
}
Results so far:
I've gotten 404 errors, and I have been able to connect. Most of the time the Java client blows up when I get to the creating the InputStream. I've never been able to trip the breakpoint in the C# server.
Questions:
1) Is what I am doing even feasible? I'm really just trying to test the Java Client, without calling the API, before I am ready. Maybe it has something to do with not running the service on the default HTTP port of 80?
2) Is there a better way of testing this? I don't want to make call to our actual service until we are done.
Thanks in advance for an assistance.
We're using stormpath with Java & also trying to combine form Login with REST API authentication on the same application.
I've setup stormpath servlet plugin as described here https://docs.stormpath.com/java/servlet-plugin/quickstart.html... This works very fine.
Now, on the same application, we have APIs where I've implemented oAuth authentication with stormpath see here http://docs.stormpath.com/guides/api-key-management/
The first request for an access-token works fine by sending Basic Base64(keyId:keySecret) in the request header and grant_type = client_credentials in the body. Access tokens are being returned nicely. However trying to authenticate subsequent requests with the header Bearer <the-obtained-access-token> does not even hit the application before
returning the following json error message...
{
"error": "invalid_client",
"error_description": "access_token is invalid."
}
This is confusing because I've set breakpoints all over the application and I'm pretty sure that the API request doesn't hit the anywhere within the application before stormpath kicks in and returns this error. And even if stormpath somehow intercepts the request before getting to the REST interface, this message doesn't make any sense to me because i'm certainly making the subsequent API calls with a valid access-token obtained from the first call to get access-token.
I have run out of ideas why this could be happening but i'm suspecting that it may have something to do with stormpath config especially with a combination
of form Login/Authentication for web views and oAuth Athentication for REST endpoints. With that said, here's what my stormpath.properties looks like. Hope this could help point at anything I may be doing wrong.
stormpath.application.href=https://api.stormpath.com/v1/applications/[app-id]
stormpath.web.filters.authr=com.app.security.AuthorizationFilter
stormpath.web.request.event.listener = com.app.security.AuthenticationListener
stormpath.web.uris./resources/**=anon
stormpath.web.uris./assets/**=anon
stormpath.web.uris./v1.0/**=anon
stormpath.web.uris./** = authc,authr
stormpath.web.uris./**/**=authc,authr
Help with this would be highly appreciated.
The problem might be related to an incorrect request.
Is it possible for you to try this code in your app?:
private boolean verify(String accessToken) throws OauthAuthenticationException {
HttpRequest request = createRequestForOauth2AuthenticatedOperation(accessToken);
AccessTokenResult result = Applications.oauthRequestAuthenticator(application)
.authenticate(request);
System.out.println(result.getAccount().getEmail() + " was successfully verified, you can allow your protect operation to continue");
return true;
}
private HttpRequest createRequestForOauth2AuthenticatedOperation(String token) {
try {
Map<String, String[]> headers = new LinkedHashMap<String, String[]>();
headers.put("Accept", new String[]{"application/json"});
headers.put("Authorization", new String[]{"Bearer " + token});
HttpRequest request = HttpRequests.method(HttpMethod.GET)
.headers(headers)
.build();
return request;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I've prepared an example that demonstrates oauth token creation as well as authorized access to protected pages using access tokens.
It builds off of the servlet example in the Stormpath SDK. The repo can be found here: https://github.com/stormpath/stormpath-java-oauth-servlet-sample
It demonstrates running a servlet application and having an out-of-band program get and use oauth tokens to access protected resources.
The core of the oauth part is in TokenAuthTest.java:
public class TokenAuthTest {
public static void main(String[] args) throws Exception {
String command = System.getProperty("command");
if (command == null || !("getToken".equals(command) || "getPage".equals(command))) {
System.err.println("Must supply a command:");
System.err.println("\t-Dcommand=getToken OR");
System.err.println("\t-Dcommand=getPage OR");
System.exit(1);
}
if ("getToken".equals(command)) {
getToken();
} else {
getPage();
}
}
private static final String APP_URL = "http://localhost:8080";
private static final String OAUTH_URI = "/oauth/token";
private static final String PROTECTED_URI = "/dashboard";
private static void getToken() throws Exception {
String username = System.getProperty("username");
String password = System.getProperty("password");
if (username == null || password == null) {
System.err.println("Must supply -Dusername=<username> -Dpassword=<password> on the command line");
System.exit(1);
}
PostMethod method = new PostMethod(APP_URL + OAUTH_URI);
method.setRequestHeader("Origin", APP_URL);
method.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
method.addParameter("grant_type", "password");
method.addParameter("username", username);
method.addParameter("password", password);
HttpClient client = new HttpClient();
client.executeMethod(method);
BufferedReader br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
String readLine;
while(((readLine = br.readLine()) != null)) {
System.out.println(readLine);
}
}
private static void getPage() throws Exception {
String token = System.getProperty("token");
if (token == null) {
System.err.println("Must supply -Dtoken=<access token> on the command line");
System.exit(1);
}
GetMethod method = new GetMethod(APP_URL + PROTECTED_URI);
HttpClient client = new HttpClient();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " without token...");
int returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
System.out.println();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " with token...");
method.addRequestHeader("Authorization", "Bearer " + token);
returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
}
}