001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.main; 029 030import org.opencms.crypto.CmsEncryptionException; 031import org.opencms.file.CmsObject; 032import org.opencms.gwt.CmsCoreService; 033import org.opencms.gwt.CmsGwtActionElement; 034import org.opencms.i18n.CmsMessages; 035import org.opencms.security.CmsDefaultAuthorizationHandler; 036import org.opencms.security.CmsRoleViolationException; 037import org.opencms.ui.Messages; 038import org.opencms.ui.login.CmsLoginUI; 039import org.opencms.ui.shared.CmsVaadinConstants; 040import org.opencms.util.CmsRequestUtil; 041import org.opencms.workplace.CmsWorkplace; 042import org.opencms.workplace.CmsWorkplaceLoginHandler; 043import org.opencms.workplace.CmsWorkplaceManager; 044 045import java.io.IOException; 046import java.net.URLEncoder; 047import java.util.Locale; 048import java.util.Map; 049import java.util.concurrent.ConcurrentHashMap; 050 051import javax.servlet.ServletException; 052import javax.servlet.http.HttpServletRequest; 053import javax.servlet.http.HttpServletResponse; 054 055import org.apache.commons.logging.Log; 056 057import org.jsoup.nodes.Document; 058import org.jsoup.nodes.Element; 059import org.jsoup.nodes.Node; 060import org.jsoup.parser.Tag; 061import org.jsoup.select.Elements; 062import org.slf4j.bridge.SLF4JBridgeHandler; 063 064import com.vaadin.server.BootstrapFragmentResponse; 065import com.vaadin.server.BootstrapListener; 066import com.vaadin.server.BootstrapPageResponse; 067import com.vaadin.server.CustomizedSystemMessages; 068import com.vaadin.server.DeploymentConfiguration; 069import com.vaadin.server.RequestHandler; 070import com.vaadin.server.ServiceException; 071import com.vaadin.server.SessionInitEvent; 072import com.vaadin.server.SessionInitListener; 073import com.vaadin.server.SystemMessages; 074import com.vaadin.server.SystemMessagesInfo; 075import com.vaadin.server.SystemMessagesProvider; 076import com.vaadin.server.UIClassSelectionEvent; 077import com.vaadin.server.UIProvider; 078import com.vaadin.server.VaadinRequest; 079import com.vaadin.server.VaadinResponse; 080import com.vaadin.server.VaadinService; 081import com.vaadin.server.VaadinServlet; 082import com.vaadin.server.VaadinServletService; 083import com.vaadin.server.VaadinSession; 084import com.vaadin.shared.ApplicationConstants; 085import com.vaadin.ui.UI; 086 087/** 088 * Servlet for workplace UI requests.<p> 089 */ 090public class CmsUIServlet extends VaadinServlet implements SystemMessagesProvider, SessionInitListener { 091 092 /** The bootstrap listener. */ 093 static final BootstrapListener BOOTSTRAP_LISTENER = new BootstrapListener() { 094 095 private static final long serialVersionUID = -6249561809984101044L; 096 097 public void modifyBootstrapFragment(BootstrapFragmentResponse response) { 098 099 // nothing to do 100 } 101 102 public void modifyBootstrapPage(BootstrapPageResponse response) { 103 104 CmsCoreService svc = new CmsCoreService(); 105 HttpServletRequest request = (HttpServletRequest)VaadinService.getCurrentRequest(); 106 svc.setRequest(request); 107 CmsObject cms = ((CmsUIServlet)getCurrent()).getCmsObject(); 108 svc.setCms(cms); 109 110 Document doc = response.getDocument(); 111 Elements appLoadingElements = doc.getElementsByClass("v-app-loading"); 112 if (appLoadingElements.size() > 0) { 113 for (Node node : appLoadingElements.get(0).childNodes()) { 114 node.remove(); 115 116 } 117 118 appLoadingElements.get(0).append(CmsVaadinConstants.LOADING_INDICATOR_HTML); 119 } 120 121 if (shouldShowLogin()) { 122 try { 123 String html = CmsLoginUI.generateLoginHtmlFragment(cms, response.getRequest()); 124 Element el = new Element(Tag.valueOf("div"), "").html(html); 125 doc.body().appendChild(el); 126 } catch (IOException e) { 127 LOG.error(e.getLocalizedMessage(), e); 128 } 129 } 130 Locale currentLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 131 // Inject CmsCoreData etc. for GWT dialogs 132 try { 133 doc.head().append(CmsGwtActionElement.exportCommon(cms, svc.prefetch())); 134 doc.head().append(org.opencms.ade.publish.ClientMessages.get().export(currentLocale, true)); 135 doc.head().append(org.opencms.ade.upload.ClientMessages.get().export(currentLocale, true)); 136 doc.head().append(org.opencms.ade.galleries.ClientMessages.get().export(currentLocale, true)); 137 for (String cssURI : OpenCms.getWorkplaceAppManager().getWorkplaceCssUris()) { 138 doc.head().append("<link rel=\"stylesheet\" href=\"" + CmsWorkplace.getResourceUri(cssURI) + "\">"); 139 } 140 doc.head().append( 141 "<script>window.addEventListener('pageshow', function(e) { if (e.persisted) { window.location.reload(); } });</script>"); 142 } catch (Exception e) { 143 LOG.error(e.getLocalizedMessage(), e); 144 } 145 response.setHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, "no-store"); 146 } 147 }; 148 149 /** The static log object for this class. */ 150 static final Log LOG = CmsLog.getLog(CmsUIServlet.class); 151 152 /** The login UI provider, overrides the default UI to display the login dialog when required. */ 153 static final UIProvider LOGIN_UI_PROVIDER = new UIProvider() { 154 155 private static final long serialVersionUID = 9154828335594149982L; 156 157 @Override 158 public Class<? extends UI> getUIClass(UIClassSelectionEvent event) { 159 160 if (shouldShowLogin() || isLoginUIRequest(event.getRequest())) { 161 return CmsLoginUI.class; 162 } 163 return null; 164 } 165 }; 166 167 /** The login redirect handler. */ 168 static final RequestHandler REQUEST_AUTHORIZATION_HANDLER = new RequestHandler() { 169 170 private static final long serialVersionUID = 1L; 171 172 public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) 173 throws IOException { 174 175 if (shouldShowLogin() && !isLoginUIRequest(request)) { 176 177 String link = OpenCms.getLinkManager().substituteLinkForUnknownTarget( 178 ((CmsUIServlet)getCurrent()).getCmsObject(), 179 CmsWorkplaceLoginHandler.LOGIN_FORM); 180 String requestedUri = ((HttpServletRequest)request).getRequestURI(); 181 if (!requestedUri.endsWith(OpenCms.getSystemInfo().getWorkplaceContext())) { 182 try { 183 link += "?" 184 + CmsWorkplaceManager.PARAM_LOGIN_REQUESTED_RESOURCE 185 + "=" 186 + URLEncoder.encode(requestedUri, "UTF-8") 187 + "&" 188 + CmsDefaultAuthorizationHandler.PARAM_ENCRYPTED_REQUESTED_RESOURCE 189 + "=" 190 + OpenCms.getDefaultTextEncryption().encrypt(requestedUri); 191 } catch (CmsEncryptionException e) { 192 LOG.warn(e.getLocalizedMessage(), e); 193 } 194 } 195 OpenCms.getAuthorizationHandler().requestAuthorization( 196 (HttpServletRequest)request, 197 (HttpServletResponse)response, 198 link); 199 return true; 200 } 201 return false; 202 } 203 }; 204 205 /** Serialization id. */ 206 private static final long serialVersionUID = 8119684308154724518L; 207 208 // install the slf4j bridge to pipe vaadin logging to log4j 209 static { 210 SLF4JBridgeHandler.install(); 211 } 212 213 /** The VAADIN heartbeat request path prefix. */ 214 private static final String HEARTBEAT_PREFIX = '/' + ApplicationConstants.HEARTBEAT_PATH + '/'; 215 216 /** The current CMS context. */ 217 private ThreadLocal<CmsObject> m_perThreadCmsObject = new ThreadLocal<>(); 218 219 /** Map of stored system messages objects. */ 220 private Map<Locale, SystemMessages> m_systemMessages = new ConcurrentHashMap<Locale, SystemMessages>(); 221 222 /** Stores whether the current request is a broadcast poll. */ 223 private ThreadLocal<Boolean> m_perThreadBroadcastPoll = new ThreadLocal<>(); 224 225 /** 226 * Checks whether the given request was referred from the login page.<p> 227 * 228 * @param request the request 229 * 230 * @return <code>true</code> in case of login ui requests 231 */ 232 static boolean isLoginUIRequest(VaadinRequest request) { 233 234 String referrer = request.getHeader("referer"); 235 return (referrer != null) && referrer.contains(CmsWorkplaceLoginHandler.LOGIN_HANDLER); 236 } 237 238 /** 239 * Returns whether the login dialog should be shown.<p> 240 * 241 * @return <code>true</code> if the login dialog should be shown 242 */ 243 static boolean shouldShowLogin() { 244 245 return ((CmsUIServlet)getCurrent()).getCmsObject().getRequestContext().getCurrentUser().isGuestUser(); 246 } 247 248 /** 249 * Returns the current cms context.<p> 250 * 251 * @return the current cms context 252 */ 253 public CmsObject getCmsObject() { 254 255 return m_perThreadCmsObject.get(); 256 } 257 258 /** 259 * @see com.vaadin.server.SystemMessagesProvider#getSystemMessages(com.vaadin.server.SystemMessagesInfo) 260 */ 261 public SystemMessages getSystemMessages(SystemMessagesInfo systemMessagesInfo) { 262 263 Locale locale = systemMessagesInfo.getLocale(); 264 if (!m_systemMessages.containsKey(locale)) { 265 m_systemMessages.put(locale, createSystemMessages(locale)); 266 } 267 return m_systemMessages.get(locale); 268 } 269 270 /** 271 * @see com.vaadin.server.SessionInitListener#sessionInit(com.vaadin.server.SessionInitEvent) 272 */ 273 public void sessionInit(final SessionInitEvent event) { 274 275 // set the locale to the users workplace locale 276 Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject()); 277 event.getSession().setLocale(wpLocale); 278 event.getSession().addRequestHandler(REQUEST_AUTHORIZATION_HANDLER); 279 event.getSession().addUIProvider(LOGIN_UI_PROVIDER); 280 event.getSession().addBootstrapListener(BOOTSTRAP_LISTENER); 281 } 282 283 /** 284 * Sets that the current request is a broadcast call.<p> 285 */ 286 public void setBroadcastPoll() { 287 288 m_perThreadBroadcastPoll.set(Boolean.TRUE); 289 } 290 291 /** 292 * Sets the current cms context.<p> 293 * 294 * @param cms the current cms context to set 295 */ 296 public synchronized void setCms(CmsObject cms) { 297 298 m_perThreadCmsObject.set(cms); 299 } 300 301 /** 302 * @see com.vaadin.server.VaadinServlet#createServletService(com.vaadin.server.DeploymentConfiguration) 303 */ 304 @Override 305 protected VaadinServletService createServletService(DeploymentConfiguration deploymentConfiguration) 306 throws ServiceException { 307 308 CmsVaadinServletService service = new CmsVaadinServletService(this, deploymentConfiguration); 309 service.init(); 310 return service; 311 } 312 313 /** 314 * @see com.vaadin.server.VaadinServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 315 */ 316 @Override 317 protected void service(HttpServletRequest request, HttpServletResponse response) 318 throws ServletException, IOException { 319 320 CmsRequestUtil.disableCrossSiteFrameEmbedding(response); 321 if (request.getRequestURI().contains("/VAADIN")) { 322 super.service(request, response); 323 return; 324 } 325 // check to OpenCms runlevel 326 int runlevel = OpenCmsCore.getInstance().getRunLevel(); 327 328 // write OpenCms server identification in the response header 329 response.setHeader(CmsRequestUtil.HEADER_SERVER, OpenCmsCore.getInstance().getSystemInfo().getVersion()); 330 331 if (runlevel != OpenCms.RUNLEVEL_4_SERVLET_ACCESS) { 332 // not the "normal" servlet runlevel 333 if (runlevel == OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 334 // we have shell runlevel only, upgrade to servlet runlevel (required after setup wizard) 335 init(getServletConfig()); 336 } else { 337 // illegal runlevel, we can't process requests 338 // sending status code 403, indicating the server understood the request but refused to fulfill it 339 response.sendError(HttpServletResponse.SC_FORBIDDEN); 340 // goodbye 341 return; 342 } 343 } 344 345 // check if the given request matches the workplace site 346 if ((OpenCms.getSiteManager().getSites().size() > 1) && !OpenCms.getSiteManager().isWorkplaceRequest(request)) { 347 348 // do not send any redirects to the workplace site for security reasons 349 response.sendError(HttpServletResponse.SC_NOT_FOUND); 350 return; 351 } 352 353 try { 354 OpenCmsCore.getInstance().initCmsContextForUI(request, response, this); 355 super.service(request, response); 356 OpenCms.getSessionManager().updateSessionInfo(getCmsObject(), request, isHeartbeatRequest(request)); 357 } catch (CmsRoleViolationException rv) { 358 // don't log these into the error channel 359 LOG.debug(rv.getLocalizedMessage(), rv); 360 // error code not set - set "internal server error" (500) 361 int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 362 response.setStatus(status); 363 try { 364 response.sendError(status, rv.toString()); 365 } catch (IOException e) { 366 // can be ignored 367 LOG.error(e.getLocalizedMessage(), e); 368 } 369 } catch (IOException io) { 370 // probably connection aborted by client, no need to write to the ERROR channel 371 LOG.warn(io.getLocalizedMessage(), io); 372 // try so set status and send error in any case 373 int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 374 response.setStatus(status); 375 try { 376 response.sendError(status, io.toString()); 377 } catch (Exception e) { 378 // can be ignored 379 } 380 } catch (Throwable t) { 381 LOG.error(t.getLocalizedMessage(), t); 382 int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 383 response.setStatus(status); 384 try { 385 response.sendError(status, t.toString()); 386 } catch (IOException e) { 387 // can be ignored 388 LOG.error(e.getLocalizedMessage(), e); 389 } 390 } finally { 391 // remove the thread local cms context 392 clearThreadLocal(); 393 } 394 } 395 396 /** 397 * @see com.vaadin.server.VaadinServlet#servletInitialized() 398 */ 399 @Override 400 protected void servletInitialized() throws ServletException { 401 402 super.servletInitialized(); 403 getService().setSystemMessagesProvider(this); 404 getService().addSessionInitListener(this); 405 } 406 407 /** 408 * Clears the thread local storage.<p> 409 */ 410 private void clearThreadLocal() { 411 412 m_perThreadCmsObject.set(null); 413 m_perThreadBroadcastPoll.remove(); 414 } 415 416 /** 417 * Returns a system messages instance for the given locale.<p> 418 * 419 * @param locale the locale 420 * 421 * @return the system messages 422 */ 423 private SystemMessages createSystemMessages(Locale locale) { 424 425 CmsMessages messages = Messages.get().getBundle(locale); 426 CustomizedSystemMessages systemMessages = new CustomizedSystemMessages(); 427 systemMessages.setCommunicationErrorCaption(messages.key(Messages.GUI_SYSTEM_COMMUNICATION_ERROR_CAPTION_0)); 428 systemMessages.setCommunicationErrorMessage(messages.key(Messages.GUI_SYSTEM_COMMUNICATION_ERROR_MESSAGE_0)); 429 systemMessages.setCommunicationErrorNotificationEnabled(true); 430 systemMessages.setAuthenticationErrorCaption(messages.key(Messages.GUI_SYSTEM_AUTHENTICATION_ERROR_CAPTION_0)); 431 systemMessages.setAuthenticationErrorMessage(messages.key(Messages.GUI_SYSTEM_AUTHENTICATION_ERROR_MESSAGE_0)); 432 systemMessages.setAuthenticationErrorNotificationEnabled(true); 433 systemMessages.setSessionExpiredCaption(messages.key(Messages.GUI_SYSTEM_SESSION_EXPIRED_ERROR_CAPTION_0)); 434 systemMessages.setSessionExpiredMessage(messages.key(Messages.GUI_SYSTEM_SESSION_EXPIRED_ERROR_MESSAGE_0)); 435 systemMessages.setSessionExpiredNotificationEnabled(true); 436 systemMessages.setInternalErrorCaption(messages.key(Messages.GUI_SYSTEM_INTERNAL_ERROR_CAPTION_0)); 437 systemMessages.setInternalErrorMessage(messages.key(Messages.GUI_SYSTEM_INTERNAL_ERROR_MESSAGE_0)); 438 systemMessages.setInternalErrorNotificationEnabled(true); 439 return systemMessages; 440 } 441 442 /** 443 * Checks whether the given request is a heartbeat request.<p> 444 * 445 * @param request the request 446 * 447 * @return <code>true</code> in case of VAADIN heartbeat requests 448 */ 449 private boolean isHeartbeatRequest(HttpServletRequest request) { 450 451 if ((m_perThreadBroadcastPoll.get() != null) && m_perThreadBroadcastPoll.get().booleanValue()) { 452 return true; 453 } 454 String pathInfo = request.getPathInfo(); 455 return (pathInfo != null) && pathInfo.startsWith(HEARTBEAT_PREFIX); 456 } 457}