/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2006-2010 Sun Microsystems, Inc.
|
* Portions Copyright 2011-2015 ForgeRock AS
|
*/
|
package org.opends.dsml.protocol;
|
|
|
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
|
import static org.opends.server.protocols.ldap.LDAPResultCode.
|
CLIENT_SIDE_CONNECT_ERROR;
|
import static org.opends.server.util.ServerConstants.SASL_MECHANISM_PLAIN;
|
import static org.opends.messages.CoreMessages.
|
INFO_RESULT_CLIENT_SIDE_ENCODING_ERROR;
|
import static org.opends.messages.CoreMessages.INFO_RESULT_AUTHORIZATION_DENIED;
|
|
import java.io.BufferedInputStream;
|
import java.io.InputStream;
|
import java.io.IOException;
|
import java.io.OutputStream;
|
import java.net.URL;
|
import java.text.ParseException;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.ArrayList;
|
import java.util.Enumeration;
|
import java.util.HashSet;
|
import java.util.Iterator;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.StringTokenizer;
|
import java.util.logging.Level;
|
import java.util.logging.Logger;
|
|
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletException;
|
import javax.xml.XMLConstants;
|
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBElement;
|
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.Marshaller;
|
import javax.xml.bind.Unmarshaller;
|
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.SAXParserFactory;
|
import javax.xml.soap.*;
|
import javax.xml.soap.SOAPConstants;
|
import javax.xml.validation.Schema;
|
import javax.xml.validation.SchemaFactory;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.opends.server.controls.ProxiedAuthV2Control;
|
import org.opends.server.core.DirectoryServer;
|
import org.forgerock.opendj.ldap.DecodeException;
|
import org.opends.server.protocols.ldap.LDAPConstants;
|
import org.opends.server.protocols.ldap.LDAPFilter;
|
import org.opends.server.protocols.ldap.LDAPMessage;
|
import org.opends.server.protocols.ldap.LDAPResultCode;
|
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
|
import org.opends.server.schema.SchemaConstants;
|
import org.opends.server.tools.LDAPConnection;
|
import org.opends.server.tools.LDAPConnectionException;
|
import org.opends.server.tools.LDAPConnectionOptions;
|
import org.opends.server.tools.SSLConnectionException;
|
import org.opends.server.tools.SSLConnectionFactory;
|
import org.forgerock.opendj.ldap.ByteString;
|
import org.forgerock.opendj.ldap.DereferenceAliasesPolicy;
|
import org.opends.server.types.LDAPException;
|
import org.forgerock.opendj.ldap.SearchScope;
|
import org.opends.server.util.Base64;
|
|
import org.w3c.dom.Document;
|
import org.xml.sax.Attributes;
|
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.InputSource;
|
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXNotRecognizedException;
|
import org.xml.sax.SAXNotSupportedException;
|
import org.xml.sax.XMLReader;
|
|
|
/**
|
* This class provides the entry point for the DSML request.
|
* It parses the SOAP request, calls the appropriate class
|
* which performs the LDAP operation, and returns the response
|
* as a DSML response.
|
*/
|
public class DSMLServlet extends HttpServlet {
|
private static final String PKG_NAME = "org.opends.dsml.protocol";
|
private static final String PORT = "ldap.port";
|
private static final String HOST = "ldap.host";
|
private static final String USERDN = "ldap.userdn";
|
private static final String USERPWD = "ldap.userpassword";
|
private static final String USESSL = "ldap.usessl";
|
private static final String USESTARTTLS = "ldap.usestarttls";
|
private static final String TRUSTSTOREPATH = "ldap.truststore.path";
|
private static final String TRUSTSTOREPASSWORD = "ldap.truststore.password";
|
private static final String TRUSTALLCERTS = "ldap.trustall";
|
private static final String USEHTTPAUTHZID = "ldap.authzidtypeisid";
|
private static final String EXOPSTRINGPREFIX = "ldap.exop.string.";
|
private static final long serialVersionUID = -3748022009593442973L;
|
private static final AtomicInteger nextMessageID = new AtomicInteger(1);
|
|
// definitions of return error messages
|
private static final String MALFORMED_REQUEST = "malformedRequest";
|
private static final String NOT_ATTEMPTED = "notAttempted";
|
private static final String AUTHENTICATION_FAILED = "authenticationFailed";
|
private static final String COULD_NOT_CONNECT = "couldNotConnect";
|
private static final String GATEWAY_INTERNAL_ERROR = "gatewayInternalError";
|
private static final String UNRESOLVABLE_URI = "unresolvableURI";
|
|
// definitions of onError values
|
private static final String ON_ERROR_EXIT = "exit";
|
|
private static JAXBContext jaxbContext;
|
private ObjectFactory objFactory;
|
private DocumentBuilder db;
|
private static Schema schema;
|
private MessageFactory messageFactory;
|
private MessageFactory messageFactorySOAP_1_1;
|
private MessageFactory messageFactorySOAP_1_2;
|
private String contentType;
|
|
/**
|
* This extends the default handler of SAX parser. It helps to retrieve the
|
* requestID value when the xml request is malformed and thus unparsable
|
* using SOAP or JAXB.
|
*/
|
private DSMLContentHandler contentHandler;
|
/** Prevent multiple logging when trying to set unavailable/unsupported parser features */
|
private static AtomicBoolean logFeatureWarnings = new AtomicBoolean(false);
|
|
private String hostName;
|
private Integer port;
|
private String userDN;
|
private String userPassword;
|
private Boolean useSSL;
|
private Boolean useStartTLS;
|
private String trustStorePathValue;
|
private String trustStorePasswordValue;
|
private Boolean trustAll;
|
private Boolean useHTTPAuthzID;
|
private HashSet<String> exopStrings = new HashSet<String>();
|
private org.opends.server.types.Control proxyAuthzControl;
|
|
/**
|
* This method will be called by the Servlet Container when
|
* this servlet is being placed into service.
|
*
|
* @param config - the <CODE>ServletConfig</CODE> object that
|
* contains configuration information for this servlet.
|
* @throws ServletException If an error occurs during processing.
|
*/
|
@Override
|
public void init(ServletConfig config) throws ServletException {
|
|
try {
|
hostName = config.getServletContext().getInitParameter(HOST);
|
|
port = Integer.valueOf(config.getServletContext().getInitParameter(PORT));
|
|
userDN = config.getServletContext().getInitParameter(USERDN);
|
|
userPassword = config.getServletContext().getInitParameter(USERPWD);
|
|
useSSL = Boolean.valueOf(
|
config.getServletContext().getInitParameter(USESSL));
|
|
useStartTLS = Boolean.valueOf(
|
config.getServletContext().getInitParameter(USESTARTTLS));
|
|
trustStorePathValue =
|
config.getServletContext().getInitParameter(TRUSTSTOREPATH);
|
|
trustStorePasswordValue =
|
config.getServletContext().getInitParameter(TRUSTSTOREPASSWORD);
|
|
trustAll = Boolean.valueOf(
|
config.getServletContext().getInitParameter(TRUSTALLCERTS));
|
|
useHTTPAuthzID = Boolean.valueOf(
|
config.getServletContext().getInitParameter(USEHTTPAUTHZID));
|
|
/*
|
* Find all the param-names matching the pattern:
|
* ldap.exop.string.1.2.3.4.5
|
* and if the value's true then mark that OID (1.2.3.4.5) as one returning
|
* a string value.
|
*/
|
Enumeration names = config.getServletContext().getInitParameterNames();
|
while (names.hasMoreElements())
|
{
|
String name = (String) names.nextElement();
|
if (name.startsWith(EXOPSTRINGPREFIX) &&
|
Boolean.valueOf(config.getServletContext().getInitParameter(name)))
|
{
|
exopStrings.add(name.substring(EXOPSTRINGPREFIX.length()));
|
}
|
}
|
|
// allow the use of anyURI values in adds and modifies
|
System.setProperty("mapAnyUriToUri", "true");
|
|
if(jaxbContext==null)
|
jaxbContext = JAXBContext.newInstance(PKG_NAME, getClass().getClassLoader());
|
// assign the DSMLv2 schema for validation
|
if(schema==null)
|
{
|
URL url = getClass().getResource("/resources/DSMLv2.xsd");
|
if ( url != null ) {
|
SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
|
schema = sf.newSchema(url);
|
}
|
}
|
|
objFactory = new ObjectFactory();
|
messageFactorySOAP_1_1 = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
|
messageFactorySOAP_1_2 = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
dbf.setNamespaceAware(true);
|
dbf.setXIncludeAware(false);
|
dbf.setExpandEntityReferences(false);
|
db = dbf.newDocumentBuilder();
|
|
this.contentHandler = new DSMLContentHandler();
|
|
DirectoryServer.bootstrapClient();
|
} catch (Exception je) {
|
je.printStackTrace();
|
throw new ServletException(je.getMessage());
|
}
|
}
|
|
|
|
/**
|
* Check if using the proxy authz control will work, by using it to read
|
* the Root DSE.
|
*
|
* @param connection The authenticated LDAP connection used to check.
|
* @param authorizationID The authorization ID, in the format
|
* "u:<userid>" or "dn:<DN>".
|
* @return a configured proxy authz control.
|
* @throws LDAPConnectionException If an error occurs during the check.
|
*
|
*/
|
private org.opends.server.types.Control checkAuthzControl(
|
LDAPConnection connection, String authorizationID)
|
throws LDAPConnectionException
|
{
|
LinkedHashSet<String>attributes = new LinkedHashSet<String>(1);
|
attributes.add(SchemaConstants.NO_ATTRIBUTES);
|
ArrayList<org.opends.server.types.Control> controls =
|
new ArrayList<org.opends.server.types.Control>(1);
|
org.opends.server.types.Control proxyAuthzControl =
|
new ProxiedAuthV2Control(true, ByteString.valueOf(authorizationID));
|
controls.add(proxyAuthzControl);
|
|
try
|
{
|
SearchRequestProtocolOp protocolOp = new SearchRequestProtocolOp(
|
ByteString.empty(), SearchScope.BASE_OBJECT,
|
DereferenceAliasesPolicy.NEVER, 0, 0, true,
|
LDAPFilter.objectClassPresent(), attributes);
|
byte opType;
|
LDAPMessage msg =
|
new LDAPMessage(DSMLServlet.nextMessageID(), protocolOp, controls);
|
connection.getLDAPWriter().writeMessage(msg);
|
do {
|
LDAPMessage responseMessage = connection.getLDAPReader().
|
readMessage();
|
opType = responseMessage.getProtocolOpType();
|
switch (opType)
|
{
|
case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
|
switch (responseMessage.getSearchResultDoneProtocolOp().
|
getResultCode())
|
{
|
default:
|
LocalizableMessage m = INFO_RESULT_AUTHORIZATION_DENIED.get();
|
throw new LDAPConnectionException(m, CLIENT_SIDE_CONNECT_ERROR,
|
null);
|
case LDAPResultCode.SUCCESS:
|
return proxyAuthzControl;
|
}
|
}
|
} while (true);
|
}
|
catch (LDAPException le)
|
{
|
LocalizableMessage m = INFO_RESULT_CLIENT_SIDE_ENCODING_ERROR.get();
|
throw new LDAPConnectionException(m, CLIENT_SIDE_CONNECT_ERROR, null, le);
|
}
|
catch (DecodeException ae)
|
{
|
LocalizableMessage m = INFO_RESULT_CLIENT_SIDE_ENCODING_ERROR.get();
|
throw new LDAPConnectionException(m, CLIENT_SIDE_CONNECT_ERROR, null, ae);
|
}
|
catch (IOException ie)
|
{
|
LocalizableMessage m = INFO_RESULT_CLIENT_SIDE_ENCODING_ERROR.get();
|
throw new LDAPConnectionException(m, CLIENT_SIDE_CONNECT_ERROR, null, ie);
|
}
|
}
|
|
/**
|
* The HTTP POST operation. This servlet expects a SOAP message
|
* with a DSML request payload.
|
*
|
* @param req Information about the request received from the client.
|
* @param res Information about the response to send to the client.
|
* @throws ServletException If an error occurs during servlet processing.
|
* @throws IOException If an error occurs while interacting with the client.
|
*/
|
public void doPost(HttpServletRequest req, HttpServletResponse res)
|
throws ServletException, IOException {
|
LDAPConnectionOptions connOptions = new LDAPConnectionOptions();
|
connOptions.setUseSSL(useSSL);
|
connOptions.setStartTLS(useStartTLS);
|
|
LDAPConnection connection = null;
|
BatchRequest batchRequest = null;
|
|
// Keep the Servlet input stream buffered in case the SOAP un-marshalling
|
// fails, the SAX parsing will be able to retrieve the requestID even if
|
// the XML is malformed by resetting the input stream.
|
BufferedInputStream is = new BufferedInputStream(req.getInputStream(),
|
65536);
|
if ( is.markSupported() ) {
|
is.mark(65536);
|
}
|
|
// Create response in the beginning as it might be used if the parsing
|
// fails.
|
BatchResponse batchResponse = objFactory.createBatchResponse();
|
List<JAXBElement<?>> batchResponses = batchResponse.getBatchResponses();
|
Document doc = db.newDocument();
|
|
if (useSSL || useStartTLS)
|
{
|
SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
|
try
|
{
|
sslConnectionFactory.init(trustAll, null, null, null,
|
trustStorePathValue, trustStorePasswordValue);
|
}
|
catch(SSLConnectionException e)
|
{
|
batchResponses.add(
|
createErrorResponse(
|
new LDAPException(LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR,
|
LocalizableMessage.raw(
|
"Invalid SSL or TLS configuration to connect to LDAP server."))));
|
}
|
connOptions.setSSLConnectionFactory(sslConnectionFactory);
|
}
|
|
SOAPBody soapBody = null;
|
|
MimeHeaders mimeHeaders = new MimeHeaders();
|
Enumeration en = req.getHeaderNames();
|
String bindDN = null;
|
String bindPassword = null;
|
boolean authenticationInHeader = false;
|
boolean authenticationIsID = false;
|
while (en.hasMoreElements()) {
|
String headerName = (String) en.nextElement();
|
String headerVal = req.getHeader(headerName);
|
if (headerName.equalsIgnoreCase("content-type")) {
|
if (headerVal.startsWith(SOAPConstants.SOAP_1_1_CONTENT_TYPE)) {
|
messageFactory = messageFactorySOAP_1_1;
|
contentType = SOAPConstants.SOAP_1_1_CONTENT_TYPE;
|
} else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE)) {
|
messageFactory = messageFactorySOAP_1_2;
|
contentType = SOAPConstants.SOAP_1_2_CONTENT_TYPE;
|
} else {
|
throw new ServletException("Content-Type does not match SOAP 1.1 or SOAP 1.2");
|
}
|
} else if (headerName.equalsIgnoreCase("authorization") && headerVal.startsWith("Basic "))
|
{
|
authenticationInHeader = true;
|
String authorization = headerVal.substring(6).trim();
|
try {
|
String unencoded = new String(Base64.decode(authorization));
|
int colon = unencoded.indexOf(':');
|
if (colon > 0) {
|
if (useHTTPAuthzID)
|
{
|
connOptions.setSASLMechanism("mech=" + SASL_MECHANISM_PLAIN);
|
connOptions.addSASLProperty(
|
"authid=u:" + unencoded.substring(0, colon).trim());
|
authenticationIsID = true;
|
}
|
else
|
{
|
bindDN = unencoded.substring(0, colon).trim();
|
}
|
bindPassword = unencoded.substring(colon + 1);
|
}
|
} catch (ParseException ex) {
|
// user/DN:password parsing error
|
batchResponses.add(
|
createErrorResponse(
|
new LDAPException(LDAPResultCode.INVALID_CREDENTIALS,
|
LocalizableMessage.raw(ex.getMessage()))));
|
break;
|
}
|
}
|
StringTokenizer tk = new StringTokenizer(headerVal, ",");
|
while (tk.hasMoreTokens()) {
|
mimeHeaders.addHeader(headerName, tk.nextToken().trim());
|
}
|
}
|
|
if ( ! authenticationInHeader ) {
|
// if no authentication, set default user from web.xml
|
if (userDN != null)
|
{
|
bindDN = userDN;
|
if (userPassword != null)
|
{
|
bindPassword = userPassword;
|
}
|
else
|
{
|
batchResponses.add(
|
createErrorResponse(
|
new LDAPException(LDAPResultCode.INVALID_CREDENTIALS,
|
LocalizableMessage.raw("Invalid configured credentials."))));
|
}
|
}
|
else
|
{
|
bindDN = "";
|
bindPassword = "";
|
}
|
} else {
|
// otherwise if DN or password is null, send back an error
|
if (((!authenticationIsID && (bindDN == null)) || bindPassword == null)
|
&& batchResponses.isEmpty()) {
|
batchResponses.add(
|
createErrorResponse(
|
new LDAPException(LDAPResultCode.INVALID_CREDENTIALS,
|
LocalizableMessage.raw("Unable to retrieve credentials."))));
|
}
|
}
|
|
// if an error already occurred, the list is not empty
|
if ( batchResponses.isEmpty() ) {
|
try {
|
SOAPMessage message = messageFactory.createMessage(mimeHeaders, is);
|
soapBody = message.getSOAPBody();
|
} catch (SOAPException ex) {
|
// SOAP was unable to parse XML successfully
|
batchResponses.add(
|
createXMLParsingErrorResponse(is,
|
batchResponse,
|
String.valueOf(ex.getCause())));
|
}
|
}
|
|
if ( soapBody != null ) {
|
Iterator it = soapBody.getChildElements();
|
while (it.hasNext()) {
|
Object obj = it.next();
|
if (!(obj instanceof SOAPElement)) {
|
continue;
|
}
|
SOAPElement se = (SOAPElement) obj;
|
JAXBElement<BatchRequest> batchRequestElement = null;
|
try {
|
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
|
unmarshaller.setSchema(schema);
|
batchRequestElement = unmarshaller.unmarshal(se, BatchRequest.class);
|
} catch (JAXBException e) {
|
// schema validation failed
|
batchResponses.add(createXMLParsingErrorResponse(is,
|
batchResponse,
|
String.valueOf(e)));
|
}
|
if ( batchRequestElement != null ) {
|
boolean authzInBind = false;
|
boolean authzInControl = false;
|
batchRequest = batchRequestElement.getValue();
|
|
/*
|
* Process optional authRequest (i.e. use authz)
|
*/
|
if (batchRequest.authRequest != null) {
|
if (authenticationIsID == true) {
|
// If we are using SASL, then use the bind authz.
|
connOptions.addSASLProperty("authzid=" +
|
batchRequest.authRequest.getPrincipal());
|
authzInBind = true;
|
} else {
|
// If we are using simple then we have to do some work after
|
// the bind.
|
authzInControl = true;
|
}
|
}
|
// set requestID in response
|
batchResponse.setRequestID(batchRequest.getRequestID());
|
|
boolean connected = false;
|
|
if ( connection == null ) {
|
connection = new LDAPConnection(hostName, port, connOptions);
|
try {
|
|
connection.connectToHost(bindDN, bindPassword);
|
if (authzInControl)
|
{
|
proxyAuthzControl = checkAuthzControl(connection,
|
batchRequest.authRequest.getPrincipal());
|
}
|
if (authzInBind || authzInControl)
|
{
|
LDAPResult authResponse = objFactory.createLDAPResult();
|
ResultCode code = ResultCodeFactory.create(objFactory,
|
LDAPResultCode.SUCCESS);
|
authResponse.setResultCode(code);
|
batchResponses.add(
|
objFactory.createBatchResponseAuthResponse(authResponse));
|
}
|
connected = true;
|
} catch (LDAPConnectionException e) {
|
// if connection failed, return appropriate error response
|
batchResponses.add(createErrorResponse(e));
|
}
|
}
|
if ( connected ) {
|
List<DsmlMessage> list = batchRequest.getBatchRequests();
|
|
for (DsmlMessage request : list) {
|
JAXBElement<?> result = performLDAPRequest(connection, request);
|
if ( result != null ) {
|
batchResponses.add(result);
|
}
|
// evaluate response to check if an error occurred
|
Object o = result.getValue();
|
if ( o instanceof ErrorResponse ) {
|
if ( ON_ERROR_EXIT.equals(batchRequest.getOnError()) ) {
|
break;
|
}
|
} else if ( o instanceof LDAPResult ) {
|
int code = ((LDAPResult)o).getResultCode().getCode();
|
if ( code != LDAPResultCode.SUCCESS
|
&& code != LDAPResultCode.REFERRAL
|
&& code != LDAPResultCode.COMPARE_TRUE
|
&& code != LDAPResultCode.COMPARE_FALSE && ON_ERROR_EXIT.equals(batchRequest.getOnError()) )
|
{
|
break;
|
}
|
}
|
}
|
}
|
// close connection to LDAP server
|
if ( connection != null ) {
|
connection.close(nextMessageID);
|
}
|
}
|
}
|
}
|
try {
|
Marshaller marshaller = jaxbContext.createMarshaller();
|
marshaller.marshal(objFactory.createBatchResponse(batchResponse), doc);
|
sendResponse(doc, res);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
|
}
|
|
|
|
/**
|
* Safely set a feature on an XMLReader instance.
|
*
|
* @param xmlReader The reader to configure.
|
* @param feature The feature string to set.
|
* @param flag The value to set the feature to.
|
*/
|
private void safeSetFeature(XMLReader xmlReader, String feature, Boolean flag)
|
{
|
try
|
{
|
xmlReader.setFeature(feature, flag);
|
}
|
catch (SAXNotSupportedException e)
|
{
|
if (logFeatureWarnings.compareAndSet(false, true))
|
{
|
Logger.getLogger(PKG_NAME).log(Level.SEVERE, "XMLReader unsupported feature " + feature);
|
}
|
}
|
catch (SAXNotRecognizedException e)
|
{
|
if (logFeatureWarnings.compareAndSet(false, true))
|
{
|
Logger.getLogger(PKG_NAME).log(Level.SEVERE, "XMLReader unrecognized feature " + feature);
|
}
|
}
|
}
|
|
|
|
/**
|
* Returns an error response after a parsing error. The response has the
|
* requestID of the batch request, the error response message of the parsing
|
* exception message and the type 'malformed request'.
|
*
|
* @param is the XML InputStream to parse
|
* @param batchResponse the JAXB object to fill in
|
* @param parserErrorMessage the parsing error message
|
*
|
* @return a JAXBElement that contains an ErrorResponse
|
*/
|
private JAXBElement<ErrorResponse> createXMLParsingErrorResponse(
|
InputStream is,
|
BatchResponse batchResponse,
|
String parserErrorMessage) {
|
ErrorResponse errorResponse = objFactory.createErrorResponse();
|
|
try
|
{
|
// try alternative XML parsing using SAX to retrieve requestID value
|
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
// Ensure we are doing basic secure processing.
|
saxParserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
saxParserFactory.setXIncludeAware(false);
|
saxParserFactory.setNamespaceAware(true);
|
|
// Configure a safe XMLReader appropriate for SOAP.
|
XMLReader xmlReader = saxParserFactory.newSAXParser().getXMLReader();
|
safeSetFeature(xmlReader, "http://xml.org/sax/features/validation", false);
|
safeSetFeature(xmlReader, "http://xml.org/sax/features/external-general-entities", false);
|
safeSetFeature(xmlReader, "http://xml.org/sax/features/external-parameter-entities", false);
|
safeSetFeature(xmlReader, "http://xml.org/sax/features/namespaces", true);
|
safeSetFeature(xmlReader, "http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
// clear previous match
|
this.contentHandler.requestID = null;
|
xmlReader.setContentHandler(this.contentHandler);
|
is.reset();
|
|
xmlReader.parse(new InputSource(is));
|
}
|
catch (ParserConfigurationException e)
|
{
|
// ignore
|
}
|
catch (SAXException e)
|
{
|
// ignore
|
}
|
catch (IOException e)
|
{
|
// ignore
|
}
|
if ( parserErrorMessage!= null ) {
|
errorResponse.setMessage(parserErrorMessage);
|
}
|
batchResponse.setRequestID(this.contentHandler.requestID);
|
|
errorResponse.setType(MALFORMED_REQUEST);
|
|
return objFactory.createBatchResponseErrorResponse(errorResponse);
|
}
|
|
/**
|
* Returns an error response with attributes set according to the exception
|
* provided as argument.
|
*
|
* @param t the exception that occurred
|
*
|
* @return a JAXBElement that contains an ErrorResponse
|
*/
|
private JAXBElement<ErrorResponse> createErrorResponse(Throwable t) {
|
// potential exceptions are IOException, LDAPException, DecodeException
|
|
ErrorResponse errorResponse = objFactory.createErrorResponse();
|
errorResponse.setMessage(String.valueOf(t));
|
|
if ( t instanceof LDAPException ) {
|
switch(((LDAPException)t).getResultCode()) {
|
case LDAPResultCode.AUTHORIZATION_DENIED:
|
case LDAPResultCode.INAPPROPRIATE_AUTHENTICATION:
|
case LDAPResultCode.INVALID_CREDENTIALS:
|
case LDAPResultCode.STRONG_AUTH_REQUIRED:
|
errorResponse.setType(AUTHENTICATION_FAILED);
|
break;
|
|
case LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR:
|
errorResponse.setType(COULD_NOT_CONNECT);
|
break;
|
|
case LDAPResultCode.UNWILLING_TO_PERFORM:
|
errorResponse.setType(NOT_ATTEMPTED);
|
break;
|
|
default:
|
errorResponse.setType(MALFORMED_REQUEST);
|
break;
|
}
|
} else if ( t instanceof LDAPConnectionException ) {
|
errorResponse.setType(COULD_NOT_CONNECT);
|
} else if ( t instanceof IOException ) {
|
errorResponse.setType(UNRESOLVABLE_URI);
|
} else {
|
errorResponse.setType(GATEWAY_INTERNAL_ERROR);
|
}
|
|
return objFactory.createBatchResponseErrorResponse(errorResponse);
|
}
|
|
/**
|
* Performs the LDAP operation and sends back the result (if any). In case
|
* of error, an error response is returned.
|
*
|
* @param connection a connected connection
|
* @param request the JAXB request to perform
|
*
|
* @return null for an abandon request, the expect result for all other
|
* requests or an error in case of unexpected behaviour.
|
*/
|
private JAXBElement<?> performLDAPRequest(LDAPConnection connection,
|
DsmlMessage request) {
|
ArrayList<org.opends.server.types.Control> controls =
|
new ArrayList<org.opends.server.types.Control>(1);
|
if (proxyAuthzControl != null)
|
{
|
controls.add(proxyAuthzControl);
|
}
|
try {
|
if (request instanceof SearchRequest) {
|
// Process the search request.
|
SearchRequest sr = (SearchRequest) request;
|
DSMLSearchOperation ds = new DSMLSearchOperation(connection);
|
SearchResponse searchResponse = ds.doSearch(objFactory, sr, controls);
|
return objFactory.createBatchResponseSearchResponse(searchResponse);
|
} else if (request instanceof AddRequest) {
|
// Process the add request.
|
AddRequest ar = (AddRequest) request;
|
DSMLAddOperation addOp = new DSMLAddOperation(connection);
|
LDAPResult addResponse = addOp.doOperation(objFactory, ar, controls);
|
return objFactory.createBatchResponseAddResponse(addResponse);
|
} else if (request instanceof AbandonRequest) {
|
// Process the abandon request.
|
AbandonRequest ar = (AbandonRequest) request;
|
DSMLAbandonOperation ao = new DSMLAbandonOperation(connection);
|
LDAPResult abandonResponse = ao.doOperation(objFactory, ar, controls);
|
return null;
|
} else if (request instanceof ExtendedRequest) {
|
// Process the extended request.
|
ExtendedRequest er = (ExtendedRequest) request;
|
DSMLExtendedOperation eo = new DSMLExtendedOperation(connection,
|
exopStrings);
|
ExtendedResponse extendedResponse = eo.doOperation(objFactory, er,
|
controls);
|
return objFactory.createBatchResponseExtendedResponse(extendedResponse);
|
|
} else if (request instanceof DelRequest) {
|
// Process the delete request.
|
DelRequest dr = (DelRequest) request;
|
DSMLDeleteOperation delOp = new DSMLDeleteOperation(connection);
|
LDAPResult delResponse = delOp.doOperation(objFactory, dr, controls);
|
return objFactory.createBatchResponseDelResponse(delResponse);
|
} else if (request instanceof CompareRequest) {
|
// Process the compare request.
|
CompareRequest cr = (CompareRequest) request;
|
DSMLCompareOperation compareOp =
|
new DSMLCompareOperation(connection);
|
LDAPResult compareResponse = compareOp.doOperation(objFactory, cr,
|
controls);
|
return objFactory.createBatchResponseCompareResponse(compareResponse);
|
} else if (request instanceof ModifyDNRequest) {
|
// Process the Modify DN request.
|
ModifyDNRequest mr = (ModifyDNRequest) request;
|
DSMLModifyDNOperation moddnOp =
|
new DSMLModifyDNOperation(connection);
|
LDAPResult moddnResponse = moddnOp.doOperation(objFactory, mr,
|
controls);
|
return objFactory.createBatchResponseModDNResponse(moddnResponse);
|
} else if (request instanceof ModifyRequest) {
|
// Process the Modify request.
|
ModifyRequest modr = (ModifyRequest) request;
|
DSMLModifyOperation modOp = new DSMLModifyOperation(connection);
|
LDAPResult modResponse = modOp.doOperation(objFactory, modr, controls);
|
return objFactory.createBatchResponseModifyResponse(modResponse);
|
} else if (request instanceof AuthRequest) {
|
// Process the Auth request.
|
// Only returns an BatchResponse with an AuthResponse containing the
|
// LDAP result code AUTH_METHOD_NOT_SUPPORTED
|
ResultCode resultCode = objFactory.createResultCode();
|
resultCode.setCode(LDAPResultCode.AUTH_METHOD_NOT_SUPPORTED);
|
|
LDAPResult ldapResult = objFactory.createLDAPResult();
|
ldapResult.setResultCode(resultCode);
|
|
return objFactory.createBatchResponseAuthResponse(ldapResult);
|
}
|
} catch (Throwable t) {
|
return createErrorResponse(t);
|
}
|
// should never happen as the schema was validated
|
return null;
|
}
|
|
|
/**
|
* Send a response back to the client. This could be either a SOAP fault
|
* or a correct DSML response.
|
*
|
* @param doc The document to include in the response.
|
* @param res Information about the HTTP response to the client.
|
*
|
* @throws IOException If an error occurs while interacting with the client.
|
* @throws SOAPException If an encoding or decoding error occurs.
|
*/
|
private void sendResponse(Document doc, HttpServletResponse res)
|
throws IOException, SOAPException {
|
|
SOAPMessage reply = messageFactory.createMessage();
|
SOAPHeader header = reply.getSOAPHeader();
|
header.detachNode();
|
SOAPBody replyBody = reply.getSOAPBody();
|
|
res.setHeader("Content-Type", contentType);
|
|
replyBody.addDocument(doc);
|
|
reply.saveChanges();
|
|
OutputStream os = res.getOutputStream();
|
reply.writeTo(os);
|
os.flush();
|
}
|
|
|
/**
|
* Retrieves a message ID that may be used for the next LDAP message sent to
|
* the Directory Server.
|
*
|
* @return A message ID that may be used for the next LDAP message sent to
|
* the Directory Server.
|
*/
|
public static int nextMessageID() {
|
int nextID = nextMessageID.getAndIncrement();
|
if (nextID == Integer.MAX_VALUE) {
|
nextMessageID.set(1);
|
}
|
|
return nextID;
|
}
|
|
/**
|
* This class is used when an XML request is malformed to retrieve the
|
* requestID value using an event XML parser.
|
*/
|
private static class DSMLContentHandler extends DefaultHandler {
|
private String requestID;
|
/**
|
* This function fetches the requestID value of the batchRequest xml
|
* element and call the default implementation (super).
|
*/
|
public void startElement(String uri, String localName, String qName,
|
Attributes attributes) throws SAXException {
|
if ( requestID==null && localName.equals("batchRequest") ) {
|
requestID = attributes.getValue("requestID");
|
}
|
super.startElement(uri, localName, qName, attributes);
|
}
|
}
|
}
|