use of com.zimbra.common.soap.SoapProtocol in project zm-mailbox by Zimbra.
the class SoapEngine method dispatch.
/**
* dispatch to the given serviceName the specified document,
* which should be a soap envelope containing a document to
* execute.
*
* @param path the path (i.e., /service/foo) of the service to dispatch to
* @param envelope the top-level element of the message
* @param context user context parameters
* @return an XmlObject which is a SoapEnvelope containing the response
* @throws CsrfTokenException if CSRF token validation fails
*/
private Element dispatch(String path, Element envelope, Map<String, Object> context) {
SoapProtocol soapProto = SoapProtocol.determineProtocol(envelope);
if (soapProto == null) {
// FIXME: have to pick 1.1 or 1.2 since we can't parse any
soapProto = SoapProtocol.Soap12;
return soapFaultEnv(soapProto, "SOAP exception", ServiceException.INVALID_REQUEST("unable to determine SOAP version", null));
}
Element doc = soapProto.getBodyElement(envelope);
if (doc == null) {
return soapFaultEnv(soapProto, "SOAP exception", ServiceException.INVALID_REQUEST("No SOAP body", null));
}
ServletRequest servReq = (ServletRequest) context.get(SoapServlet.SERVLET_REQUEST);
//Check if this handler requires authentication.
//Do not perform CSRF checks for handlers that do not require authentication
DocumentHandler handler = dispatcher.getHandler(doc);
ZimbraSoapContext zsc = null;
Element ectxt = soapProto.getHeader(envelope, HeaderConstants.CONTEXT);
try {
zsc = new ZimbraSoapContext(ectxt, doc.getQName(), handler, context, soapProto);
} catch (ServiceException e) {
return soapFaultEnv(soapProto, "unable to construct SOAP context", e);
}
boolean doCsrfCheck = false;
if (servReq.getAttribute(CsrfFilter.CSRF_TOKEN_CHECK) != null) {
doCsrfCheck = (Boolean) servReq.getAttribute(CsrfFilter.CSRF_TOKEN_CHECK);
} else if (zsc.getAuthToken() != null && zsc.getAuthToken().isCsrfTokenEnabled()) {
doCsrfCheck = true;
}
if (handler == null) {
// handler, all other request should be mapped to a Handler
if (!doc.getName().equals("BatchRequest")) {
doCsrfCheck = false;
} else {
LOG.info("Only BatchRequest does not have a handler mapped to it. Request: %s, does not have a " + "handler, log for future handling.", path);
}
} else {
if (doc.getName().equals("AuthRequest")) {
// this is a Auth request, no CSRF validation happens
doCsrfCheck = false;
} else {
doCsrfCheck = doCsrfCheck && handler.needsAuth(context);
}
}
if (doCsrfCheck) {
try {
HttpServletRequest httpReq = (HttpServletRequest) servReq;
// Bug: 96167 SoapEngine should be able to read CSRF token from HTTP headers
String csrfToken = httpReq.getHeader(Constants.CSRF_TOKEN);
if (StringUtil.isNullOrEmpty(csrfToken)) {
Element contextElmt = soapProto.getHeader(envelope).getElement(HeaderConstants.E_CONTEXT);
csrfToken = contextElmt.getAttribute(HeaderConstants.E_CSRFTOKEN);
}
AuthToken authToken = zsc.getAuthToken();
if (!CsrfUtil.isValidCsrfToken(csrfToken, authToken)) {
LOG.info("CSRF token validation failed for account");
return soapFaultEnv(soapProto, "cannot dispatch request", ServiceException.AUTH_REQUIRED());
}
} catch (ServiceException e) {
// we came here which implies clients supports CSRF authorization
// and CSRF token is generated
LOG.info("Error during CSRF validation.", e);
return soapFaultEnv(soapProto, "cannot dispatch request", ServiceException.AUTH_REQUIRED());
}
}
SoapProtocol responseProto = zsc.getResponseProtocol();
String rid = zsc.getRequestedAccountId();
String proxyAuthToken = null;
if (rid != null) {
Provisioning prov = Provisioning.getInstance();
AccountUtil.addAccountToLogContext(prov, rid, ZimbraLog.C_NAME, ZimbraLog.C_ID, zsc.getAuthToken());
String aid = zsc.getAuthtokenAccountId();
if (aid != null && !rid.equals(aid)) {
AccountUtil.addAccountToLogContext(prov, aid, ZimbraLog.C_ANAME, ZimbraLog.C_AID, zsc.getAuthToken());
} else if (zsc.getAuthToken() != null && zsc.getAuthToken().getAdminAccountId() != null) {
AccountUtil.addAccountToLogContext(prov, zsc.getAuthToken().getAdminAccountId(), ZimbraLog.C_ANAME, ZimbraLog.C_AID, zsc.getAuthToken());
}
try {
Mailbox mbox = MailboxManager.getInstance().getMailboxByAccountId(rid, false);
if (mbox != null) {
ZimbraLog.addMboxToContext(mbox.getId());
}
} catch (ServiceException ignore) {
}
try {
AuthToken at = zsc.getAuthToken();
if (at != null) {
proxyAuthToken = prov.getProxyAuthToken(rid, context);
at.setProxyAuthToken(proxyAuthToken);
}
} catch (ServiceException e) {
LOG.warn("failed to set proxy auth token: %s", e.getMessage());
}
}
if (zsc.getUserAgent() != null) {
ZimbraLog.addUserAgentToContext(zsc.getUserAgent());
}
if (zsc.getVia() != null) {
ZimbraLog.addViaToContext(zsc.getVia());
}
if (zsc.getSoapRequestId() != null) {
ZimbraLog.addSoapIdToContext(zsc.getSoapRequestId());
}
logRequest(context, envelope);
context.put(ZIMBRA_CONTEXT, zsc);
context.put(ZIMBRA_ENGINE, this);
HttpServletRequest servletRequest = (HttpServletRequest) context.get(SoapServlet.SERVLET_REQUEST);
boolean isResumed = !ContinuationSupport.getContinuation(servletRequest).isInitial();
Element responseBody = null;
if (!zsc.isProxyRequest()) {
// if the client's told us that they've seen through notification block 50, we can drop old notifications up to that point
acknowledgeNotifications(zsc);
if (doc.getQName().equals(ZimbraNamespace.E_BATCH_REQUEST)) {
boolean contOnError = doc.getAttribute(ZimbraNamespace.A_ONERROR, ZimbraNamespace.DEF_ONERROR).equals("continue");
responseBody = zsc.createElement(ZimbraNamespace.E_BATCH_RESPONSE);
if (!isResumed) {
ZimbraLog.soap.info(doc.getName());
}
for (Element req : doc.listElements()) {
String id = req.getAttribute(A_REQUEST_CORRELATOR, null);
long start = System.currentTimeMillis();
Element br = dispatchRequest(dispatcher.getHandler(req), req, context, zsc);
if (!isResumed) {
ZimbraLog.soap.info("(batch) %s elapsed=%d", req.getName(), System.currentTimeMillis() - start);
}
if (id != null) {
br.addAttribute(A_REQUEST_CORRELATOR, id);
}
responseBody.addNonUniqueElement(br);
if (!contOnError && responseProto.isFault(br)) {
break;
}
if (proxyAuthToken != null) {
// requests will invalidate it when proxying locally;
// make sure it's set for each sub-request in batch
zsc.getAuthToken().setProxyAuthToken(proxyAuthToken);
}
}
} else {
String id = doc.getAttribute(A_REQUEST_CORRELATOR, null);
long start = System.currentTimeMillis();
responseBody = dispatchRequest(handler, doc, context, zsc);
if (!isResumed) {
ZimbraLog.soap.info("%s elapsed=%d", doc.getName(), System.currentTimeMillis() - start);
}
if (id != null) {
responseBody.addAttribute(A_REQUEST_CORRELATOR, id);
}
}
} else {
// We stick to local server's session when talking to the client.
try {
// Detach doc from its current parent, because it will be added as a child element of a new SOAP
// envelope in the proxy dispatcher. IllegalAddException will be thrown if we don't detach it first.
doc.detach();
ZimbraSoapContext zscTarget = new ZimbraSoapContext(zsc, zsc.getRequestedAccountId()).disableNotifications();
long start = System.currentTimeMillis();
responseBody = zsc.getProxyTarget().dispatch(doc, zscTarget);
ZimbraLog.soap.info("%s proxy=%s,elapsed=%d", doc.getName(), zsc.getProxyTarget(), System.currentTimeMillis() - start);
responseBody.detach();
} catch (SoapFaultException e) {
responseBody = e.getFault() != null ? e.getFault().detach() : responseProto.soapFault(e);
LOG.debug("proxy handler exception", e);
} catch (ServiceException e) {
responseBody = responseProto.soapFault(e);
LOG.info("proxy handler exception", e);
} catch (Throwable e) {
responseBody = responseProto.soapFault(ServiceException.FAILURE(e.toString(), e));
if (e instanceof OutOfMemoryError) {
Zimbra.halt("proxy handler exception", e);
}
LOG.warn("proxy handler exception", e);
}
}
// put notifications (new sessions and incremental change notifications) to header...
Element responseHeader = generateResponseHeader(zsc);
// ... and return the composed response
return responseProto.soapEnvelope(responseBody, responseHeader);
}
use of com.zimbra.common.soap.SoapProtocol in project zm-mailbox by Zimbra.
the class SoapEngine method dispatch.
public Element dispatch(String path, byte[] soapMessage, Map<String, Object> context) throws CsrfTokenException {
if (soapMessage == null || soapMessage.length == 0) {
SoapProtocol soapProto = SoapProtocol.Soap12;
return soapFaultEnv(soapProto, "SOAP exception", ServiceException.PARSE_ERROR("empty request payload", null));
}
InputStream in = new ByteArrayInputStream(soapMessage);
Element document = null;
try {
if (soapMessage[0] == '<') {
document = Element.parseXML(in);
} else {
document = Element.parseJSON(in);
}
} catch (SoapParseException e) {
SoapProtocol soapProto = SoapProtocol.SoapJS;
logUnparsableRequest(context, soapMessage, e.getMessage());
return soapFaultEnv(soapProto, "SOAP exception", ServiceException.PARSE_ERROR(e.getMessage(), e));
} catch (XmlParseException e) {
logUnparsableRequest(context, soapMessage, e.getMessage());
SoapProtocol soapProto = chooseFaultProtocolFromBadXml(new ByteArrayInputStream(soapMessage));
return soapFaultEnv(soapProto, "SOAP exception", e);
}
Element resp = dispatch(path, document, context);
/*
* For requests(e.g. AuthRequest) that don't have account info in time when they
* are normally added to the logging context in dispatch after zsc is established
* from the SOAP request header. Thus account logging for zimbra.soap won't be
* effective when the SOAP request is logged in TRACE level normally.
*
* For AuthRequest, we call Account.addAccountToLogContext from the handler as
* soon as the account, which is only available in the SOAP body, is discovered.
* Account info should be available after dispatch() so account logger can be
* triggered.
*/
logRequest(context, document);
return resp;
}
use of com.zimbra.common.soap.SoapProtocol in project zm-mailbox by Zimbra.
the class SoapServlet method sendResponse.
private void sendResponse(HttpServletRequest req, HttpServletResponse resp, Element envelope) throws IOException {
SoapProtocol soapProto = SoapProtocol.determineProtocol(envelope);
int statusCode = soapProto.hasFault(envelope) ? HttpServletResponse.SC_INTERNAL_SERVER_ERROR : HttpServletResponse.SC_OK;
boolean chunkingEnabled = LC.soap_response_chunked_transfer_encoding_enabled.booleanValue();
if (chunkingEnabled) {
// disable chunking if proto < HTTP 1.1
String proto = req.getProtocol();
try {
HttpVersion httpVer = HttpVersion.parse(proto);
chunkingEnabled = !httpVer.lessEquals(HttpVersion.HTTP_1_0);
} catch (ProtocolException e) {
ZimbraLog.soap.warn("cannot parse http version in request: %s, http chunked transfer encoding disabled", proto, e);
chunkingEnabled = false;
}
}
// use jetty default if the LC key is not set
int responseBufferSize = soapResponseBufferSize();
if (responseBufferSize != -1)
resp.setBufferSize(responseBufferSize);
resp.setContentType(soapProto.getContentType());
resp.setStatus(statusCode);
resp.setHeader("Cache-Control", "no-store, no-cache");
if (chunkingEnabled) {
// Let jetty chunk the response if applicable.
ZimbraServletOutputStream out = new ZimbraServletOutputStream(resp.getOutputStream());
envelope.output(out);
out.flush();
} else {
// serialize the envelope to a byte array and send the response with Content-Length header.
byte[] soapBytes = envelope.toUTF8();
resp.setContentLength(soapBytes.length);
resp.getOutputStream().write(soapBytes);
resp.getOutputStream().flush();
}
envelope.destroy();
}
use of com.zimbra.common.soap.SoapProtocol in project zm-mailbox by Zimbra.
the class ProxyTarget method execute.
public Pair<Element, Element> execute(Element request, ZimbraSoapContext zsc) throws ServiceException {
if (zsc == null)
return new Pair<Element, Element>(null, dispatch(request));
SoapProtocol proto = request instanceof Element.JSONElement ? SoapProtocol.SoapJS : SoapProtocol.Soap12;
if (proto == SoapProtocol.Soap12 && zsc.getRequestProtocol() == SoapProtocol.Soap11) {
proto = SoapProtocol.Soap11;
}
/* Bug 77604 When a user has been configured to change their password on next login, the resulting proxied
* ChangePasswordRequest was failing because account was specified in context but no authentication token
* was supplied. The server handler rejects a context which has account information but no authentication
* info - see ZimbraSoapContext constructor - solution is to exclude the account info from the context.
*/
boolean excludeAccountDetails = AccountConstants.CHANGE_PASSWORD_REQUEST.equals(request.getQName());
Element envelope = proto.soapEnvelope(request, zsc.toProxyContext(proto, excludeAccountDetails));
SoapHttpTransport transport = null;
try {
transport = new SoapHttpTransport(mURL);
transport.setTargetAcctId(zsc.getRequestedAccountId());
if (mMaxAttempts > 0)
transport.setRetryCount(mMaxAttempts);
if (mTimeout >= 0)
transport.setTimeout((int) Math.min(mTimeout, Integer.MAX_VALUE));
transport.setResponseProtocol(zsc.getResponseProtocol());
AuthToken authToken = AuthToken.getCsrfUnsecuredAuthToken(zsc.getAuthToken());
if (authToken != null && !StringUtil.isNullOrEmpty(authToken.getProxyAuthToken())) {
transport.setAuthToken(authToken.getProxyAuthToken());
}
if (ZimbraLog.soap.isDebugEnabled()) {
ZimbraLog.soap.debug("Proxying request: proxy=%s targetAcctId=%s", toString(), zsc.getRequestedAccountId());
}
disableCsrfFlagInAuthToken(envelope, authToken, request.getQName());
Element response = transport.invokeRaw(envelope);
Element body = transport.extractBodyElement(response);
return new Pair<Element, Element>(transport.getZimbraContext(), body);
} catch (IOException e) {
throw ServiceException.PROXY_ERROR(e, mURL);
} finally {
if (transport != null)
transport.shutdown();
}
}
use of com.zimbra.common.soap.SoapProtocol in project zm-mailbox by Zimbra.
the class TestCsrfRequest method getCreateSigWithAuthAndCsrfDisabled.
@Test
public void getCreateSigWithAuthAndCsrfDisabled() throws Exception {
Account acct = provUtil.createAccount(genAcctNameLocalPart(), domain);
boolean csrfEnabled = Boolean.FALSE;
SoapTransport transport = authUser(acct.getName(), csrfEnabled, Boolean.FALSE);
String sigContent = "xss<script>alert(\"XSS\")</script><a href=javascript:alert(\"XSS\")><";
Signature sig = new Signature(null, "testSig", sigContent, "text/html");
CreateSignatureRequest req = new CreateSignatureRequest(sig);
SoapProtocol proto = SoapProtocol.Soap12;
Element sigReq = JaxbUtil.jaxbToElement(req, proto.getFactory());
try {
Element element = transport.invoke(sigReq, false, false, null);
String sigt = element.getElement("signature").getAttribute("id");
assertNotNull(sigt);
} catch (SoapFaultException e) {
e.printStackTrace();
assertNull(e);
}
}
Aggregations